committee 3.1.0 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/committee-stub +1 -0
- data/lib/committee.rb +12 -34
- data/lib/committee/bin/committee_stub.rb +6 -4
- data/lib/committee/drivers.rb +15 -67
- data/lib/committee/drivers/driver.rb +47 -0
- data/lib/committee/drivers/hyper_schema.rb +8 -171
- data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
- data/lib/committee/drivers/hyper_schema/link.rb +68 -0
- data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
- data/lib/committee/drivers/open_api_2.rb +9 -416
- data/lib/committee/drivers/open_api_2/driver.rb +253 -0
- data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
- data/lib/committee/drivers/open_api_2/link.rb +36 -0
- data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
- data/lib/committee/drivers/open_api_2/schema.rb +26 -0
- data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
- data/lib/committee/drivers/open_api_3.rb +7 -75
- data/lib/committee/drivers/open_api_3/driver.rb +51 -0
- data/lib/committee/drivers/open_api_3/schema.rb +41 -0
- data/lib/committee/drivers/schema.rb +23 -0
- data/lib/committee/errors.rb +2 -0
- data/lib/committee/middleware.rb +11 -0
- data/lib/committee/middleware/base.rb +38 -34
- data/lib/committee/middleware/request_validation.rb +51 -30
- data/lib/committee/middleware/response_validation.rb +49 -26
- data/lib/committee/middleware/stub.rb +55 -51
- data/lib/committee/request_unpacker.rb +3 -1
- data/lib/committee/schema_validator.rb +23 -0
- data/lib/committee/schema_validator/hyper_schema.rb +85 -74
- data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +60 -54
- data/lib/committee/schema_validator/hyper_schema/request_validator.rb +43 -37
- data/lib/committee/schema_validator/hyper_schema/response_generator.rb +86 -80
- data/lib/committee/schema_validator/hyper_schema/response_validator.rb +65 -59
- data/lib/committee/schema_validator/hyper_schema/router.rb +35 -29
- data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +87 -81
- data/lib/committee/schema_validator/open_api_3.rb +71 -61
- data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +121 -115
- data/lib/committee/schema_validator/open_api_3/request_validator.rb +24 -18
- data/lib/committee/schema_validator/open_api_3/response_validator.rb +22 -16
- data/lib/committee/schema_validator/open_api_3/router.rb +30 -24
- data/lib/committee/schema_validator/option.rb +42 -38
- data/lib/committee/test/methods.rb +55 -51
- data/lib/committee/validation_error.rb +2 -0
- data/test/bin/committee_stub_test.rb +3 -1
- data/test/bin_test.rb +3 -1
- data/test/committee_test.rb +3 -1
- data/test/drivers/hyper_schema/driver_test.rb +49 -0
- data/test/drivers/{hyper_schema_test.rb → hyper_schema/link_test.rb} +2 -45
- data/test/drivers/open_api_2/driver_test.rb +156 -0
- data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
- data/test/drivers/open_api_2/link_test.rb +52 -0
- data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
- data/test/drivers/{open_api_3_test.rb → open_api_3/driver_test.rb} +5 -3
- data/test/drivers_test.rb +12 -10
- data/test/middleware/base_test.rb +3 -1
- data/test/middleware/request_validation_open_api_3_test.rb +4 -2
- data/test/middleware/request_validation_test.rb +46 -5
- data/test/middleware/response_validation_open_api_3_test.rb +3 -1
- data/test/middleware/response_validation_test.rb +39 -4
- data/test/middleware/stub_test.rb +3 -1
- data/test/request_unpacker_test.rb +2 -2
- data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +2 -2
- data/test/schema_validator/hyper_schema/request_validator_test.rb +3 -1
- data/test/schema_validator/hyper_schema/response_generator_test.rb +3 -1
- data/test/schema_validator/hyper_schema/response_validator_test.rb +3 -1
- data/test/schema_validator/hyper_schema/router_test.rb +5 -3
- data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +3 -1
- data/test/schema_validator/open_api_3/operation_wrapper_test.rb +3 -1
- data/test/schema_validator/open_api_3/request_validator_test.rb +11 -1
- data/test/schema_validator/open_api_3/response_validator_test.rb +3 -1
- data/test/test/methods_new_version_test.rb +3 -1
- data/test/test/methods_test.rb +4 -2
- data/test/test_helper.rb +16 -16
- data/test/validation_error_test.rb +3 -1
- metadata +52 -6
- data/lib/committee/schema_validator/schema_validator.rb +0 -15
- data/test/drivers/open_api_2_test.rb +0 -416
@@ -1,70 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Stub is not yet supported in OpenAPI 3
|
2
4
|
|
3
|
-
module Committee
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
module Committee
|
6
|
+
module Middleware
|
7
|
+
class Stub < Base
|
8
|
+
def initialize(app, options={})
|
9
|
+
super
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
# A bug in Committee's cache implementation meant that it wasn't working
|
12
|
+
# for a very long time, even for people who thought they were taking
|
13
|
+
# advantage of it. I repaired the caching feature, but have disable it by
|
14
|
+
# default so that we don't need to introduce any class-level variables
|
15
|
+
# that could have memory leaking implications. To enable caching, just
|
16
|
+
# pass an empty hash to this option.
|
17
|
+
@cache = options[:cache]
|
15
18
|
|
16
|
-
|
19
|
+
@call = options[:call]
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
raise Committee::OpenAPI3Unsupported.new("Stubs are not yet supported for OpenAPI 3") unless @schema.supports_stub?
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
def handle(request)
|
25
|
+
link, _ = @router.find_request_link(request)
|
26
|
+
if link
|
27
|
+
headers = { "Content-Type" => "application/json" }
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
data, schema = cache(link) do
|
30
|
+
Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
if @call
|
34
|
+
request.env["committee.response"] = data
|
35
|
+
request.env["committee.response_schema"] = schema
|
36
|
+
call_status, call_headers, call_body = @app.call(request.env)
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
# a committee.suppress signal initiates a direct pass through
|
39
|
+
if request.env["committee.suppress"] == true
|
40
|
+
return call_status, call_headers, call_body
|
41
|
+
end
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
# otherwise keep the headers and whatever data manipulations were
|
44
|
+
# made, and stub normally
|
45
|
+
headers.merge!(call_headers)
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
# allow the handler to change the data object (if unchanged, it
|
48
|
+
# will be the same one that we set above)
|
49
|
+
data = request.env["committee.response"]
|
50
|
+
end
|
48
51
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
[link.status_success, headers, [JSON.pretty_generate(data)]]
|
53
|
+
else
|
54
|
+
@app.call(request.env)
|
55
|
+
end
|
52
56
|
end
|
53
|
-
end
|
54
57
|
|
55
|
-
|
58
|
+
private
|
56
59
|
|
57
|
-
|
58
|
-
|
60
|
+
def cache(link)
|
61
|
+
return yield unless @cache
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
# Just the object ID is enough to uniquely identify the link, but store
|
64
|
+
# the method and href so that we can more easily introspect the cache if
|
65
|
+
# necessary.
|
66
|
+
key = "#{link.object_id}##{link.method}+#{link.href}"
|
67
|
+
if @cache[key]
|
68
|
+
@cache[key]
|
69
|
+
else
|
70
|
+
@cache[key] = yield
|
71
|
+
end
|
68
72
|
end
|
69
73
|
end
|
70
74
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Committee
|
2
4
|
class RequestUnpacker
|
3
5
|
def initialize(request, options={})
|
@@ -96,7 +98,7 @@ module Committee
|
|
96
98
|
headers
|
97
99
|
end
|
98
100
|
|
99
|
-
base
|
101
|
+
base['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
|
100
102
|
base
|
101
103
|
end
|
102
104
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module SchemaValidator
|
5
|
+
class << self
|
6
|
+
def request_media_type(request)
|
7
|
+
request.content_type.to_s.split(";").first.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param [String] prefix
|
11
|
+
# @return [Regexp]
|
12
|
+
def build_prefix_regexp(prefix)
|
13
|
+
return nil unless prefix
|
14
|
+
|
15
|
+
/\A#{Regexp.escape(prefix)}/.freeze
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require_relative "schema_validator/hyper_schema"
|
22
|
+
require_relative "schema_validator/open_api_3"
|
23
|
+
require_relative "schema_validator/option"
|
@@ -1,94 +1,105 @@
|
|
1
|
-
|
2
|
-
class HyperSchema
|
3
|
-
attr_reader :link, :param_matches, :validator_option
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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, _test_method = false)
|
29
|
-
return unless link_exist?
|
3
|
+
module Committee
|
4
|
+
module SchemaValidator
|
5
|
+
class HyperSchema
|
6
|
+
attr_reader :link, :param_matches, :validator_option
|
30
7
|
|
31
|
-
|
32
|
-
|
33
|
-
|
8
|
+
def initialize(router, request, validator_option)
|
9
|
+
@link, @param_matches = router.find_request_link(request)
|
10
|
+
@validator_option = validator_option
|
34
11
|
end
|
35
|
-
data = full_body.empty? ? {} : JSON.parse(full_body)
|
36
|
-
Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only).call(status, headers, data)
|
37
|
-
end
|
38
12
|
|
39
|
-
|
40
|
-
|
41
|
-
|
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 : {}
|
42
17
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(parameter, link.schema).call!
|
47
|
-
end
|
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
|
48
21
|
|
49
|
-
|
22
|
+
request_unpack(request)
|
50
23
|
|
51
|
-
|
52
|
-
return unless link_exist?
|
24
|
+
request.env[validator_option.params_key].merge!(param_matches_hash) if param_matches_hash
|
53
25
|
|
54
|
-
|
55
|
-
|
26
|
+
request_schema_validation(request)
|
27
|
+
parameter_coerce!(request, link, validator_option.params_key)
|
28
|
+
parameter_coerce!(request, link, "rack.request.query_hash") if link_exist? && !request.GET.nil? && !link.schema.nil?
|
56
29
|
end
|
57
30
|
|
58
|
-
def
|
31
|
+
def response_validate(status, headers, response, _test_method = false)
|
59
32
|
return unless link_exist?
|
60
|
-
return if request.GET.nil? || link.schema.nil?
|
61
33
|
|
62
|
-
|
34
|
+
full_body = +""
|
35
|
+
response.each do |chunk|
|
36
|
+
full_body << chunk
|
37
|
+
end
|
38
|
+
data = full_body.empty? ? {} : JSON.parse(full_body)
|
39
|
+
Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only).call(status, headers, data)
|
63
40
|
end
|
64
41
|
|
65
|
-
def
|
66
|
-
|
67
|
-
request,
|
68
|
-
allow_form_params: validator_option.allow_form_params,
|
69
|
-
allow_get_body: validator_option.allow_get_body,
|
70
|
-
allow_query_params: validator_option.allow_query_params,
|
71
|
-
coerce_form_params: validator_option.coerce_form_params,
|
72
|
-
optimistic_json: validator_option.optimistic_json,
|
73
|
-
schema_validator: self
|
74
|
-
).call
|
42
|
+
def link_exist?
|
43
|
+
!link.nil?
|
75
44
|
end
|
76
45
|
|
77
|
-
def
|
46
|
+
def coerce_form_params(parameter)
|
78
47
|
return unless link_exist?
|
79
|
-
|
80
|
-
|
48
|
+
return unless link.schema
|
49
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(parameter, link.schema).call!
|
81
50
|
end
|
82
51
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
52
|
+
private
|
53
|
+
|
54
|
+
def coerce_path_params
|
55
|
+
return unless link_exist?
|
56
|
+
|
57
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(param_matches, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
|
58
|
+
param_matches
|
59
|
+
end
|
60
|
+
|
61
|
+
def coerce_query_params(request)
|
62
|
+
return unless link_exist?
|
63
|
+
return if request.GET.nil? || link.schema.nil?
|
64
|
+
|
65
|
+
Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(request.GET, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
|
66
|
+
end
|
67
|
+
|
68
|
+
def request_unpack(request)
|
69
|
+
request.env[validator_option.params_key], request.env[validator_option.headers_key] = Committee::RequestUnpacker.new(
|
70
|
+
request,
|
71
|
+
allow_form_params: validator_option.allow_form_params,
|
72
|
+
allow_get_body: validator_option.allow_get_body,
|
73
|
+
allow_query_params: validator_option.allow_query_params,
|
74
|
+
coerce_form_params: validator_option.coerce_form_params,
|
75
|
+
optimistic_json: validator_option.optimistic_json,
|
76
|
+
schema_validator: self
|
77
|
+
).call
|
78
|
+
end
|
79
|
+
|
80
|
+
def request_schema_validation(request)
|
81
|
+
return unless link_exist?
|
82
|
+
validator = Committee::SchemaValidator::HyperSchema::RequestValidator.new(link, check_content_type: validator_option.check_content_type, check_header: validator_option.check_header)
|
83
|
+
validator.call(request, request.env[validator_option.params_key], request.env[validator_option.headers_key])
|
84
|
+
end
|
85
|
+
|
86
|
+
def parameter_coerce!(request, link, coerce_key)
|
87
|
+
return unless link_exist?
|
88
|
+
|
89
|
+
Committee::SchemaValidator::HyperSchema::ParameterCoercer.
|
90
|
+
new(request.env[coerce_key],
|
91
|
+
link.schema,
|
92
|
+
coerce_date_times: validator_option.coerce_date_times,
|
93
|
+
coerce_recursive: validator_option.coerce_recursive).
|
94
|
+
call!
|
95
|
+
end
|
96
|
+
end
|
93
97
|
end
|
94
|
-
end
|
98
|
+
end
|
99
|
+
|
100
|
+
require_relative "hyper_schema/request_validator"
|
101
|
+
require_relative "hyper_schema/response_generator"
|
102
|
+
require_relative "hyper_schema/response_validator"
|
103
|
+
require_relative "hyper_schema/router"
|
104
|
+
require_relative "hyper_schema/string_params_coercer"
|
105
|
+
require_relative "hyper_schema/parameter_coercer"
|
@@ -1,73 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Committee
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
module SchemaValidator
|
5
|
+
class HyperSchema
|
6
|
+
class ParameterCoercer
|
7
|
+
def initialize(params, schema, options = {})
|
8
|
+
@params = params
|
9
|
+
@schema = schema
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
@coerce_date_times = options.fetch(:coerce_date_times, false)
|
12
|
+
@coerce_recursive = options.fetch(:coerce_recursive, false)
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
def call!
|
16
|
+
coerce_object!(@params, @schema)
|
17
|
+
end
|
14
18
|
|
15
|
-
|
19
|
+
private
|
16
20
|
|
17
|
-
|
18
|
-
|
21
|
+
def coerce_object!(hash, schema)
|
22
|
+
return false unless schema.respond_to?(:properties)
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
is_coerced = false
|
25
|
+
schema.properties.each do |k, s|
|
26
|
+
original_val = hash[k]
|
27
|
+
unless original_val.nil?
|
28
|
+
new_value, is_changed = coerce_value!(original_val, s)
|
29
|
+
if is_changed
|
30
|
+
hash[k] = new_value
|
31
|
+
is_coerced = true
|
32
|
+
end
|
33
|
+
end
|
28
34
|
end
|
35
|
+
|
36
|
+
is_coerced
|
29
37
|
end
|
30
|
-
end
|
31
38
|
|
32
|
-
|
33
|
-
|
39
|
+
def coerce_value!(original_val, s)
|
40
|
+
s.type.each do |to_type|
|
41
|
+
if @coerce_date_times && to_type == "string" && s.format == "date-time"
|
42
|
+
coerced_val = parse_date_time(original_val)
|
43
|
+
return coerced_val, true if coerced_val
|
44
|
+
end
|
45
|
+
|
46
|
+
return original_val, true if @coerce_recursive && (to_type == "array") && coerce_array_data!(original_val, s)
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
coerced_val = parse_date_time(original_val)
|
39
|
-
return coerced_val, true if coerced_val
|
48
|
+
return original_val, true if @coerce_recursive && (to_type == "object") && coerce_object!(original_val, s)
|
49
|
+
end
|
50
|
+
return nil, false
|
40
51
|
end
|
41
52
|
|
42
|
-
|
53
|
+
def coerce_array_data!(original_val, schema)
|
54
|
+
return false unless schema.respond_to?(:items)
|
55
|
+
return false unless original_val.is_a?(Array)
|
43
56
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
57
|
+
is_coerced = false
|
58
|
+
original_val.each_with_index do |d, index|
|
59
|
+
new_value, is_changed = coerce_value!(d, schema.items)
|
60
|
+
if is_changed
|
61
|
+
original_val[index] = new_value
|
62
|
+
is_coerced = true
|
63
|
+
end
|
64
|
+
end
|
52
65
|
|
53
|
-
|
54
|
-
original_val.each_with_index do |d, index|
|
55
|
-
new_value, is_changed = coerce_value!(d, schema.items)
|
56
|
-
if is_changed
|
57
|
-
original_val[index] = new_value
|
58
|
-
is_coerced = true
|
66
|
+
is_coerced
|
59
67
|
end
|
60
|
-
end
|
61
|
-
|
62
|
-
is_coerced
|
63
|
-
end
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
def parse_date_time(original_val)
|
70
|
+
begin
|
71
|
+
DateTime.parse(original_val)
|
72
|
+
rescue ArgumentError => e
|
73
|
+
raise ::Committee::InvalidResponse unless e.message =~ /invalid date/
|
74
|
+
end
|
75
|
+
end
|
71
76
|
end
|
77
|
+
end
|
72
78
|
end
|
73
79
|
end
|