committee 2.5.1 → 3.0.0.alpha

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