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
@@ -0,0 +1,17 @@
1
+ module Committee
2
+ class SchemaValidator::OpenAPI3::RequestValidator
3
+ def initialize(operation_object, validator_option:)
4
+ @operation_object = operation_object
5
+ @validator_option = validator_option
6
+ end
7
+
8
+ def call(request, params, headers)
9
+ # TODO: support @check_content_type
10
+ # check_content_type!(request, params) if @check_content_type
11
+
12
+ @operation_object.validate_request_params(params, @validator_option)
13
+
14
+ # TODO: support header
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module Committee
2
+ class SchemaValidator::OpenAPI3::ResponseValidator
3
+ attr_reader :validate_errors
4
+
5
+ # @param [Committee::SchemaValidator::OpenAPI3::OperationWrapper] operation_wrapper
6
+ def initialize(operation_wrapper, validator_option)
7
+ @operation_wrapper = operation_wrapper
8
+ @validate_errors = validator_option.validate_errors
9
+ end
10
+
11
+ def self.validate?(status, options = {})
12
+ validate_errors = options[:validate_errors]
13
+
14
+ status != 204 && (validate_errors || (200...300).include?(status))
15
+ end
16
+
17
+ def call(status, headers, data)
18
+ return unless self.class.validate?(status, validate_errors: validate_errors)
19
+
20
+ content_type = headers['Content-Type'].to_s.split(";").first.to_s
21
+ operation_wrapper.validate_response_params(status, content_type, data)
22
+ end
23
+
24
+ private
25
+
26
+ # @return [Committee::SchemaValidator::OpenAPI3::OperationWrapper]
27
+ attr_reader :operation_wrapper
28
+
29
+ def check_content_type!(response)
30
+ # TODO: fix
31
+ # OpenAPI3 support multi content type definitions, so we should get OperationObject by content type and this function don't need
32
+ # We should support if content exist and not exist content-type definition, raise error (if not exist content, we don't raise error)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ module Committee
2
+ class SchemaValidator::OpenAPI3::Router
3
+ def initialize(schema, validator_option)
4
+ @schema = schema
5
+ @prefix = validator_option.prefix
6
+ @validator_option = validator_option
7
+ end
8
+
9
+ def includes_request?(request)
10
+ return true unless @prefix
11
+ #path = request.path
12
+ #path.start_with?(@prefix)
13
+ end
14
+
15
+ def build_schema_validator(request)
16
+ Committee::SchemaValidator::OpenAPI3.new(self, request, @validator_option)
17
+ end
18
+
19
+ def operation_object(request)
20
+ path = request.path
21
+ request_method = request.request_method.downcase
22
+
23
+ @schema.operation_object(path, request_method)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ class Committee::SchemaValidator
2
+ class Option
3
+ attr_reader :coerce_recursive, :params_key, :allow_form_params, :allow_query_params, :optimistic_json, :coerce_form_params, :headers_key, :coerce_query_params, :coerce_path_params, :check_content_type, :check_header, :coerce_date_times, :validate_errors, :prefix
4
+
5
+ def initialize(options, schema, schema_type)
6
+ @headers_key = options[:headers_key] || "committee.headers"
7
+ @params_key = options[:params_key] || "committee.params"
8
+
9
+ @prefix = options[:prefix]
10
+
11
+ # response option
12
+ @validate_errors = options[:validate_errors]
13
+
14
+ @coerce_recursive = options.fetch(:coerce_recursive, true)
15
+ @allow_form_params = options.fetch(:allow_form_params, true)
16
+ @allow_query_params = options.fetch(:allow_query_params, true)
17
+ @optimistic_json = options.fetch(:optimistic_json, false)
18
+
19
+ @coerce_form_params = options.fetch(:coerce_form_params, schema.driver.default_coerce_form_params)
20
+ @coerce_date_times = options.fetch(:coerce_date_times, schema.driver.default_coerce_date_times)
21
+ @coerce_query_params = options.fetch(:coerce_query_params, schema.driver.default_query_params)
22
+ @coerce_path_params = options.fetch(:coerce_path_params, schema.driver.default_path_params)
23
+
24
+ raise 'OpenAPI3 not support @check_content_type option' if schema_type == :open_api_3 && options[:check_content_type]
25
+ @check_content_type = options.fetch(:check_content_type, true)
26
+
27
+ raise 'OpenAPI3 not support @check_header option' if schema_type == :open_api_3 && options[:check_header]
28
+ @check_header = options.fetch(:check_header, true)
29
+ end
30
+ end
31
+ end
@@ -1,139 +1,36 @@
1
1
  module Committee::Test
