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.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/lib/committee.rb +16 -5
  3. data/lib/committee/bin/committee_stub.rb +4 -0
  4. data/lib/committee/drivers.rb +12 -29
  5. data/lib/committee/drivers/hyper_schema.rb +9 -0
  6. data/lib/committee/drivers/open_api_2.rb +9 -20
  7. data/lib/committee/drivers/open_api_3.rb +70 -0
  8. data/lib/committee/errors.rb +3 -0
  9. data/lib/committee/middleware/base.rb +20 -62
  10. data/lib/committee/middleware/request_validation.rb +5 -62
  11. data/lib/committee/middleware/response_validation.rb +5 -19
  12. data/lib/committee/middleware/stub.rb +5 -1
  13. data/lib/committee/parameter_coercer.rb +1 -0
  14. data/lib/committee/request_unpacker.rb +2 -5
  15. data/lib/committee/schema_validator/hyper_schema.rb +92 -0
  16. data/lib/committee/{request_validator.rb → schema_validator/hyper_schema/request_validator.rb} +1 -1
  17. data/lib/committee/{response_generator.rb → schema_validator/hyper_schema/response_generator.rb} +1 -1
  18. data/lib/committee/{response_validator.rb → schema_validator/hyper_schema/response_validator.rb} +6 -6
  19. data/lib/committee/{router.rb → schema_validator/hyper_schema/router.rb} +10 -4
  20. data/lib/committee/{string_params_coercer.rb → schema_validator/hyper_schema/string_params_coercer.rb} +1 -1
  21. data/lib/committee/schema_validator/open_api_3.rb +67 -0
  22. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +98 -0
  23. data/lib/committee/schema_validator/open_api_3/request_validator.rb +17 -0
  24. data/lib/committee/schema_validator/open_api_3/response_validator.rb +35 -0
  25. data/lib/committee/schema_validator/open_api_3/router.rb +26 -0
  26. data/lib/committee/schema_validator/option.rb +31 -0
  27. data/lib/committee/test/methods.rb +14 -117
  28. data/test/bin/committee_stub_test.rb +6 -0
  29. data/test/drivers/open_api_2_test.rb +0 -81
  30. data/test/drivers/open_api_3_test.rb +81 -0
  31. data/test/drivers_test.rb +2 -42
  32. data/test/middleware/base_test.rb +42 -21
  33. data/test/middleware/request_validation_open_api_3_test.rb +499 -0
  34. data/test/middleware/request_validation_test.rb +18 -0
  35. data/test/middleware/response_validation_open_api_3_test.rb +96 -0
  36. data/test/middleware/response_validation_test.rb +7 -30
  37. data/test/middleware/stub_test.rb +9 -0
  38. data/test/request_unpacker_test.rb +55 -21
  39. data/test/{request_validator_test.rb → schema_validator/hyper_schema/request_validator_test.rb} +4 -4
  40. data/test/{response_generator_test.rb → schema_validator/hyper_schema/response_generator_test.rb} +11 -11
  41. data/test/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +3 -3
  42. data/test/{router_test.rb → schema_validator/hyper_schema/router_test.rb} +8 -10
  43. data/test/{string_params_coercer_test.rb → schema_validator/hyper_schema/string_params_coercer_test.rb} +3 -3
  44. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +132 -0
  45. data/test/schema_validator/open_api_3/request_validator_test.rb +151 -0
  46. data/test/schema_validator/open_api_3/response_validator_test.rb +55 -0
  47. data/test/test/methods_new_version_test.rb +11 -20
  48. data/test/test/methods_test.rb +51 -55
  49. data/test/test_helper.rb +22 -8
  50. metadata +46 -18
@@ -1,32 +1,18 @@
1
1
  module Committee::Middleware
2
2
  class ResponseValidation < Base
3
- attr_reader :validate_success_only
3
+ attr_reader :validate_errors
4
4
 
5
5
  def initialize(app, options = {})
6
6
  super
7
- @validate_success_only = options.fetch(:validate_success_only, true)
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
- link, _ = @router.find_request_link(request)
22
- if validate?(status) && link
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
- Committee::ResponseValidator.validate?(status, validate_success_only: validate_success_only)
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
@@ -1,3 +1,4 @@
1
+ # TODO: Support OpenAPI3
1
2
  module Committee
2
3
  class ParameterCoercer
3
4
  def initialize(params, schema, options = {})
@@ -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
- @coerce_recursive = options[:coerce_recursive]
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
- if @coerce_form_params && @schema
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
@@ -1,5 +1,5 @@
1
1
  module Committee
2
- class RequestValidator
2
+ class SchemaValidator::HyperSchema::RequestValidator
3
3
  def initialize(link, options = {})
4
4
  @link = link
5
5
  @check_content_type = options.fetch(:check_content_type, true)
@@ -1,5 +1,5 @@
1
1
  module Committee
2
- class ResponseGenerator
2
+ class SchemaValidator::HyperSchema::ResponseGenerator
3
3
  def call(link)
4
4
  schema = target_schema(link)
5
5
  data = generate_properties(link, schema)
@@ -1,18 +1,18 @@
1
1
  module Committee
2
- class ResponseValidator
3
- attr_reader :validate_success_only
2
+ class SchemaValidator::HyperSchema::ResponseValidator
3
+ attr_reader :validate_errors
4
4
 
5
5
  def initialize(link, options = {})
6
6
  @link = link
7
- @validate_success_only = options[:validate_success_only]
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
- validate_success_only = options[:validate_success_only]
13
+ validate_errors = options[:validate_errors]
14
14
 
15
- status != 204 and !validate_success_only || (200...300).include?(status)
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, validate_success_only: validate_success_only) && !@validator.validate(data)
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, options = {})
4
- @prefix = options[: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.sort_by(&:first).first
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