committee 2.5.1 → 3.0.0.alpha
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 +5 -5
- data/lib/committee.rb +16 -5
- data/lib/committee/bin/committee_stub.rb +4 -0
- data/lib/committee/drivers.rb +12 -29
- data/lib/committee/drivers/hyper_schema.rb +9 -0
- data/lib/committee/drivers/open_api_2.rb +9 -20
- data/lib/committee/drivers/open_api_3.rb +70 -0
- data/lib/committee/errors.rb +3 -0
- data/lib/committee/middleware/base.rb +20 -62
- data/lib/committee/middleware/request_validation.rb +5 -62
- data/lib/committee/middleware/response_validation.rb +5 -19
- data/lib/committee/middleware/stub.rb +5 -1
- data/lib/committee/parameter_coercer.rb +1 -0
- data/lib/committee/request_unpacker.rb +2 -5
- data/lib/committee/schema_validator/hyper_schema.rb +92 -0
- data/lib/committee/{request_validator.rb → schema_validator/hyper_schema/request_validator.rb} +1 -1
- data/lib/committee/{response_generator.rb → schema_validator/hyper_schema/response_generator.rb} +1 -1
- data/lib/committee/{response_validator.rb → schema_validator/hyper_schema/response_validator.rb} +6 -6
- data/lib/committee/{router.rb → schema_validator/hyper_schema/router.rb} +10 -4
- data/lib/committee/{string_params_coercer.rb → schema_validator/hyper_schema/string_params_coercer.rb} +1 -1
- data/lib/committee/schema_validator/open_api_3.rb +67 -0
- data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +98 -0
- data/lib/committee/schema_validator/open_api_3/request_validator.rb +17 -0
- data/lib/committee/schema_validator/open_api_3/response_validator.rb +35 -0
- data/lib/committee/schema_validator/open_api_3/router.rb +26 -0
- data/lib/committee/schema_validator/option.rb +31 -0
- data/lib/committee/test/methods.rb +14 -117
- data/test/bin/committee_stub_test.rb +6 -0
- data/test/drivers/open_api_2_test.rb +0 -81
- data/test/drivers/open_api_3_test.rb +81 -0
- data/test/drivers_test.rb +2 -42
- data/test/middleware/base_test.rb +42 -21
- data/test/middleware/request_validation_open_api_3_test.rb +499 -0
- data/test/middleware/request_validation_test.rb +18 -0
- data/test/middleware/response_validation_open_api_3_test.rb +96 -0
- data/test/middleware/response_validation_test.rb +7 -30
- data/test/middleware/stub_test.rb +9 -0
- data/test/request_unpacker_test.rb +55 -21
- data/test/{request_validator_test.rb → schema_validator/hyper_schema/request_validator_test.rb} +4 -4
- data/test/{response_generator_test.rb → schema_validator/hyper_schema/response_generator_test.rb} +11 -11
- data/test/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +3 -3
- data/test/{router_test.rb → schema_validator/hyper_schema/router_test.rb} +8 -10
- data/test/{string_params_coercer_test.rb → schema_validator/hyper_schema/string_params_coercer_test.rb} +3 -3
- data/test/schema_validator/open_api_3/operation_wrapper_test.rb +132 -0
- data/test/schema_validator/open_api_3/request_validator_test.rb +151 -0
- data/test/schema_validator/open_api_3/response_validator_test.rb +55 -0
- data/test/test/methods_new_version_test.rb +11 -20
- data/test/test/methods_test.rb +51 -55
- data/test/test_helper.rb +22 -8
- metadata +46 -18
@@ -1,32 +1,18 @@
|
|
1
1
|
module Committee::Middleware
|
2
2
|
class ResponseValidation < Base
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :validate_errors
|
4
4
|
|
5
5
|
def initialize(app, options = {})
|
6
6
|
super
|
7
|
-
@
|
8
|
-
|
9
|
-
unless options[:validate_errors].nil?
|
10
|
-
@validate_success_only = !options[:validate_errors]
|
11
|
-
Committee.warn_deprecated("Committee: validate_errors option is deprecated; " \
|
12
|
-
"please use validate_success_only=#{@validate_success_only}.")
|
13
|
-
end
|
14
|
-
|
7
|
+
@validate_errors = options[:validate_errors]
|
15
8
|
@error_handler = options[:error_handler]
|
16
9
|
end
|
17
10
|
|
18
11
|
def handle(request)
|
19
12
|
status, headers, response = @app.call(request.env)
|
20
13
|
|
21
|
-
|
22
|
-
if validate?(status)
|
23
|
-
full_body = ""
|
24
|
-
response.each do |chunk|
|
25
|
-
full_body << chunk
|
26
|
-
end
|
27
|
-
data = full_body.empty? ? {} : JSON.parse(full_body)
|
28
|
-
Committee::ResponseValidator.new(link, validate_success_only: validate_success_only).call(status, headers, data)
|
29
|
-
end
|
14
|
+
v = build_schema_validator(request)
|
15
|
+
v.response_validate(status, headers, response) if v.link_exist? && validate?(status)
|
30
16
|
|
31
17
|
[status, headers, response]
|
32
18
|
rescue Committee::InvalidResponse
|
@@ -40,7 +26,7 @@ module Committee::Middleware
|
|
40
26
|
end
|
41
27
|
|
42
28
|
def validate?(status)
|
43
|
-
|
29
|
+
status != 204 and validate_errors || (200...300).include?(status)
|
44
30
|
end
|
45
31
|
end
|
46
32
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# Don't Support OpenAPI3
|
2
|
+
|
1
3
|
module Committee::Middleware
|
2
4
|
class Stub < Base
|
3
5
|
def initialize(app, options={})
|
@@ -12,6 +14,8 @@ module Committee::Middleware
|
|
12
14
|
@cache = options[:cache]
|
13
15
|
|
14
16
|
@call = options[:call]
|
17
|
+
|
18
|
+
raise Committee::NotSupportOpenAPI3.new("OpenAPI3 not support stub") unless @schema.support_stub?
|
15
19
|
end
|
16
20
|
|
17
21
|
def handle(request)
|
@@ -20,7 +24,7 @@ module Committee::Middleware
|
|
20
24
|
headers = { "Content-Type" => "application/json" }
|
21
25
|
|
22
26
|
data, schema = cache(link) do
|
23
|
-
Committee::ResponseGenerator.new.call(link)
|
27
|
+
Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
24
28
|
end
|
25
29
|
|
26
30
|
if @call
|
@@ -7,8 +7,7 @@ module Committee
|
|
7
7
|
@allow_query_params = options[:allow_query_params]
|
8
8
|
@coerce_form_params = options[:coerce_form_params]
|
9
9
|
@optimistic_json = options[:optimistic_json]
|
10
|
-
@
|
11
|
-
@schema = options[:schema]
|
10
|
+
@schema_validator = options[:schema_validator]
|
12
11
|
end
|
13
12
|
|
14
13
|
def call
|
@@ -31,9 +30,7 @@ module Committee
|
|
31
30
|
# PUT or PATCH too. Silly Rack.
|
32
31
|
p = @request.POST
|
33
32
|
|
34
|
-
|
35
|
-
Committee::StringParamsCoercer.new(p, @schema, coerce_recursive: @coerce_recursive).call!
|
36
|
-
end
|
33
|
+
@schema_validator.coerce_form_params(p) if @coerce_form_params
|
37
34
|
|
38
35
|
p
|
39
36
|
else
|
@@ -0,0 +1,92 @@
|
|
1
|
+
class Committee::SchemaValidator
|
2
|
+
class HyperSchema
|
3
|
+
attr_reader :link, :param_matches, :validator_option
|
4
|
+
|
5
|
+
def initialize(router, request, validator_option)
|
6
|
+
@link, @param_matches = router.find_request_link(request)
|
7
|
+
@validator_option = validator_option
|
8
|
+
end
|
9
|
+
|
10
|
+
def request_validate(request)
|
11
|
+
# Attempts to coerce parameters that appear in a link's URL to Ruby
|
12
|
+
# types that can be validated with a schema.
|
13
|
+
param_matches_hash = validator_option.coerce_path_params ? coerce_path_params : {}
|
14
|
+
|
15
|
+
# Attempts to coerce parameters that appear in a query string to Ruby
|
16
|
+
# types that can be validated with a schema.
|
17
|
+
coerce_query_params(request) if validator_option.coerce_query_params
|
18
|
+
|
19
|
+
request_unpack(request)
|
20
|
+
|
21
|
+
request.env[validator_option.params_key].merge!(param_matches_hash) if param_matches_hash
|
22
|
+
|
23
|
+
request_schema_validation(request)
|
24
|
+
parameter_coerce!(request, link, validator_option.params_key)
|
25
|
+
parameter_coerce!(request, link, "rack.request.query_hash") if link_exist? && !request.GET.nil? && !link.schema.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def response_validate(status, headers, response)
|
29
|
+
return unless link_exist?
|
30
|
+
|
31
|
+
full_body = ""
|
32
|
+
response.each do |chunk|
|
33
|
+
full_body << chunk
|
34
|
+
end
|
35
|
+
data = JSON.parse(full_body)
|
36
|
+
Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_errors: validator_option.validate_errors).call(status, headers, data)
|
37
|
+
end
|
38
|
+
|
39
|
+
def link_exist?
|
40
|
+
!link.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def coerce_form_params(parameter)
|
44
|
+
return unless link.schema
|
45
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(parameter, link.schema).call!
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def coerce_path_params
|
51
|
+
return unless link_exist?
|
52
|
+
|
53
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(param_matches, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
|
54
|
+
param_matches
|
55
|
+
end
|
56
|
+
|
57
|
+
def coerce_query_params(request)
|
58
|
+
return unless link_exist?
|
59
|
+
return if request.GET.nil? || link.schema.nil?
|
60
|
+
|
61
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(request.GET, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_unpack(request)
|
65
|
+
request.env[validator_option.params_key], request.env[validator_option.headers_key] = Committee::RequestUnpacker.new(
|
66
|
+
request,
|
67
|
+
allow_form_params: validator_option.allow_form_params,
|
68
|
+
allow_query_params: validator_option.allow_query_params,
|
69
|
+
coerce_form_params: validator_option.coerce_form_params,
|
70
|
+
optimistic_json: validator_option.optimistic_json,
|
71
|
+
schema_validator: self
|
72
|
+
).call
|
73
|
+
end
|
74
|
+
|
75
|
+
def request_schema_validation(request)
|
76
|
+
return unless link_exist?
|
77
|
+
validator = Committee::SchemaValidator::HyperSchema::RequestValidator.new(link, check_content_type: validator_option.check_content_type, check_header: validator_option.check_header)
|
78
|
+
validator.call(request, request.env[validator_option.params_key], request.env[validator_option.headers_key])
|
79
|
+
end
|
80
|
+
|
81
|
+
def parameter_coerce!(request, link, coerce_key)
|
82
|
+
return unless link_exist?
|
83
|
+
|
84
|
+
Committee::ParameterCoercer.
|
85
|
+
new(request.env[coerce_key],
|
86
|
+
link.schema,
|
87
|
+
coerce_date_times: validator_option.coerce_date_times,
|
88
|
+
coerce_recursive: validator_option.coerce_recursive).
|
89
|
+
call!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/committee/{response_validator.rb → schema_validator/hyper_schema/response_validator.rb}
RENAMED
@@ -1,18 +1,18 @@
|
|
1
1
|
module Committee
|
2
|
-
class ResponseValidator
|
3
|
-
attr_reader :
|
2
|
+
class SchemaValidator::HyperSchema::ResponseValidator
|
3
|
+
attr_reader :validate_errors
|
4
4
|
|
5
5
|
def initialize(link, options = {})
|
6
6
|
@link = link
|
7
|
-
@
|
7
|
+
@validate_errors = options[:validate_errors]
|
8
8
|
|
9
9
|
@validator = JsonSchema::Validator.new(target_schema(link))
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.validate?(status, options = {})
|
13
|
-
|
13
|
+
validate_errors = options[:validate_errors]
|
14
14
|
|
15
|
-
status != 204 and
|
15
|
+
status != 204 and validate_errors || (200...300).include?(status)
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(status, headers, data)
|
@@ -41,7 +41,7 @@ module Committee
|
|
41
41
|
return if data == nil
|
42
42
|
end
|
43
43
|
|
44
|
-
if self.class.validate?(status,
|
44
|
+
if self.class.validate?(status, validate_errors: validate_errors) && !@validator.validate(data)
|
45
45
|
errors = JsonSchema::SchemaError.aggregate(@validator.errors).join("\n")
|
46
46
|
raise InvalidResponse, "Invalid response.\n\n#{errors}"
|
47
47
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
module Committee
|
2
|
-
class Router
|
3
|
-
def initialize(schema,
|
4
|
-
@prefix =
|
2
|
+
class SchemaValidator::HyperSchema::Router
|
3
|
+
def initialize(schema, validator_option)
|
4
|
+
@prefix = validator_option.prefix
|
5
5
|
@prefix_regexp = /\A#{Regexp.escape(@prefix)}/.freeze if @prefix
|
6
6
|
@schema = schema
|
7
|
+
|
8
|
+
@validator_option = validator_option
|
7
9
|
end
|
8
10
|
|
9
11
|
def includes?(path)
|
@@ -23,12 +25,16 @@ module Committee
|
|
23
25
|
else
|
24
26
|
nil
|
25
27
|
end
|
26
|
-
end.compact.
|
28
|
+
end.compact.sort.first
|
27
29
|
link_with_matches.nil? ? nil : link_with_matches.slice(1, 2)
|
28
30
|
end
|
29
31
|
|
30
32
|
def find_request_link(request)
|
31
33
|
find_link(request.request_method, request.path_info)
|
32
34
|
end
|
35
|
+
|
36
|
+
def build_schema_validator(request)
|
37
|
+
Committee::SchemaValidator::HyperSchema.new(self, request, @validator_option)
|
38
|
+
end
|
33
39
|
end
|
34
40
|
end
|
@@ -9,7 +9,7 @@ module Committee
|
|
9
9
|
# +call+ returns a hash of all params which could be coerced - coercion
|
10
10
|
# errors are simply ignored and expected to be handled later by schema
|
11
11
|
# validation.
|
12
|
-
class StringParamsCoercer
|
12
|
+
class SchemaValidator::HyperSchema::StringParamsCoercer
|
13
13
|
def initialize(query_hash, schema, options = {})
|
14
14
|
@query_hash = query_hash
|
15
15
|
@schema = schema
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Committee::SchemaValidator
|
2
|
+
class OpenAPI3
|
3
|
+
def initialize(router, request, validator_option)
|
4
|
+
@router = router
|
5
|
+
@request = request
|
6
|
+
@operation_object = router.operation_object(request)
|
7
|
+
@validator_option = validator_option
|
8
|
+
end
|
9
|
+
|
10
|
+
def request_validate(request)
|
11
|
+
path_params = validator_option.coerce_path_params ? coerce_path_params : {}
|
12
|
+
# coerce_query_params(request) if validator_option.coerce_query_params
|
13
|
+
|
14
|
+
request_unpack(request)
|
15
|
+
|
16
|
+
request.env[validator_option.params_key]&.merge!(path_params) unless path_params.empty?
|
17
|
+
|
18
|
+
request_schema_validation(request)
|
19
|
+
|
20
|
+
@operation_object&.coerce_request_parameter(request.env["rack.request.query_hash"], validator_option) if !request.GET.nil? && !request.env["rack.request.query_hash"].empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def response_validate(status, headers, response)
|
24
|
+
full_body = ""
|
25
|
+
response.each do |chunk|
|
26
|
+
full_body << chunk
|
27
|
+
end
|
28
|
+
data = JSON.parse(full_body)
|
29
|
+
Committee::SchemaValidator::OpenAPI3::ResponseValidator.new(@operation_object, validator_option).call(status, headers, data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def link_exist?
|
33
|
+
!@operation_object.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def coerce_form_params(_parameter)
|
37
|
+
# nothing because when request_schema_validation, check and coerce
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :validator_option
|
43
|
+
|
44
|
+
def coerce_path_params
|
45
|
+
return {} unless link_exist?
|
46
|
+
@operation_object.coerce_path_parameter(@validator_option)
|
47
|
+
end
|
48
|
+
|
49
|
+
def request_schema_validation(request)
|
50
|
+
return unless @operation_object
|
51
|
+
|
52
|
+
validator = Committee::SchemaValidator::OpenAPI3::RequestValidator.new(@operation_object, validator_option: validator_option)
|
53
|
+
validator.call(request, request.env[validator_option.params_key], request.env[validator_option.headers_key])
|
54
|
+
end
|
55
|
+
|
56
|
+
def request_unpack(request)
|
57
|
+
request.env[validator_option.params_key], request.env[validator_option.headers_key] = Committee::RequestUnpacker.new(
|
58
|
+
request,
|
59
|
+
allow_form_params: validator_option.allow_form_params,
|
60
|
+
allow_query_params: validator_option.allow_query_params,
|
61
|
+
coerce_form_params: validator_option.coerce_form_params,
|
62
|
+
optimistic_json: validator_option.optimistic_json,
|
63
|
+
schema_validator: self
|
64
|
+
).call
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Committee
|
2
|
+
class SchemaValidator::OpenAPI3::OperationWrapper
|
3
|
+
# # @param request_operation [OpenAPIParser::RequestOperation]
|
4
|
+
def initialize(request_operation)
|
5
|
+
@request_operation = request_operation
|
6
|
+
end
|
7
|
+
|
8
|
+
def path_params
|
9
|
+
request_operation.path_params
|
10
|
+
end
|
11
|
+
|
12
|
+
def original_path
|
13
|
+
request_operation.original_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def coerce_path_parameter(validator_option)
|
17
|
+
options = build_openapi_parser_path_option(validator_option)
|
18
|
+
return {} unless options.coerce_value
|
19
|
+
|
20
|
+
request_operation.validate_path_params(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def coerce_request_parameter(params, validator_option)
|
24
|
+
options = build_openapi_parser_get_option(validator_option)
|
25
|
+
return unless options.coerce_value
|
26
|
+
|
27
|
+
request_operation.validate_request_parameter(params, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_response_params(status_code, content_type, params)
|
31
|
+
return request_operation.validate_response_body(status_code, content_type, params)
|
32
|
+
rescue OpenAPIParser::OpenAPIError => e
|
33
|
+
raise Committee::InvalidRequest.new(e.message)
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_request_params(params, validator_option)
|
37
|
+
ret, err = case request_operation.http_method
|
38
|
+
when 'get'
|
39
|
+
validate_get_request_params(params, validator_option)
|
40
|
+
when 'post'
|
41
|
+
validate_post_request_params(params, validator_option)
|
42
|
+
when 'put'
|
43
|
+
validate_post_request_params(params, validator_option)
|
44
|
+
when 'patch'
|
45
|
+
validate_post_request_params(params, validator_option)
|
46
|
+
when 'delete'
|
47
|
+
validate_get_request_params(params, validator_option)
|
48
|
+
else
|
49
|
+
raise "Committee OpenAPI3 not support #{request_operation.http_method} method"
|
50
|
+
end
|
51
|
+
raise err if err
|
52
|
+
ret
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :request_operation
|
58
|
+
|
59
|
+
# @!attribute [r] request_operation
|
60
|
+
# @return [OpenAPIParser::RequestOperation]
|
61
|
+
|
62
|
+
# @return [OpenAPIParser::SchemaValidator::Options]
|
63
|
+
def build_openapi_parser_path_option(validator_option)
|
64
|
+
coerce_value = validator_option.coerce_path_params
|
65
|
+
datetime_coerce_class = validator_option.coerce_date_times ? DateTime : nil
|
66
|
+
OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,datetime_coerce_class: datetime_coerce_class)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [OpenAPIParser::SchemaValidator::Options]
|
70
|
+
def build_openapi_parser_post_option(validator_option)
|
71
|
+
coerce_value = validator_option.coerce_form_params
|
72
|
+
datetime_coerce_class = validator_option.coerce_date_times ? DateTime : nil
|
73
|
+
OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,datetime_coerce_class: datetime_coerce_class)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [OpenAPIParser::SchemaValidator::Options]
|
77
|
+
def build_openapi_parser_get_option(validator_option)
|
78
|
+
coerce_value = validator_option.coerce_query_params
|
79
|
+
datetime_coerce_class = validator_option.coerce_date_times ? DateTime : nil
|
80
|
+
OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,datetime_coerce_class: datetime_coerce_class)
|
81
|
+
end
|
82
|
+
|
83
|
+
def validate_get_request_params(params, validator_option)
|
84
|
+
# bad performance because when we coerce value, same check
|
85
|
+
request_operation.validate_request_parameter(params, build_openapi_parser_get_option(validator_option))
|
86
|
+
rescue OpenAPIParser::OpenAPIError => e
|
87
|
+
raise Committee::InvalidRequest.new(e.message)
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_post_request_params(params, validator_option)
|
91
|
+
# bad performance because when we coerce value, same check
|
92
|
+
# TODO: support other content type
|
93
|
+
return request_operation.validate_request_body('application/json', params, build_openapi_parser_post_option(validator_option))
|
94
|
+
rescue => e
|
95
|
+
raise Committee::InvalidRequest.new(e.message)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|