2
2
  module Methods
3
3
  def assert_schema_conform
4
- @committee_schema ||= begin
5
- # The preferred option. The user has already parsed a schema elsewhere
6
- # and we therefore don't have to worry about any performance
7
- # implications of having to do it for every single test suite.
8
- if committee_schema
9
- committee_schema
10
- else
11
- schema = schema_contents
4
+ @schema ||= Committee::Middleware::Base.get_schema(committee_options)
5
+ @router ||= @schema.build_router(committee_options)
6
+ @validate_errors ||= committee_options[:validate_errors]
12
7
 
13
- if schema.is_a?(String)
14
- warn_string_deprecated
15
- elsif schema.is_a?(Hash)
16
- warn_hash_deprecated
17
- end
8
+ v = @router.build_schema_validator(request_object)
18
9
 
19
- if schema.is_a?(String)
20
- schema = JSON.parse(schema)
21
- end
22
-
23
- if schema.is_a?(Hash) || schema.is_a?(JsonSchema::Schema)
24
- driver = Committee::Drivers::HyperSchema.new
25
-
26
- # The driver itself has its own special cases to be able to parse
27
- # either a hash or JsonSchema::Schema object.
28
- schema = driver.parse(schema)
29
- end
30
-
31
- schema
32
- end
33
- end
34
-
35
- @committee_router ||= Committee::Router.new(@committee_schema,
36
- prefix: schema_url_prefix)
37
-
38
- link, _ = @committee_router.find_request_link(request_object)
39
- unless link
10
+ unless v.link_exist?
40
11
  response = "`#{request_object.request_method} #{request_object.path_info}` undefined in schema."
41
12
  raise Committee::InvalidResponse.new(response)
42
13
  end
43
14
 
44
15
  status, headers, body = response_data
45
- if validate?(status)
46
- data = JSON.parse(body)
47
- Committee::ResponseValidator.new(link).call(status, headers, data)
48
- end
49
- end
50
-
51
- def request_object
52
- last_request
16
+ v.response_validate(status, headers, [body]) if validate?(status)
53
17
  end
54
18
 
55
- def response_data
56
- [last_response.status, last_response.headers, last_response.body]
57
- end
58
-
59
- def assert_schema_content_type
60
- Committee.warn_deprecated("Committee: use of #assert_schema_content_type is deprecated; use #assert_schema_conform instead.")
61
- end
62
-
63
- # we use this method 3.0 or later
64
19
  def committee_options
65
- unless defined?(@call_committee_options_deprecated)
66
- @call_committee_options_deprecated = true
67
- Committee.warn_deprecated("Committee: committee 3.0 require overwrite committee options so please use this method.")
68
- end
69
-
70
- {}
71
- end
72
-
73
- # Can be overridden with a different driver name for other API definition
74
- # formats.
75
- def committee_schema
76
- schema = committee_options[:schema]
77
- return schema if schema
78
-
79
- Committee.warn_deprecated("Committee: we'll remove committee_schema method in committee 3.0;" \
80
- "please use committee_options.")
81
- nil
82
- end
83
-
84
- # can be overridden alternatively to #schema_path in case the schema is
85
- # easier to access as a string
86
- # blob
87
- def schema_contents
88
- Committee.warn_deprecated("Committee: we'll remove schema_contents method in committee 3.0;" \
89
- "please use committee_options.")
90
- JSON.parse(File.read(schema_path))
91
- end
92
-
93
- def schema_path
94
- Committee.warn_deprecated("Committee: we'll remove schema_path method in committee 3.0;" \
95
- "please use committee_options.")
96
- raise "Please override #committee_schema."
97
- end
98
-
99
- def schema_url_prefix
100
- prefix = committee_options[:prefix]
101
- return prefix if prefix
102
-
103
- schema = committee_options[:schema]
104
- return nil if schema # committee_options set so we don't show warn message
105
-
106
- Committee.warn_deprecated("Committee: we'll remove schema_url_prefix method in committee 3.0;" \
107
- "please use committee_options.")
108
- nil
20
+ raise "please set options"
109
21
  end
110
22
 
111
- def warn_hash_deprecated
112
- Committee.warn_deprecated("Committee: returning a hash from " \
113
- "#schema_contents and using #schema_path is deprecated; please " \
114
- "override #committee_schema instead.")
23
+ def request_object
24
+ raise "please set object like 'last_request'"
115
25
  end
116
26
 
