committee 1.15.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/committee-stub +11 -38
- data/lib/committee/bin/committee_stub.rb +67 -0
- data/lib/committee/drivers/driver.rb +47 -0
- data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
- data/lib/committee/drivers/hyper_schema/link.rb +68 -0
- data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
- data/lib/committee/drivers/hyper_schema.rb +12 -0
- data/lib/committee/drivers/open_api_2/driver.rb +252 -0
- data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
- data/lib/committee/drivers/open_api_2/link.rb +36 -0
- data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
- data/lib/committee/drivers/open_api_2/schema.rb +26 -0
- data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
- data/lib/committee/drivers/open_api_2.rb +13 -0
- data/lib/committee/drivers/open_api_3/driver.rb +51 -0
- data/lib/committee/drivers/open_api_3/schema.rb +41 -0
- data/lib/committee/drivers/open_api_3.rb +11 -0
- data/lib/committee/drivers/schema.rb +23 -0
- data/lib/committee/drivers.rb +84 -0
- data/lib/committee/errors.rb +17 -0
- data/lib/committee/middleware/base.rb +46 -29
- data/lib/committee/middleware/request_validation.rb +31 -49
- data/lib/committee/middleware/response_validation.rb +48 -25
- data/lib/committee/middleware/stub.rb +62 -37
- data/lib/committee/middleware.rb +11 -0
- data/lib/committee/request_unpacker.rb +58 -50
- data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +79 -0
- data/lib/committee/schema_validator/hyper_schema/request_validator.rb +55 -0
- data/lib/committee/schema_validator/hyper_schema/response_generator.rb +102 -0
- data/lib/committee/schema_validator/hyper_schema/response_validator.rb +89 -0
- data/lib/committee/schema_validator/hyper_schema/router.rb +46 -0
- data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +105 -0
- data/lib/committee/schema_validator/hyper_schema.rb +119 -0
- data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +139 -0
- data/lib/committee/schema_validator/open_api_3/request_validator.rb +52 -0
- data/lib/committee/schema_validator/open_api_3/response_validator.rb +29 -0
- data/lib/committee/schema_validator/open_api_3/router.rb +45 -0
- data/lib/committee/schema_validator/open_api_3.rb +120 -0
- data/lib/committee/schema_validator/option.rb +60 -0
- data/lib/committee/schema_validator.rb +23 -0
- data/lib/committee/test/methods.rb +68 -38
- data/lib/committee/test/schema_coverage.rb +101 -0
- data/lib/committee/utils.rb +28 -0
- data/lib/committee/validation_error.rb +5 -2
- data/lib/committee/version.rb +5 -0
- data/lib/committee.rb +31 -18
- data/test/bin/committee_stub_test.rb +57 -0
- data/test/bin_test.rb +25 -0
- data/test/committee_test.rb +77 -0
- data/test/drivers/hyper_schema/driver_test.rb +49 -0
- data/test/drivers/hyper_schema/link_test.rb +56 -0
- data/test/drivers/open_api_2/driver_test.rb +156 -0
- data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
- data/test/drivers/open_api_2/link_test.rb +52 -0
- data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
- data/test/drivers/open_api_3/driver_test.rb +84 -0
- data/test/drivers_test.rb +154 -0
- data/test/middleware/base_test.rb +96 -7
- data/test/middleware/request_validation_open_api_3_test.rb +626 -0
- data/test/middleware/request_validation_test.rb +423 -32
- data/test/middleware/response_validation_open_api_3_test.rb +291 -0
- data/test/middleware/response_validation_test.rb +125 -23
- data/test/middleware/stub_test.rb +81 -20
- data/test/request_unpacker_test.rb +126 -52
- data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +111 -0
- data/test/schema_validator/hyper_schema/request_validator_test.rb +151 -0
- data/test/schema_validator/hyper_schema/response_generator_test.rb +142 -0
- data/test/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +43 -6
- data/test/schema_validator/hyper_schema/router_test.rb +88 -0
- data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +137 -0
- data/test/schema_validator/open_api_3/operation_wrapper_test.rb +218 -0
- data/test/schema_validator/open_api_3/request_validator_test.rb +110 -0
- data/test/schema_validator/open_api_3/response_validator_test.rb +92 -0
- data/test/test/methods_new_version_test.rb +97 -0
- data/test/test/methods_test.rb +334 -27
- data/test/test/schema_coverage_test.rb +216 -0
- data/test/test_helper.rb +108 -1
- data/test/validation_error_test.rb +3 -1
- metadata +190 -27
- data/lib/committee/query_params_coercer.rb +0 -45
- data/lib/committee/request_validator.rb +0 -44
- data/lib/committee/response_generator.rb +0 -35
- data/lib/committee/response_validator.rb +0 -59
- data/lib/committee/router.rb +0 -62
- data/test/query_params_coercer_test.rb +0 -70
- data/test/request_validator_test.rb +0 -103
- data/test/response_generator_test.rb +0 -61
- data/test/router_test.rb +0 -38
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI2
|
6
|
+
# Link abstracts an API link specifically for OpenAPI 2.
|
7
|
+
class Link
|
8
|
+
# The link's input media type. i.e. How requests should be encoded.
|
9
|
+
attr_accessor :enc_type
|
10
|
+
|
11
|
+
attr_accessor :href
|
12
|
+
|
13
|
+
# The link's output media type. i.e. How responses should be encoded.
|
14
|
+
attr_accessor :media_type
|
15
|
+
|
16
|
+
attr_accessor :method
|
17
|
+
|
18
|
+
# The link's input schema. i.e. How we validate an endpoint's incoming
|
19
|
+
# parameters.
|
20
|
+
attr_accessor :schema
|
21
|
+
|
22
|
+
attr_accessor :status_success
|
23
|
+
|
24
|
+
# The link's output schema. i.e. How we validate an endpoint's response
|
25
|
+
# data.
|
26
|
+
attr_accessor :target_schema
|
27
|
+
|
28
|
+
attr_accessor :header_schema
|
29
|
+
|
30
|
+
def rel
|
31
|
+
raise "Committee: rel not implemented for OpenAPI"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI2
|
6
|
+
# ParameterSchemaBuilder converts OpenAPI 2 link parameters, which are not
|
7
|
+
# quite JSON schemas (but will be in OpenAPI 3) into synthetic schemas that
|
8
|
+
# we can use to do some basic request validation.
|
9
|
+
class ParameterSchemaBuilder < SchemaBuilder
|
10
|
+
# Returns a tuple of (schema, schema_data) where only one of the two
|
11
|
+
# values is present. This is either a full schema that's ready to go _or_
|
12
|
+
# a hash of unparsed schema data.
|
13
|
+
def call
|
14
|
+
if link_data["parameters"]
|
15
|
+
body_param = link_data["parameters"].detect { |p| p["in"] == "body" }
|
16
|
+
if body_param
|
17
|
+
check_required_fields!(body_param)
|
18
|
+
|
19
|
+
if link_data["parameters"].detect { |p| p["in"] == "form" } != nil
|
20
|
+
raise ArgumentError, "Committee: can't mix body parameter " \
|
21
|
+
"with form parameters."
|
22
|
+
end
|
23
|
+
|
24
|
+
schema_data = body_param["schema"]
|
25
|
+
[nil, schema_data]
|
26
|
+
else
|
27
|
+
link_schema = JsonSchema::Schema.new
|
28
|
+
link_schema.properties = {}
|
29
|
+
link_schema.required = []
|
30
|
+
|
31
|
+
parameters = link_data["parameters"].reject { |param_data| param_data["in"] == "header" }
|
32
|
+
parameters.each do |param_data|
|
33
|
+
check_required_fields!(param_data)
|
34
|
+
|
35
|
+
param_schema = JsonSchema::Schema.new
|
36
|
+
|
37
|
+
# We could probably use more validation here, but the formats of
|
38
|
+
# OpenAPI 2 are based off of what's available in JSON schema, and
|
39
|
+
# therefore this should map over quite well.
|
40
|
+
param_schema.type = [param_data["type"]]
|
41
|
+
|
42
|
+
param_schema.enum = param_data["enum"] unless param_data["enum"].nil?
|
43
|
+
|
44
|
+
# validation: string
|
45
|
+
param_schema.format = param_data["format"] unless param_data["format"].nil?
|
46
|
+
param_schema.pattern = Regexp.new(param_data["pattern"]) unless param_data["pattern"].nil?
|
47
|
+
param_schema.min_length = param_data["minLength"] unless param_data["minLength"].nil?
|
48
|
+
param_schema.max_length = param_data["maxLength"] unless param_data["maxLength"].nil?
|
49
|
+
|
50
|
+
# validation: array
|
51
|
+
param_schema.min_items = param_data["minItems"] unless param_data["minItems"].nil?
|
52
|
+
param_schema.max_items = param_data["maxItems"] unless param_data["maxItems"].nil?
|
53
|
+
param_schema.unique_items = param_data["uniqueItems"] unless param_data["uniqueItems"].nil?
|
54
|
+
|
55
|
+
# validation: number/integer
|
56
|
+
param_schema.min = param_data["minimum"] unless param_data["minimum"].nil?
|
57
|
+
param_schema.min_exclusive = param_data["exclusiveMinimum"] unless param_data["exclusiveMinimum"].nil?
|
58
|
+
param_schema.max = param_data["maximum"] unless param_data["maximum"].nil?
|
59
|
+
param_schema.max_exclusive = param_data["exclusiveMaximum"] unless param_data["exclusiveMaximum"].nil?
|
60
|
+
param_schema.multiple_of = param_data["multipleOf"] unless param_data["multipleOf"].nil?
|
61
|
+
|
62
|
+
# And same idea: despite parameters not being schemas, the items
|
63
|
+
# key (if preset) is actually a schema that defines each item of an
|
64
|
+
# array type, so we can just reflect that directly onto our
|
65
|
+
# artificial schema.
|
66
|
+
if param_data["type"] == "array" && param_data["items"]
|
67
|
+
param_schema.items = param_data["items"]
|
68
|
+
end
|
69
|
+
|
70
|
+
link_schema.properties[param_data["name"]] = param_schema
|
71
|
+
if param_data["required"] == true
|
72
|
+
link_schema.required << param_data["name"]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
[link_schema, nil]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI2
|
6
|
+
class Schema < ::Committee::Drivers::Schema
|
7
|
+
attr_accessor :base_path
|
8
|
+
attr_accessor :consumes
|
9
|
+
|
10
|
+
# A link back to the derivative instance of Committee::Drivers::Driver
|
11
|
+
# that create this schema.
|
12
|
+
attr_accessor :driver
|
13
|
+
|
14
|
+
attr_accessor :definitions
|
15
|
+
attr_accessor :produces
|
16
|
+
attr_accessor :routes
|
17
|
+
attr_reader :validator_option
|
18
|
+
|
19
|
+
def build_router(options)
|
20
|
+
@validator_option = Committee::SchemaValidator::Option.new(options, self, :hyper_schema)
|
21
|
+
Committee::SchemaValidator::HyperSchema::Router.new(self, @validator_option)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI2
|
6
|
+
class SchemaBuilder
|
7
|
+
def initialize(link_data)
|
8
|
+
@link_data = link_data
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
LINK_REQUIRED_FIELDS = [
|
14
|
+
:name
|
15
|
+
].map(&:to_s).freeze
|
16
|
+
|
17
|
+
attr_accessor :link_data
|
18
|
+
|
19
|
+
def check_required_fields!(param_data)
|
20
|
+
LINK_REQUIRED_FIELDS.each do |field|
|
21
|
+
if !param_data[field]
|
22
|
+
raise ArgumentError,
|
23
|
+
"Committee: no #{field} section in link data."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require_relative 'header_schema_builder'
|
33
|
+
require_relative 'parameter_schema_builder'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI2
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'open_api_2/driver'
|
11
|
+
require_relative 'open_api_2/link'
|
12
|
+
require_relative 'open_api_2/schema'
|
13
|
+
require_relative 'open_api_2/schema_builder'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI3
|
6
|
+
class Driver < ::Committee::Drivers::Driver
|
7
|
+
def default_coerce_date_times
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
# Whether parameters that were form-encoded will be coerced by default.
|
12
|
+
def default_coerce_form_params
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_allow_get_body
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Whether parameters in a request's path will be considered and coerced by
|
21
|
+
# default.
|
22
|
+
def default_path_params
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# Whether parameters in a request's query string will be considered and
|
27
|
+
# coerced by default.
|
28
|
+
def default_query_params
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_validate_success_only
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
:open_api_3
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Committee::Drivers::OpenAPI3::Schema]
|
41
|
+
def parse(open_api)
|
42
|
+
schema_class.new(self, open_api)
|
43
|
+
end
|
44
|
+
|
45
|
+
def schema_class
|
46
|
+
Committee::Drivers::OpenAPI3::Schema
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
module OpenAPI3
|
6
|
+
class Schema < ::Committee::Drivers::Schema
|
7
|
+
attr_reader :open_api
|
8
|
+
attr_reader :validator_option
|
9
|
+
|
10
|
+
# @!attribute [r] open_api
|
11
|
+
# @return [OpenAPIParser::Schemas::OpenAPI]
|
12
|
+
|
13
|
+
def initialize(driver, open_api)
|
14
|
+
@open_api = open_api
|
15
|
+
@driver = driver
|
16
|
+
end
|
17
|
+
|
18
|
+
def supports_stub?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def driver # we don't use attr_reader because this method override super class
|
23
|
+
@driver
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_router(options)
|
27
|
+
@validator_option = Committee::SchemaValidator::Option.new(options, self, :open_api_3)
|
28
|
+
Committee::SchemaValidator::OpenAPI3::Router.new(self, @validator_option)
|
29
|
+
end
|
30
|
+
|
31
|
+
# OpenAPI3 only
|
32
|
+
def operation_object(path, method)
|
33
|
+
request_operation = open_api.request_operation(method, path)
|
34
|
+
return nil unless request_operation
|
35
|
+
|
36
|
+
Committee::SchemaValidator::OpenAPI3::OperationWrapper.new(request_operation)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
# Schema is a base class for driver schema implementations.
|
6
|
+
class Schema
|
7
|
+
# A link back to the derivative instance of Committee::Drivers::Driver
|
8
|
+
# that create this schema.
|
9
|
+
def driver
|
10
|
+
raise "needs implementation"
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_router(options)
|
14
|
+
raise "needs implementation"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Stubs are supported in JSON Hyper-Schema and OpenAPI 2, but not yet in OpenAPI 3
|
18
|
+
def supports_stub?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Drivers
|
5
|
+
# Gets a driver instance from the specified name. Raises ArgumentError for
|
6
|
+
# an unknown driver name.
|
7
|
+
def self.driver_from_name(name)
|
8
|
+
case name
|
9
|
+
when :hyper_schema
|
10
|
+
Committee::Drivers::HyperSchema::Driver.new
|
11
|
+
when :open_api_2
|
12
|
+
Committee::Drivers::OpenAPI2::Driver.new
|
13
|
+
when :open_api_3
|
14
|
+
Committee::Drivers::OpenAPI3::Driver.new
|
15
|
+
else
|
16
|
+
raise ArgumentError, %{Committee: unknown driver "#{name}".}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# load and build drive from JSON file
|
21
|
+
# @param [String] schema_path
|
22
|
+
# @return [Committee::Driver]
|
23
|
+
def self.load_from_json(schema_path, parser_options: {})
|
24
|
+
load_from_data(JSON.parse(File.read(schema_path)), schema_path, parser_options: parser_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# load and build drive from YAML file
|
28
|
+
# @param [String] schema_path
|
29
|
+
# @return [Committee::Driver]
|
30
|
+
def self.load_from_yaml(schema_path, parser_options: {})
|
31
|
+
data = YAML.respond_to?(:unsafe_load_file) ? YAML.unsafe_load_file(schema_path) : YAML.load_file(schema_path)
|
32
|
+
load_from_data(data, schema_path, parser_options: parser_options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# load and build drive from file
|
36
|
+
# @param [String] schema_path
|
37
|
+
# @return [Committee::Driver]
|
38
|
+
def self.load_from_file(schema_path, parser_options: {})
|
39
|
+
case File.extname(schema_path)
|
40
|
+
when '.json'
|
41
|
+
load_from_json(schema_path, parser_options: parser_options)
|
42
|
+
when '.yaml', '.yml'
|
43
|
+
load_from_yaml(schema_path, parser_options: parser_options)
|
44
|
+
else
|
45
|
+
raise "Committee only supports the following file extensions: '.json', '.yaml', '.yml'"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# load and build drive from Hash object
|
50
|
+
# @param [Hash] hash
|
51
|
+
# @return [Committee::Driver]
|
52
|
+
def self.load_from_data(hash, schema_path = nil, parser_options: {})
|
53
|
+
if hash['openapi']&.start_with?('3.0.')
|
54
|
+
# From the next major version, we want to ensure `{ strict_reference_validation: true }`
|
55
|
+
# as a parser option here, but since it may break existing implementations, just warn
|
56
|
+
# if it is not explicitly set. See: https://github.com/interagent/committee/issues/343#issuecomment-997400329
|
57
|
+
opts = parser_options.dup
|
58
|
+
|
59
|
+
Committee.warn_deprecated_until_6(!opts.key?(:strict_reference_validation), 'openapi_parser will default to strict reference validation ' +
|
60
|
+
'from next version. Pass config `strict_reference_validation: true` (or false, if you must) ' +
|
61
|
+
'to quiet this warning.')
|
62
|
+
opts[:strict_reference_validation] ||= false
|
63
|
+
|
64
|
+
openapi = OpenAPIParser.parse_with_filepath(hash, schema_path, opts)
|
65
|
+
return Committee::Drivers::OpenAPI3::Driver.new.parse(openapi)
|
66
|
+
end
|
67
|
+
|
68
|
+
driver = if hash['swagger'] == '2.0'
|
69
|
+
Committee::Drivers::OpenAPI2::Driver.new
|
70
|
+
else
|
71
|
+
Committee::Drivers::HyperSchema::Driver.new
|
72
|
+
end
|
73
|
+
|
74
|
+
# TODO: in the future, pass `opts` here and allow optionality in other drivers?
|
75
|
+
driver.parse(hash)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
require_relative "drivers/driver"
|
81
|
+
require_relative "drivers/schema"
|
82
|
+
require_relative "drivers/hyper_schema"
|
83
|
+
require_relative "drivers/open_api_2"
|
84
|
+
require_relative "drivers/open_api_3"
|
data/lib/committee/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Committee
|
2
4
|
class Error < StandardError
|
3
5
|
end
|
@@ -6,9 +8,21 @@ module Committee
|
|
6
8
|
end
|
7
9
|
|
8
10
|
class InvalidRequest < Error
|
11
|
+
attr_reader :original_error
|
12
|
+
|
13
|
+
def initialize(error_message=nil, original_error: nil)
|
14
|
+
@original_error = original_error
|
15
|
+
super(error_message)
|
16
|
+
end
|
9
17
|
end
|
10
18
|
|
11
19
|
class InvalidResponse < Error
|
20
|
+
attr_reader :original_error
|
21
|
+
|
22
|
+
def initialize(error_message=nil, original_error: nil)
|
23
|
+
@original_error = original_error
|
24
|
+
super(error_message)
|
25
|
+
end
|
12
26
|
end
|
13
27
|
|
14
28
|
class NotFound < Error
|
@@ -16,4 +30,7 @@ module Committee
|
|
16
30
|
|
17
31
|
class ReferenceNotFound < Error
|
18
32
|
end
|
33
|
+
|
34
|
+
class OpenAPI3Unsupported < Error
|
35
|
+
end
|
19
36
|
end
|
@@ -1,40 +1,57 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committee
|
4
|
+
module Middleware
|
5
|
+
class Base
|
6
|
+
def initialize(app, options={})
|
7
|
+
@app = app
|
8
|
+
|
9
|
+
@error_class = options.fetch(:error_class, Committee::ValidationError)
|
10
|
+
@error_handler = options[:error_handler]
|
11
|
+
@ignore_error = options.fetch(:ignore_error, false)
|
12
|
+
|
13
|
+
@raise = options[:raise]
|
14
|
+
@schema = self.class.get_schema(options)
|
15
|
+
|
16
|
+
@router = @schema.build_router(options)
|
17
|
+
@accept_request_filter = options[:accept_request_filter] || -> (_) { true }
|
14
18
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
request = Rack::Request.new(env)
|
22
|
+
|
23
|
+
if @router.includes_request?(request) && @accept_request_filter.call(request)
|
24
|
+
handle(request)
|
25
|
+
else
|
26
|
+
@app.call(request.env)
|
27
|
+
end
|
18
28
|
end
|
19
|
-
@schema = schema
|
20
29
|
|
21
|
-
|
22
|
-
|
30
|
+
class << self
|
31
|
+
def get_schema(options)
|
32
|
+
schema = options[:schema]
|
33
|
+
if !schema && options[:schema_path]
|
34
|
+
# In the future, we could have `parser_options` as an exposed config?
|
35
|
+
parser_options = options.key?(:strict_reference_validation) ? { strict_reference_validation: options[:strict_reference_validation] } : {}
|
36
|
+
schema = Committee::Drivers::load_from_file(options[:schema_path], parser_options: parser_options)
|
37
|
+
end
|
38
|
+
raise(ArgumentError, "Committee: need option `schema` or `schema_path`") unless schema
|
23
39
|
|
24
|
-
|
25
|
-
|
40
|
+
# Expect the type we want by now. If we don't have it, the user passed
|
41
|
+
# something else non-standard in.
|
42
|
+
if !schema.is_a?(Committee::Drivers::Schema)
|
43
|
+
raise ArgumentError, "Committee: schema expected to be an instance of Committee::Drivers::Schema."
|
44
|
+
end
|
26
45
|
|
27
|
-
|
28
|
-
|
29
|
-
else
|
30
|
-
@app.call(request.env)
|
46
|
+
return schema
|
47
|
+
end
|
31
48
|
end
|
32
|
-
end
|
33
49
|
|
34
|
-
|
50
|
+
private
|
35
51
|
|
36
|
-
|
37
|
-
|
52
|
+
def build_schema_validator(request)
|
53
|
+
@router.build_schema_validator(request)
|
54
|
+
end
|
38
55
|
end
|
39
56
|
end
|
40
57
|
end
|
@@ -1,59 +1,41 @@
|
|
1
|
-
|
2
|
-
class RequestValidation < Base
|
3
|
-
def initialize(app, options={})
|
4
|
-
super
|
5
|
-
@allow_form_params = options.fetch(:allow_form_params, true)
|
6
|
-
@allow_query_params = options.fetch(:allow_query_params, true)
|
7
|
-
@check_content_type = options.fetch(:check_content_type, true)
|
8
|
-
@optimistic_json = options.fetch(:optimistic_json, false)
|
9
|
-
@coerce_query_params = options.fetch(:coerce_query_params, false)
|
10
|
-
@strict = options[:strict]
|
1
|
+
# frozen_string_literal: true
|
11
2
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
link = @router.find_request_link(request)
|
3
|
+
module Committee
|
4
|
+
module Middleware
|
5
|
+
class RequestValidation < Base
|
6
|
+
def initialize(app, options={})
|
7
|
+
super
|
18
8
|
|
19
|
-
|
20
|
-
request.env["rack.request.query_hash"].merge!(
|
21
|
-
Committee::QueryParamsCoercer.new(
|
22
|
-
request.GET,
|
23
|
-
link.schema
|
24
|
-
).call
|
25
|
-
)
|
9
|
+
@strict = options[:strict]
|
26
10
|
end
|
27
11
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
12
|
+
def handle(request)
|
13
|
+
begin
|
14
|
+
schema_validator = build_schema_validator(request)
|
15
|
+
schema_validator.request_validate(request)
|
16
|
+
|
17
|
+
raise Committee::NotFound, "That request method and path combination isn't defined." if !schema_validator.link_exist? && @strict
|
18
|
+
rescue Committee::BadRequest, Committee::InvalidRequest
|
19
|
+
handle_exception($!, request.env)
|
20
|
+
raise if @raise
|
21
|
+
return @error_class.new(400, :bad_request, $!.message, request).render unless @ignore_error
|
22
|
+
rescue Committee::NotFound => e
|
23
|
+
raise if @raise
|
24
|
+
return @error_class.new(404, :not_found, e.message, request).render unless @ignore_error
|
25
|
+
rescue JSON::ParserError
|
26
|
+
handle_exception($!, request.env)
|
27
|
+
raise Committee::InvalidRequest if @raise
|
28
|
+
return @error_class.new(400, :bad_request, "Request body wasn't valid JSON.", request).render unless @ignore_error
|
29
|
+
end
|
34
30
|
|
35
|
-
if link
|
36
|
-
validator = Committee::RequestValidator.new(link, check_content_type: @check_content_type)
|
37
|
-
validator.call(request, request.env[@params_key])
|
38
|
-
@app.call(request.env)
|
39
|
-
elsif @strict
|
40
|
-
raise Committee::NotFound
|
41
|
-
else
|
42
31
|
@app.call(request.env)
|
43
32
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
404,
|
51
|
-
:not_found,
|
52
|
-
"That request method and path combination isn't defined."
|
53
|
-
).render
|
54
|
-
rescue JSON::ParserError
|
55
|
-
raise Committee::InvalidRequest if @raise
|
56
|
-
@error_class.new(400, :bad_request, "Request body wasn't valid JSON.").render
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def handle_exception(e, env)
|
37
|
+
@error_handler.call(e, env) if @error_handler
|
38
|
+
end
|
57
39
|
end
|
58
40
|
end
|
59
41
|
end
|