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
@@ -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