117
- def warn_string_deprecated
118
- Committee.warn_deprecated("Committee: returning a string from " \
119
- "#schema_contents is deprecated; please override #committee_schema " \
120
- "instead.")
27
+ def response_data
28
+ raise "please set response data like 'last_response.status, last_response.headers, last_response.body'"
121
29
  end
122
30
 
123
- def validate_response?(status)
124
- Committee.warn_deprecated("Committee: w'll remove validate_response? method in committee 3.0")
125
-
126
- Committee::ResponseValidator.validate?(status, validate_success_only: validate_success_only)
31
+ # TODO: refactoring
32
+ def validate?(status)
33
+ status != 204 and @validate_errors || (200...300).include?(status)
127
34
  end
128
-
129
- private
130
-
131
- def validate_success_only
132
- committee_options.fetch(:validate_success_only, true)
133
- end
134
-
135
- def validate?(status)
136
- Committee::ResponseValidator.validate?(status, validate_success_only: validate_success_only)
137
- end
138
35
  end
139
36
  end
@@ -25,6 +25,12 @@ describe Committee::Bin::CommitteeStub do
25
25
  assert_equal true, options[:tolerant]
26
26
  assert_equal "1234", options[:port]
27
27
  end
28
+
29
+ it "OpenAPI3 not support Stub" do
30
+ assert_raises(Committee::NotSupportOpenAPI3) do
31
+ @bin.get_app(open_api_3_schema, {})
32
+ end
33
+ end
28
34
  end
29
35
 
30
36
  describe Committee::Bin::CommitteeStub, "app" do
@@ -219,19 +219,6 @@ describe Committee::Drivers::OpenAPI2::ParameterSchemaBuilder do
219
219
  assert_equal ["limit"], schema.properties.keys
220
220
  assert_equal [], schema.required
221
221
  assert_equal ["integer"], schema.properties["limit"].type
222
- assert_nil schema.properties["limit"].enum
223
- assert_nil schema.properties["limit"].format
224
- assert_nil schema.properties["limit"].pattern
225
- assert_nil schema.properties["limit"].min_length
226
- assert_nil schema.properties["limit"].max_length
227
- assert_nil schema.properties["limit"].min_items
228
- assert_nil schema.properties["limit"].max_items
229
- assert_nil schema.properties["limit"].unique_items
230
- assert_nil schema.properties["limit"].min
231
- assert_nil schema.properties["limit"].min_exclusive
232
- assert_nil schema.properties["limit"].max
233
- assert_nil schema.properties["limit"].max_exclusive
234
- assert_nil schema.properties["limit"].multiple_of
235
222
  end
236
223
 
237
224
  it "reflects a required property into a schema" do
