From 1256fbc773df00a0c1705c1a4456db72ce983ea0 Mon Sep 17 00:00:00 2001 From: Andreas Haller Date: Fri, 11 Sep 2026 18:14:43 +0200 Subject: [PATCH] Version 4.0.0 --- CHANGELOG.md | 54 +++++++++++++++++++----------------- Gemfile.lock | 40 +++++++++++++------------- README.md | 12 ++++---- benchmarks/Gemfile.lock | 12 ++++---- lib/openapi_first/failure.rb | 2 +- lib/openapi_first/version.rb | 2 +- 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 436f67d6..32777b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -No stricter / less strict request validation. Mostly internal stuff. Plus a Sinatra integration. +## 4.0.0 + +This release has no stricter or less strict request validation. It changes mostly internal stuff and adds a Sinatra integration. It's a major version, but it should be safe to upgrade. #### Breaking changes - Uploaded files are no longer read during request validation. Before, the whole content of every `multipart/form-data` part that was sent as a file was read into memory, which allowed a single large upload to any documented multipart route to exhaust the memory of the server process. Such a field is now passed through as Rack parsed it (`{ filename:, type:, name:, tempfile:, head: }`), which is the same shape that Sinatra and Hanami hand to your application. Use `parsed_body['file'][:tempfile]` to read or stream the file. @@ -11,6 +13,7 @@ No stricter / less strict request validation. Mostly internal stuff. Plus a Sina - Fields that were not sent as a file, and fields with a JSON `contentType` in the `encoding` map, are read and validated as before. - The `openapi_parameters` gem was merged into openapi_first and is not a dependency anymore. Parameter parsing now lives in openapi_first itself. If you registered a parser for parameters that use a `content` field, use `OpenapiFirst::ParameterContentParsers.register` instead of `OpenapiParameters::ContentParsers.register`. - Changed: `OpenapiFirst::ResponseHeader` (returned by `Response#headers`, renamed from `OpenapiFirst::Header`) exposes `parameter`, an `OpenapiFirst::Parameter`, instead of `node`. +- Changed: `OpenapiFirst::Request#parameters` returns the parameters that are defined for a request as `OpenapiFirst::Parameter` objects, which expose `name`, `location`, `schema`, `required?`, `deprecated?`, `style`, `explode?` and `media_type`. #### Removed deprecations - Removed: `OpenapiFirst::Configuration#request_validation_raise_error` and `#response_validation_raise_error` (both reader and writer), deprecated since 3.0.0. Pass `raise_error:` to middlewares instead. @@ -20,42 +23,41 @@ No stricter / less strict request validation. Mostly internal stuff. Plus a Sina - Removed: `OpenapiFirst::Test::Coverage::TerminalReporter#format`, deprecated since 3.4.0. Use `#report` instead. #### Added -- API coverage now reports skipped requests and responses. -- Added: OpenAPI 3.2 documents are accepted, but not fully supported yet. They are handled using the OpenAPI 3.1 rules, so features introduced in 3.2 may be ignored. Loading such a document prints a warning. Operations defined under `additionalOperations` are routed. See #469. -- Added: `OpenapiFirst::Request#parameters` returns the parameters that are defined for a request as `OpenapiFirst::Parameter` objects, which expose `name`, `location`, `schema`, `required?`, `deprecated?`, `style`, `explode?` and `media_type`. It used to return an internal object with a different interface. -- Added: Show all covered endpoints in HTML coverage reporter and filter covered/uncovered endpoints +- Added: API coverage now reports skipped requests and responses. +- Added: OpenAPI 3.2 documents are accepted, but not fully supported yet. They are handled using the OpenAPI 3.1 rules, so features introduced in 3.2 may be ignored. Loading such a document prints a warning. Operations defined under `additionalOperations` are routed. See [#469](https://github.com/ahx/openapi_first/issues/469). +- Added: Show all covered endpoints in HTML coverage reporter and filter covered/uncovered endpoints. - Added: Sinatra integration (OpenapiFirst::Sinatra) A Sinatra extension to define routes by referencing OpenAPI operations: ```ruby - require 'openapi_first/sinatra' - - class PetsApi < Sinatra::Base - register OpenapiFirst::Sinatra - openapi 'openapi.yaml' - - operation :index_pets do |params| - json index_pets(params[:filter]) - end - - operation :create_pet do - pet = create_pet(parsed_body) - headers['Location'] = operation_url(:show_pet, petId: pet.id) - status :created - json pet - end - - operation :show_pet do |params| - json show_pet(params[:petId]) - end + require 'openapi_first/sinatra' + + class PetsApi < Sinatra::Base + register OpenapiFirst::Sinatra + openapi 'openapi.yaml' + + operation :index_pets do |params| + json index_pets(params[:filter]) + end + + operation :create_pet do + pet = create_pet(parsed_body) + headers['Location'] = operation_url(:show_pet, petId: pet.id) + status :created + json pet + end + + operation :show_pet do |params| + json show_pet(params[:petId]) end + end ``` The HTTP method and path for each route come from the operationId. Request validation is called automatically for these operations. #### Fixed -- Fixed: Validating against a schema from a referenced file raised `ArgumentError` in OpenAPI 3.0 documents when a top-level key of that file collides with a JSON Schema keyword, such as `$ref: 'parameters.yaml#/id'`. The containing file is no longer parsed as a schema itself, so such keys work like any other now. See #348. +- Fixed: Validating against a schema from a referenced file raised `ArgumentError` in OpenAPI 3.0 documents when a top-level key of that file collides with a JSON Schema keyword, such as `$ref: 'parameters.yaml#/id'`. The containing file is no longer parsed as a schema itself, so such keys work like any other now. See [#348](https://github.com/ahx/openapi_first/issues/348). - Fixed: `$ref`s nested inside the schema of a parameter or a response header are resolved now, so these values are unpacked and converted as described. Before, only a `$ref` at the top level of the schema was resolved. See #450. - Fixed: The JSON schema of a parameter that uses a `content` field with a `$ref`'d schema is resolved now. - Fixed: Loading a document no longer raises `NoMethodError` when a parameter has neither `schema` nor `content`. diff --git a/Gemfile.lock b/Gemfile.lock index 539afe40..8b67a9c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - openapi_first (3.4.3) + openapi_first (4.0.0) drb (~> 2.0) hana (~> 1.3) json_schemer (>= 2.1, < 3.0) @@ -95,20 +95,20 @@ GEM date (3.5.1) diff-lcs (1.6.2) drb (2.2.3) - erb (6.0.6) + erb (6.0.7) erubi (1.13.1) globalid (1.4.0) activesupport (>= 6.1) hana (1.3.7) i18n (1.15.2) concurrent-ruby (~> 1.0) - io-console (0.8.2) + io-console (0.9.2) irb (1.18.0) pp (>= 0.6.0) prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.21.2) + json (3.0.2) json_schemer (2.5.0) bigdecimal hana (~> 1.3) @@ -132,12 +132,12 @@ GEM drb (~> 2.0) prism (~> 1.5) mustermann (3.1.1) - net-imap (0.6.6) + net-imap (0.6.7) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.2) + net-protocol (0.3.0) timeout net-smtp (0.5.1) net-protocol @@ -146,7 +146,7 @@ GEM racc (~> 1.4) nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) - parallel (2.1.0) + parallel (2.2.0) parser (3.3.12.0) ast (~> 2.4.1) racc @@ -155,7 +155,7 @@ GEM prettyprint (0.2.0) prism (1.9.0) racc (1.8.1) - rack (3.2.6) + rack (3.2.7) rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) @@ -199,7 +199,7 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.4.2) - rbs (4.1.0) + rbs (4.2.0) logger prism (>= 1.6.0) tsort @@ -209,7 +209,7 @@ GEM rbs (>= 4.0.0) tsort regexp_parser (2.12.0) - reline (0.6.3) + reline (0.7.0) io-console (~> 0.5) rspec (3.13.2) rspec-core (~> 3.13.0) @@ -224,8 +224,8 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.7) - rubocop (1.89.0) - json (~> 2.3) + rubocop (1.91.0) + json (>= 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) parallel (>= 1.10) @@ -238,16 +238,16 @@ GEM rubocop-ast (1.50.0) parser (>= 3.3.7.2) prism (~> 1.7) - rubocop-performance (1.26.1) + rubocop-performance (1.27.0) lint_roller (~> 1.1) - rubocop (>= 1.75.0, < 2.0) + rubocop (>= 1.89.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) - rubydex (0.3.0-arm64-darwin) - rubydex (0.3.0-x86_64-linux) + rubydex (0.4.1-arm64-darwin) + rubydex (0.4.1-x86_64-linux) securerandom (0.4.1) - simplecov (1.0.3) - simpleidn (0.2.3) + simplecov (1.2.0) + simpleidn (0.3.0) sinatra (4.2.1) logger (>= 1.6.0) mustermann (~> 3.0) @@ -256,7 +256,7 @@ GEM rack-session (>= 2.0.0, < 3) tilt (~> 2.0) thor (1.5.0) - tilt (2.8.0) + tilt (2.9.0) timeout (0.6.1) tsort (0.2.0) tzinfo (2.0.6) @@ -270,7 +270,7 @@ GEM base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.8.2) + zeitwerk (2.8.3) PLATFORMS arm64-darwin-23 diff --git a/README.md b/README.md index e303c1b4..d94e8b3f 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ use OpenapiFirst::Middlewares::ResponseValidation if ENV['RACK_ENV'] == 'test' use OpenapiFirst::Middlewares::ResponseValidation, raise_error: false ``` -If you are adopting OpenAPI you can use these options together with [hooks](#hooks) to get notified about requests/responses that do match your API description. +If you are adopting OpenAPI you can use these options together with [hooks](#hooks) to get notified about requests/responses that do not match your API description. ## Contract Testing @@ -239,7 +239,7 @@ OpenapiFirst::Test.setup do |test| test.ignore_response_error do |validated_response, rack_request| # Ignore invalid response bodies on certain paths - validated_request.path.start_with?('/api/legacy/stuff') && validated_request.error.type == :invalid_body + rack_request.path.start_with?('/api/legacy/stuff') && validated_response.error.type == :invalid_body end end ``` @@ -275,7 +275,7 @@ Skip coverage for a request and all responses alltogether of a route with `skip_ ```ruby OpenapiFirst::Test.setup do |test| test.skip_coverage do |path, request_method| - path == '/bookings/{bookingId}' && requests_method == 'DELETE' + path == '/bookings/{bookingId}' && request_method == 'DELETE' end end ``` @@ -371,7 +371,7 @@ definition.validate_request(rack_request, raise_error: true) # Raises OpenapiFir ```ruby validated_response = definition.validate_response(rack_request, rack_response) -# Inspect the response and access parsed parameters and +# Inspect the response and access parsed parameters validated_response.valid? validated_response.invalid? validated_response.error # => Failure object or nil @@ -380,7 +380,7 @@ validated_response.parsed_body validated_response.parsed_headers # Or you can raise an exception if validation fails: -definition.validate_response(rack_request,rack_response, raise_error: true) # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError +definition.validate_response(rack_request, rack_response, raise_error: true) # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError ``` ## Hooks @@ -494,7 +494,7 @@ Here your OpenAPI schema defines endpoints starting with `/resource` but your ac ```ruby oad = OpenapiFirst.load('openapi.yaml') do |config| - config.path = ->(req) { request.path.delete_prefix('/api') } + config.path = ->(req) { req.path.delete_prefix('/api') } end use OpenapiFirst::Middlewares::RequestValidation, oad ``` diff --git a/benchmarks/Gemfile.lock b/benchmarks/Gemfile.lock index 7c9b2163..7fa3b07e 100644 --- a/benchmarks/Gemfile.lock +++ b/benchmarks/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - openapi_first (3.4.3) + openapi_first (4.0.0) drb (~> 2.0) hana (~> 1.3) json_schemer (>= 2.1, < 3.0) @@ -40,7 +40,7 @@ GEM webrick puma (8.0.2) nio4r (~> 2.0) - rack (3.2.6) + rack (3.2.7) rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) @@ -49,7 +49,7 @@ GEM base64 (>= 0.1.0) rack (>= 3.0.0) regexp_parser (2.12.0) - simpleidn (0.2.3) + simpleidn (0.3.0) sinatra (4.2.1) logger (>= 1.6.0) mustermann (~> 3.0) @@ -57,9 +57,9 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - tilt (2.7.0) + tilt (2.9.0) uri (1.1.1) - vernier (1.10.1) + vernier (1.11.0) webrick (1.9.2) PLATFORMS @@ -79,4 +79,4 @@ DEPENDENCIES vernier BUNDLED WITH - 2.6.7 + 4.0.17 diff --git a/lib/openapi_first/failure.rb b/lib/openapi_first/failure.rb index 97cfee01..e9caa061 100644 --- a/lib/openapi_first/failure.rb +++ b/lib/openapi_first/failure.rb @@ -3,7 +3,7 @@ module OpenapiFirst # A failure object returned when validation or parsing of a request or response has failed. # This returned in ValidatedRequest#error and ValidatedResponse#error. - class Failure < Data.define(:type, :message, :errors) # rubocop:disable Style/DataInheritance + class Failure < Data.define(:type, :message, :errors) TYPES = { not_found: [NotFoundError, 'Not found.'], method_not_allowed: [RequestInvalidError, 'Request method is not defined.'], diff --git a/lib/openapi_first/version.rb b/lib/openapi_first/version.rb index 412d3c89..f812d346 100644 --- a/lib/openapi_first/version.rb +++ b/lib/openapi_first/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenapiFirst - VERSION = '3.4.3' + VERSION = '4.0.0' end