openapi_first 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db682408d3927edb6c83c2778f16edecfa428c3e85bdc33ec96664259ddd3f0b
4
- data.tar.gz: 4519ccef3e6c27da6372f52fea09a1e36aa09b5648534edce6caf4d7bdbca8b3
3
+ metadata.gz: ef15e925e193150f3425c32bd94986f3c069f12af86a88ad043886de13c1ca8a
4
+ data.tar.gz: d4f11e849a469be720cbd5973ffb04e039664101ed44484d4b4f4b2c5ef542f8
5
5
  SHA512:
6
- metadata.gz: d37cc2c5d0d99331907a5d565eba3a404e492c7cef71c154e5badd52fe05fb2c7761d17cbe2f94dfec14cda1df59e354f5848bcdc44b515069d325a1316d14aa
7
- data.tar.gz: 20d09dc7debef84d08504202bd6b2acabc33d172486d79d82685bce22362dbf15f920bda84287619bc7517f42da5701c454bf1e23c74a7eeb0c870ab8ae7025c
6
+ metadata.gz: 8aa700d2587944d67f5e2372fe7793c1cd71a76e6c772bf56938564e4f4c4221cb29e54e0ec37bec66c1da370415aabbebb7f722f4cd4fbeba478919308df6cb
7
+ data.tar.gz: 6ef3870f3b011ac35065775da7265d6bc6dff0b121ec0ee42a7e5b47a9fd69d17dbc25ea85723615206d9704afcaee4f65ff3f04e6c85f430ea4781316260ff8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 4.4.0
6
+
7
+ - Changed: Percent-encoded path parameters are now decoded. For example `/files/my%20file` yields `my file` for `/files/{name}`. A `+` stays a `+`. Values with a malformed encoding or that do not decode to valid UTF-8 are kept as they are. If your application decodes path parameters itself, remove that, or values get decoded twice. Known limitation: An encoded delimiter inside an array or object path parameter is decoded before the value is split, so `/files/a%2Cb,c` yields `["a", "b", "c"]`.
8
+ - Added: You can pass a block to `OpenapiFirst::Middlewares::RequestValidation` to configure hooks and other options specifically for that middleware. With a registered name or a Definition it creates a copy of that OAD, so the registered OAD stays unchanged.
9
+ - Fix: `OpenapiFirst.load` now accepts and applies a configuration block.
10
+
5
11
  ## 4.3.0
6
12
 
7
13
  - Fix: Passing a block to `config.register` not works when called inside `OpenapiFirst.configure` block. Before the block was ignored.
data/README.md CHANGED
@@ -84,6 +84,19 @@ The request validation middleware returns a 4xx if the request is invalid or not
84
84
  use OpenapiFirst::Middlewares::RequestValidation