@@ -255,9 +242,6 @@ describe Committee::Drivers::OpenAPI2::ParameterSchemaBuilder do
255
242
  {
256
243
  "name" => "tags",
257
244
  "type" => "array",
258
- "minItems" => 1,
259
- "maxItems" => 10,
260
- "uniqueItems" => true,
261
245
  "items" => {
262
246
  "type" => "string"
263
247
  }
@@ -268,74 +252,9 @@ describe Committee::Drivers::OpenAPI2::ParameterSchemaBuilder do
268
252
 
269
253
  assert_nil schema_data
270
254
  assert_equal ["array"], schema.properties["tags"].type
271
- assert_equal 1, schema.properties["tags"].min_items
272
- assert_equal 10, schema.properties["tags"].max_items
273
- assert_equal true, schema.properties["tags"].unique_items
274
255
  assert_equal({ "type" => "string" }, schema.properties["tags"].items)
275
256
  end
276
257
 
277
- it "reflects a enum property into a schema" do
278
- data = {
279
- "parameters" => [
280
- {
281
- "name" => "type",
282
- "type" => "string",
283
- "enum" => ["hoge", "fuga"]
284
- }
285
- ]
286
- }
287
- schema, schema_data = call(data)
288
-
289
- assert_nil schema_data
290
- assert_equal ["hoge", "fuga"], schema.properties["type"].enum
291
- end
292
-
293
- it "reflects string properties into a schema" do
294
- data = {
295
- "parameters" => [
296
- {
297
- "name" => "password",
298
- "type" => "string",
299
- "format" => "password",
300
- "pattern" => "[a-zA-Z0-9]+",
301
- "minLength" => 6,
302
- "maxLength" => 30
303
- }
304
- ]
305
- }
306
- schema, schema_data = call(data)
307
-
308
- assert_nil schema_data
309
- assert_equal "password", schema.properties["password"].format
310
- assert_equal Regexp.new("[a-zA-Z0-9]+"), schema.properties["password"].pattern
311
- assert_equal 6, schema.properties["password"].min_length
312
- assert_equal 30, schema.properties["password"].max_length
313
- end
314
-
315
- it "reflects number properties into a schema" do
316
- data = {
317
- "parameters" => [
318
- {
319
- "name" => "limit",
320
- "type" => "integer",
321
- "minimum" => 20,
322
- "exclusiveMinimum" => true,
323
- "maximum" => 100,
324
- "exclusiveMaximum" => false,
325
- "multipleOf" => 10
326
- }
327
- ]
328
- }
329
- schema, schema_data = call(data)
330
-
331
- assert_nil schema_data
332
- assert_equal 20, schema.properties["limit"].min
333
- assert_equal true, schema.properties["limit"].min_exclusive
334
- assert_equal 100, schema.properties["limit"].max
335
- assert_equal false, schema.properties["limit"].max_exclusive
336
- assert_equal 10, schema.properties["limit"].multiple_of
337
- end
338
-
339
258
  it "returns schema data for a body parameter" do
340
259
  data = {
341
260
  "parameters" => [
@@ -0,0 +1,81 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe Committee::Drivers::OpenAPI3 do
4
+ before do
5
+ @driver = Committee::Drivers::OpenAPI3.new
6
+ end
7
+
8
+ it "has a name" do
9
+ assert_equal :open_api_3, @driver.name
10
+ end
11
+
12
+ it "has a schema class" do
13
+ assert_equal Committee::Drivers::OpenAPI3::Schema, @driver.schema_class
14
+ end
15
+
16
+ it "override methods" do
17
+ schema = @driver.parse(open_api_3_data)
18
+
19
+ assert_kind_of Committee::Drivers::OpenAPI3::Schema, schema
20
+ assert_kind_of OpenAPIParser::Schemas::OpenAPI, schema.open_api
21
+ assert_equal @driver, schema.driver
22
+ end
23
+
24
+ it "defaults to coercing form parameters" do
25
+ assert_equal true, @driver.default_coerce_form_params
26
+ end
27
+
28
+ it "defaults to path parameters" do
29
+ assert_equal true, @driver.default_path_params
30
+ end
31
+
32
+ it "defaults to query parameters" do
33
+ assert_equal true, @driver.default_query_params
34
+ end
35
+
36
+ describe "Schema" do
37
+ describe "#operation_object" do
38
+ describe "path template" do
39
+ it "get normal path" do
40
+ obj = open_api_3_schema.operation_object("/path_template_test/no_template", "get")
41
+ assert_equal "/path_template_test/no_template", obj.original_path
42
+ end
43
+
44
+ it "get template path" do
45
+ obj = open_api_3_schema.operation_object("/path_template_test/test", "get")
46
+ assert_equal "/path_template_test/{template_name}", obj.original_path
47
+ end
48
+
49
+ it "get nested template path" do
50
+ obj = open_api_3_schema.operation_object("/path_template_test/abc/nested", "get")
51
+ assert_equal "/path_template_test/{template_name}/nested", obj.original_path
52
+ assert_equal({"template_name"=>"abc"}, obj.path_params)
53
+ end
54
+
55
+ it "get double nested template path" do
56
+ obj = open_api_3_schema.operation_object("/path_template_test/test/nested/abc", "get")
57
+ assert_equal "/path_template_test/{template_name}/nested/{nested_parameter}", obj.original_path
58
+ assert_equal({"template_name"=>"test", "nested_parameter" => "abc"}, obj.path_params)
59
+ end
60
+
61
+ it "get twice nested template path" do
62
+ obj = open_api_3_schema.operation_object("/path_template_test/test/abc", "get")
63
+ assert_equal "/path_template_test/{template_name}/{nested_parameter}", obj.original_path
64
+ assert_equal({"template_name"=>"test", "nested_parameter" => "abc"}, obj.path_params)
65
+ end
66
+
67
+ it "get twice nested concrete path" do
68
+ obj = open_api_3_schema.operation_object("/path_template_test/test/abc/finish", "get")
69
+ assert_equal "/path_template_test/{template_name}/{nested_parameter}/finish", obj.original_path
70
+ assert_equal({"template_name"=>"test", "nested_parameter" => "abc"}, obj.path_params)
71
+ end
72
+
73
+ it "get ambiguous path" do
74
+ obj = open_api_3_schema.operation_object("/ambiguous/no_template", "get")
75
+ assert_equal "/{ambiguous}/no_template", obj.original_path
76
+ assert_equal({"ambiguous"=>"ambiguous"}, obj.path_params)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end