openapi_first 2.1.1 → 2.2.1
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 +15 -0
- data/README.md +0 -3
- data/lib/openapi_first/body_parser.rb +29 -16
- data/lib/openapi_first/builder.rb +143 -30
- data/lib/openapi_first/definition.rb +5 -5
- data/lib/openapi_first/error_responses/default.rb +1 -1
- data/lib/openapi_first/error_responses/jsonapi.rb +1 -1
- data/lib/openapi_first/file_loader.rb +21 -0
- data/lib/openapi_first/json.rb +25 -0
- data/lib/openapi_first/json_pointer.rb +22 -0
- data/lib/openapi_first/ref_resolver.rb +142 -0
- data/lib/openapi_first/request.rb +17 -56
- data/lib/openapi_first/request_body_parsers.rb +47 -0
- data/lib/openapi_first/request_parser.rb +11 -9
- data/lib/openapi_first/request_validator.rb +16 -9
- data/lib/openapi_first/response.rb +3 -21
- data/lib/openapi_first/response_body_parsers.rb +29 -0
- data/lib/openapi_first/response_parser.rb +9 -26
- data/lib/openapi_first/response_validator.rb +2 -2
- data/lib/openapi_first/test/methods.rb +9 -10
- data/lib/openapi_first/test/minitest_helpers.rb +28 -0
- data/lib/openapi_first/test/plain_helpers.rb +26 -0
- data/lib/openapi_first/test.rb +6 -0
- data/lib/openapi_first/validated_request.rb +13 -29
- data/lib/openapi_first/validators/request_body.rb +9 -23
- data/lib/openapi_first/validators/request_parameters.rb +17 -25
- data/lib/openapi_first/validators/response_body.rb +7 -3
- data/lib/openapi_first/validators/response_headers.rb +6 -4
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +10 -17
- metadata +11 -23
- data/lib/openapi_first/json_refs.rb +0 -151
- data/lib/openapi_first/schema.rb +0 -44
@@ -11,82 +11,43 @@ module OpenapiFirst
|
|
11
11
|
# This class represents one of those requests.
|
12
12
|
class Request
|
13
13
|
def initialize(path:, request_method:, operation_object:,
|
14
|
-
parameters:, content_type:, content_schema:, required_body
|
15
|
-
hooks:, openapi_version:)
|
14
|
+
parameters:, content_type:, content_schema:, required_body:)
|
16
15
|
@path = path
|
17
16
|
@request_method = request_method
|
18
17
|
@content_type = content_type
|
19
18
|
@content_schema = content_schema
|
20
|
-
@required_request_body = required_body == true
|
21
19
|
@operation = operation_object
|
22
|
-
@parameters = build_parameters(parameters)
|
23
20
|
@request_parser = RequestParser.new(
|
24
|
-
query_parameters:
|
25
|
-
path_parameters:
|
26
|
-
header_parameters:
|
27
|
-
cookie_parameters:
|
21
|
+
query_parameters: parameters.query,
|
22
|
+
path_parameters: parameters.path,
|
23
|
+
header_parameters: parameters.header,
|
24
|
+
cookie_parameters: parameters.cookie,
|
28
25
|
content_type:
|
29
26
|
)
|
30
|
-
@validator = RequestValidator.new(
|
27
|
+
@validator = RequestValidator.new(
|
28
|
+
content_schema:,
|
29
|
+
required_request_body: required_body == true,
|
30
|
+
path_schema: parameters.path_schema,
|
31
|
+
query_schema: parameters.query_schema,
|
32
|
+
header_schema: parameters.header_schema,
|
33
|
+
cookie_schema: parameters.cookie_schema
|
34
|
+
)
|
31
35
|
end
|
32
36
|
|
33
37
|
attr_reader :content_type, :content_schema, :operation, :request_method, :path
|
34
38
|
|
35
39
|
def validate(request, route_params:)
|
36
|
-
|
40
|
+
parsed_request = nil
|
37
41
|
error = catch FAILURE do
|
38
|
-
|
39
|
-
@validator.call(
|
42
|
+
parsed_request = @request_parser.parse(request, route_params:)
|
43
|
+
@validator.call(parsed_request)
|
40
44
|
nil
|
41
45
|
end
|
42
|
-
ValidatedRequest.new(request,
|
43
|
-
end
|
44
|
-
|
45
|
-
# These return a Schema instance for each type of parameters
|
46
|
-
%i[path query header cookie].each do |location|
|
47
|
-
define_method(:"#{location}_schema") do
|
48
|
-
build_parameters_schema(@parameters[location])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def required_request_body?
|
53
|
-
@required_request_body
|
46
|
+
ValidatedRequest.new(request, parsed_request:, error:, request_definition: self)
|
54
47
|
end
|
55
48
|
|
56
49
|
def operation_id
|
57
50
|
@operation['operationId']
|
58
51
|
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
IGNORED_HEADERS = Set['Content-Type', 'Accept', 'Authorization'].freeze
|
63
|
-
private_constant :IGNORED_HEADERS
|
64
|
-
|
65
|
-
def build_parameters(parameter_definitions)
|
66
|
-
result = {}
|
67
|
-
parameter_definitions&.each do |parameter|
|
68
|
-
(result[parameter['in'].to_sym] ||= []) << parameter
|
69
|
-
end
|
70
|
-
result[:header]&.reject! { IGNORED_HEADERS.include?(_1['name']) }
|
71
|
-
result
|
72
|
-
end
|
73
|
-
|
74
|
-
def build_parameters_schema(parameters)
|
75
|
-
return unless parameters
|
76
|
-
|
77
|
-
properties = {}
|
78
|
-
required = []
|
79
|
-
parameters.each do |parameter|
|
80
|
-
schema = parameter['schema']
|
81
|
-
name = parameter['name']
|
82
|
-
properties[name] = schema if schema
|
83
|
-
required << name if parameter['required']
|
84
|
-
end
|
85
|
-
|
86
|
-
{
|
87
|
-
'properties' => properties,
|
88
|
-
'required' => required
|
89
|
-
}
|
90
|
-
end
|
91
52
|
end
|
92
53
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
# @!visibility private
|
5
|
+
module RequestBodyParsers
|
6
|
+
DEFAULT = ->(request) { Utils.read_body(request) }
|
7
|
+
|
8
|
+
@parsers = {}
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_reader :parsers
|
12
|
+
|
13
|
+
def register(pattern, parser)
|
14
|
+
parsers[pattern] = parser
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](content_type)
|
18
|
+
key = parsers.keys.find { content_type.match?(_1) }
|
19
|
+
parsers.fetch(key) { DEFAULT }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Not sure where to put this
|
24
|
+
module Utils
|
25
|
+
def self.read_body(request)
|
26
|
+
body = request.body&.read
|
27
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
28
|
+
body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
register(/json/i, lambda do |request|
|
33
|
+
body = Utils.read_body(request)
|
34
|
+
JSON.parse(body) unless body.nil? || body.empty?
|
35
|
+
rescue JSON::ParserError
|
36
|
+
Failure.fail!(:invalid_body, message: 'Failed to parse request body as JSON')
|
37
|
+
end)
|
38
|
+
|
39
|
+
register('multipart/form-data', lambda { |request|
|
40
|
+
request.POST.transform_values do |value|
|
41
|
+
value.is_a?(Hash) && value[:tempfile] ? value[:tempfile].read : value
|
42
|
+
end
|
43
|
+
})
|
44
|
+
|
45
|
+
register('application/x-www-form-urlencoded', lambda(&:POST))
|
46
|
+
end
|
47
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'openapi_parameters'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'request_body_parsers'
|
5
5
|
|
6
6
|
module OpenapiFirst
|
7
|
+
ParsedRequest = Data.define(:path, :query, :headers, :body, :cookies)
|
8
|
+
|
7
9
|
# Parse a request
|
8
10
|
class RequestParser
|
9
11
|
def initialize(
|
@@ -17,19 +19,19 @@ module OpenapiFirst
|
|
17
19
|
@path_parser = OpenapiParameters::Path.new(path_parameters) if path_parameters
|
18
20
|
@headers_parser = OpenapiParameters::Header.new(header_parameters) if header_parameters
|
19
21
|
@cookies_parser = OpenapiParameters::Cookie.new(cookie_parameters) if cookie_parameters
|
20
|
-
@
|
22
|
+
@body_parsers = RequestBodyParsers[content_type] if content_type
|
21
23
|
end
|
22
24
|
|
23
25
|
attr_reader :query, :path, :headers, :cookies
|
24
26
|
|
25
27
|
def parse(request, route_params:)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
ParsedRequest.new(
|
29
|
+
path: @path_parser&.unpack(route_params),
|
30
|
+
query: @query_parser&.unpack(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
|
+
)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
@@ -7,15 +7,22 @@ require_relative 'validators/request_body'
|
|
7
7
|
module OpenapiFirst
|
8
8
|
# Validates a Request against a request definition.
|
9
9
|
class RequestValidator
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
def initialize(
|
11
|
+
content_schema:,
|
12
|
+
required_request_body:,
|
13
|
+
path_schema:,
|
14
|
+
query_schema:,
|
15
|
+
header_schema:,
|
16
|
+
cookie_schema:
|
17
|
+
)
|
18
|
+
@validators = []
|
19
|
+
@validators << Validators::RequestBody.new(content_schema:, required_request_body:) if content_schema
|
20
|
+
@validators.concat Validators::RequestParameters.for(
|
21
|
+
path_schema:,
|
22
|
+
query_schema:,
|
23
|
+
header_schema:,
|
24
|
+
cookie_schema:
|
25
|
+
)
|
19
26
|
end
|
20
27
|
|
21
28
|
def call(parsed_request)
|
@@ -9,14 +9,14 @@ module OpenapiFirst
|
|
9
9
|
# This is not a direct reflecton of the OpenAPI 3.X response definition, but a combination of
|
10
10
|
# status, content type and content schema.
|
11
11
|
class Response
|
12
|
-
def initialize(status:, headers:, content_type:, content_schema
|
12
|
+
def initialize(status:, headers:, headers_schema:, content_type:, content_schema:)
|
13
13
|
@status = status
|
14
14
|
@content_type = content_type
|
15
15
|
@content_schema = content_schema
|
16
16
|
@headers = headers
|
17
|
-
@headers_schema =
|
17
|
+
@headers_schema = headers_schema
|
18
18
|
@parser = ResponseParser.new(headers:, content_type:)
|
19
|
-
@validator = ResponseValidator.new(self
|
19
|
+
@validator = ResponseValidator.new(self)
|
20
20
|
end
|
21
21
|
|
22
22
|
# @attr_reader [Integer] status The HTTP status code of the response definition.
|
@@ -35,23 +35,5 @@ module OpenapiFirst
|
|
35
35
|
def parse(request)
|
36
36
|
@parser.parse(request)
|
37
37
|
end
|
38
|
-
|
39
|
-
def build_headers_schema(headers_object)
|
40
|
-
return unless headers_object&.any?
|
41
|
-
|
42
|
-
properties = {}
|
43
|
-
required = []
|
44
|
-
headers_object.each do |name, header|
|
45
|
-
schema = header['schema']
|
46
|
-
next if name.casecmp('content-type').zero?
|
47
|
-
|
48
|
-
properties[name] = schema if schema
|
49
|
-
required << name if header['required']
|
50
|
-
end
|
51
|
-
{
|
52
|
-
'properties' => properties,
|
53
|
-
'required' => required
|
54
|
-
}
|
55
|
-
end
|
56
38
|
end
|
57
39
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
# @visibility private
|
5
|
+
module ResponseBodyParsers
|
6
|
+
DEFAULT = ->(body) { body }
|
7
|
+
|
8
|
+
@parsers = {}
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_reader :parsers
|
12
|
+
|
13
|
+
def register(pattern, parser)
|
14
|
+
parsers[pattern] = parser
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](content_type)
|
18
|
+
key = parsers.keys.find { content_type&.match?(_1) }
|
19
|
+
parsers.fetch(key) { DEFAULT }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
register(/json/i, lambda do |body|
|
24
|
+
JSON.parse(body)
|
25
|
+
rescue JSON::ParserError
|
26
|
+
Failure.fail!(:invalid_response_body, message: 'Response body is invalid: Failed to parse response body as JSON')
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
end
|
@@ -1,40 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'response_body_parsers'
|
4
|
+
|
3
5
|
module OpenapiFirst
|
4
6
|
ParsedResponse = Data.define(:body, :headers)
|
5
7
|
|
6
8
|
# Parse a response
|
7
9
|
class ResponseParser
|
8
10
|
def initialize(headers:, content_type:)
|
9
|
-
@
|
10
|
-
@
|
11
|
+
@headers_parser = build_headers_parser(headers)
|
12
|
+
@body_parser = ResponseBodyParsers[content_type]
|
11
13
|
end
|
12
14
|
|
13
15
|
def parse(rack_response)
|
14
16
|
ParsedResponse.new(
|
15
|
-
body:
|
16
|
-
headers:
|
17
|
+
body: @body_parser.call(read_body(rack_response)),
|
18
|
+
headers: @headers_parser.call(rack_response.headers)
|
17
19
|
)
|
18
20
|
end
|
19
21
|
|
20
22
|
private
|
21
23
|
|
22
|
-
attr_reader :headers
|
23
|
-
|
24
|
-
def json? = @json
|
25
|
-
|
26
|
-
def parse_body(body)
|
27
|
-
return parse_json(body) if json?
|
28
|
-
|
29
|
-
body
|
30
|
-
end
|
31
|
-
|
32
|
-
def parse_json(body)
|
33
|
-
MultiJson.load(body)
|
34
|
-
rescue MultiJson::ParseError
|
35
|
-
Failure.fail!(:invalid_response_body, message: 'Response body is invalid: Failed to parse response body as JSON')
|
36
|
-
end
|
37
|
-
|
38
24
|
def read_body(rack_response)
|
39
25
|
buffered_body = +''
|
40
26
|
if rack_response.body.respond_to?(:each)
|
@@ -44,14 +30,11 @@ module OpenapiFirst
|
|
44
30
|
rack_response.body
|
45
31
|
end
|
46
32
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
# TODO: memoize unpacker
|
51
|
-
headers_as_parameters = headers.map do |name, definition|
|
33
|
+
def build_headers_parser(header_definitions)
|
34
|
+
headers_as_parameters = header_definitions.to_a.map do |name, definition|
|
52
35
|
definition.merge('name' => name, 'in' => 'header')
|
53
36
|
end
|
54
|
-
OpenapiParameters::Header.new(headers_as_parameters).unpack
|
37
|
+
OpenapiParameters::Header.new(headers_as_parameters).method(:unpack)
|
55
38
|
end
|
56
39
|
end
|
57
40
|
end
|
@@ -11,9 +11,9 @@ module OpenapiFirst
|
|
11
11
|
Validators::ResponseBody
|
12
12
|
].freeze
|
13
13
|
|
14
|
-
def initialize(response_definition
|
14
|
+
def initialize(response_definition)
|
15
15
|
@validators = VALIDATORS.filter_map do |klass|
|
16
|
-
klass.for(response_definition
|
16
|
+
klass.for(response_definition)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -1,20 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'minitest_helpers'
|
4
|
+
require_relative 'plain_helpers'
|
5
|
+
|
3
6
|
module OpenapiFirst
|
7
|
+
# Test integration
|
4
8
|
module Test
|
5
9
|
# Methods to use in integration tests
|
6
10
|
module Methods
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
raise OpenapiFirst::Error,
|
13
|
-
"Expected status #{status}, but got #{response.status} " \
|
14
|
-
"from #{request.request_method.upcase} #{request.path}."
|
11
|
+
def self.included(base)
|
12
|
+
if Test.minitest?(base)
|
13
|
+
base.include(OpenapiFirst::Test::MinitestHelpers)
|
14
|
+
else
|
15
|
+
base.include(OpenapiFirst::Test::PlainHelpers)
|
15
16
|
end
|
16
|
-
api.validate_request(request, raise_error: true)
|
17
|
-
api.validate_response(request, response, raise_error: true)
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
# Assertion methods for Minitest
|
6
|
+
module MinitestHelpers
|
7
|
+
# :nocov:
|
8
|
+
def assert_api_conform(status: nil, api: :default)
|
9
|
+
api = OpenapiFirst::Test[api]
|
10
|
+
request = respond_to?(:last_request) ? last_request : @request
|
11
|
+
response = respond_to?(:last_response) ? last_response : @response
|
12
|
+
|
13
|
+
if status
|
14
|
+
assert_equal status, response.status,
|
15
|
+
"Expected status #{status}, but got #{response.status} " \
|
16
|
+
"from #{request.request_method.upcase} #{request.path}."
|
17
|
+
end
|
18
|
+
|
19
|
+
validated_request = api.validate_request(request, raise_error: false)
|
20
|
+
validated_response = api.validate_response(request, response, raise_error: false)
|
21
|
+
|
22
|
+
assert validated_request.valid?, validated_request.error&.exception_message
|
23
|
+
assert validated_response.valid?, validated_response.error&.exception_message
|
24
|
+
end
|
25
|
+
# :nocov:
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
# Assertion methods to use when no known test framework was found
|
6
|
+
# These methods just raise an exception if an error was found
|
7
|
+
module PlainHelpers
|
8
|
+
def assert_api_conform(status: nil, api: :default)
|
9
|
+
api = OpenapiFirst::Test[api]
|
10
|
+
# :nocov:
|
11
|
+
request = respond_to?(:last_request) ? last_request : @request
|
12
|
+
response = respond_to?(:last_response) ? last_response : @response
|
13
|
+
# :nocov:
|
14
|
+
|
15
|
+
if status && status != response.status
|
16
|
+
raise OpenapiFirst::Error,
|
17
|
+
"Expected status #{status}, but got #{response.status} " \
|
18
|
+
"from #{request.request_method.upcase} #{request.path}."
|
19
|
+
end
|
20
|
+
|
21
|
+
api.validate_request(request, raise_error: true)
|
22
|
+
api.validate_response(request, response, raise_error: true)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/openapi_first/test.rb
CHANGED
@@ -5,6 +5,12 @@ require_relative 'test/methods'
|
|
5
5
|
module OpenapiFirst
|
6
6
|
# Test integration
|
7
7
|
module Test
|
8
|
+
def self.minitest?(base)
|
9
|
+
base.include?(::Minitest::Assertions)
|
10
|
+
rescue NameError
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
8
14
|
class NotRegisteredError < StandardError; end
|
9
15
|
|
10
16
|
DEFINITIONS = {} # rubocop:disable Style/MutableConstant
|
@@ -8,9 +8,9 @@ module OpenapiFirst
|
|
8
8
|
class ValidatedRequest < SimpleDelegator
|
9
9
|
extend Forwardable
|
10
10
|
|
11
|
-
def initialize(original_request, error:,
|
11
|
+
def initialize(original_request, error:, parsed_request: nil, request_definition: nil)
|
12
12
|
super(original_request)
|
13
|
-
@
|
13
|
+
@parsed_request = parsed_request
|
14
14
|
@error = error
|
15
15
|
@request_definition = request_definition
|
16
16
|
end
|
@@ -33,57 +33,41 @@ module OpenapiFirst
|
|
33
33
|
|
34
34
|
# Parsed path parameters
|
35
35
|
# @return [Hash<String, anything>]
|
36
|
-
def parsed_path_parameters
|
37
|
-
@parsed_values[:path]
|
38
|
-
end
|
36
|
+
def parsed_path_parameters = @parsed_request&.path || {}
|
39
37
|
|
40
38
|
# Parsed query parameters. This only returns the query parameters that are defined in the OpenAPI spec.
|
41
39
|
# @return [Hash<String, anything>]
|
42
|
-
def parsed_query
|
43
|
-
@parsed_values[:query]
|
44
|
-
end
|
40
|
+
def parsed_query = @parsed_request&.query || {}
|
45
41
|
|
46
42
|
# Parsed headers. This only returns the query parameters that are defined in the OpenAPI spec.
|
47
43
|
# @return [Hash<String, anything>]
|
48
|
-
def parsed_headers
|
49
|
-
@parsed_values[:headers]
|
50
|
-
end
|
44
|
+
def parsed_headers = @parsed_request&.headers || {}
|
51
45
|
|
52
46
|
# Parsed cookies. This only returns the query parameters that are defined in the OpenAPI spec.
|
53
47
|
# @return [Hash<String, anything>]
|
54
|
-
def parsed_cookies
|
55
|
-
@parsed_values[:cookies]
|
56
|
-
end
|
48
|
+
def parsed_cookies = @parsed_request&.cookies || {}
|
57
49
|
|
58
50
|
# Parsed body. This parses the body according to the content type.
|
59
51
|
# Note that this returns the hole body, not only the fields that are defined in the OpenAPI spec.
|
60
52
|
# You can use JSON Schemas `additionalProperties` or `unevaluatedProperties` to
|
61
|
-
#
|
62
|
-
# @return [Hash<String, anything
|
63
|
-
def parsed_body
|
64
|
-
@parsed_values[:body]
|
65
|
-
end
|
53
|
+
# return a validation error if the body contains unknown fields.
|
54
|
+
# @return [Hash<String, anything>, anything]
|
55
|
+
def parsed_body = @parsed_request&.body
|
66
56
|
|
67
57
|
# Checks if the request is valid.
|
68
|
-
def valid?
|
69
|
-
error.nil?
|
70
|
-
end
|
58
|
+
def valid? = error.nil?
|
71
59
|
|
72
60
|
# Checks if the request is invalid.
|
73
|
-
def invalid?
|
74
|
-
!valid?
|
75
|
-
end
|
61
|
+
def invalid? = !valid?
|
76
62
|
|
77
63
|
# Returns true if the request is defined.
|
78
|
-
def known?
|
79
|
-
request_definition != nil
|
80
|
-
end
|
64
|
+
def known? = request_definition != nil
|
81
65
|
|
82
66
|
# Merged path, query, body parameters.
|
83
67
|
# Here path has the highest precedence, then query, then body.
|
84
68
|
# @return [Hash<String, anything>]
|
85
69
|
def parsed_params
|
86
|
-
@parsed_params ||= parsed_body.merge(parsed_query, parsed_path_parameters)
|
70
|
+
@parsed_params ||= parsed_body.to_h.merge(parsed_query, parsed_path_parameters) || {}
|
87
71
|
end
|
88
72
|
end
|
89
73
|
end
|
@@ -3,37 +3,23 @@
|
|
3
3
|
module OpenapiFirst
|
4
4
|
module Validators
|
5
5
|
class RequestBody
|
6
|
-
def
|
7
|
-
schema =
|
8
|
-
|
9
|
-
|
10
|
-
after_property_validation = hooks[:after_request_body_property_validation]
|
11
|
-
|
12
|
-
new(Schema.new(schema, after_property_validation:, openapi_version:),
|
13
|
-
required: request_definition.required_request_body?)
|
14
|
-
end
|
15
|
-
|
16
|
-
def initialize(schema, required:)
|
17
|
-
@schema = schema
|
18
|
-
@required = required
|
6
|
+
def initialize(content_schema:, required_request_body:)
|
7
|
+
@schema = content_schema
|
8
|
+
@required = required_request_body
|
19
9
|
end
|
20
10
|
|
21
|
-
def call(
|
22
|
-
|
23
|
-
if
|
11
|
+
def call(parsed_request)
|
12
|
+
body = parsed_request.body
|
13
|
+
if body.nil?
|
24
14
|
Failure.fail!(:invalid_body, message: 'Request body is not defined') if @required
|
25
15
|
return
|
26
16
|
end
|
27
17
|
|
28
|
-
validation =
|
18
|
+
validation = Schema::ValidationResult.new(
|
19
|
+
@schema.validate(body, access_mode: 'write')
|
20
|
+
)
|
29
21
|
Failure.fail!(:invalid_body, errors: validation.errors) if validation.error?
|
30
22
|
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def read_body(request)
|
35
|
-
request[:body]
|
36
|
-
end
|
37
23
|
end
|
38
24
|
end
|
39
25
|
end
|
@@ -2,31 +2,35 @@
|
|
2
2
|
|
3
3
|
module OpenapiFirst
|
4
4
|
module Validators
|
5
|
-
|
5
|
+
module RequestParameters
|
6
6
|
RequestHeaders = Data.define(:schema) do
|
7
|
-
def call(
|
8
|
-
validation = schema.validate(
|
7
|
+
def call(parsed_request)
|
8
|
+
validation = schema.validate(parsed_request.headers)
|
9
|
+
validation = Schema::ValidationResult.new(validation.to_a)
|
9
10
|
Failure.fail!(:invalid_header, errors: validation.errors) if validation.error?
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
Path = Data.define(:schema) do
|
14
|
-
def call(
|
15
|
-
validation = schema.validate(
|
15
|
+
def call(parsed_request)
|
16
|
+
validation = schema.validate(parsed_request.path)
|
17
|
+
validation = Schema::ValidationResult.new(validation.to_a)
|
16
18
|
Failure.fail!(:invalid_path, errors: validation.errors) if validation.error?
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
22
|
Query = Data.define(:schema) do
|
21
|
-
def call(
|
22
|
-
validation = schema.validate(
|
23
|
+
def call(parsed_request)
|
24
|
+
validation = schema.validate(parsed_request.query)
|
25
|
+
validation = Schema::ValidationResult.new(validation.to_a)
|
23
26
|
Failure.fail!(:invalid_query, errors: validation.errors) if validation.error?
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
27
30
|
RequestCookies = Data.define(:schema) do
|
28
|
-
def call(
|
29
|
-
validation = schema.validate(
|
31
|
+
def call(parsed_request)
|
32
|
+
validation = schema.validate(parsed_request.cookies)
|
33
|
+
validation = Schema::ValidationResult.new(validation.to_a)
|
30
34
|
Failure.fail!(:invalid_cookie, errors: validation.errors) if validation.error?
|
31
35
|
end
|
32
36
|
end
|
@@ -38,23 +42,11 @@ module OpenapiFirst
|
|
38
42
|
cookie_schema: RequestCookies
|
39
43
|
}.freeze
|
40
44
|
|
41
|
-
def self.for(
|
42
|
-
|
43
|
-
|
44
|
-
schema
|
45
|
-
klass.new(Schema.new(schema, after_property_validation:, openapi_version:)) if schema
|
45
|
+
def self.for(args)
|
46
|
+
VALIDATORS.filter_map do |key, klass|
|
47
|
+
schema = args[key]
|
48
|
+
klass.new(schema) if schema.value
|
46
49
|
end
|
47
|
-
return if validators.empty?
|
48
|
-
|
49
|
-
new(validators)
|
50
|
-
end
|
51
|
-
|
52
|
-
def initialize(validators)
|
53
|
-
@validators = validators
|
54
|
-
end
|
55
|
-
|
56
|
-
def call(parsed_values)
|
57
|
-
@validators.each { |validator| validator.call(parsed_values) }
|
58
50
|
end
|
59
51
|
end
|
60
52
|
end
|