committee 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/bin/committee-stub +1 -0
  3. data/lib/committee.rb +12 -34
  4. data/lib/committee/bin/committee_stub.rb +6 -4
  5. data/lib/committee/drivers.rb +15 -67
  6. data/lib/committee/drivers/driver.rb +47 -0
  7. data/lib/committee/drivers/hyper_schema.rb +8 -171
  8. data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
  9. data/lib/committee/drivers/hyper_schema/link.rb +68 -0
  10. data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
  11. data/lib/committee/drivers/open_api_2.rb +9 -416
  12. data/lib/committee/drivers/open_api_2/driver.rb +253 -0
  13. data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
  14. data/lib/committee/drivers/open_api_2/link.rb +36 -0
  15. data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
  16. data/lib/committee/drivers/open_api_2/schema.rb +26 -0
  17. data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
  18. data/lib/committee/drivers/open_api_3.rb +7 -75
  19. data/lib/committee/drivers/open_api_3/driver.rb +51 -0
  20. data/lib/committee/drivers/open_api_3/schema.rb +41 -0
  21. data/lib/committee/drivers/schema.rb +23 -0
  22. data/lib/committee/errors.rb +2 -0
  23. data/lib/committee/middleware.rb +11 -0
  24. data/lib/committee/middleware/base.rb +38 -34
  25. data/lib/committee/middleware/request_validation.rb +51 -30
  26. data/lib/committee/middleware/response_validation.rb +49 -26
  27. data/lib/committee/middleware/stub.rb +55 -51
  28. data/lib/committee/request_unpacker.rb +3 -1
  29. data/lib/committee/schema_validator.rb +23 -0
  30. data/lib/committee/schema_validator/hyper_schema.rb +85 -74
  31. data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +60 -54
  32. data/lib/committee/schema_validator/hyper_schema/request_validator.rb +43 -37
  33. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +86 -80
  34. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +65 -59
  35. data/lib/committee/schema_validator/hyper_schema/router.rb +35 -29
  36. data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +87 -81
  37. data/lib/committee/schema_validator/open_api_3.rb +71 -61
  38. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +121 -115
  39. data/lib/committee/schema_validator/open_api_3/request_validator.rb +24 -18
  40. data/lib/committee/schema_validator/open_api_3/response_validator.rb +22 -16
  41. data/lib/committee/schema_validator/open_api_3/router.rb +30 -24
  42. data/lib/committee/schema_validator/option.rb +42 -38
  43. data/lib/committee/test/methods.rb +55 -51
  44. data/lib/committee/validation_error.rb +2 -0
  45. data/test/bin/committee_stub_test.rb +3 -1
  46. data/test/bin_test.rb +3 -1
  47. data/test/committee_test.rb +3 -1
  48. data/test/drivers/hyper_schema/driver_test.rb +49 -0
  49. data/test/drivers/{hyper_schema_test.rb → hyper_schema/link_test.rb} +2 -45
  50. data/test/drivers/open_api_2/driver_test.rb +156 -0
  51. data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
  52. data/test/drivers/open_api_2/link_test.rb +52 -0
  53. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
  54. data/test/drivers/{open_api_3_test.rb → open_api_3/driver_test.rb} +5 -3
  55. data/test/drivers_test.rb +12 -10
  56. data/test/middleware/base_test.rb +3 -1
  57. data/test/middleware/request_validation_open_api_3_test.rb +4 -2
  58. data/test/middleware/request_validation_test.rb +46 -5
  59. data/test/middleware/response_validation_open_api_3_test.rb +3 -1
  60. data/test/middleware/response_validation_test.rb +39 -4
  61. data/test/middleware/stub_test.rb +3 -1
  62. data/test/request_unpacker_test.rb +2 -2
  63. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +2 -2
  64. data/test/schema_validator/hyper_schema/request_validator_test.rb +3 -1
  65. data/test/schema_validator/hyper_schema/response_generator_test.rb +3 -1
  66. data/test/schema_validator/hyper_schema/response_validator_test.rb +3 -1
  67. data/test/schema_validator/hyper_schema/router_test.rb +5 -3
  68. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +3 -1
  69. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +3 -1
  70. data/test/schema_validator/open_api_3/request_validator_test.rb +11 -1
  71. data/test/schema_validator/open_api_3/response_validator_test.rb +3 -1
  72. data/test/test/methods_new_version_test.rb +3 -1
  73. data/test/test/methods_test.rb +4 -2
  74. data/test/test_helper.rb +16 -16
  75. data/test/validation_error_test.rb +3 -1
  76. metadata +52 -6
  77. data/lib/committee/schema_validator/schema_validator.rb +0 -15
  78. data/test/drivers/open_api_2_test.rb +0 -416
