diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fd23d6..1748e31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,27 +15,39 @@ jobs: strategy: matrix: include: - - os: ubuntu-latest - ruby: "2.5" - - os: ubuntu-latest - ruby: "2.6" - os: ubuntu-latest ruby: "2.7" + flags: "--test --cucumber" - os: ubuntu-latest ruby: "3.0" + flags: "--test --cucumber" - os: ubuntu-latest ruby: "3.1" + flags: "--test --cucumber" - os: ubuntu-latest ruby: "3.2" - tool: ci + flags: "--test --cucumber" + - os: ubuntu-latest + ruby: "3.3" + flags: "--test --cucumber" + - os: ubuntu-latest + ruby: "3.4" + flags: "--test --cucumber" + - os: ubuntu-latest + ruby: "3.4" + flags: "--rubocop --yard --build" - os: ubuntu-latest ruby: jruby + flags: "--test --cucumber" - os: ubuntu-latest ruby: truffleruby + flags: "--test --cucumber" - os: macos-latest - ruby: "3.2" + ruby: "3.4" + flags: "--test --cucumber" - os: windows-latest - ruby: "3.2" + ruby: "3.4" + flags: "--test --cucumber" fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -44,13 +56,11 @@ jobs: with: ruby-version: ${{ matrix.ruby }} - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Install dependencies shell: bash run: "bundle install && gem install --no-document toys" - - name: Run ${{ matrix.tool || 'test' }} + - name: Run ${{ matrix.flags }} shell: bash - env: - MT_COMPAT: "true" run: | - toys "${{ matrix.tool || 'test' }}" < /dev/null + toys ci --only ${{ matrix.flags }} < /dev/null diff --git a/.toys/ci.rb b/.toys/ci.rb index 6e766b3..b94a82c 100644 --- a/.toys/ci.rb +++ b/.toys/ci.rb @@ -1,24 +1,18 @@ # frozen_string_literal: true -desc "Run all CI checks" - -include :exec, result_callback: :handle_result -include :terminal +load_git remote: "https://github.com/dazuma/toys.git", + path: "common-tools/ci", + update: 3600 -def handle_result result - if result.success? - puts "** #{result.name} passed\n\n", :green, :bold - else - puts "** CI terminated: #{result.name} failed!", :red, :bold - exit 1 - end -end +desc "Run all CI checks" -def run - ::Dir.chdir context_directory - exec_tool ["test"], name: "Tests" - exec_tool ["cucumber"], name: "Behaviors" - exec_tool ["rubocop"], name: "Style checker" - exec_tool ["yardoc"], name: "Docs generation" - exec_tool ["build"], name: "Gem build" +expand("toys-ci") do |toys_ci| + toys_ci.only_flag = true + toys_ci.fail_fast_flag = true + toys_ci.job("Bundle update", flag: :bundle, exec: ["bundle", "update"]) + toys_ci.job("Rubocop", flag: :rubocop, tool: ["rubocop"]) + toys_ci.job("Tests", flag: :test, tool: ["test"]) + toys_ci.job("Cucumber", flag: :cucumber, tool: ["cucumber"]) + toys_ci.job("Yardoc", flag: :yard, tool: ["yardoc"]) + toys_ci.job("Gem build", flag: :build, tool: ["build"]) end diff --git a/.toys/cucumber.rb b/.toys/cucumber.rb index 7e2bf40..e0b7c1d 100644 --- a/.toys/cucumber.rb +++ b/.toys/cucumber.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -toys_version! ">= 0.12" +toys_version! ">= 0.15" desc "Run cucumber tests" diff --git a/Gemfile b/Gemfile index e51ac70..6254c58 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,15 @@ source "https://rubygems.org" gemspec -gem "cucumber", "7.0" -gem "google-style", "~> 1.25.1" -gem "minitest", "~> 5.14" -gem "minitest-focus", "~> 1.1" -gem "minitest-rg", "~> 5.2" -gem "rack", "~> 2.2" -gem "redcarpet", "~> 3.5" unless ::RUBY_PLATFORM == "java" -gem "webrick", "~> 1.7" -gem "yard", "~> 0.9.25" +gem "base64", "~> 0.3" +gem "cucumber", "~> 9.2" +gem "google-style", "~> 1.27.1" +gem "logger", "~> 1.7" +gem "minitest", "~> 5.25" +gem "minitest-focus", "~> 1.4" +gem "minitest-rg", "~> 5.3" +gem "ostruct", "~> 0.6" +gem "rack", "~> 3.2" +gem "redcarpet", "~> 3.6" unless ::RUBY_PLATFORM == "java" +gem "webrick", "~> 1.9" +gem "yard", "~> 0.9.37" diff --git a/cloud_events.gemspec b/cloud_events.gemspec index 5590add..703c5f1 100644 --- a/cloud_events.gemspec +++ b/cloud_events.gemspec @@ -1,8 +1,6 @@ # frozen_string_literal: true -lib = ::File.expand_path "lib", __dir__ -$LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib -require "cloud_events/version" +require_relative "lib/cloud_events/version" version = ::CloudEvents::VERSION ::Gem::Specification.new do |spec| @@ -13,15 +11,15 @@ version = ::CloudEvents::VERSION spec.email = ["dazuma@gmail.com"] spec.summary = "Ruby SDK for CloudEvents" - spec.description = \ - "The official Ruby implementation of the CloudEvents Specification." \ - " Provides data types for events, and HTTP/JSON bindings for marshalling" \ - " and unmarshalling event data." + spec.description = + "The official Ruby implementation of the CloudEvents Specification. " \ + "Provides data types for events, and HTTP/JSON bindings for marshalling " \ + "and unmarshalling event data." spec.homepage = "https://github.com/cloudevents/sdk-ruby" spec.files = ::Dir.glob("lib/**/*.rb") + ::Dir.glob("*.md") + [".yardopts"] spec.require_paths = ["lib"] - spec.required_ruby_version = ">= 2.5" + spec.required_ruby_version = ">= 2.7" if spec.respond_to? :metadata spec.metadata["changelog_uri"] = "https://cloudevents.github.io/sdk-ruby/v#{version}/file.CHANGELOG.html" diff --git a/features/step_definitions/steps.rb b/features/step_definitions/steps.rb index a5e02be..502138b 100644 --- a/features/step_definitions/steps.rb +++ b/features/step_definitions/steps.rb @@ -9,6 +9,11 @@ end Given "an HTTP request" do |str| + # WEBrick parsing wants the lines delimited by \r\n, but the input + # content-length assumes \n within the body. + parts = str.split "\n\n" + parts[0].gsub! "\n", "\r\n" + str = "#{parts[0]}\r\n\r\n#{parts[1]}" webrick_request = WEBrick::HTTPRequest.new WEBrick::Config::HTTP webrick_request.parse StringIO.new str @rack_request = {} diff --git a/lib/cloud_events/content_type.rb b/lib/cloud_events/content_type.rb index f043ce4..e7bcc26 100644 --- a/lib/cloud_events/content_type.rb +++ b/lib/cloud_events/content_type.rb @@ -152,7 +152,7 @@ def consume_token str, downcase: false, error_message: nil def consume_special str, expected, error_message: nil raise ParseError, error_message || "Expected #{expected.inspect}" unless str.start_with? expected - consume_comments str[1..-1].strip + consume_comments str[1..].strip end def consume_token_or_quoted str, error_message: nil @@ -177,7 +177,7 @@ def consume_token_or_quoted str, error_message: nil end end index += 1 - str = consume_comments str[index..-1].strip + str = consume_comments str[index..].strip [arr.join, str] end @@ -194,14 +194,14 @@ def consume_comments str when "\\" index += 2 when "(" - str = consume_comments str[index..-1] + str = consume_comments str[index..] index = 0 else index += 1 end end index += 1 - consume_comments str[index..-1].strip + consume_comments str[index..].strip end def maybe_quote str diff --git a/lib/cloud_events/event/field_interpreter.rb b/lib/cloud_events/event/field_interpreter.rb index 08c2427..c6323be 100644 --- a/lib/cloud_events/event/field_interpreter.rb +++ b/lib/cloud_events/event/field_interpreter.rb @@ -30,8 +30,8 @@ def string keys, required: false, allow_empty: false value.freeze [value, value] else - raise AttributeError, "Illegal type for #{keys.first}:" \ - " String expected but #{value.class} found" + raise AttributeError, "Illegal type for #{keys.first}: " \ + "String expected but #{value.class} found" end end end @@ -49,8 +49,8 @@ def uri keys, required: false when ::URI::Generic [Utils.deep_freeze(value), value.to_s.freeze] else - raise AttributeError, "Illegal type for #{keys.first}:" \ - " String or URI expected but #{value.class} found" + raise AttributeError, "Illegal type for #{keys.first}: " \ + "String or URI expected but #{value.class} found" end end end @@ -70,8 +70,8 @@ def rfc3339_date_time keys, required: false value = value.to_datetime [Utils.deep_freeze(value), value.rfc3339.freeze] else - raise AttributeError, "Illegal type for #{keys.first}:" \ - " String, Time, or DateTime expected but #{value.class} found" + raise AttributeError, "Illegal type for #{keys.first}: " \ + "String, Time, or DateTime expected but #{value.class} found" end end end @@ -85,8 +85,8 @@ def content_type keys, required: false when ContentType [value, value.to_s] else - raise AttributeError, "Illegal type for #{keys.first}:" \ - " String, or ContentType expected but #{value.class} found" + raise AttributeError, "Illegal type for #{keys.first}: " \ + "String, or ContentType expected but #{value.class} found" end end end @@ -99,8 +99,8 @@ def spec_version keys, accept: value.freeze [value, value] else - raise AttributeError, "Illegal type for #{keys.first}:" \ - " String expected but #{value.class} found" + raise AttributeError, "Illegal type for #{keys.first}: " \ + "String expected but #{value.class} found" end end end @@ -121,7 +121,7 @@ def object keys, required: false, allow_nil: false keys.each do |key| key_present = @args.key? key val = @args.delete key - value = val if allow_nil && key_present || !allow_nil && !val.nil? + value = val if (allow_nil && key_present) || (!allow_nil && !val.nil?) end if value == UNDEFINED raise AttributeError, "The #{keys.first} field is required" if required diff --git a/lib/cloud_events/event/opaque.rb b/lib/cloud_events/event/opaque.rb index 78f8cb2..a8f38b1 100644 --- a/lib/cloud_events/event/opaque.rb +++ b/lib/cloud_events/event/opaque.rb @@ -73,7 +73,7 @@ def == other ## @private def hash - @content.hash ^ @content_type.hash ^ @batch.hash + [@content, @content_type, @batch].hash end end end diff --git a/lib/cloud_events/http_binding.rb b/lib/cloud_events/http_binding.rb index 7fc9473..eeba6d4 100644 --- a/lib/cloud_events/http_binding.rb +++ b/lib/cloud_events/http_binding.rb @@ -46,10 +46,10 @@ def initialize end @event_encoders = {} @data_decoders = Format::Multi.new do |result| - result&.key?(:data) && result&.key?(:content_type) ? result : nil + result&.key?(:data) && result.key?(:content_type) ? result : nil end @data_encoders = Format::Multi.new do |result| - result&.key?(:content) && result&.key?(:content_type) ? result : nil + result&.key?(:content) && result.key?(:content_type) ? result : nil end text_format = TextFormat.new @data_decoders.formats.replace [text_format, DefaultDataFormat] @@ -110,7 +110,7 @@ def register_formatter_methods formatter, @event_decoders.formats.unshift formatter if decode_event if encode_event encoders = @event_encoders[encode_event] ||= Format::Multi.new do |result| - result&.key?(:content) && result&.key?(:content_type) ? result : nil + result&.key?(:content) && result.key?(:content_type) ? result : nil end encoders.formats.unshift formatter end @@ -325,7 +325,7 @@ def encode_binary_content event, legacy_data_encode: true, **format_args # def percent_decode str str = str.gsub(/"((?:[^"\\]|\\.)*)"/) { ::Regexp.last_match(1).gsub(/\\(.)/, '\1') } - decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1..-1].to_i(16)].pack "C" } + decoded_str = str.gsub(/%[0-9a-fA-F]{2}/) { |m| [m[1..].to_i(16)].pack "C" } decoded_str.force_encoding ::Encoding::UTF_8 end diff --git a/lib/cloud_events/text_format.rb b/lib/cloud_events/text_format.rb index 706b80a..8be1605 100644 --- a/lib/cloud_events/text_format.rb +++ b/lib/cloud_events/text_format.rb @@ -67,7 +67,7 @@ def encode_data data: UNSPECIFIED, content_type: nil, **_other_kwargs def text_content_type? content_type content_type&.media_type == "text" || - content_type&.media_type == "application" && content_type&.subtype == "octet-stream" + (content_type&.media_type == "application" && content_type&.subtype == "octet-stream") end end end diff --git a/test/test_http_binding.rb b/test/test_http_binding.rb index 1e164f3..8153ee6 100644 --- a/test/test_http_binding.rb +++ b/test/test_http_binding.rb @@ -6,7 +6,6 @@ require "json" require "stringio" require "uri" -require "rack/lint" describe CloudEvents::HttpBinding do let(:http_binding) { CloudEvents::HttpBinding.default } @@ -261,7 +260,7 @@ def assert_request_matches env, headers, body end it "decodes a binary mode rack env using an InputWrapper" do - my_simple_binary_mode["rack.input"] = Rack::Lint::InputWrapper.new StringIO.new my_simple_data + my_simple_binary_mode["rack.input"] = StringIO.new my_simple_data event = http_binding.decode_event my_simple_binary_mode assert_equal my_simple_event, event end