From 3da45078ec0be5a4165a5b76f3d085123dcca71c Mon Sep 17 00:00:00 2001 From: Andreas Haller Date: Sat, 29 Mar 2025 22:25:46 +0100 Subject: [PATCH] Allow passing the OAD as a first argument of the Middlewares This is simpler and I don't like "spec" as an argument name --- CHANGELOG.md | 1 + .../middlewares/request_validation.rb | 13 +++++++++--- .../middlewares/response_validation.rb | 12 +++++++---- spec/middlewares/request_validation_spec.rb | 16 +++++++++++++++ spec/middlewares/response_validation_spec.rb | 20 +++++++++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7d22da..dfafdd17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- Middlewares now accept the OAD as a first positional argument instead of `:spec` inside the options hash. - `OpenapiFirst::Test::Methods[MyApplication]` returns a Module which adds an `app` method to be used by rack-test alonside the `assert_api_conform` method. - Make default coverage report less verbose The default formatter (TerminalFormatter) no longer prints all un-requested requests by default. You can set `test.coverage_formatter_options = { focused: false }` to get back the old behavior diff --git a/lib/openapi_first/middlewares/request_validation.rb b/lib/openapi_first/middlewares/request_validation.rb index eddf44f2..bfca5ebe 100644 --- a/lib/openapi_first/middlewares/request_validation.rb +++ b/lib/openapi_first/middlewares/request_validation.rb @@ -6,19 +6,26 @@ module Middlewares # A Rack middleware to validate requests against an OpenAPI API description class RequestValidation # @param app The parent Rack application - # @param options An optional Hash of configuration options to override defaults + # @param spec [String, OpenapiFirst::Definition] Path to the OpenAPI file or an instance of Definition. + # @param options Hash + # :spec [String, OpenapiFirst::Definition] Path to the OpenAPI file or an instance of Definition. + # This will be deprecated. Please use spec argument instead. # :raise_error A Boolean indicating whether to raise an error if validation fails. # default: false # :error_response The Class to use for error responses. # This can be a Symbol-name of an registered error response (:default, :jsonapi) # or it can be set to false to disable returning a response. # default: OpenapiFirst::Plugins::Default::ErrorResponse (Config.default_options.error_response) - def initialize(app, options = {}) + def initialize(app, spec = nil, options = {}) @app = app + if spec.is_a?(Hash) + options = spec + spec = options.fetch(:spec) + end @raise = options.fetch(:raise_error, OpenapiFirst.configuration.request_validation_raise_error) @error_response_class = error_response_option(options[:error_response]) - spec = options.fetch(:spec) + spec ||= options.fetch(:spec) raise "You have to pass spec: when initializing #{self.class}" unless spec @definition = spec.is_a?(Definition) ? spec : OpenapiFirst.load(spec) diff --git a/lib/openapi_first/middlewares/response_validation.rb b/lib/openapi_first/middlewares/response_validation.rb index bfe4963c..62b9a634 100644 --- a/lib/openapi_first/middlewares/response_validation.rb +++ b/lib/openapi_first/middlewares/response_validation.rb @@ -6,15 +6,19 @@ module OpenapiFirst module Middlewares # A Rack middleware to validate requests against an OpenAPI API description class ResponseValidation - # @param app The parent Rack application + # @param spec [String, OpenapiFirst::Definition] Path to the OpenAPI file or an instance of Definition # @param options Hash - # :spec [String, OpenapiFirst::Definition] Path to the OpenAPI file or an instance of Definition + # :spec [String, OpenapiFirst::Definition] Path to the OpenAPI file or an instance of Definition. + # This will be deprecated. Please use spec argument instead. # :raise_error [Boolean] Whether to raise an error if validation fails. default: true - def initialize(app, options = {}) + def initialize(app, spec = nil, options = {}) @app = app + if spec.is_a?(Hash) + options = spec + spec = options.fetch(:spec) + end @raise = options.fetch(:raise_error, OpenapiFirst.configuration.response_validation_raise_error) - spec = options.fetch(:spec) raise "You have to pass spec: when initializing #{self.class}" unless spec @definition = spec.is_a?(Definition) ? spec : OpenapiFirst.load(spec) diff --git a/spec/middlewares/request_validation_spec.rb b/spec/middlewares/request_validation_spec.rb index 96793de9..258d4057 100644 --- a/spec/middlewares/request_validation_spec.rb +++ b/spec/middlewares/request_validation_spec.rb @@ -32,6 +32,22 @@ end end + context 'when OAD is passed as first argument' do + let(:app) do + Rack::Builder.app do + use(OpenapiFirst::Middlewares::RequestValidation, File.expand_path('../data/petstore-expanded.yaml', __dir__)) + run ->(_) {} + end + end + + it 'returns 400 is request is invalid' do + header 'Content-Type', 'application/json' + post '/pets', 'not json' + + expect(last_response.status).to eq 400 + end + end + context 'when parameter is invalid' do it 'returns 400' do get '/pets?limit=three' diff --git a/spec/middlewares/response_validation_spec.rb b/spec/middlewares/response_validation_spec.rb index f0af4fe2..b27eb2ed 100644 --- a/spec/middlewares/response_validation_spec.rb +++ b/spec/middlewares/response_validation_spec.rb @@ -25,6 +25,26 @@ end let(:response) { Rack::Response.new(response_body, status, headers) } + context 'with path to OAD as first argument' do + let(:response_body) { '2' } + + let(:app) do + res = response + definition = spec + Rack::Builder.app do + use Rack::Lint + use OpenapiFirst::Middlewares::ResponseValidation, definition + run ->(_env) { res.finish } + end + end + + it 'fails if request is inavlid' do + expect do + get '/pets' + end.to raise_error OpenapiFirst::ResponseInvalidError + end + end + context 'without content-type header' do let(:headers) do { 'X-HEAD' => '/api/next-page' }