committee 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
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