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