openapi_first 3.0.1 → 3.1.0.beta1
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 +8 -0
- data/README.md +1 -1
- data/lib/openapi_first/parsed_request.rb +5 -0
- data/lib/openapi_first/request.rb +32 -14
- data/lib/openapi_first/test.rb +44 -12
- data/lib/openapi_first/validated_request.rb +7 -1
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +2 -2
- metadata +4 -4
- data/lib/openapi_first/request_parser.rb +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d07efa6286183d4d90a9126e3b5082e27fad9efa966f9aa7623ca280b51c6f8
|
|
4
|
+
data.tar.gz: 8c6e34a8dffd1b1dd7f8e71cfbb85ded8dd260d2f96631c8e7b91944ddb4033b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f60e8101c9a716ae9dbaf27dc9a815d6c44988f65e7d3cef73d93630f668ca664d13f1a0d356d777a6ba75dd0888a3baf44ad5a28716f4d93a02556c331bf46f
|
|
7
|
+
data.tar.gz: d9161ef397b3f8974f2540d37b12bf07d1b6ff2acb55413504a8fd541c3e91bde36a34712609ef0ef48f93cda59fb6898f4856a0a8c67ce8923351d06ef61709
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 3.1.0.beta1
|
|
6
|
+
|
|
7
|
+
### openapi_first/test
|
|
8
|
+
|
|
9
|
+
#### Changed
|
|
10
|
+
- Test now raises `OpenapiFirst::Test::UnknownQueryParameterError` when it sees unknown query parameters. Note that `OpenapiFirst` ("core") still allows unknown query parameters.
|
|
11
|
+
- Test does not track requests/responses unless the OAD was registered via Test.register (or OpenapiFirst.register)
|
|
12
|
+
|
|
5
13
|
## 3.0.1
|
|
6
14
|
- Add missing gem dependency "drb", which is no longer installed by default with newer rubies. This is used in openapi_first/test to make parallel tests work.
|
|
7
15
|
|
data/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'openapi_parameters'
|
|
4
|
+
require_relative 'parsed_request'
|
|
4
5
|
require_relative 'request_validator'
|
|
5
6
|
require_relative 'validated_request'
|
|
7
|
+
require_relative 'request_body_parsers'
|
|
6
8
|
|
|
7
9
|
module OpenapiFirst
|
|
8
10
|
# Represents one request definition of an OpenAPI description.
|
|
@@ -10,8 +12,7 @@ module OpenapiFirst
|
|
|
10
12
|
# An 3.x Operation object can accept multiple requests, because it can handle multiple content-types.
|
|
11
13
|
# This class represents one of those requests.
|
|
12
14
|
class Request
|
|
13
|
-
# rubocop:disable Metrics/MethodLength
|
|
14
|
-
def initialize(path:, request_method:, operation_object:,
|
|
15
|
+
def initialize(path:, request_method:, operation_object:, # rubocop:disable Metrics/MethodLength
|
|
15
16
|
parameters:, content_type:, content_schema:, required_body:, key:)
|
|
16
17
|
@path = path
|
|
17
18
|
@request_method = request_method
|
|
@@ -20,13 +21,11 @@ module OpenapiFirst
|
|
|
20
21
|
@operation = operation_object
|
|
21
22
|
@allow_empty_content = content_type.nil? || required_body == false
|
|
22
23
|
@key = key
|
|
23
|
-
@
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
content_type:
|
|
29
|
-
)
|
|
24
|
+
@query_parser = parameters.query&.then { |params| OpenapiParameters::Query.new(params) }
|
|
25
|
+
@path_parser = parameters.path&.then { |params| OpenapiParameters::Path.new(params) }
|
|
26
|
+
@headers_parser = parameters.header&.then { |params| OpenapiParameters::Header.new(params) }
|
|
27
|
+
@cookies_parser = parameters.cookie&.then { |params| OpenapiParameters::Cookie.new(params) }
|
|
28
|
+
@body_parsers = RequestBodyParsers[content_type] if content_type
|
|
30
29
|
@validator = RequestValidator.new(
|
|
31
30
|
content_schema:,
|
|
32
31
|
required_request_body: required_body == true,
|
|
@@ -35,10 +34,11 @@ module OpenapiFirst
|
|
|
35
34
|
header_schema: parameters.header_schema,
|
|
36
35
|
cookie_schema: parameters.cookie_schema
|
|
37
36
|
)
|
|
37
|
+
@parameters = parameters
|
|
38
38
|
end
|
|
39
|
-
# rubocop:enable Metrics/MethodLength
|
|
40
39
|
|
|
41
|
-
attr_reader :content_type, :content_schema, :operation, :request_method, :path, :key
|
|
40
|
+
attr_reader :content_type, :content_schema, :operation, :request_method, :path, :key, :query_schema, :parameters
|
|
41
|
+
private attr_reader :query_parser
|
|
42
42
|
|
|
43
43
|
def allow_empty_content?
|
|
44
44
|
@allow_empty_content
|
|
@@ -47,15 +47,33 @@ module OpenapiFirst
|
|
|
47
47
|
def validate(request, route_params:)
|
|
48
48
|
parsed_request = nil
|
|
49
49
|
error = catch FAILURE do
|
|
50
|
-
parsed_request =
|
|
50
|
+
parsed_request = parse_request(request, route_params:)
|
|
51
51
|
@validator.call(parsed_request)
|
|
52
52
|
nil
|
|
53
53
|
end
|
|
54
|
-
ValidatedRequest.new(request, parsed_request:, error:, request_definition: self)
|
|
54
|
+
ValidatedRequest.new(request, parsed_request:, error:, request_definition: self, query_parser:)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def operation_id
|
|
58
58
|
@operation['operationId']
|
|
59
59
|
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def parse_request(request, route_params:)
|
|
64
|
+
ParsedRequest.new(
|
|
65
|
+
path: @path_parser&.unpack(route_params),
|
|
66
|
+
query: parse_query(request.env[Rack::QUERY_STRING]),
|
|
67
|
+
headers: @headers_parser&.unpack_env(request.env),
|
|
68
|
+
cookies: @cookies_parser&.unpack(request.env[Rack::HTTP_COOKIE]),
|
|
69
|
+
body: @body_parsers&.call(request)
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_query(query_string)
|
|
74
|
+
@query_parser&.unpack(query_string)
|
|
75
|
+
rescue OpenapiParameters::InvalidParameterError
|
|
76
|
+
Failure.fail!(:invalid_query, message: 'Invalid query parameter.')
|
|
77
|
+
end
|
|
60
78
|
end
|
|
61
79
|
end
|
data/lib/openapi_first/test.rb
CHANGED
|
@@ -5,7 +5,7 @@ require_relative 'registry'
|
|
|
5
5
|
|
|
6
6
|
module OpenapiFirst
|
|
7
7
|
# Test integration
|
|
8
|
-
module Test
|
|
8
|
+
module Test # rubocop:disable Metrics/ModuleLength
|
|
9
9
|
autoload :Coverage, 'openapi_first/test/coverage'
|
|
10
10
|
autoload :Methods, 'openapi_first/test/methods'
|
|
11
11
|
autoload :Observe, 'openapi_first/test/observe'
|
|
@@ -13,6 +13,7 @@ module OpenapiFirst
|
|
|
13
13
|
extend Registry
|
|
14
14
|
|
|
15
15
|
class CoverageError < Error; end
|
|
16
|
+
class UnknownQueryParameterError < Error; end
|
|
16
17
|
|
|
17
18
|
# Inject request/response validation in a rack app class
|
|
18
19
|
def self.observe(app, api: :default)
|
|
@@ -33,6 +34,11 @@ module OpenapiFirst
|
|
|
33
34
|
@configuration ||= Configuration.new
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
def self.registered?(oad)
|
|
38
|
+
key = oad.key
|
|
39
|
+
definitions.any? { |(_name, registered)| registered.key == key }
|
|
40
|
+
end
|
|
41
|
+
|
|
36
42
|
# Sets up OpenAPI test coverage and OAD registration.
|
|
37
43
|
# @yieldparam [OpenapiFirst::Test::Configuration] configuration A configuration to setup test integration
|
|
38
44
|
def self.setup
|
|
@@ -99,12 +105,16 @@ module OpenapiFirst
|
|
|
99
105
|
|
|
100
106
|
OpenapiFirst.configure do |config|
|
|
101
107
|
@after_request_validation = config.after_request_validation do |validated_request, oad|
|
|
108
|
+
next unless registered?(oad)
|
|
102
109
|
raise validated_request.error.exception if raise_request_error?(validated_request)
|
|
103
110
|
|
|
111
|
+
check_unknown_query_parameters(validated_request)
|
|
112
|
+
|
|
104
113
|
Coverage.track_request(validated_request, oad)
|
|
105
114
|
end
|
|
106
115
|
|
|
107
116
|
@after_response_validation = config.after_response_validation do |validated_response, rack_request, oad|
|
|
117
|
+
next unless registered?(oad)
|
|
108
118
|
if validated_response.invalid? && raise_response_error?(validated_response)
|
|
109
119
|
raise validated_response.error.exception
|
|
110
120
|
end
|
|
@@ -115,17 +125,6 @@ module OpenapiFirst
|
|
|
115
125
|
@installed = true
|
|
116
126
|
end
|
|
117
127
|
|
|
118
|
-
def self.raise_request_error?(validated_request)
|
|
119
|
-
return false if validated_request.valid?
|
|
120
|
-
return false if validated_request.known?
|
|
121
|
-
|
|
122
|
-
!configuration.ignore_unknown_requests
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def self.raise_response_error?(invalid_response)
|
|
126
|
-
configuration.response_raise_error && !configuration.ignore_response?(invalid_response)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
128
|
def self.uninstall
|
|
130
129
|
configuration = OpenapiFirst.configuration
|
|
131
130
|
configuration.after_request_validation.delete(@after_request_validation)
|
|
@@ -135,5 +134,38 @@ module OpenapiFirst
|
|
|
135
134
|
@installed = nil
|
|
136
135
|
@exit_handler = nil
|
|
137
136
|
end
|
|
137
|
+
|
|
138
|
+
class << self
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def check_unknown_query_parameters(validated_request)
|
|
142
|
+
return unless validated_request.known?
|
|
143
|
+
|
|
144
|
+
unknown_parameters = validated_request.unknown_query_parameters
|
|
145
|
+
return unless unknown_parameters
|
|
146
|
+
|
|
147
|
+
message = unknown_parameters_message(unknown_parameters, validated_request)
|
|
148
|
+
raise UnknownQueryParameterError, message
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def unknown_parameters_message(unknown_parameters, validated_request)
|
|
152
|
+
s = 's' if many?(unknown_parameters)
|
|
153
|
+
list = unknown_parameters.keys.map(&:inspect).join(', ')
|
|
154
|
+
"Unknown query parameter#{s} #{list} for #{validated_request.fullpath}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def raise_request_error?(validated_request)
|
|
158
|
+
return false if validated_request.valid?
|
|
159
|
+
return false if validated_request.known?
|
|
160
|
+
|
|
161
|
+
!configuration.ignore_unknown_requests
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def many?(array) = array.length > 1
|
|
165
|
+
|
|
166
|
+
def raise_response_error?(invalid_response)
|
|
167
|
+
configuration.response_raise_error && !configuration.ignore_response?(invalid_response)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
138
170
|
end
|
|
139
171
|
end
|
|
@@ -8,11 +8,12 @@ module OpenapiFirst
|
|
|
8
8
|
class ValidatedRequest < SimpleDelegator
|
|
9
9
|
extend Forwardable
|
|
10
10
|
|
|
11
|
-
def initialize(original_request, error:, parsed_request: nil, request_definition: nil)
|
|
11
|
+
def initialize(original_request, error:, parsed_request: nil, request_definition: nil, query_parser: nil)
|
|
12
12
|
super(original_request)
|
|
13
13
|
@parsed_request = parsed_request
|
|
14
14
|
@error = error
|
|
15
15
|
@request_definition = request_definition
|
|
16
|
+
@query_parser = query_parser
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# A Failure object if the request is invalid
|
|
@@ -31,6 +32,11 @@ module OpenapiFirst
|
|
|
31
32
|
# @return [Hash] The raw OpenAPI 3 operation object
|
|
32
33
|
def_delegator :request_definition, :operation
|
|
33
34
|
|
|
35
|
+
# @return [Hash] Query parameters and values that are not defined in the OpenAPI spec.
|
|
36
|
+
def unknown_query_parameters
|
|
37
|
+
@query_parser&.unknown_values(query_string)
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
# Parsed path parameters
|
|
35
41
|
# @return [Hash<String, anything>]
|
|
36
42
|
def parsed_path_parameters = @parsed_request&.path || {}
|
data/lib/openapi_first.rb
CHANGED
|
@@ -47,8 +47,8 @@ module OpenapiFirst
|
|
|
47
47
|
# @return [Class] The error response class
|
|
48
48
|
def self.find_error_response(name)
|
|
49
49
|
ERROR_RESPONSES.fetch(name) do
|
|
50
|
-
raise "Unknown error response: #{name}. "
|
|
51
|
-
'Register your error response class via `OpenapiFirst.register_error_response(name, klass)`. '
|
|
50
|
+
raise "Unknown error response: #{name}. " \
|
|
51
|
+
'Register your error response class via `OpenapiFirst.register_error_response(name, klass)`. ' \
|
|
52
52
|
"Registered error responses are: #{ERROR_RESPONSES.keys.join(', ')}."
|
|
53
53
|
end
|
|
54
54
|
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: 3.0.
|
|
4
|
+
version: 3.1.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Haller
|
|
@@ -63,7 +63,7 @@ dependencies:
|
|
|
63
63
|
requirements:
|
|
64
64
|
- - ">="
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: 0.
|
|
66
|
+
version: 0.10.0
|
|
67
67
|
- - "<"
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
69
|
version: '2.0'
|
|
@@ -73,7 +73,7 @@ dependencies:
|
|
|
73
73
|
requirements:
|
|
74
74
|
- - ">="
|
|
75
75
|
- !ruby/object:Gem::Version
|
|
76
|
-
version: 0.
|
|
76
|
+
version: 0.10.0
|
|
77
77
|
- - "<"
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '2.0'
|
|
@@ -121,11 +121,11 @@ files:
|
|
|
121
121
|
- lib/openapi_first/json.rb
|
|
122
122
|
- lib/openapi_first/middlewares/request_validation.rb
|
|
123
123
|
- lib/openapi_first/middlewares/response_validation.rb
|
|
124
|
+
- lib/openapi_first/parsed_request.rb
|
|
124
125
|
- lib/openapi_first/ref_resolver.rb
|
|
125
126
|
- lib/openapi_first/registry.rb
|
|
126
127
|
- lib/openapi_first/request.rb
|
|
127
128
|
- lib/openapi_first/request_body_parsers.rb
|
|
128
|
-
- lib/openapi_first/request_parser.rb
|
|
129
129
|
- lib/openapi_first/request_validator.rb
|
|
130
130
|
- lib/openapi_first/response.rb
|
|
131
131
|
- lib/openapi_first/response_body_parsers.rb
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'openapi_parameters'
|
|
4
|
-
require_relative 'request_body_parsers'
|
|
5
|
-
|
|
6
|
-
module OpenapiFirst
|
|
7
|
-
ParsedRequest = Data.define(:path, :query, :headers, :body, :cookies)
|
|
8
|
-
|
|
9
|
-
# Parse a request
|
|
10
|
-
class RequestParser
|
|
11
|
-
def initialize(
|
|
12
|
-
query_parameters:,
|
|
13
|
-
path_parameters:,
|
|
14
|
-
header_parameters:,
|
|
15
|
-
cookie_parameters:,
|
|
16
|
-
content_type:
|
|
17
|
-
)
|
|
18
|
-
@query_parser = OpenapiParameters::Query.new(query_parameters) if query_parameters
|
|
19
|
-
@path_parser = OpenapiParameters::Path.new(path_parameters) if path_parameters
|
|
20
|
-
@headers_parser = OpenapiParameters::Header.new(header_parameters) if header_parameters
|
|
21
|
-
@cookies_parser = OpenapiParameters::Cookie.new(cookie_parameters) if cookie_parameters
|
|
22
|
-
@body_parsers = RequestBodyParsers[content_type] if content_type
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
attr_reader :query, :path, :headers, :cookies
|
|
26
|
-
|
|
27
|
-
def parse(request, route_params:)
|
|
28
|
-
ParsedRequest.new(
|
|
29
|
-
path: @path_parser&.unpack(route_params),
|
|
30
|
-
query: parse_query(request.env[Rack::QUERY_STRING]),
|
|
31
|
-
headers: @headers_parser&.unpack_env(request.env),
|
|
32
|
-
cookies: @cookies_parser&.unpack(request.env[Rack::HTTP_COOKIE]),
|
|
33
|
-
body: @body_parsers&.call(request)
|
|
34
|
-
)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def parse_query(query_string)
|
|
40
|
-
@query_parser&.unpack(query_string)
|
|
41
|
-
rescue OpenapiParameters::InvalidParameterError
|
|
42
|
-
Failure.fail!(:invalid_query, message: 'Invalid query parameter.')
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|