openapi_first 4.0.0 → 4.1.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/openapi_first/builder.rb +3 -3
- data/lib/openapi_first/file_loader.rb +9 -1
- data/lib/openapi_first/parameters_parser.rb +14 -2
- data/lib/openapi_first/query_string_parser.rb +14 -2
- data/lib/openapi_first/ref_resolver.rb +12 -1
- data/lib/openapi_first/request.rb +10 -1
- data/lib/openapi_first/response_parser.rb +1 -1
- data/lib/openapi_first/utils.rb +10 -0
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +2 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac70785f7221fecd32d0a1408c8c12ee14ea75f5d45be6c9c1efce38e735d02e
|
|
4
|
+
data.tar.gz: 0cea3e400f9eed814fc9924c551893d78444920d99f7573d59426a7fa7a61068
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c8883be1aad4a502ac4596ecb91765b855e2d515ec3e6ad11519a350ed43f29ae591645b71f7f7da709775cd1322c9dd4803f1474032bf5e8004abfd4b0eb51
|
|
7
|
+
data.tar.gz: 21e78cbcc5607436c1a32e108f786b0ddfc7ad434d2d103cee6f506eb90bef74d6dfcc4977ce748b612aeeab0c9e50c3a372c77da66a291275ead799bf677226
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 4.1.0
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- A query string with more parameters than Rack accepts (4096 by default) produces a 400 response now instead of raising `Rack::QueryParser::QueryLimitError`. See the [rack documentation](https://rack.github.io/rack/main/index.html#configuration) about how to configure this.
|
|
9
|
+
|
|
10
|
+
## 4.0.1
|
|
11
|
+
|
|
12
|
+
- Fix: Reduced memory usage with large API descriptions. Schemas no longer each deep-copy (stringify keys) the whole OAD to build a root schema. This is a json_schemer specific optimization.
|
|
13
|
+
|
|
5
14
|
## 4.0.0
|
|
6
15
|
|
|
7
16
|
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.
|
|
@@ -126,10 +126,10 @@ module OpenapiFirst
|
|
|
126
126
|
cookie = build_parameters(grouped[:cookie])
|
|
127
127
|
ParsedParameters.new(
|
|
128
128
|
all: [*path, *query, *header, *cookie].freeze,
|
|
129
|
-
path_parser: grouped[:path] && ParametersParser.new(path),
|
|
129
|
+
path_parser: grouped[:path] && ParametersParser.new(path, check_encoding: false),
|
|
130
130
|
query_parser: QueryStringParser.new(query),
|
|
131
|
-
header_parser: grouped[:header] && ParametersParser.new(header),
|
|
132
|
-
cookie_parser: grouped[:cookie] && ParametersParser.new(cookie),
|
|
131
|
+
header_parser: grouped[:header] && ParametersParser.new(header, check_encoding: false),
|
|
132
|
+
cookie_parser: grouped[:cookie] && ParametersParser.new(cookie, check_encoding: true),
|
|
133
133
|
path_schema: build_parameter_schema(grouped[:path]),
|
|
134
134
|
query_schema: build_parameter_schema(grouped[:query]),
|
|
135
135
|
header_schema: build_parameter_schema(grouped[:header]),
|
|
@@ -26,12 +26,20 @@ module OpenapiFirst
|
|
|
26
26
|
if extname == '.json'
|
|
27
27
|
::JSON.parse(body)
|
|
28
28
|
elsif ['.yaml', '.yml'].include?(extname)
|
|
29
|
-
|
|
29
|
+
load_yaml(body, file_path)
|
|
30
30
|
else
|
|
31
31
|
body
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def load_yaml(body, file_path)
|
|
40
|
+
Utils.deep_stringify_keys(YAML.unsafe_load(body))
|
|
41
|
+
rescue ::JSON::GeneratorError => e
|
|
42
|
+
raise Error, "Could not load #{file_path.inspect}: #{e.message}"
|
|
43
|
+
end
|
|
36
44
|
end
|
|
37
45
|
end
|
|
@@ -5,8 +5,10 @@ module OpenapiFirst
|
|
|
5
5
|
# @visibility private
|
|
6
6
|
class ParametersParser
|
|
7
7
|
# @param parameters [Array<Parameter>]
|
|
8
|
-
|
|
8
|
+
# @param check_encoding [Boolean] Raise Rack::Utils::InvalidParameterError if a value has an invalid encoding.
|
|
9
|
+
def initialize(parameters, check_encoding:)
|
|
9
10
|
@parameters = parameters
|
|
11
|
+
@check_encoding = check_encoding
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
attr_reader :parameters
|
|
@@ -16,8 +18,18 @@ module OpenapiFirst
|
|
|
16
18
|
parameters.each_with_object({}) do |parameter, result|
|
|
17
19
|
next unless parameters_hash.key?(parameter.name)
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
value = parameters_hash[parameter.name]
|
|
22
|
+
check_encoding!(value) if @check_encoding
|
|
23
|
+
result[parameter.name] = parameter.unpack_and_convert(value)
|
|
20
24
|
end
|
|
21
25
|
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def check_encoding!(value)
|
|
30
|
+
return if !value.is_a?(::String) || value.valid_encoding?
|
|
31
|
+
|
|
32
|
+
raise Rack::Utils::InvalidParameterError, "invalid encoding (#{value.inspect})"
|
|
33
|
+
end
|
|
22
34
|
end
|
|
23
35
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rack'
|
|
4
|
+
require 'rack/query_parser'
|
|
4
5
|
require_relative 'parameters_parser'
|
|
5
6
|
|
|
6
7
|
module OpenapiFirst
|
|
@@ -10,11 +11,15 @@ module OpenapiFirst
|
|
|
10
11
|
DEEP_PROP = '\[([\w-]+)\]$'
|
|
11
12
|
private_constant :DEEP_PROP
|
|
12
13
|
|
|
14
|
+
# Rack < 3.1 does not know about Rack::BadRequest
|
|
15
|
+
BAD_REQUEST = defined?(Rack::BadRequest) ? Rack::BadRequest : Rack::QueryParser::QueryLimitError
|
|
16
|
+
private_constant :BAD_REQUEST
|
|
17
|
+
|
|
13
18
|
# @param parameters [Array<Parameter>]
|
|
14
19
|
def initialize(parameters)
|
|
15
20
|
@parameters = parameters
|
|
16
21
|
@deep_object_parameters, flat_parameters = parameters.partition(&:deep_object?)
|
|
17
|
-
@flat_parser = ParametersParser.new(flat_parameters)
|
|
22
|
+
@flat_parser = ParametersParser.new(flat_parameters, check_encoding: false)
|
|
18
23
|
@deep_object_properties = {}
|
|
19
24
|
@deep_object_regex = {}
|
|
20
25
|
@deep_object_parameters.each do |parameter|
|
|
@@ -54,16 +59,23 @@ module OpenapiFirst
|
|
|
54
59
|
return if unknown.empty?
|
|
55
60
|
|
|
56
61
|
unknown
|
|
62
|
+
rescue Rack::Utils::InvalidParameterError
|
|
63
|
+
nil
|
|
57
64
|
end
|
|
58
65
|
|
|
59
66
|
private
|
|
60
67
|
|
|
61
68
|
def parse_query(query_string)
|
|
62
69
|
Rack::Utils.parse_query(query_string) do |string|
|
|
63
|
-
Rack::Utils.unescape(string)
|
|
70
|
+
unescaped = Rack::Utils.unescape(string)
|
|
71
|
+
next unescaped if unescaped.valid_encoding?
|
|
72
|
+
|
|
73
|
+
raise Rack::Utils::InvalidParameterError, "invalid encoding (#{string})"
|
|
64
74
|
rescue ArgumentError => e
|
|
65
75
|
raise Rack::Utils::InvalidParameterError, e.message
|
|
66
76
|
end
|
|
77
|
+
rescue BAD_REQUEST => e
|
|
78
|
+
raise Rack::Utils::InvalidParameterError, e.message
|
|
67
79
|
end
|
|
68
80
|
|
|
69
81
|
def parse_deep_object(parameter, parsed_query)
|
|
@@ -184,6 +184,17 @@ module OpenapiFirst
|
|
|
184
184
|
vocabulary: { 'https://json-schema.org/draft/2020-12/vocab/core' => true }
|
|
185
185
|
)
|
|
186
186
|
|
|
187
|
+
# This class is here to monkey-patch JSONSchemer::Schema when we are passing
|
|
188
|
+
# an instance to sub-schemas. It skips stringifying all keys of the passed object to
|
|
189
|
+
# allocate fewer objects. We don't have to stringify the keys, because they are already stringified upstream.
|
|
190
|
+
# @visibility private
|
|
191
|
+
class DocumentRootSchema < JSONSchemer::Schema
|
|
192
|
+
private
|
|
193
|
+
|
|
194
|
+
def deep_stringify_keys(value) = value
|
|
195
|
+
end
|
|
196
|
+
private_constant :DocumentRootSchema
|
|
197
|
+
|
|
187
198
|
def initialize(value:, context:, base_uri:, options:)
|
|
188
199
|
@value = value
|
|
189
200
|
@context = context
|
|
@@ -197,7 +208,7 @@ module OpenapiFirst
|
|
|
197
208
|
|
|
198
209
|
def schema
|
|
199
210
|
@schema ||= begin
|
|
200
|
-
root_schema =
|
|
211
|
+
root_schema = DocumentRootSchema.new(context, base_uri:, **options, meta_schema: DOCUMENT_META_SCHEMA)
|
|
201
212
|
apply_dialect(root_schema)
|
|
202
213
|
JSONSchemer::Schema.new(value, nil, root_schema, base_uri:, **options)
|
|
203
214
|
end
|
|
@@ -61,6 +61,9 @@ module OpenapiFirst
|
|
|
61
61
|
query, query_error = parse_query(request.env[Rack::QUERY_STRING])
|
|
62
62
|
return [nil, query_error] if query_error
|
|
63
63
|
|
|
64
|
+
cookies = parse_cookies(request.env[Rack::HTTP_COOKIE])
|
|
65
|
+
return [nil, cookies] if cookies.is_a?(Failure)
|
|
66
|
+
|
|
64
67
|
body = @body_parsers&.call(request)
|
|
65
68
|
return [nil, body] if body.is_a?(Failure)
|
|
66
69
|
|
|
@@ -68,7 +71,7 @@ module OpenapiFirst
|
|
|
68
71
|
path: @path_parser&.unpack(route_params),
|
|
69
72
|
query:,
|
|
70
73
|
headers: @header_parser&.unpack(RequestHeaders.new(request.env)),
|
|
71
|
-
cookies
|
|
74
|
+
cookies:,
|
|
72
75
|
body:
|
|
73
76
|
), nil]
|
|
74
77
|
end
|
|
@@ -79,6 +82,12 @@ module OpenapiFirst
|
|
|
79
82
|
[nil, Failure.new(:invalid_query, message: 'Invalid query parameter.')]
|
|
80
83
|
end
|
|
81
84
|
|
|
85
|
+
def parse_cookies(cookie_header)
|
|
86
|
+
@cookie_parser&.unpack(Rack::Utils.parse_cookies_header(cookie_header))
|
|
87
|
+
rescue Rack::Utils::InvalidParameterError
|
|
88
|
+
Failure.new(:invalid_cookie, message: 'Invalid cookie value.')
|
|
89
|
+
end
|
|
90
|
+
|
|
82
91
|
def build_body_parser(content_type, encoding)
|
|
83
92
|
RequestBodyParsers[content_type, { encoding: encoding || {} }]
|
|
84
93
|
end
|
data/lib/openapi_first.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'openapi_first/json'
|
|
|
4
4
|
require_relative 'openapi_first/file_loader'
|
|
5
5
|
require_relative 'openapi_first/errors'
|
|
6
6
|
require_relative 'openapi_first/registry'
|
|
7
|
+
require_relative 'openapi_first/utils'
|
|
7
8
|
require_relative 'openapi_first/configuration'
|
|
8
9
|
require_relative 'openapi_first/child_configuration'
|
|
9
10
|
require_relative 'openapi_first/definition'
|
|
@@ -82,7 +83,7 @@ module OpenapiFirst
|
|
|
82
83
|
# @return [Definition]
|
|
83
84
|
# TODO: This needs to work with unresolved contents as well
|
|
84
85
|
def self.parse(contents, only: nil, filepath: nil, path_prefix: nil, &)
|
|
85
|
-
contents =
|
|
86
|
+
contents = Utils.deep_stringify_keys(contents) # Deeply stringify keys, because of YAML. See https://github.com/ahx/openapi_first/issues/367
|
|
86
87
|
contents['paths'].filter!(&->(key, _) { only.call(key) }) if only
|
|
87
88
|
Definition.new(contents, filepath, path_prefix, &)
|
|
88
89
|
end
|
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.
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Haller
|
|
@@ -154,6 +154,7 @@ files:
|
|
|
154
154
|
- lib/openapi_first/test/observe.rb
|
|
155
155
|
- lib/openapi_first/test/observer_middleware.rb
|
|
156
156
|
- lib/openapi_first/test/plain_helpers.rb
|
|
157
|
+
- lib/openapi_first/utils.rb
|
|
157
158
|
- lib/openapi_first/validated_request.rb
|
|
158
159
|
- lib/openapi_first/validated_response.rb
|
|
159
160
|
- lib/openapi_first/validators/multipart_request_body.rb
|