committee 3.1.0 → 3.1.1
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.
- checksums.yaml +4 -4
- data/bin/committee-stub +1 -0
- data/lib/committee.rb +12 -34
- data/lib/committee/bin/committee_stub.rb +6 -4
- data/lib/committee/drivers.rb +15 -67
- data/lib/committee/drivers/driver.rb +47 -0
- data/lib/committee/drivers/hyper_schema.rb +8 -171
- 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/open_api_2.rb +9 -416
- data/lib/committee/drivers/open_api_2/driver.rb +253 -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_3.rb +7 -75
- 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/schema.rb +23 -0
- data/lib/committee/errors.rb +2 -0
- data/lib/committee/middleware.rb +11 -0
- data/lib/committee/middleware/base.rb +38 -34
- data/lib/committee/middleware/request_validation.rb +51 -30
- data/lib/committee/middleware/response_validation.rb +49 -26
- data/lib/committee/middleware/stub.rb +55 -51
- data/lib/committee/request_unpacker.rb +3 -1
- data/lib/committee/schema_validator.rb +23 -0
- data/lib/committee/schema_validator/hyper_schema.rb +85 -74
- data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +60 -54
- data/lib/committee/schema_validator/hyper_schema/request_validator.rb +43 -37
- data/lib/committee/schema_validator/hyper_schema/response_generator.rb +86 -80
- data/lib/committee/schema_validator/hyper_schema/response_validator.rb +65 -59
- data/lib/committee/schema_validator/hyper_schema/router.rb +35 -29
- data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +87 -81
- data/lib/committee/schema_validator/open_api_3.rb +71 -61
- data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +121 -115
- data/lib/committee/schema_validator/open_api_3/request_validator.rb +24 -18
- data/lib/committee/schema_validator/open_api_3/response_validator.rb +22 -16
- data/lib/committee/schema_validator/open_api_3/router.rb +30 -24
- data/lib/committee/schema_validator/option.rb +42 -38
- data/lib/committee/test/methods.rb +55 -51
- data/lib/committee/validation_error.rb +2 -0
- data/test/bin/committee_stub_test.rb +3 -1
- data/test/bin_test.rb +3 -1
- data/test/committee_test.rb +3 -1
- data/test/drivers/hyper_schema/driver_test.rb +49 -0
- data/test/drivers/{hyper_schema_test.rb → hyper_schema/link_test.rb} +2 -45
- 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_test.rb → open_api_3/driver_test.rb} +5 -3
- data/test/drivers_test.rb +12 -10
- data/test/middleware/base_test.rb +3 -1
- data/test/middleware/request_validation_open_api_3_test.rb +4 -2
- data/test/middleware/request_validation_test.rb +46 -5
- data/test/middleware/response_validation_open_api_3_test.rb +3 -1
- data/test/middleware/response_validation_test.rb +39 -4
- data/test/middleware/stub_test.rb +3 -1
- data/test/request_unpacker_test.rb +2 -2
- data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +2 -2
- data/test/schema_validator/hyper_schema/request_validator_test.rb +3 -1
- data/test/schema_validator/hyper_schema/response_generator_test.rb +3 -1
- data/test/schema_validator/hyper_schema/response_validator_test.rb +3 -1
- data/test/schema_validator/hyper_schema/router_test.rb +5 -3
- data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +3 -1
- data/test/schema_validator/open_api_3/operation_wrapper_test.rb +3 -1
- data/test/schema_validator/open_api_3/request_validator_test.rb +11 -1
- data/test/schema_validator/open_api_3/response_validator_test.rb +3 -1
- data/test/test/methods_new_version_test.rb +3 -1
- data/test/test/methods_test.rb +4 -2
- data/test/test_helper.rb +16 -16
- data/test/validation_error_test.rb +3 -1
- metadata +52 -6
- data/lib/committee/schema_validator/schema_validator.rb +0 -15
- data/test/drivers/open_api_2_test.rb +0 -416
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Committee
|
|
4
|
+
module Drivers
|
|
5
|
+
module HyperSchema
|
|
6
|
+
# Link abstracts an API link specifically for JSON hyper-schema.
|
|
7
|
+
#
|
|
8
|
+
# For most operations, it's a simple pass through to a
|
|
9
|
+
# JsonSchema::Schema::Link, but implements some exotic behavior in a few
|
|
10
|
+
# places.
|
|
11
|
+
class Link
|
|
12
|
+
def initialize(hyper_schema_link)
|
|
13
|
+
@hyper_schema_link = hyper_schema_link
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The link's input media type. i.e. How requests should be encoded.
|
|
17
|
+
def enc_type
|
|
18
|
+
hyper_schema_link.enc_type
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def href
|
|
22
|
+
hyper_schema_link.href
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The link's output media type. i.e. How responses should be encoded.
|
|
26
|
+
def media_type
|
|
27
|
+
hyper_schema_link.media_type
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def method
|
|
31
|
+
hyper_schema_link.method
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Passes through a link's parent resource. Note that this is *not* part
|
|
35
|
+
# of the Link interface and is here to support a legacy Heroku-ism
|
|
36
|
+
# behavior that allowed a link tagged with rel=instances to imply that a
|
|
37
|
+
# list will be returned.
|
|
38
|
+
def parent
|
|
39
|
+
hyper_schema_link.parent
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def rel
|
|
43
|
+
hyper_schema_link.rel
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The link's input schema. i.e. How we validate an endpoint's incoming
|
|
47
|
+
# parameters.
|
|
48
|
+
def schema
|
|
49
|
+
hyper_schema_link.schema
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def status_success
|
|
53
|
+
hyper_schema_link.rel == "create" ? 201 : 200
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The link's output schema. i.e. How we validate an endpoint's response
|
|
57
|
+
# data.
|
|
58
|
+
def target_schema
|
|
59
|
+
hyper_schema_link.target_schema
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
attr_accessor :hyper_schema_link
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Committee
|
|
4
|
+
module Drivers
|
|
5
|
+
module HyperSchema
|
|
6
|
+
class Schema < ::Committee::Drivers::Schema
|
|
7
|
+
# A link back to the derivative instance of Committee::Drivers::Driver
|
|
8
|
+
# that create this schema.
|
|
9
|
+
attr_accessor :driver
|
|
10
|
+
|
|
11
|
+
attr_accessor :routes
|
|
12
|
+
|
|
13
|
+
attr_reader :validator_option
|
|
14
|
+
|
|
15
|
+
def build_router(options)
|
|
16
|
+
@validator_option = Committee::SchemaValidator::Option.new(options, self, :hyper_schema)
|
|
17
|
+
Committee::SchemaValidator::HyperSchema::Router.new(self, @validator_option)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -1,420 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
class OpenAPI2 < Committee::Drivers::Driver
|
|
3
|
-
def default_coerce_date_times
|
|
4
|
-
false
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
# Whether parameters that were form-encoded will be coerced by default.
|
|
8
|
-
def default_coerce_form_params
|
|
9
|
-
true
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def default_allow_get_body
|
|
13
|
-
true
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Whether parameters in a request's path will be considered and coerced by
|
|
17
|
-
# default.
|
|
18
|
-
def default_path_params
|
|
19
|
-
true
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Whether parameters in a request's query string will be considered and
|
|
23
|
-
# coerced by default.
|
|
24
|
-
def default_query_params
|
|
25
|
-
true
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def default_validate_success_only
|
|
29
|
-
true
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def name
|
|
33
|
-
:open_api_2
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Parses an API schema and builds a set of route definitions for use with
|
|
37
|
-
# Committee.
|
|
38
|
-
#
|
|
39
|
-
# The expected input format is a data hash with keys as strings (as opposed
|
|
40
|
-
# to symbols) like the kind produced by JSON.parse or YAML.load.
|
|
41
|
-
def parse(data)
|
|
42
|
-
REQUIRED_FIELDS.each do |field|
|
|
43
|
-
if !data[field]
|
|
44
|
-
raise ArgumentError, "Committee: no #{field} section in spec data."
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
if data['swagger'] != '2.0'
|
|
49
|
-
raise ArgumentError, "Committee: driver requires OpenAPI 2.0."
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
schema = Schema.new
|
|
53
|
-
schema.driver = self
|
|
54
|
-
|
|
55
|
-
schema.base_path = data['basePath'] || ''
|
|
56
|
-
|
|
57
|
-
# Arbitrarily choose the first media type found in these arrays. This
|
|
58
|
-
# appraoch could probably stand to be improved, but at least users will
|
|
59
|
-
# for now have the option of turning media type validation off if they so
|
|
60
|
-
# choose.
|
|
61
|
-
schema.consumes = data['consumes'].first
|
|
62
|
-
schema.produces = data['produces'].first
|
|
63
|
-
|
|
64
|
-
schema.definitions, store = parse_definitions!(data)
|
|
65
|
-
schema.routes = parse_routes!(data, schema, store)
|
|
66
|
-
|
|
67
|
-
schema
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def schema_class
|
|
71
|
-
Committee::Drivers::OpenAPI2::Schema
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Link abstracts an API link specifically for OpenAPI 2.
|
|
75
|
-
class Link
|
|
76
|
-
# The link's input media type. i.e. How requests should be encoded.
|
|
77
|
-
attr_accessor :enc_type
|
|
78
|
-
|
|
79
|
-
attr_accessor :href
|
|
80
|
-
|
|
81
|
-
# The link's output media type. i.e. How responses should be encoded.
|
|
82
|
-
attr_accessor :media_type
|
|
83
|
-
|
|
84
|
-
attr_accessor :method
|
|
85
|
-
|
|
86
|
-
# The link's input schema. i.e. How we validate an endpoint's incoming
|
|
87
|
-
# parameters.
|
|
88
|
-
attr_accessor :schema
|
|
89
|
-
|
|
90
|
-
attr_accessor :status_success
|
|
91
|
-
|
|
92
|
-
# The link's output schema. i.e. How we validate an endpoint's response
|
|
93
|
-
# data.
|
|
94
|
-
attr_accessor :target_schema
|
|
95
|
-
|
|
96
|
-
attr_accessor :header_schema
|
|
97
|
-
|
|
98
|
-
def rel
|
|
99
|
-
raise "Committee: rel not implemented for OpenAPI"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
class SchemaBuilder
|
|
104
|
-
def initialize(link_data)
|
|
105
|
-
self.link_data = link_data
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
private
|
|
109
|
-
|
|
110
|
-
LINK_REQUIRED_FIELDS = [
|
|
111
|
-
:name
|
|
112
|
-
].map(&:to_s).freeze
|
|
113
|
-
|
|
114
|
-
attr_accessor :link_data
|
|
115
|
-
|
|
116
|
-
def check_required_fields!(param_data)
|
|
117
|
-
LINK_REQUIRED_FIELDS.each do |field|
|
|
118
|
-
if !param_data[field]
|
|
119
|
-
raise ArgumentError,
|
|
120
|
-
"Committee: no #{field} section in link data."
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
class HeaderSchemaBuilder < SchemaBuilder
|
|
127
|
-
def call
|
|
128
|
-
if link_data["parameters"]
|
|
129
|
-
link_schema = JsonSchema::Schema.new
|
|
130
|
-
link_schema.properties = {}
|
|
131
|
-
link_schema.required = []
|
|
132
|
-
|
|
133
|
-
header_parameters = link_data["parameters"].select { |param_data| param_data["in"] == "header" }
|
|
134
|
-
header_parameters.each do |param_data|
|
|
135
|
-
check_required_fields!(param_data)
|
|
136
|
-
|
|
137
|
-
param_schema = JsonSchema::Schema.new
|
|
138
|
-
|
|
139
|
-
param_schema.type = [param_data["type"]]
|
|
140
|
-
|
|
141
|
-
link_schema.properties[param_data["name"]] = param_schema
|
|
142
|
-
if param_data["required"] == true
|
|
143
|
-
link_schema.required << param_data["name"]
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
link_schema
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# ParameterSchemaBuilder converts OpenAPI 2 link parameters, which are not
|
|
153
|
-
# quite JSON schemas (but will be in OpenAPI 3) into synthetic schemas that
|
|
154
|
-
# we can use to do some basic request validation.
|
|
155
|
-
class ParameterSchemaBuilder < SchemaBuilder
|
|
156
|
-
# Returns a tuple of (schema, schema_data) where only one of the two
|
|
157
|
-
# values is present. This is either a full schema that's ready to go _or_
|
|
158
|
-
# a hash of unparsed schema data.
|
|
159
|
-
def call
|
|
160
|
-
if link_data["parameters"]
|
|
161
|
-
body_param = link_data["parameters"].detect { |p| p["in"] == "body" }
|
|
162
|
-
if body_param
|
|
163
|
-
check_required_fields!(body_param)
|
|
164
|
-
|
|
165
|
-
if link_data["parameters"].detect { |p| p["in"] == "form" } != nil
|
|
166
|
-
raise ArgumentError, "Committee: can't mix body parameter " \
|
|
167
|
-
"with form parameters."
|
|
168
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
169
2
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
link_schema = JsonSchema::Schema.new
|
|
174
|
-
link_schema.properties = {}
|
|
175
|
-
link_schema.required = []
|
|
176
|
-
|
|
177
|
-
parameters = link_data["parameters"].reject { |param_data| param_data["in"] == "header" }
|
|
178
|
-
parameters.each do |param_data|
|
|
179
|
-
check_required_fields!(param_data)
|
|
180
|
-
|
|
181
|
-
param_schema = JsonSchema::Schema.new
|
|
182
|
-
|
|
183
|
-
# We could probably use more validation here, but the formats of
|
|
184
|
-
# OpenAPI 2 are based off of what's available in JSON schema, and
|
|
185
|
-
# therefore this should map over quite well.
|
|
186
|
-
param_schema.type = [param_data["type"]]
|
|
187
|
-
|
|
188
|
-
param_schema.enum = param_data["enum"] unless param_data["enum"].nil?
|
|
189
|
-
|
|
190
|
-
# validation: string
|
|
191
|
-
param_schema.format = param_data["format"] unless param_data["format"].nil?
|
|
192
|
-
param_schema.pattern = Regexp.new(param_data["pattern"]) unless param_data["pattern"].nil?
|
|
193
|
-
param_schema.min_length = param_data["minLength"] unless param_data["minLength"].nil?
|
|
194
|
-
param_schema.max_length = param_data["maxLength"] unless param_data["maxLength"].nil?
|
|
195
|
-
|
|
196
|
-
# validation: array
|
|
197
|
-
param_schema.min_items = param_data["minItems"] unless param_data["minItems"].nil?
|
|
198
|
-
param_schema.max_items = param_data["maxItems"] unless param_data["maxItems"].nil?
|
|
199
|
-
param_schema.unique_items = param_data["uniqueItems"] unless param_data["uniqueItems"].nil?
|
|
200
|
-
|
|
201
|
-
# validation: number/integer
|
|
202
|
-
param_schema.min = param_data["minimum"] unless param_data["minimum"].nil?
|
|
203
|
-
param_schema.min_exclusive = param_data["exclusiveMinimum"] unless param_data["exclusiveMinimum"].nil?
|
|
204
|
-
param_schema.max = param_data["maximum"] unless param_data["maximum"].nil?
|
|
205
|
-
param_schema.max_exclusive = param_data["exclusiveMaximum"] unless param_data["exclusiveMaximum"].nil?
|
|
206
|
-
param_schema.multiple_of = param_data["multipleOf"] unless param_data["multipleOf"].nil?
|
|
207
|
-
|
|
208
|
-
# And same idea: despite parameters not being schemas, the items
|
|
209
|
-
# key (if preset) is actually a schema that defines each item of an
|
|
210
|
-
# array type, so we can just reflect that directly onto our
|
|
211
|
-
# artifical schema.
|
|
212
|
-
if param_data["type"] == "array" && param_data["items"]
|
|
213
|
-
param_schema.items = param_data["items"]
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
link_schema.properties[param_data["name"]] = param_schema
|
|
217
|
-
if param_data["required"] == true
|
|
218
|
-
link_schema.required << param_data["name"]
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
[link_schema, nil]
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
class Schema < Committee::Drivers::Schema
|
|
229
|
-
attr_accessor :base_path
|
|
230
|
-
attr_accessor :consumes
|
|
231
|
-
|
|
232
|
-
# A link back to the derivative instace of Committee::Drivers::Driver
|
|
233
|
-
# that create this schema.
|
|
234
|
-
attr_accessor :driver
|
|
235
|
-
|
|
236
|
-
attr_accessor :definitions
|
|
237
|
-
attr_accessor :produces
|
|
238
|
-
attr_accessor :routes
|
|
239
|
-
attr_reader :validator_option
|
|
240
|
-
|
|
241
|
-
def build_router(options)
|
|
242
|
-
@validator_option = Committee::SchemaValidator::Option.new(options, self, :hyper_schema)
|
|
243
|
-
Committee::SchemaValidator::HyperSchema::Router.new(self, @validator_option)
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
private
|
|
248
|
-
|
|
249
|
-
DEFINITIONS_PSEUDO_URI = "http://json-schema.org/committee-definitions"
|
|
250
|
-
|
|
251
|
-
# These are fields that the OpenAPI 2 spec considers mandatory to be
|
|
252
|
-
# included in the document's top level.
|
|
253
|
-
REQUIRED_FIELDS = [
|
|
254
|
-
:consumes,
|
|
255
|
-
:definitions,
|
|
256
|
-
:paths,
|
|
257
|
-
:produces,
|
|
258
|
-
:swagger,
|
|
259
|
-
].map(&:to_s).freeze
|
|
260
|
-
|
|
261
|
-
def find_best_fit_response(link_data)
|
|
262
|
-
if response_data = link_data["responses"]["200"] || response_data = link_data["responses"][200]
|
|
263
|
-
[200, response_data]
|
|
264
|
-
elsif response_data = link_data["responses"]["201"] || response_data = link_data["responses"][201]
|
|
265
|
-
[201, response_data]
|
|
266
|
-
else
|
|
267
|
-
# Sort responses so that we can try to prefer any 3-digit status code.
|
|
268
|
-
# If there are none, we'll just take anything from the list.
|
|
269
|
-
ordered_responses = link_data["responses"].
|
|
270
|
-
select { |k, v| k.to_s =~ /[0-9]{3}/ }
|
|
271
|
-
if first = ordered_responses.first
|
|
272
|
-
[first[0].to_i, first[1]]
|
|
273
|
-
else
|
|
274
|
-
[nil, nil]
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def href_to_regex(href)
|
|
280
|
-
href.gsub(/\{(.*?)\}/, '(?<\1>[^/]+)')
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def parse_definitions!(data)
|
|
284
|
-
# The "definitions" section of an OpenAPI 2 spec is a valid JSON schema.
|
|
285
|
-
# We extract it from the spec and parse it as a schema in isolation so
|
|
286
|
-
# that all references to it will still have correct paths (i.e. we can
|
|
287
|
-
# still find a resource at '#/definitions/resource' instead of
|
|
288
|
-
# '#/resource').
|
|
289
|
-
schema = JsonSchema.parse!({
|
|
290
|
-
"definitions" => data['definitions'],
|
|
291
|
-
})
|
|
292
|
-
schema.expand_references!
|
|
293
|
-
schema.uri = DEFINITIONS_PSEUDO_URI
|
|
294
|
-
|
|
295
|
-
# So this is a little weird: an OpenAPI specification is _not_ a valid
|
|
296
|
-
# JSON schema and yet it self-references like it is a valid JSON schema.
|
|
297
|
-
# To work around this what we do is parse its "definitions" section as a
|
|
298
|
-
# JSON schema and then build a document store here containing that. When
|
|
299
|
-
# trying to resolve a reference from elsewhere in the spec, we build a
|
|
300
|
-
# synthetic schema with a JSON reference to the document created from
|
|
301
|
-
# "definitions" and then expand references against this store.
|
|
302
|
-
store = JsonSchema::DocumentStore.new
|
|
303
|
-
store.add_schema(schema)
|
|
304
|
-
|
|
305
|
-
[schema, store]
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def parse_routes!(data, schema, store)
|
|
309
|
-
routes = {}
|
|
310
|
-
|
|
311
|
-
# This is a performance optimization: instead of going through each link
|
|
312
|
-
# and parsing out its JSON schema separately, instead we just aggregate
|
|
313
|
-
# all schemas into one big hash and then parse it all at the end. After
|
|
314
|
-
# we parse it, go through each link and assign a proper schema object. In
|
|
315
|
-
# practice this comes out to somewhere on the order of 50x faster.
|
|
316
|
-
schemas_data = { "properties" => {} }
|
|
317
|
-
|
|
318
|
-
# Exactly the same idea, but for response schemas.
|
|
319
|
-
target_schemas_data = { "properties" => {} }
|
|
320
|
-
|
|
321
|
-
data['paths'].each do |path, methods|
|
|
322
|
-
href = schema.base_path + path
|
|
323
|
-
schemas_data["properties"][href] = { "properties" => {} }
|
|
324
|
-
target_schemas_data["properties"][href] = { "properties" => {} }
|
|
325
|
-
|
|
326
|
-
methods.each do |method, link_data|
|
|
327
|
-
method = method.upcase
|
|
328
|
-
|
|
329
|
-
link = Link.new
|
|
330
|
-
link.enc_type = schema.consumes
|
|
331
|
-
link.href = href
|
|
332
|
-
link.media_type = schema.produces
|
|
333
|
-
link.method = method
|
|
334
|
-
|
|
335
|
-
# Convert the spec's parameter pseudo-schemas into JSON schemas that
|
|
336
|
-
# we can use for some basic request validation.
|
|
337
|
-
link.schema, schema_data = ParameterSchemaBuilder.new(link_data).call
|
|
338
|
-
link.header_schema = HeaderSchemaBuilder.new(link_data).call
|
|
339
|
-
|
|
340
|
-
# If data came back instead of a schema (this occurs when a route has
|
|
341
|
-
# a single `body` parameter instead of a collection of URL/query/form
|
|
342
|
-
# parameters), store it for later parsing.
|
|
343
|
-
if schema_data
|
|
344
|
-
schemas_data["properties"][href]["properties"][method] = schema_data
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
# Arbitrarily pick one response for the time being. Prefers in order:
|
|
348
|
-
# a 200, 201, any 3-digit numerical response, then anything at all.
|
|
349
|
-
status, response_data = find_best_fit_response(link_data)
|
|
350
|
-
if status
|
|
351
|
-
link.status_success = status
|
|
352
|
-
|
|
353
|
-
# A link need not necessarily specify a target schema.
|
|
354
|
-
if response_data["schema"]
|
|
355
|
-
target_schemas_data["properties"][href]["properties"][method] =
|
|
356
|
-
response_data["schema"]
|
|
357
|
-
end
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
rx = %r{^#{href_to_regex(link.href)}$}
|
|
361
|
-
Committee.log_debug "Created route: #{link.method} #{link.href} (regex #{rx})"
|
|
362
|
-
|
|
363
|
-
routes[method] ||= []
|
|
364
|
-
routes[method] << [rx, link]
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
# See the note on our DocumentStore's initialization in
|
|
369
|
-
# #parse_definitions!, but what we're doing here is prefixing references
|
|
370
|
-
# with a specialized internal URI so that they can reference definitions
|
|
371
|
-
# from another document in the store.
|
|
372
|
-
schemas =
|
|
373
|
-
rewrite_references_and_parse(schemas_data, store)
|
|
374
|
-
target_schemas =
|
|
375
|
-
rewrite_references_and_parse(target_schemas_data, store)
|
|
376
|
-
|
|
377
|
-
# As noted above, now that we've parsed our aggregate response schema, go
|
|
378
|
-
# back through each link and them their response schema.
|
|
379
|
-
routes.each do |method, method_routes|
|
|
380
|
-
method_routes.each do |(_, link)|
|
|
381
|
-
# request
|
|
382
|
-
#
|
|
383
|
-
# Differs slightly from responses in that the schema may already have
|
|
384
|
-
# been set for endpoints with non-body parameters, so check for nil
|
|
385
|
-
# before we set it.
|
|
386
|
-
if schema = schemas.properties[link.href].properties[method]
|
|
387
|
-
link.schema = schema
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
# response
|
|
391
|
-
link.target_schema =
|
|
392
|
-
target_schemas.properties[link.href].properties[method]
|
|
393
|
-
end
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
routes
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
def rewrite_references_and_parse(schemas_data, store)
|
|
400
|
-
schemas = rewrite_references(schemas_data)
|
|
401
|
-
schemas = JsonSchema.parse!(schemas_data)
|
|
402
|
-
schemas.expand_references!(:store => store)
|
|
403
|
-
schemas
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
def rewrite_references(schema)
|
|
407
|
-
if schema.is_a?(Hash)
|
|
408
|
-
ref = schema["$ref"]
|
|
409
|
-
if ref && ref.is_a?(String) && ref[0] == "#"
|
|
410
|
-
schema["$ref"] = DEFINITIONS_PSEUDO_URI + ref
|
|
411
|
-
else
|
|
412
|
-
schema.each do |_, v|
|
|
413
|
-
rewrite_references(v)
|
|
414
|
-
end
|
|
415
|
-
end
|
|
416
|
-
end
|
|
417
|
-
schema
|
|
3
|
+
module Committee
|
|
4
|
+
module Drivers
|
|
5
|
+
module OpenAPI2
|
|
418
6
|
end
|
|
419
7
|
end
|
|
420
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'
|