committee 3.3.0 → 5.0.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/lib/committee/drivers/open_api_2/driver.rb +1 -2
- data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +1 -1
- data/lib/committee/drivers.rb +22 -10
- data/lib/committee/errors.rb +12 -0
- data/lib/committee/middleware/base.rb +5 -4
- data/lib/committee/middleware/request_validation.rb +4 -18
- data/lib/committee/middleware/response_validation.rb +15 -16
- data/lib/committee/request_unpacker.rb +46 -60
- data/lib/committee/schema_validator/hyper_schema/response_validator.rb +8 -2
- data/lib/committee/schema_validator/hyper_schema.rb +41 -27
- data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +44 -37
- data/lib/committee/schema_validator/open_api_3/request_validator.rb +11 -2
- data/lib/committee/schema_validator/open_api_3/router.rb +3 -1
- data/lib/committee/schema_validator/open_api_3.rb +52 -26
- data/lib/committee/schema_validator/option.rb +14 -3
- data/lib/committee/schema_validator.rb +1 -1
- data/lib/committee/test/methods.rb +27 -16
- data/lib/committee/test/schema_coverage.rb +101 -0
- data/lib/committee/utils.rb +28 -0
- data/lib/committee/validation_error.rb +3 -2
- data/lib/committee/version.rb +5 -0
- data/lib/committee.rb +11 -4
- data/test/bin/committee_stub_test.rb +5 -1
- data/test/committee_test.rb +29 -3
- data/test/drivers/open_api_3/driver_test.rb +1 -1
- data/test/drivers_test.rb +20 -7
- data/test/middleware/base_test.rb +9 -10
- data/test/middleware/request_validation_open_api_3_test.rb +175 -18
- data/test/middleware/request_validation_test.rb +20 -28
- data/test/middleware/response_validation_open_api_3_test.rb +96 -7
- data/test/middleware/response_validation_test.rb +21 -26
- data/test/middleware/stub_test.rb +4 -0
- data/test/request_unpacker_test.rb +51 -110
- data/test/schema_validator/hyper_schema/response_validator_test.rb +10 -0
- data/test/schema_validator/hyper_schema/router_test.rb +4 -0
- data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +1 -1
- data/test/schema_validator/open_api_3/operation_wrapper_test.rb +72 -20
- data/test/schema_validator/open_api_3/request_validator_test.rb +27 -0
- data/test/schema_validator/open_api_3/response_validator_test.rb +26 -5
- data/test/test/methods_new_version_test.rb +17 -5
- data/test/test/methods_test.rb +155 -31
- data/test/test/schema_coverage_test.rb +216 -0
- data/test/test_helper.rb +34 -4
- metadata +47 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 284c8c1198255435e959dcee2b92cb25c3d80dd5fe6d4622b8077a69525712ed
|
4
|
+
data.tar.gz: b306ec0ad5e628d9cc7b6382781c4b710c99bedb127a7000edadf9d0e0b8fa84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 687d9a47d2a17786313bde939339d464a8d775795458b09ad7d7784bfa337f848a9ffabe240e9f39173c4466589c026f64a8eede768e7abfff4b01b2672339f5
|
7
|
+
data.tar.gz: 4ee7781341de9ebafae28d01d98c0f72d54067c425f3ee10b221c4eb88da85e79b36853bbda4818b20348177f8d975103cc5be694ff7e8252a6634a856853b7c
|
@@ -59,7 +59,7 @@ module Committee
|
|
59
59
|
schema.base_path = data['basePath'] || ''
|
60
60
|
|
61
61
|
# Arbitrarily choose the first media type found in these arrays. This
|
62
|
-
#
|
62
|
+
# approach could probably stand to be improved, but at least users will
|
63
63
|
# for now have the option of turning media type validation off if they so
|
64
64
|
# choose.
|
65
65
|
schema.consumes = data['consumes'].first
|
@@ -156,7 +156,6 @@ module Committee
|
|
156
156
|
|
157
157
|
methods.each do |method, link_data|
|
158
158
|
method = method.upcase
|
159
|
-
|
160
159
|
link = Link.new
|
161
160
|
link.enc_type = schema.consumes
|
162
161
|
link.href = href
|
@@ -62,7 +62,7 @@ module Committee
|
|
62
62
|
# And same idea: despite parameters not being schemas, the items
|
63
63
|
# key (if preset) is actually a schema that defines each item of an
|
64
64
|
# array type, so we can just reflect that directly onto our
|
65
|
-
#
|
65
|
+
# artificial schema.
|
66
66
|
if param_data["type"] == "array" && param_data["items"]
|
67
67
|
param_schema.items = param_data["items"]
|
68
68
|
end
|
data/lib/committee/drivers.rb
CHANGED
@@ -20,26 +20,27 @@ module Committee
|
|
20
20
|
# load and build drive from JSON file
|
21
21
|
# @param [String] schema_path
|
22
22
|
# @return [Committee::Driver]
|
23
|
-
def self.load_from_json(schema_path)
|
24
|
-
load_from_data(JSON.parse(File.read(schema_path)))
|
23
|
+
def self.load_from_json(schema_path, parser_options: {})
|
24
|
+
load_from_data(JSON.parse(File.read(schema_path)), schema_path, parser_options: parser_options)
|
25
25
|
end
|
26
26
|
|
27
27
|
# load and build drive from YAML file
|
28
28
|
# @param [String] schema_path
|
29
29
|
# @return [Committee::Driver]
|
30
|
-
def self.load_from_yaml(schema_path)
|
31
|
-
|
30
|
+
def self.load_from_yaml(schema_path, parser_options: {})
|
31
|
+
data = YAML.respond_to?(:unsafe_load_file) ? YAML.unsafe_load_file(schema_path) : YAML.load_file(schema_path)
|
32
|
+
load_from_data(data, schema_path, parser_options: parser_options)
|
32
33
|
end
|
33
34
|
|
34
35
|
# load and build drive from file
|
35
36
|
# @param [String] schema_path
|
36
37
|
# @return [Committee::Driver]
|
37
|
-
def self.load_from_file(schema_path)
|
38
|
+
def self.load_from_file(schema_path, parser_options: {})
|
38
39
|
case File.extname(schema_path)
|
39
40
|
when '.json'
|
40
|
-
load_from_json(schema_path)
|
41
|
+
load_from_json(schema_path, parser_options: parser_options)
|
41
42
|
when '.yaml', '.yml'
|
42
|
-
load_from_yaml(schema_path)
|
43
|
+
load_from_yaml(schema_path, parser_options: parser_options)
|
43
44
|
else
|
44
45
|
raise "Committee only supports the following file extensions: '.json', '.yaml', '.yml'"
|
45
46
|
end
|
@@ -48,10 +49,20 @@ module Committee
|
|
48
49
|
# load and build drive from Hash object
|
49
50
|
# @param [Hash] hash
|
50
51
|
# @return [Committee::Driver]
|
51
|
-
def self.load_from_data(hash)
|
52
|
+
def self.load_from_data(hash, schema_path = nil, parser_options: {})
|
52
53
|
if hash['openapi']&.start_with?('3.0.')
|
53
|
-
|
54
|
-
|
54
|
+
# From the next major version, we want to ensure `{ strict_reference_validation: true }`
|
55
|
+
# as a parser option here, but since it may break existing implementations, just warn
|
56
|
+
# if it is not explicitly set. See: https://github.com/interagent/committee/issues/343#issuecomment-997400329
|
57
|
+
opts = parser_options.dup
|
58
|
+
|
59
|
+
Committee.warn_deprecated_until_6(!opts.key?(:strict_reference_validation), 'openapi_parser will default to strict reference validation ' +
|
60
|
+
'from next version. Pass config `strict_reference_validation: true` (or false, if you must) ' +
|
61
|
+
'to quiet this warning.')
|
62
|
+
opts[:strict_reference_validation] ||= false
|
63
|
+
|
64
|
+
openapi = OpenAPIParser.parse_with_filepath(hash, schema_path, opts)
|
65
|
+
return Committee::Drivers::OpenAPI3::Driver.new.parse(openapi)
|
55
66
|
end
|
56
67
|
|
57
68
|
driver = if hash['swagger'] == '2.0'
|
@@ -60,6 +71,7 @@ module Committee
|
|
60
71
|
Committee::Drivers::HyperSchema::Driver.new
|
61
72
|
end
|
62
73
|
|
74
|
+
# TODO: in the future, pass `opts` here and allow optionality in other drivers?
|
63
75
|
driver.parse(hash)
|
64
76
|
end
|
65
77
|
end
|
data/lib/committee/errors.rb
CHANGED
@@ -8,9 +8,21 @@ module Committee
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class InvalidRequest < Error
|
11
|
+
attr_reader :original_error
|
12
|
+
|
13
|
+
def initialize(error_message=nil, original_error: nil)
|
14
|
+
@original_error = original_error
|
15
|
+
super(error_message)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
class InvalidResponse < Error
|
20
|
+
attr_reader :original_error
|
21
|
+
|
22
|
+
def initialize(error_message=nil, original_error: nil)
|
23
|
+
@original_error = original_error
|
24
|
+
super(error_message)
|
25
|
+
end
|
14
26
|
end
|
15
27
|
|
16
28
|
class NotFound < Error
|
@@ -30,11 +30,12 @@ module Committee
|
|
30
30
|
class << self
|
31
31
|
def get_schema(options)
|
32
32
|
schema = options[:schema]
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
if !schema && options[:schema_path]
|
34
|
+
# In the future, we could have `parser_options` as an exposed config?
|
35
|
+
parser_options = options.key?(:strict_reference_validation) ? { strict_reference_validation: options[:strict_reference_validation] } : {}
|
36
|
+
schema = Committee::Drivers::load_from_file(options[:schema_path], parser_options: parser_options)
|
37
37
|
end
|
38
|
+
raise(ArgumentError, "Committee: need option `schema` or `schema_path`") unless schema
|
38
39
|
|
39
40
|
# Expect the type we want by now. If we don't have it, the user passed
|
40
41
|
# something else non-standard in.
|
@@ -7,9 +7,6 @@ module Committee
|
|
7
7
|
super
|
8
8
|
|
9
9
|
@strict = options[:strict]
|
10
|
-
|
11
|
-
# deprecated
|
12
|
-
@allow_extra = options[:allow_extra]
|
13
10
|
end
|
14
11
|
|
15
12
|
def handle(request)
|
@@ -21,14 +18,14 @@ module Committee
|
|
21
18
|
rescue Committee::BadRequest, Committee::InvalidRequest
|
22
19
|
handle_exception($!, request.env)
|
23
20
|
raise if @raise
|
24
|
-
return @error_class.new(400, :bad_request, $!.message).render unless @ignore_error
|
21
|
+
return @error_class.new(400, :bad_request, $!.message, request).render unless @ignore_error
|
25
22
|
rescue Committee::NotFound => e
|
26
23
|
raise if @raise
|
27
|
-
return @error_class.new(404, :not_found, e.message).render unless @ignore_error
|
24
|
+
return @error_class.new(404, :not_found, e.message, request).render unless @ignore_error
|
28
25
|
rescue JSON::ParserError
|
29
26
|
handle_exception($!, request.env)
|
30
27
|
raise Committee::InvalidRequest if @raise
|
31
|
-
return @error_class.new(400, :bad_request, "Request body wasn't valid JSON.").render unless @ignore_error
|
28
|
+
return @error_class.new(400, :bad_request, "Request body wasn't valid JSON.", request).render unless @ignore_error
|
32
29
|
end
|
33
30
|
|
34
31
|
@app.call(request.env)
|
@@ -37,18 +34,7 @@ module Committee
|
|
37
34
|
private
|
38
35
|
|
39
36
|
def handle_exception(e, env)
|
40
|
-
|
41
|
-
|
42
|
-
if @error_handler.arity > 1
|
43
|
-
@error_handler.call(e, env)
|
44
|
-
else
|
45
|
-
warn <<-MESSAGE
|
46
|
-
[DEPRECATION] Using `error_handler.call(exception)` is deprecated and will be change to
|
47
|
-
`error_handler.call(exception, request.env)` in next major version.
|
48
|
-
MESSAGE
|
49
|
-
|
50
|
-
@error_handler.call(e)
|
51
|
-
end
|
37
|
+
@error_handler.call(e, env) if @error_handler
|
52
38
|
end
|
53
39
|
end
|
54
40
|
end
|
@@ -7,15 +7,16 @@ module Committee
|
|
7
7
|
|
8
8
|
def initialize(app, options = {})
|
9
9
|
super
|
10
|
+
@strict = options[:strict]
|
10
11
|
@validate_success_only = @schema.validator_option.validate_success_only
|
11
12
|
end
|
12
13
|
|
13
14
|
def handle(request)
|
14
|
-
|
15
|
-
status, headers, response = @app.call(request.env)
|
15
|
+
status, headers, response = @app.call(request.env)
|
16
16
|
|
17
|
+
begin
|
17
18
|
v = build_schema_validator(request)
|
18
|
-
v.response_validate(status, headers, response) if v.link_exist? && self.class.validate?(status, validate_success_only)
|
19
|
+
v.response_validate(status, headers, response, @strict) if v.link_exist? && self.class.validate?(status, validate_success_only)
|
19
20
|
|
20
21
|
rescue Committee::InvalidResponse
|
21
22
|
handle_exception($!, request.env)
|
@@ -34,25 +35,23 @@ module Committee
|
|
34
35
|
|
35
36
|
class << self
|
36
37
|
def validate?(status, validate_success_only)
|
37
|
-
|
38
|
+
case status
|
39
|
+
when 204
|
40
|
+
false
|
41
|
+
when 200..299
|
42
|
+
true
|
43
|
+
when 304
|
44
|
+
false
|
45
|
+
else
|
46
|
+
!validate_success_only
|
47
|
+
end
|
38
48
|
end
|
39
49
|
end
|
40
50
|
|
41
51
|
private
|
42
52
|
|
43
53
|
def handle_exception(e, env)
|
44
|
-
|
45
|
-
|
46
|
-
if @error_handler.arity > 1
|
47
|
-
@error_handler.call(e, env)
|
48
|
-
else
|
49
|
-
warn <<-MESSAGE
|
50
|
-
[DEPRECATION] Using `error_handler.call(exception)` is deprecated and will be change to
|
51
|
-
`error_handler.call(exception, request.env)` in next major version.
|
52
|
-
MESSAGE
|
53
|
-
|
54
|
-
@error_handler.call(e)
|
55
|
-
end
|
54
|
+
@error_handler.call(e, env) if @error_handler
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end
|
@@ -2,84 +2,82 @@
|
|
2
2
|
|
3
3
|
module Committee
|
4
4
|
class RequestUnpacker
|
5
|
-
|
6
|
-
|
5
|
+
class << self
|
6
|
+
# Enable string or symbol key access to the nested params hash.
|
7
|
+
#
|
8
|
+
# (Copied from Sinatra)
|
9
|
+
def indifferent_params(object)
|
10
|
+
case object
|
11
|
+
when Hash
|
12
|
+
new_hash = Committee::Utils.indifferent_hash
|
13
|
+
object.each { |key, value| new_hash[key] = indifferent_params(value) }
|
14
|
+
new_hash
|
15
|
+
when Array
|
16
|
+
object.map { |item| indifferent_params(item) }
|
17
|
+
else
|
18
|
+
object
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
7
22
|
|
23
|
+
def initialize(options={})
|
8
24
|
@allow_form_params = options[:allow_form_params]
|
9
25
|
@allow_get_body = options[:allow_get_body]
|
10
26
|
@allow_query_params = options[:allow_query_params]
|
11
|
-
@coerce_form_params = options[:coerce_form_params]
|
12
27
|
@optimistic_json = options[:optimistic_json]
|
13
|
-
@schema_validator = options[:schema_validator]
|
14
28
|
end
|
15
29
|
|
16
|
-
|
30
|
+
# return params and is_form_params
|
31
|
+
def unpack_request_params(request)
|
17
32
|
# if Content-Type is empty or JSON, and there was a request body, try to
|
18
33
|
# interpret it as JSON
|
19
|
-
params = if
|
20
|
-
parse_json
|
34
|
+
params = if !request.media_type || request.media_type =~ %r{application/(?:.*\+)?json}
|
35
|
+
parse_json(request)
|
21
36
|
elsif @optimistic_json
|
22
37
|
begin
|
23
|
-
parse_json
|
38
|
+
parse_json(request)
|
24
39
|
rescue JSON::ParserError
|
25
40
|
nil
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
29
|
-
params
|
30
|
-
|
31
|
-
|
44
|
+
return [params, false] if params
|
45
|
+
|
46
|
+
if @allow_form_params && %w[application/x-www-form-urlencoded multipart/form-data].include?(request.media_type)
|
32
47
|
# Actually, POST means anything in the request body, could be from
|
33
48
|
# PUT or PATCH too. Silly Rack.
|
34
|
-
|
35
|
-
|
36
|
-
@schema_validator.coerce_form_params(p) if @coerce_form_params
|
37
|
-
|
38
|
-
p
|
39
|
-
else
|
40
|
-
{}
|
49
|
+
return [request.POST, true] if request.POST
|
41
50
|
end
|
42
51
|
|
43
|
-
|
44
|
-
[indifferent_params(@request.GET).merge(params), headers]
|
45
|
-
else
|
46
|
-
[params, headers]
|
47
|
-
end
|
52
|
+
[{}, false]
|
48
53
|
end
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
# Creates a Hash with indifferent access.
|
53
|
-
#
|
54
|
-
# (Copied from Sinatra)
|
55
|
-
def indifferent_hash
|
56
|
-
Hash.new { |hash,key| hash[key.to_s] if Symbol === key }
|
55
|
+
def unpack_query_params(request)
|
56
|
+
@allow_query_params ? self.class.indifferent_params(request.GET) : {}
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
new_hash = indifferent_hash
|
66
|
-
object.each { |key, value| new_hash[key] = indifferent_params(value) }
|
67
|
-
new_hash
|
68
|
-
when Array
|
69
|
-
object.map { |item| indifferent_params(item) }
|
70
|
-
else
|
71
|
-
object
|
59
|
+
def unpack_headers(request)
|
60
|
+
env = request.env
|
61
|
+
base = env.keys.grep(/HTTP_/).inject({}) do |headers, key|
|
62
|
+
headerized_key = key.gsub(/^HTTP_/, '').gsub(/_/, '-')
|
63
|
+
headers[headerized_key] = env[key]
|
64
|
+
headers
|
72
65
|
end
|
66
|
+
|
67
|
+
base['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
|
68
|
+
base
|
73
69
|
end
|
74
70
|
|
75
|
-
|
76
|
-
|
71
|
+
private
|
72
|
+
|
73
|
+
def parse_json(request)
|
74
|
+
return nil if request.request_method == "GET" && !@allow_get_body
|
77
75
|
|
78
|
-
body =
|
76
|
+
body = request.body.read
|
79
77
|
# if request body is empty, we just have empty params
|
80
78
|
return nil if body.length == 0
|
81
79
|
|
82
|
-
|
80
|
+
request.body.rewind
|
83
81
|
hash = JSON.parse(body)
|
84
82
|
# We want a hash specifically. '42', 42, and [42] will all be
|
85
83
|
# decoded properly, but we can't use them here.
|
@@ -87,19 +85,7 @@ module Committee
|
|
87
85
|
raise BadRequest,
|
88
86
|
"Invalid JSON input. Require object with parameters as keys."
|
89
87
|
end
|
90
|
-
indifferent_params(hash)
|
91
|
-
end
|
92
|
-
|
93
|
-
def headers
|
94
|
-
env = @request.env
|
95
|
-
base = env.keys.grep(/HTTP_/).inject({}) do |headers, key|
|
96
|
-
headerized_key = key.gsub(/^HTTP_/, '').gsub(/_/, '-')
|
97
|
-
headers[headerized_key] = env[key]
|
98
|
-
headers
|
99
|
-
end
|
100
|
-
|
101
|
-
base['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
|
102
|
-
base
|
88
|
+
self.class.indifferent_params(hash)
|
103
89
|
end
|
104
90
|
end
|
105
91
|
end
|
@@ -14,7 +14,7 @@ module Committee
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def call(status, headers, data)
|
17
|
-
unless status
|
17
|
+
unless [204, 304].include?(status) # 204 No Content or 304 Not Modified
|
18
18
|
response = Rack::Response.new(data, status, headers)
|
19
19
|
check_content_type!(response)
|
20
20
|
end
|
@@ -48,9 +48,15 @@ module Committee
|
|
48
48
|
private
|
49
49
|
|
50
50
|
def response_media_type(response)
|
51
|
-
response.
|
51
|
+
if response.respond_to?(:media_type)
|
52
|
+
response.media_type.to_s
|
53
|
+
else
|
54
|
+
# for rack compatibility. In rack v 1.5.0, Rack::Response doesn't have media_type
|
55
|
+
response.content_type.to_s.split(";").first.to_s
|
56
|
+
end
|
52
57
|
end
|
53
58
|
|
59
|
+
|
54
60
|
def check_content_type!(response)
|
55
61
|
if @link.media_type
|
56
62
|
unless Rack::Mime.match?(response_media_type(response), @link.media_type)
|
@@ -11,18 +11,8 @@ module Committee
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def request_validate(request)
|
14
|
-
# Attempts to coerce parameters that appear in a link's URL to Ruby
|
15
|
-
# types that can be validated with a schema.
|
16
|
-
param_matches_hash = validator_option.coerce_path_params ? coerce_path_params : {}
|
17
|
-
|
18
|
-
# Attempts to coerce parameters that appear in a query string to Ruby
|
19
|
-
# types that can be validated with a schema.
|
20
|
-
coerce_query_params(request) if validator_option.coerce_query_params
|
21
|
-
|
22
14
|
request_unpack(request)
|
23
15
|
|
24
|
-
request.env[validator_option.params_key].merge!(param_matches_hash) if param_matches_hash
|
25
|
-
|
26
16
|
request_schema_validation(request)
|
27
17
|
parameter_coerce!(request, link, validator_option.params_key)
|
28
18
|
parameter_coerce!(request, link, "rack.request.query_hash") if link_exist? && !request.GET.nil? && !link.schema.nil?
|
@@ -35,7 +25,14 @@ module Committee
|
|
35
25
|
response.each do |chunk|
|
36
26
|
full_body << chunk
|
37
27
|
end
|
38
|
-
|
28
|
+
|
29
|
+
data = {}
|
30
|
+
unless full_body.empty?
|
31
|
+
parse_to_json = !validator_option.parse_response_by_content_type ||
|
32
|
+
headers.fetch('Content-Type', nil)&.start_with?('application/json')
|
33
|
+
data = JSON.parse(full_body) if parse_to_json
|
34
|
+
end
|
35
|
+
|
39
36
|
Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only).call(status, headers, data)
|
40
37
|
end
|
41
38
|
|
@@ -43,16 +40,10 @@ module Committee
|
|
43
40
|
!link.nil?
|
44
41
|
end
|
45
42
|
|
46
|
-
def coerce_form_params(parameter)
|
47
|
-
return unless link_exist?
|
48
|
-
return unless link.schema
|
49
|
-
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(parameter, link.schema).call!
|
50
|
-
end
|
51
|
-
|
52
43
|
private
|
53
44
|
|
54
45
|
def coerce_path_params
|
55
|
-
return unless link_exist?
|
46
|
+
return {} unless link_exist?
|
56
47
|
|
57
48
|
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(param_matches, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
|
58
49
|
param_matches
|
@@ -66,15 +57,38 @@ module Committee
|
|
66
57
|
end
|
67
58
|
|
68
59
|
def request_unpack(request)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
60
|
+
unpacker = Committee::RequestUnpacker.new(
|
61
|
+
allow_form_params: validator_option.allow_form_params,
|
62
|
+
allow_get_body: validator_option.allow_get_body,
|
63
|
+
allow_query_params: validator_option.allow_query_params,
|
64
|
+
optimistic_json: validator_option.optimistic_json,
|
65
|
+
)
|
66
|
+
|
67
|
+
request.env[validator_option.headers_key] = unpacker.unpack_headers(request)
|
68
|
+
|
69
|
+
# Attempts to coerce parameters that appear in a link's URL to Ruby
|
70
|
+
# types that can be validated with a schema.
|
71
|
+
param_matches_hash = validator_option.coerce_path_params ? coerce_path_params : {}
|
72
|
+
|
73
|
+
# Attempts to coerce parameters that appear in a query string to Ruby
|
74
|
+
# types that can be validated with a schema.
|
75
|
+
coerce_query_params(request) if validator_option.coerce_query_params
|
76
|
+
|
77
|
+
query_param = unpacker.unpack_query_params(request)
|
78
|
+
request_param, is_form_params = unpacker.unpack_request_params(request)
|
79
|
+
coerce_form_params(request_param) if validator_option.coerce_form_params && is_form_params
|
80
|
+
request.env[validator_option.request_body_hash_key] = request_param
|
81
|
+
|
82
|
+
request.env[validator_option.params_key] = Committee::Utils.indifferent_hash
|
83
|
+
request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(query_param))
|
84
|
+
request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(request_param))
|
85
|
+
request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(param_matches_hash))
|
86
|
+
end
|
87
|
+
|
88
|
+
def coerce_form_params(parameter)
|
89
|
+
return unless link_exist?
|
90
|
+
return unless link.schema
|
91
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(parameter, link.schema).call!
|
78
92
|
end
|
79
93
|
|
80
94
|
def request_schema_validation(request)
|
@@ -17,13 +17,17 @@ module Committee
|
|
17
17
|
request_operation.original_path
|
18
18
|
end
|
19
19
|
|
20
|
+
def http_method
|
21
|
+
request_operation.http_method
|
22
|
+
end
|
23
|
+
|
20
24
|
def coerce_path_parameter(validator_option)
|
21
25
|
options = build_openapi_parser_path_option(validator_option)
|
22
26
|
return {} unless options.coerce_value
|
23
27
|
|
24
28
|
request_operation.validate_path_params(options)
|
25
29
|
rescue OpenAPIParser::OpenAPIError => e
|
26
|
-
raise Committee::InvalidRequest.new(e.message)
|
30
|
+
raise Committee::InvalidRequest.new(e.message, original_error: e)
|
27
31
|
end
|
28
32
|
|
29
33
|
# @param [Boolean] strict when not content_type or status code definition, raise error
|
@@ -32,21 +36,15 @@ module Committee
|
|
32
36
|
|
33
37
|
return request_operation.validate_response_body(response_body, response_validate_options(strict, check_header))
|
34
38
|
rescue OpenAPIParser::OpenAPIError => e
|
35
|
-
raise Committee::InvalidResponse.new(e.message)
|
39
|
+
raise Committee::InvalidResponse.new(e.message, original_error: e)
|
36
40
|
end
|
37
41
|
|
38
|
-
def validate_request_params(
|
42
|
+
def validate_request_params(path_params, query_params, body_params, headers, validator_option)
|
39
43
|
ret, err = case request_operation.http_method
|
40
|
-
when 'get'
|
41
|
-
validate_get_request_params(
|
42
|
-
when 'post'
|
43
|
-
validate_post_request_params(
|
44
|
-
when 'put'
|
45
|
-
validate_post_request_params(params, headers, validator_option)
|
46
|
-
when 'patch'
|
47
|
-
validate_post_request_params(params, headers, validator_option)
|
48
|
-
when 'delete'
|
49
|
-
validate_get_request_params(params, headers, validator_option)
|
44
|
+
when 'get', 'delete', 'head'
|
45
|
+
validate_get_request_params(path_params, query_params, headers, validator_option)
|
46
|
+
when 'post', 'put', 'patch', 'options'
|
47
|
+
validate_post_request_params(path_params, query_params, body_params, headers, validator_option)
|
50
48
|
else
|
51
49
|
raise "Committee OpenAPI3 not support #{request_operation.http_method} method"
|
52
50
|
end
|
@@ -54,6 +52,10 @@ module Committee
|
|
54
52
|
ret
|
55
53
|
end
|
56
54
|
|
55
|
+
def optional_body?
|
56
|
+
!request_operation.operation_object&.request_body&.required
|
57
|
+
end
|
58
|
+
|
57
59
|
def valid_request_content_type?(content_type)
|
58
60
|
if (request_body = request_operation.operation_object&.request_body)
|
59
61
|
!request_body.select_media_type(content_type).nil?
|
@@ -76,28 +78,17 @@ module Committee
|
|
76
78
|
# @return [OpenAPIParser::RequestOperation]
|
77
79
|
|
78
80
|
# @return [OpenAPIParser::SchemaValidator::Options]
|
79
|
-
def
|
80
|
-
|
81
|
-
datetime_coerce_class = validator_option.coerce_date_times ? DateTime : nil
|
82
|
-
validate_header = validator_option.check_header
|
83
|
-
OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,
|
84
|
-
datetime_coerce_class: datetime_coerce_class,
|
85
|
-
validate_header: validate_header)
|
81
|
+
def build_openapi_parser_body_option(validator_option)
|
82
|
+
build_openapi_parser_option(validator_option, validator_option.coerce_form_params)
|
86
83
|
end
|
87
84
|
|
88
85
|
# @return [OpenAPIParser::SchemaValidator::Options]
|
89
|
-
def
|
90
|
-
|
91
|
-
datetime_coerce_class = validator_option.coerce_date_times ? DateTime : nil
|
92
|
-
validate_header = validator_option.check_header
|
93
|
-
OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,
|
94
|
-
datetime_coerce_class: datetime_coerce_class,
|
95
|
-
validate_header: validate_header)
|
86
|
+
def build_openapi_parser_path_option(validator_option)
|
87
|
+
build_openapi_parser_option(validator_option, validator_option.coerce_query_params)
|
96
88
|
end
|
97
89
|
|
98
90
|
# @return [OpenAPIParser::SchemaValidator::Options]
|
99
|
-
def
|
100
|
-
coerce_value = validator_option.coerce_query_params
|
91
|
+
def build_openapi_parser_option(validator_option, coerce_value)
|
101
92
|
datetime_coerce_class = validator_option.coerce_date_times ? DateTime : nil
|
102
93
|
validate_header = validator_option.check_header
|
103
94
|
OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,
|
@@ -105,22 +96,38 @@ module Committee
|
|
105
96
|
validate_header: validate_header)
|
106
97
|
end
|
107
98
|
|
108
|
-
def validate_get_request_params(
|
99
|
+
def validate_get_request_params(path_params, query_params, headers, validator_option)
|
109
100
|
# bad performance because when we coerce value, same check
|
110
|
-
|
101
|
+
validate_path_and_query_params(path_params, query_params, headers, validator_option)
|
111
102
|
rescue OpenAPIParser::OpenAPIError => e
|
112
|
-
raise Committee::InvalidRequest.new(e.message)
|
103
|
+
raise Committee::InvalidRequest.new(e.message, original_error: e)
|
113
104
|
end
|
114
105
|
|
115
|
-
def validate_post_request_params(
|
106
|
+
def validate_post_request_params(path_params, query_params, body_params, headers, validator_option)
|
116
107
|
content_type = headers['Content-Type'].to_s.split(";").first.to_s
|
117
108
|
|
118
109
|
# bad performance because when we coerce value, same check
|
119
|
-
|
120
|
-
request_operation.
|
121
|
-
request_operation.validate_request_body(content_type, params, schema_validator_options)
|
110
|
+
validate_path_and_query_params(path_params, query_params, headers, validator_option)
|
111
|
+
request_operation.validate_request_body(content_type, body_params, build_openapi_parser_body_option(validator_option))
|
122
112
|
rescue => e
|
123
|
-
raise Committee::InvalidRequest.new(e.message)
|
113
|
+
raise Committee::InvalidRequest.new(e.message, original_error: e)
|
114
|
+
end
|
115
|
+
|
116
|
+
def validate_path_and_query_params(path_params, query_params, headers, validator_option)
|
117
|
+
# it's currently impossible to validate path params and query params separately
|
118
|
+
# so we have to resort to this workaround
|
119
|
+
|
120
|
+
path_keys = path_params.keys.to_set
|
121
|
+
query_keys = query_params.keys.to_set
|
122
|
+
|
123
|
+
merged_params = query_params.merge(path_params)
|
124
|
+
|
125
|
+
request_operation.validate_request_parameter(merged_params, headers, build_openapi_parser_path_option(validator_option))
|
126
|
+
|
127
|
+
merged_params.each do |k, v|
|
128
|
+
path_params[k] = v if path_keys.include?(k)
|
129
|
+
query_params[k] = v if query_keys.include?(k)
|
130
|
+
end
|
124
131
|
end
|
125
132
|
|
126
133
|
def response_validate_options(strict, check_header)
|