@@ -1,4 +1,6 @@
1
- require_relative "test_helper"
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
2
4
 
3
5
  describe Committee::ValidationError do
4
6
  before do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: committee
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandur
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-08-07 00:00:00.000000000 Z
13
+ date: 2019-09-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json_schema
@@ -144,6 +144,34 @@ dependencies:
144
144
  - - ">="
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
+ - !ruby/object:Gem::Dependency
148
+ name: rubocop
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ - !ruby/object:Gem::Dependency
162
+ name: rubocop-performance
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ type: :development
169
+ prerelease: false
170
+ version_requirements: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
147
175
  - !ruby/object:Gem::Dependency
148
176
  name: simplecov
149
177
  requirement: !ruby/object:Gem::Requirement
@@ -172,15 +200,30 @@ files:
172
200
  - lib/committee.rb
173
201
  - lib/committee/bin/committee_stub.rb
174
202
  - lib/committee/drivers.rb
203
+ - lib/committee/drivers/driver.rb
175
204
  - lib/committee/drivers/hyper_schema.rb
205
+ - lib/committee/drivers/hyper_schema/driver.rb
206
+ - lib/committee/drivers/hyper_schema/link.rb
207
+ - lib/committee/drivers/hyper_schema/schema.rb
176
208
  - lib/committee/drivers/open_api_2.rb
209
+ - lib/committee/drivers/open_api_2/driver.rb
210
+ - lib/committee/drivers/open_api_2/header_schema_builder.rb
211
+ - lib/committee/drivers/open_api_2/link.rb
212
+ - lib/committee/drivers/open_api_2/parameter_schema_builder.rb
213
+ - lib/committee/drivers/open_api_2/schema.rb
214
+ - lib/committee/drivers/open_api_2/schema_builder.rb
177
215
  - lib/committee/drivers/open_api_3.rb
216
+ - lib/committee/drivers/open_api_3/driver.rb
217
+ - lib/committee/drivers/open_api_3/schema.rb
218
+ - lib/committee/drivers/schema.rb
178
219
  - lib/committee/errors.rb
220
+ - lib/committee/middleware.rb
179
221
  - lib/committee/middleware/base.rb
180
222
  - lib/committee/middleware/request_validation.rb
181
223
  - lib/committee/middleware/response_validation.rb
182
224
  - lib/committee/middleware/stub.rb
183
225
  - lib/committee/request_unpacker.rb
226
+ - lib/committee/schema_validator.rb
184
227
  - lib/committee/schema_validator/hyper_schema.rb
185
228
  - lib/committee/schema_validator/hyper_schema/parameter_coercer.rb
186
229
  - lib/committee/schema_validator/hyper_schema/request_validator.rb
@@ -194,15 +237,18 @@ files:
194
237
  - lib/committee/schema_validator/open_api_3/response_validator.rb
195
238
  - lib/committee/schema_validator/open_api_3/router.rb
196
239
  - lib/committee/schema_validator/option.rb
197
- - lib/committee/schema_validator/schema_validator.rb
198
240
  - lib/committee/test/methods.rb
199
241
  - lib/committee/validation_error.rb
200
242
  - test/bin/committee_stub_test.rb
201
243
  - test/bin_test.rb
202
244
  - test/committee_test.rb
