From 3d15f074deb1ce82424c3b5383fd881935b3dcb1 Mon Sep 17 00:00:00 2001 From: Joshua Studt Date: Tue, 16 Dec 2025 17:31:45 -0500 Subject: [PATCH] add after_response_body_property_validation_hook --- .gitignore | 3 + README.md | 1 + lib/openapi_first/builder.rb | 5 +- lib/openapi_first/configuration.rb | 1 + spec/hooks_spec.rb | 88 ++++++++++++++++++++++++------ 5 files changed, 80 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 2914690d..a57732fa 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ # rspec failure tracking .rspec_status Gemfile.*.lock + +# MacOS +.DS_Store diff --git a/README.md b/README.md index f30c39ae..998a9a16 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,7 @@ Available hooks: - `after_response_validation` - `after_request_parameter_property_validation` - `after_request_body_property_validation` +- `after_response_body_property_validation` Setup per per instance: diff --git a/lib/openapi_first/builder.rb b/lib/openapi_first/builder.rb index 8cb1cb2b..3e28f59e 100644 --- a/lib/openapi_first/builder.rb +++ b/lib/openapi_first/builder.rb @@ -165,7 +165,10 @@ def build_responses(responses:, request:) responses.flat_map do |status, response_object| headers = build_response_headers(response_object['headers']) response_object['content']&.map do |content_type, content_object| - content_schema = content_object['schema'].schema(configuration: schemer_configuration) + content_schema = content_object['schema'].schema( + configuration: schemer_configuration, + after_property_validation: config.after_response_body_property_validation + ) Response.new(status:, headers:, content_type:, diff --git a/lib/openapi_first/configuration.rb b/lib/openapi_first/configuration.rb index 090c7c97..bc49c86b 100644 --- a/lib/openapi_first/configuration.rb +++ b/lib/openapi_first/configuration.rb @@ -8,6 +8,7 @@ class Configuration after_response_validation after_request_parameter_property_validation after_request_body_property_validation + after_response_body_property_validation ].freeze def initialize diff --git a/spec/hooks_spec.rb b/spec/hooks_spec.rb index 07e07463..418b45ed 100644 --- a/spec/hooks_spec.rb +++ b/spec/hooks_spec.rb @@ -191,22 +191,58 @@ def build_request(path, method: 'GET', body: nil) } } ], - 'get' => { - 'parameters' => [ - { - 'name' => 'page', - 'in' => 'query', - 'schema' => { - 'type' => 'integer' + 'post' => { + 'requestBody' => { + 'content' => { + 'application/json' => { + 'schema' => { + 'type' => 'object', + 'properties' => { + 'name' => { + 'type' => 'string' + } + } + } } } - ], - 'responses' => { - '200' => { - 'description' => 'ok' + } + } + } + } + } + definition = OpenapiFirst.parse(spec) do |config| + config.after_request_body_property_validation do |data, property, property_schema| + called << [data, property, property_schema] + end + end + + request = build_request('/blue?page=2', method: 'POST', body: '{"name": "Quentin"}') + validated = definition.validate_request(request) + + expect(validated).to be_valid + expect(called).to eq([ + [{ 'name' => 'Quentin' }, 'name', { 'type' => 'string' }] + ]) + end + end + + describe 'after_response_body_property_validation' do + let(:called) { [] } + + it 'calls the hook' do + spec = { + 'openapi' => '3.1.0', + 'paths' => { + '/{color}' => { + 'parameters' => [ + { + 'name' => 'color', + 'in' => 'path', + 'schema' => { + 'type' => 'string' } } - }, + ], 'post' => { 'requestBody' => { 'content' => { @@ -221,23 +257,41 @@ def build_request(path, method: 'GET', body: nil) } } } + }, + 'responses' => { + '200' => { + 'description' => 'ok', + 'content' => { + 'application/json' => { + 'schema' => { + 'type' => 'object', + 'properties' => { + 'foo' => { + 'type' => 'string' + } + } + } + } + } + } } } } } } definition = OpenapiFirst.parse(spec) do |config| - config.after_request_body_property_validation do |data, property, property_schema| + config.after_response_body_property_validation do |data, property, property_schema| called << [data, property, property_schema] end end - definition.validate_request(build_request('/blue?page=2', method: 'POST', body: '{"name": "Quentin"}')) + request = build_request('/blue?page=2', method: 'POST', body: '{"name": "Quentin"}') + response = Rack::Response.new('{"foo": "bar"}', 200, { 'Content-Type' => 'application/json' }) + validated = definition.validate_response(request, response) + expect(validated).to be_valid expect(called).to eq([ - [{ 'name' => 'Quentin' }, 'name', { - 'type' => 'string' - }] + [{ 'foo' => 'bar' }, 'foo', { 'type' => 'string' }] ]) end end