diff --git a/CHANGELOG.md b/CHANGELOG.md index bc85748a..f4fdbe85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 4.2.0 + +- Added `config.regexp_resolver` configuration option, `'ruby'` (default) or `'ecma'` (also as Symbol). The value is passed to json_schemer's `regexp_resolver` configuration. In OpenAPI, the pattern keyword specifies a regular expression that must conform to the ECMA-262 standard. So using `'ecma'` is recommended. + ## 4.1.0 - Query and cookie parameter values with invalid encoding (like `?search=%C3`) produce a 400 response now. Before such a value passed validation or raised an `ArgumentError`. This applies to documented and undocumented query parameter, but only to documented cookie parameters. diff --git a/Gemfile.lock b/Gemfile.lock index cb1213f8..589b22ad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - openapi_first (4.1.0) + openapi_first (4.2.0) drb (~> 2.0) hana (~> 1.3) json_schemer (>= 2.1, < 3.0) @@ -146,7 +146,7 @@ GEM racc (~> 1.4) nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) - parallel (2.2.0) + parallel (2.3.0) parser (3.3.12.0) ast (~> 2.4.1) racc @@ -246,7 +246,7 @@ GEM rubydex (0.4.1-arm64-darwin) rubydex (0.4.1-x86_64-linux) securerandom (0.4.1) - simplecov (1.3.1) + simplecov (1.3.2) simpleidn (0.3.0) sinatra (4.2.1) logger (>= 1.6.0) diff --git a/benchmarks/Gemfile.lock b/benchmarks/Gemfile.lock index 7ac78ab2..9afb1370 100644 --- a/benchmarks/Gemfile.lock +++ b/benchmarks/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - openapi_first (4.1.0) + openapi_first (4.2.0) drb (~> 2.0) hana (~> 1.3) json_schemer (>= 2.1, < 3.0) diff --git a/lib/openapi_first/builder.rb b/lib/openapi_first/builder.rb index ecff7367..0acb7e27 100644 --- a/lib/openapi_first/builder.rb +++ b/lib/openapi_first/builder.rb @@ -27,9 +27,9 @@ def self.build_router(contents, filepath:, config:) end def initialize(contents, filepath:, config:) + @config = config meta_schema = detect_meta_schema(contents, filepath) @schemer_configuration = build_schemer_config(filepath:, meta_schema:) - @config = config @file_loader = FileLoader.new ref_resolver = RefResolver.new(file_loader:) @contents = ref_resolver.for(contents, filepath:) @@ -47,6 +47,7 @@ def build_schemer_config(filepath:, meta_schema:) end result.meta_schema = meta_schema result.insert_property_defaults = true + result.regexp_resolver = config.regexp_resolver if config.regexp_resolver result end diff --git a/lib/openapi_first/child_configuration.rb b/lib/openapi_first/child_configuration.rb index 0a32ebfe..7c842d39 100644 --- a/lib/openapi_first/child_configuration.rb +++ b/lib/openapi_first/child_configuration.rb @@ -8,6 +8,7 @@ def initialize(parent:) @parent = parent @request_validation_error_response = parent.request_validation_error_response @path = parent.path + @regexp_resolver = parent.regexp_resolver end private attr_reader :parent diff --git a/lib/openapi_first/configuration.rb b/lib/openapi_first/configuration.rb index 5479b835..4b33de84 100644 --- a/lib/openapi_first/configuration.rb +++ b/lib/openapi_first/configuration.rb @@ -16,13 +16,14 @@ def initialize @request_validation_error_response = OpenapiFirst.find_error_response(:default) @hooks = HOOKS.to_h { [_1, Set.new] } @path = nil + @regexp_resolver = nil end def register(path_or_definition, as: :default) OpenapiFirst.register(path_or_definition, as:) end - attr_reader :hooks, :request_validation_error_response + attr_reader :hooks, :request_validation_error_response, :regexp_resolver attr_accessor :path # Return a child configuration that still receives updates of global hooks. @@ -49,6 +50,10 @@ def plugin(name, **) Plugins.load(name).configure(self, **) end + def regexp_resolver=(resolver) + @regexp_resolver = resolver.is_a?(Symbol) ? resolver.to_s : resolver + end + def request_validation_error_response=(mod) @request_validation_error_response = if mod.is_a?(Symbol) OpenapiFirst.find_error_response(mod) diff --git a/lib/openapi_first/version.rb b/lib/openapi_first/version.rb index f13ceffc..7d5814fd 100644 --- a/lib/openapi_first/version.rb +++ b/lib/openapi_first/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module OpenapiFirst - VERSION = '4.1.0' + VERSION = '4.2.0' end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 86695fc7..bc809cbb 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -39,6 +39,44 @@ end end + describe '#regexp_resolver=' do + let(:document) do + { + 'openapi' => '3.1.0', + 'info' => { 'title' => 'Ids', 'version' => '1' }, + 'paths' => { + '/ids' => { + 'get' => { + 'parameters' => [ + { 'name' => 'ids', 'in' => 'query', 'schema' => { 'type' => 'string', 'pattern' => '^\d+(,\d+)*$' } } + ], + 'responses' => { '200' => { 'description' => 'OK' } } + } + } + } + } + end + + def valid?(definition, query) + request = Rack::Request.new(Rack::MockRequest.env_for("/ids?#{query}")) + definition.validate_request(request).valid? + end + + it 'anchors ^ and $ to lines by default' do + definition = OpenapiFirst.parse(document) + + expect(valid?(definition, 'ids=1,2')).to be(true) + expect(valid?(definition, 'ids=1%0Aa')).to be(true) + end + + it 'anchors ^ and $ to the whole value with ecma' do + definition = OpenapiFirst.parse(document) { |config| config.regexp_resolver = :ecma } + + expect(valid?(definition, 'ids=1,2')).to be(true) + expect(valid?(definition, 'ids=1%0Aa')).to be(false) + end + end + describe '#child' do it 'clones actions' do config = OpenapiFirst::Configuration.new