203
- - test/drivers/hyper_schema_test.rb
204
- - test/drivers/open_api_2_test.rb
205
- - test/drivers/open_api_3_test.rb
245
+ - test/drivers/hyper_schema/driver_test.rb
246
+ - test/drivers/hyper_schema/link_test.rb
247
+ - test/drivers/open_api_2/driver_test.rb
248
+ - test/drivers/open_api_2/header_schema_builder_test.rb
249
+ - test/drivers/open_api_2/link_test.rb
250
+ - test/drivers/open_api_2/parameter_schema_builder_test.rb
251
+ - test/drivers/open_api_3/driver_test.rb
206
252
  - test/drivers_test.rb
207
253
  - test/middleware/base_test.rb
208
254
  - test/middleware/request_validation_open_api_3_test.rb
@@ -1,15 +0,0 @@
1
- class Committee::SchemaValidator
2
- class << self
3
- def request_media_type(request)
4
- request.content_type.to_s.split(";").first.to_s
5
- end
6
-
7
- # @param [String] prefix
8
- # @return [Regexp]
9
- def build_prefix_regexp(prefix)
10
- return nil unless prefix
11
-
12
- /\A#{Regexp.escape(prefix)}/.freeze
13
- end
14
- end
15
- end
@@ -1,416 +0,0 @@
1
- require_relative "../test_helper"
2
-
3
- describe Committee::Drivers::OpenAPI2 do
4
- before do
5
- @driver = Committee::Drivers::OpenAPI2.new
6
- end
7
-
8
- it "has a name" do
9
- assert_equal :open_api_2, @driver.name
10
- end
11
-
12
- it "has a schema class" do
13
- assert_equal Committee::Drivers::OpenAPI2::Schema, @driver.schema_class
14
- end
15
-
16
- it "parses an OpenAPI 2 spec" do
17
- schema = @driver.parse(open_api_2_data)
18
- assert_kind_of Committee::Drivers::OpenAPI2::Schema, schema
19
- assert_kind_of JsonSchema::Schema, schema.definitions
20
- assert_equal @driver, schema.driver
21
-
22
- assert_kind_of Hash, schema.routes
23
- refute schema.routes.empty?
24
- assert(schema.routes.keys.all? { |m|
25
- ["DELETE", "GET", "PATCH", "POST", "PUT"].include?(m)
26
- })
27
-
28
- schema.routes.each do |(_, method_routes)|
29
- method_routes.each do |regex, link|
30
- assert_kind_of Regexp, regex
31
- assert_kind_of Committee::Drivers::OpenAPI2::Link, link
32
-
33
- # verify that we've correct generated a parameters schema for each link
34
- if link.target_schema
35
- assert_kind_of JsonSchema::Schema, link.schema if link.schema
36
- end
37
-
38
- if link.target_schema
39
- assert_kind_of JsonSchema::Schema, link.target_schema
40
- end
41
- end
42
- end
43
- end
44
-
45
- it "names capture groups into href regexes" do
46
- schema = @driver.parse(open_api_2_data)
47
- assert_equal %r{^\/api\/pets\/(?<id>[^\/]+)$}.inspect,
48
- schema.routes["DELETE"][0][0].inspect
49
- end
50
-
51
- it "prefers a 200 response first" do
52
- schema_data = schema_data_with_responses({
53
- '201' => { 'schema' => { 'description' => '201 response' } },
54
- '200' => { 'schema' => { 'description' => '200 response' } },
55
- })
56
-
57
- schema = @driver.parse(schema_data)
58
- link = schema.routes['GET'][0][1]
59
- assert_equal 200, link.status_success
60
- assert_equal({ 'description' => '200 response' }, link.target_schema.data)
61
- end
62
-
63
- it "prefers a 201 response next" do
64
- schema_data = schema_data_with_responses({
65
- '302' => { 'schema' => { 'description' => '302 response' } },
66
- '201' => { 'schema' => { 'description' => '201 response' } },
67
- })
68
-
69
- schema = @driver.parse(schema_data)
70
- link = schema.routes['GET'][0][1]
71
- assert_equal 201, link.status_success
72
- assert_equal({ 'description' => '201 response' }, link.target_schema.data)
73
- end
74
-
75
- it "prefers any three-digit response next" do
76
- schema_data = schema_data_with_responses({
77
- 'default' => { 'schema' => { 'description' => 'default response' } },
78
- '302' => { 'schema' => { 'description' => '302 response' } },
79
- })
80
-
81
- schema = @driver.parse(schema_data)
82
- link = schema.routes['GET'][0][1]
83
- assert_equal 302, link.status_success
84
- assert_equal({ 'description' => '302 response' }, link.target_schema.data)
85
- end
86
-
87
- it "prefers any numeric three-digit response next" do
88
- schema_data = schema_data_with_responses({
89
- 'default' => { 'schema' => { 'description' => 'default response' } },
90
- 302 => { 'schema' => { 'description' => '302 response' } },
91
- })
92
-
93
- schema = @driver.parse(schema_data)
94
- link = schema.routes['GET'][0][1]
95
- assert_equal 302, link.status_success
96
- assert_equal({ 'description' => '302 response' }, link.target_schema.data)
97
- end
98
-
99
- it "falls back to no response" do
100
- schema_data = schema_data_with_responses({})
101
-
102
- schema = @driver.parse(schema_data)
103
- link = schema.routes['GET'][0][1]
104
- assert_nil link.status_success
105
- assert_nil link.target_schema
106
- end
107
-
108
- it "refuses to parse other version of OpenAPI" do
109
- data = open_api_2_data
110
- data['swagger'] = '3.0'
111
- e = assert_raises(ArgumentError) do
112
- @driver.parse(data)
113
- end
114
- assert_equal "Committee: driver requires OpenAPI 2.0.", e.message
115
- end
116
-
117
- it "refuses to parse a spec without mandatory fields" do
118
- data = open_api_2_data
119
- data['definitions'] = nil
120
- e = assert_raises(ArgumentError) do
121
- @driver.parse(data)
122
- end
123
- assert_equal "Committee: no definitions section in spec data.", e.message
124
- end
125
-
126
- it "defaults to coercing form parameters" do
127
- assert_equal true, @driver.default_coerce_form_params
128
- end
129
-
130
- it "defaults to path parameters" do
131
- assert_equal true, @driver.default_path_params
132
- end
133
-
134
- it "defaults to query parameters" do
135
- assert_equal true, @driver.default_query_params
136
- end
137
-
138
- def schema_data_with_responses(response_data)
139
- {
140
- 'swagger' => '2.0',
141
- 'consumes' => ['application/json'],
142
- 'produces' => ['application/json'],
143
- 'paths' => {
144
- '/foos' => {
145
- 'get' => {
146
- 'responses' => response_data,
147
- },
148
- },
149
- },
150
- 'definitions' => {},
151
- }
152
- end
153
- end
154
-
155
- describe Committee::Drivers::OpenAPI2::Link do
156
- before do
157
- @link = Committee::Drivers::OpenAPI2::Link.new
158
- @link.enc_type = "application/x-www-form-urlencoded"
159
- @link.href = "/apps"
160
- @link.media_type = "application/json"
161
- @link.method = "GET"
162
- @link.status_success = 200
163
- @link.schema = { "title" => "input" }
164
- @link.target_schema = { "title" => "target" }
165
- end
166
-
167
- it "uses set #enc_type" do
168
- assert_equal "application/x-www-form-urlencoded", @link.enc_type
169
- end
170
-
171
- it "uses set #href" do
172
- assert_equal "/apps", @link.href
173
- end
174
-
175
- it "uses set #media_type" do
176
- assert_equal "application/json", @link.media_type
177
- end
178
-
179
- it "uses set #method" do
180
- assert_equal "GET", @link.method
181
- end
182
-
183
- it "proxies #rel" do
184
- e = assert_raises do
185
- @link.rel
186
- end
187
- assert_equal "Committee: rel not implemented for OpenAPI", e.message
188
- end
189
-
190
- it "uses set #schema" do
191
- assert_equal({ "title" => "input" }, @link.schema)
192
- end
193
-
194
- it "uses set #status_success" do
195
- assert_equal 200, @link.status_success
196
- end
197
-
198
- it "uses set #target_schema" do
199
- assert_equal({ "title" => "target" }, @link.target_schema)
200
- end
201
- end
202
-
203
- describe Committee::Drivers::OpenAPI2::ParameterSchemaBuilder do
204
- before do
205
- end
206
-
207
- it "reflects a basic type into a schema" do
208
- data = {
209
- "parameters" => [
210
- {
211
- "name" => "limit",
212
- "type" => "integer",
213
- }
214
- ]
215
- }
216
- schema, schema_data = call(data)
217
-
218
- assert_nil schema_data
219
- assert_equal ["limit"], schema.properties.keys
220
- assert_equal [], schema.required
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
- end
236
-
237
- it "reflects a required property into a schema" do
238
- data = {
239
- "parameters" => [
240
- {
241
- "name" => "limit",
242
- "required" => true,
243
- }
244
- ]
245
- }
246
- schema, schema_data = call(data)
247
-
248
- assert_nil schema_data
249
- assert_equal ["limit"], schema.required
250
- end
251
-
252
- it "reflects an array with an items schema into a schema" do
253
- data = {
254
- "parameters" => [
255
- {
256
- "name" => "tags",
257
- "type" => "array",
258
- "minItems" => 1,
259
- "maxItems" => 10,
260
- "uniqueItems" => true,
261
- "items" => {
262
- "type" => "string"
263
- }
264
- }
265
- ]
266
- }
267
- schema, schema_data = call(data)
268
-
269
- assert_nil schema_data
270
- 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
- assert_equal({ "type" => "string" }, schema.properties["tags"].items)
275
- end
276
-
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
- it "returns schema data for a body parameter" do
340
- data = {
341
- "parameters" => [
342
- {
343
- "name" => "payload",
344
- "in" => "body",
345
- "schema" => {
346
- "$ref" => "#/definitions/foo",
347
- }
348
- }
349
- ]
350
- }
351
- schema, schema_data = call(data)
352
-
353
- assert_nil schema
354
- assert_equal({ "$ref" => "#/definitions/foo" }, schema_data)
355
- end
356
-
357
- it "requires that certain fields are present" do
358
- data = {
359
- "parameters" => [
360
- {
361
- }
362
- ]
363
- }
364
- e = assert_raises ArgumentError do
365
- call(data)
366
- end
367
- assert_equal "Committee: no name section in link data.", e.message
368
- end
369
-
370
- it "requires that body parameters not be mixed with form parameters" do
371
- data = {
372
- "parameters" => [
373
- {
374
- "name" => "payload",
375
- "in" => "body",
376
- },
377
- {
378
- "name" => "limit",
379
- "in" => "form",
380
- },
381
- ]
382
- }
383
- e = assert_raises ArgumentError do
384
- call(data)
385
- end
386
- assert_equal "Committee: can't mix body parameter with form parameters.",
387
- e.message
388
- end
389
-
390
- def call(data)
391
- Committee::Drivers::OpenAPI2::ParameterSchemaBuilder.new(data).call
392
- end
393
- end
394
-
395
- describe Committee::Drivers::OpenAPI2::HeaderSchemaBuilder do
396
- it "returns schema data for header" do
397
- data = {
398
- "parameters" => [
399
- {
400
- "name" => "AUTH_TOKEN",
401
- "type" => "string",
402
- "in" => "header",
403
- }
404
- ]
405
- }
406
- schema = call(data)
407
-
408
- assert_equal ["string"], schema.properties["AUTH_TOKEN"].type
409
- end
410
-
411
- private
412
-
413
- def call(data)
414
- Committee::Drivers::OpenAPI2::HeaderSchemaBuilder.new(data).call
415
- end
416
- end