85
85
  ```
86
86
 
87
+ You can pass a block to configure [hooks](#hooks) and other options for this middleware only:
88
+
89
+ ```ruby
90
+ use OpenapiFirst::Middlewares::RequestValidation, :v1 do |config|
91
+ config.request_validation_error_response = :jsonapi
92
+ config.after_request_validation do |validated_request|
93
+ warn validated_request.error.message if validated_request.invalid?
94
+ end
95
+ end
96
+ ```
97
+
98
+ If you pass a registered name or a Definition together with a block, the middleware uses a copy of that OAD that keeps its configuration and adds yours. The registered OAD stays unchanged.
99
+
87
100
  #### Error responses
88
101
 
89
102
  openapi_first produces a useful machine readable error response that can be customized.
@@ -6,16 +6,20 @@ module OpenapiFirst
6
6
  def initialize(parent:)
7
7
  super()
8
8
  @parent = parent
9
- @request_validation_error_response = parent.request_validation_error_response
9
+ @request_validation_error_response = nil
10
10
  @path = parent.path
11
11
  @regexp_resolver = parent.regexp_resolver
12
12
  end
13
13
 
14
14
  private attr_reader :parent
15
15
 
16
+ def request_validation_error_response
17
+ @request_validation_error_response || parent.request_validation_error_response
18
+ end
19
+
16
20
  HOOKS.each do |hook|
17
21
  define_method(hook) do |&block|
18
- return hooks[hook].chain(parent.hooks[hook]) if block.nil?
22
+ return hooks[hook].chain(parent.public_send(hook)) if block.nil?
19
23
 
20
24
  hooks[hook] << block
21
25
  block
@@ -24,10 +24,11 @@ module OpenapiFirst
24
24
  # @param contents [Hash] The OpenAPI document.
25
25
  # @param filepath [String] The file path of the OpenAPI document.
26
26
  # @param path_prefix [String,nil] An optional path prefix, that is not documented, that all requests begin with.
27
- def initialize(contents, filepath = nil, path_prefix = nil)
27
+ # @param config [Configuration] The configuration to use. Gets yielded and frozen.
28
+ def initialize(contents, filepath = nil, path_prefix = nil, config: OpenapiFirst.configuration.child)
28
29
  @filepath = filepath
29
30
  @path_prefix = path_prefix
30
- @config = OpenapiFirst.configuration.child
31
+ @config = config
31
32
  yield @config if block_given?
32
33
  @config.freeze
33
34
  @router = Builder.build_router(contents, filepath:, config:)
@@ -35,6 +36,14 @@ module OpenapiFirst
35
36
  @paths = @router.routes.map(&:path).to_a.uniq # TODO: Refactor
36
37
  end
37
38
 
39
+ # Returns a new Definition of the same document whose configuration is a child of this one's.
40
+ # @visibility private
41
+ # @yield [Configuration]
42
+ # @return [Definition]
43
+ def configure(&)
44
+ Definition.new(@resolved, filepath, path_prefix, config: @config.child, &)
45
+ end
46
+
38
47
  # Gives access to the raw resolved Hash. Like `mydefinition['components'].dig('schemas', 'Stations')`
39
48
  # @!method [](key)
40
49
  # @return [Hash]
@@ -29,20 +29,19 @@ module OpenapiFirst
29
29
  # :error_response The Class to use for error responses.
30
30
  # This can be a Symbol-name of an registered error response (:default, :jsonapi)
31
31
  # or it can be set to false to disable returning a response.
32
- # default: OpenapiFirst::Plugins::Default::ErrorResponse (Config.default_options.error_response)
33
- def initialize(app, spec = nil, options = {})
32
+ # default: the configured request_validation_error_response
33
+ # @yield [Configuration] Optional block to configure hooks and other options for this middleware.
34
+ # If spec is a registered name or a Definition, this uses a new Definition that inherits its configuration,
35
+ # so the registered Definition is not changed.
36
+ def initialize(app, spec = nil, options = {}, &)
34
37
  @app = app
35
38
  if spec.is_a?(Hash)
36
39
  options = spec
37
40
  spec = options[:spec]
38
41
  end
39
42
  @raise = options.fetch(:raise_error, false)
43
+ @definition = OpenapiFirst.load(spec || :default, &)
40
44
  @error_response_class = error_response_option(options[:error_response])
41
-
42
- spec ||= :default
43
- spec = OpenapiFirst[spec] if spec.is_a?(Symbol)
44
-
45
- @definition = spec.is_a?(Definition) ? spec : OpenapiFirst.load(spec)
46
45
  end
47
46
 
48
47
  # @attr_reader [Proc] app The upstream Rack application
@@ -70,7 +69,7 @@ module OpenapiFirst
70
69
  return if value == false
71
70
  return OpenapiFirst.find_error_response(value) if value.is_a?(Symbol)
72
71
 
73
- value || OpenapiFirst.configuration.request_validation_error_response
72
+ value || @definition.config.request_validation_error_response
74
73
  end
75
74
  end
76
75
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rack/utils'
3
4
  require_relative '../path_template'
4
5
  require_relative 'segment_pattern'
5
6
 
@@ -23,7 +24,10 @@ module OpenapiFirst
23
24
  def match(path)
24
25
  values = []
25
26
  value, names = find(path.split('/', -1), 0, values)
26
- [value, names.zip(values).to_h] if value
27
+ return unless value
28
+
29
+ values.map! { decode(_1) } if path.include?('%')
30
+ [value, names.zip(values).to_h]
27
31
  end
28
32
 
29
33
  def find(segments, index, values)
@@ -47,6 +51,11 @@ module OpenapiFirst
47
51
 
48
52
  private
49
53
 
54
+ def decode(value)
55
+ decoded = Rack::Utils.unescape_path(value).force_encoding(Encoding::UTF_8)
56
+ decoded.valid_encoding? ? decoded : value
57
+ end
58
+
50
59
  def find_partial(segments, index, values)
51
60
  @partial.each_value do |pattern, node|
52
61
  captures = pattern.match(segments[index])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '4.3.0'
4
+ VERSION = '4.4.0'
5
5
  end
data/lib/openapi_first.rb CHANGED
@@ -62,15 +62,20 @@ module OpenapiFirst
62
62
  configuration.plugin(name, **)
63
63
  end
64
64
 
65
- # Load and dereference an OpenAPI spec file or return the Definition if it's already loaded
66
- # @param filepath_or_definition [String, Definition] The path to the file or a Definition object
65
+ # Load and dereference an OpenAPI spec file or return the Definition if it's already loaded.
66
+ # If a block is given together with a Definition or Symbol,
67
+ # this returns a new Definition with the configuration block applied.
68
+ # @param filepath_or_definition [String, Symbol, Definition] The path to the file, a registered name or a Definition
67
69
  # @param only [Proc, nil] An optional proc to filter paths. It is called with the path string and should return
68
70
  # true/false
69
71
  # @param path_prefix [String, nil] An optional path prefix, that is not documented, that all requests begin with.
72
+ # @yieldparam config [Configuration] Configuration of the returned Definition.
70
73
  # @return [Definition]
71
74
  def self.load(filepath_or_definition, only: nil, path_prefix: nil, &)
72
- return filepath_or_definition if filepath_or_definition.is_a?(Definition)
73
- return self[filepath_or_definition] if filepath_or_definition.is_a?(Symbol)
75
+ if filepath_or_definition.is_a?(Definition) || filepath_or_definition.is_a?(Symbol)
76
+ definition = self[filepath_or_definition]
77
+ return block_given? ? definition.configure(&) : definition
78
+ end
74
79
 
75
80
  filepath = filepath_or_definition
76
81
  raise FileNotFoundError, "File not found: #{filepath}" unless File.exist?(filepath)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_first
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Haller