openapi_first 0.12.0.alpha1 → 0.12.3
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/.rubocop.yml +2 -0
- data/CHANGELOG.md +18 -1
- data/Gemfile.lock +17 -17
- data/README.md +27 -9
- data/benchmarks/Gemfile.lock +11 -11
- data/benchmarks/apps/openapi_first.ru +1 -1
- data/examples/app.rb +7 -1
- data/lib/openapi_first.rb +34 -7
- data/lib/openapi_first/app.rb +11 -3
- data/lib/openapi_first/find_handler.rb +25 -21
- data/lib/openapi_first/operation.rb +14 -7
- data/lib/openapi_first/request_validation.rb +17 -12
- data/lib/openapi_first/responder.rb +10 -3
- data/lib/openapi_first/response_validation.rb +21 -21
- data/lib/openapi_first/response_validator.rb +2 -46
- data/lib/openapi_first/router.rb +19 -29
- data/lib/openapi_first/validation_format.rb +3 -1
- data/lib/openapi_first/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f5e77e342d4dba0c6ac4d535511148333efac5bc4b2cea62ee6d8b75219233e
|
|
4
|
+
data.tar.gz: 9b3341115e77bd4abc4b14d89464093d78483e251cf69e2b2508461c98aa1ccf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86f19dc76569748825731014124b689b7bddf5cce6a6fe38b5d95adb4643eeeeaa3ae6024a290aaf2aed92127147cc948a55faa062b5d97b99b7d2fe65df98cd
|
|
7
|
+
data.tar.gz: 27cef03d9ef7e0b49af33154e44ca68d3e0e860dcd94cbf6d2cdc4661ff90a4be590b92365aabfcdffe330f2aa2e96c12f7277ee2fa572fe02a2cc2c263e34b5
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.12.3
|
|
4
|
+
- Add `response_validation:`, `router_raise_error` options to standalone mode.
|
|
5
|
+
|
|
6
|
+
## 0.12.2
|
|
7
|
+
- Allow response to have no media type object specified
|
|
8
|
+
|
|
9
|
+
## 0.12.1
|
|
10
|
+
- Fix response when handler returns 404 or 405
|
|
11
|
+
- Don't validate the response content if status is 205 (no content)
|
|
12
|
+
|
|
13
|
+
## 0.12.0
|
|
14
|
+
- Change `ResponseValidator` to raise an exception if it found a problem
|
|
15
|
+
- Params have symbolized keys now
|
|
16
|
+
- Remove `not_found` option from Router. Return 405 if HTTP verb is not allowed (via Hanami::Router)
|
|
17
|
+
- Add `raise_error` option to OpenapiFirst.app (false by default)
|
|
18
|
+
- Add ResponseValidation to OpenapiFirst.app if raise_error option is true
|
|
4
19
|
- Rename `raise` option to `raise_error`
|
|
5
20
|
- Add `raise_error` option to RequestValidation middleware
|
|
21
|
+
- Raise error if handler could not be found by Responder
|
|
22
|
+
- Add `Operation#name` that returns a human readable name for an operation
|
|
6
23
|
|
|
7
24
|
## 0.11.0
|
|
8
25
|
- Raise error if you forgot to add the Router middleware
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
openapi_first (0.12.
|
|
4
|
+
openapi_first (0.12.3)
|
|
5
5
|
deep_merge (>= 1.2.1)
|
|
6
6
|
hanami-router (~> 2.0.alpha3)
|
|
7
7
|
hanami-utils (~> 2.0.alpha1)
|
|
@@ -13,7 +13,7 @@ PATH
|
|
|
13
13
|
GEM
|
|
14
14
|
remote: https://rubygems.org/
|
|
15
15
|
specs:
|
|
16
|
-
activesupport (6.0.3.
|
|
16
|
+
activesupport (6.0.3.2)
|
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
18
|
i18n (>= 0.7, < 2)
|
|
19
19
|
minitest (~> 5.1)
|
|
@@ -21,12 +21,12 @@ GEM
|
|
|
21
21
|
zeitwerk (~> 2.2, >= 2.2.2)
|
|
22
22
|
addressable (2.7.0)
|
|
23
23
|
public_suffix (>= 2.0.2, < 5.0)
|
|
24
|
-
ast (2.4.
|
|
24
|
+
ast (2.4.1)
|
|
25
25
|
builder (3.2.4)
|
|
26
26
|
coderay (1.1.3)
|
|
27
27
|
concurrent-ruby (1.1.6)
|
|
28
28
|
deep_merge (1.2.1)
|
|
29
|
-
diff-lcs (1.
|
|
29
|
+
diff-lcs (1.4.4)
|
|
30
30
|
ecma-re-validator (0.2.1)
|
|
31
31
|
regexp_parser (~> 1.2)
|
|
32
32
|
hana (1.3.6)
|
|
@@ -39,7 +39,7 @@ GEM
|
|
|
39
39
|
transproc (~> 1.0)
|
|
40
40
|
hansi (0.2.0)
|
|
41
41
|
hash-deep-merge (0.1.1)
|
|
42
|
-
i18n (1.8.
|
|
42
|
+
i18n (1.8.3)
|
|
43
43
|
concurrent-ruby (~> 1.0)
|
|
44
44
|
json_schemer (0.2.11)
|
|
45
45
|
ecma-re-validator (~> 0.2)
|
|
@@ -49,13 +49,13 @@ GEM
|
|
|
49
49
|
method_source (1.0.0)
|
|
50
50
|
mini_portile2 (2.4.0)
|
|
51
51
|
minitest (5.14.1)
|
|
52
|
-
multi_json (1.
|
|
52
|
+
multi_json (1.15.0)
|
|
53
53
|
mustermann (1.1.1)
|
|
54
54
|
ruby2_keywords (~> 0.0.1)
|
|
55
55
|
mustermann-contrib (1.1.1)
|
|
56
56
|
hansi (~> 0.2.0)
|
|
57
57
|
mustermann (= 1.1.1)
|
|
58
|
-
nokogiri (1.10.
|
|
58
|
+
nokogiri (1.10.10)
|
|
59
59
|
mini_portile2 (~> 2.4.0)
|
|
60
60
|
oas_parser (0.25.1)
|
|
61
61
|
activesupport (>= 4.0.0)
|
|
@@ -65,19 +65,19 @@ GEM
|
|
|
65
65
|
hash-deep-merge
|
|
66
66
|
mustermann-contrib (~> 1.1.1)
|
|
67
67
|
nokogiri
|
|
68
|
-
parallel (1.19.
|
|
69
|
-
parser (2.7.1.
|
|
70
|
-
ast (~> 2.4.
|
|
68
|
+
parallel (1.19.2)
|
|
69
|
+
parser (2.7.1.4)
|
|
70
|
+
ast (~> 2.4.1)
|
|
71
71
|
pry (0.13.1)
|
|
72
72
|
coderay (~> 1.1)
|
|
73
73
|
method_source (~> 1.0)
|
|
74
74
|
public_suffix (4.0.5)
|
|
75
|
-
rack (2.2.
|
|
75
|
+
rack (2.2.3)
|
|
76
76
|
rack-test (1.1.0)
|
|
77
77
|
rack (>= 1.0, < 3)
|
|
78
78
|
rainbow (3.0.0)
|
|
79
79
|
rake (13.0.1)
|
|
80
|
-
regexp_parser (1.7.
|
|
80
|
+
regexp_parser (1.7.1)
|
|
81
81
|
rexml (3.2.4)
|
|
82
82
|
rspec (3.9.0)
|
|
83
83
|
rspec-core (~> 3.9.0)
|
|
@@ -92,16 +92,16 @@ GEM
|
|
|
92
92
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
93
93
|
rspec-support (~> 3.9.0)
|
|
94
94
|
rspec-support (3.9.3)
|
|
95
|
-
rubocop (0.
|
|
95
|
+
rubocop (0.87.1)
|
|
96
96
|
parallel (~> 1.10)
|
|
97
|
-
parser (>= 2.7.
|
|
97
|
+
parser (>= 2.7.1.1)
|
|
98
98
|
rainbow (>= 2.2.2, < 4.0)
|
|
99
99
|
regexp_parser (>= 1.7)
|
|
100
100
|
rexml
|
|
101
|
-
rubocop-ast (>= 0.0.
|
|
101
|
+
rubocop-ast (>= 0.1.0, < 1.0)
|
|
102
102
|
ruby-progressbar (~> 1.7)
|
|
103
103
|
unicode-display_width (>= 1.4.0, < 2.0)
|
|
104
|
-
rubocop-ast (0.0
|
|
104
|
+
rubocop-ast (0.1.0)
|
|
105
105
|
parser (>= 2.7.0.1)
|
|
106
106
|
ruby-progressbar (1.10.1)
|
|
107
107
|
ruby2_keywords (0.0.2)
|
|
@@ -111,7 +111,7 @@ GEM
|
|
|
111
111
|
thread_safe (~> 0.1)
|
|
112
112
|
unicode-display_width (1.7.0)
|
|
113
113
|
uri_template (0.7.0)
|
|
114
|
-
zeitwerk (2.3.
|
|
114
|
+
zeitwerk (2.3.1)
|
|
115
115
|
|
|
116
116
|
PLATFORMS
|
|
117
117
|
ruby
|
data/README.md
CHANGED
|
@@ -8,6 +8,12 @@ Start with writing an OpenAPI file that describes the API, which you are about t
|
|
|
8
8
|
|
|
9
9
|
You can use OpenapiFirst via its [Rack middlewares](#rack-middlewares) or in [standalone mode](#standalone-usage).
|
|
10
10
|
|
|
11
|
+
## Alternatives
|
|
12
|
+
|
|
13
|
+
This gem is inspired by [committee](https://github.com/interagent/committee) (Ruby) and [connexion](https://github.com/zalando/connexion) (Python).
|
|
14
|
+
|
|
15
|
+
Here's a [comparison between committee and openapi_first](https://gist.github.com/ahx/1538c31f0652f459861713b5259e366a).
|
|
16
|
+
|
|
11
17
|
## Rack middlewares
|
|
12
18
|
OpenapiFirst consists of these Rack middlewares:
|
|
13
19
|
|
|
@@ -30,7 +36,6 @@ Options and their defaults:
|
|
|
30
36
|
| Name | Possible values | Description | Default
|
|
31
37
|
|:---|---|---|---|
|
|
32
38
|
|`spec:`| | The spec loaded via `OpenapiFirst.load` ||
|
|
33
|
-
| `not_found:` |`nil`, `:continue`, `Proc`| Specifies what to do if the path was not found in the API description. `nil` (default) returns a 404 response. `:continue` does nothing an calls the next app. `Proc` (or something that responds to `call`) to customize the response. | `nil` (return 404)
|
|
34
39
|
| `raise_error:` |`false`, `true` | If set to true the middleware raises `OpenapiFirst::NotFoundError` when a path or method was not found in the API description. This is useful during testing to spot an incomplete API description. | `false` (don't raise an exception)
|
|
35
40
|
|
|
36
41
|
## OpenapiFirst::RequestValidation
|
|
@@ -137,7 +142,7 @@ Instead of composing these middlewares yourself you can use `OpenapiFirst.app`.
|
|
|
137
142
|
module Pets
|
|
138
143
|
def self.find_pet(params, res)
|
|
139
144
|
{
|
|
140
|
-
id: params[
|
|
145
|
+
id: params[:id],
|
|
141
146
|
name: 'Oscar'
|
|
142
147
|
}
|
|
143
148
|
end
|
|
@@ -145,7 +150,12 @@ end
|
|
|
145
150
|
|
|
146
151
|
# In config.ru:
|
|
147
152
|
require 'openapi_first'
|
|
148
|
-
run OpenapiFirst.app(
|
|
153
|
+
run OpenapiFirst.app(
|
|
154
|
+
'./openapi/openapi.yaml',
|
|
155
|
+
namespace: Pets,
|
|
156
|
+
response_validation: ENV['RACK_ENV'] == 'test',
|
|
157
|
+
router_raise_error: ENV['RACK_ENV'] == 'test'
|
|
158
|
+
)
|
|
149
159
|
```
|
|
150
160
|
|
|
151
161
|
The above will use the mentioned Rack middlewares to:
|
|
@@ -154,6 +164,17 @@ The above will use the mentioned Rack middlewares to:
|
|
|
154
164
|
- Map the request to a method call `Pets.find_pet` based on the `operationId` in the API description
|
|
155
165
|
- Set the response content type according to your spec (here with the default status code `200`)
|
|
156
166
|
|
|
167
|
+
### Options and their defaults:
|
|
168
|
+
|
|
169
|
+
| Name | Possible values | Description | Default
|
|
170
|
+
|:---|---|---|---|
|
|
171
|
+
| `spec_path` || A filepath to an OpenAPI definition file. |
|
|
172
|
+
| `namespace:` || A class or module where to find the handler methods.|
|
|
173
|
+
| `response_validation:` | `true`, `false` | If set to true it raises an exception if the response is invalid. This is useful during testing. | `false`
|
|
174
|
+
| `router_raise_error:` | `true`, `false` | If set to true it raises an exception (subclass of `OpenapiFirst::Error` when a request path/method is not specified. This is useful during testing. | `false`
|
|
175
|
+
| `request_validation_raise_error:` | `true`, `false` | If set to true it raises an exception (subclass of `OpenapiFirst::Error` when a request is not valid. | `false`
|
|
176
|
+
|
|
177
|
+
|
|
157
178
|
Handler functions (`find_pet`) are called with two arguments:
|
|
158
179
|
|
|
159
180
|
- `params` - Holds the parsed request body, filtered query params and path parameters
|
|
@@ -184,7 +205,7 @@ OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
|
|
184
205
|
|
|
185
206
|
## Manual response validation
|
|
186
207
|
|
|
187
|
-
|
|
208
|
+
Instead of using the ResponseValidation middleware you can validate the response in your test manually via [rack-test](https://github.com/rack-test/rack-test) and ResponseValidator.
|
|
188
209
|
|
|
189
210
|
```ruby
|
|
190
211
|
# In your test (rspec example):
|
|
@@ -192,7 +213,8 @@ require 'openapi_first'
|
|
|
192
213
|
spec = OpenapiFirst.load('petstore.yaml')
|
|
193
214
|
validator = OpenapiFirst::ResponseValidator.new(spec)
|
|
194
215
|
|
|
195
|
-
|
|
216
|
+
# This will raise an exception if it found an error
|
|
217
|
+
validator.validate(last_request, last_response)
|
|
196
218
|
```
|
|
197
219
|
|
|
198
220
|
## Handling only certain paths
|
|
@@ -243,10 +265,6 @@ end
|
|
|
243
265
|
|
|
244
266
|
Out of scope. Use [Prism](https://github.com/stoplightio/prism) or [fakeit](https://github.com/JustinFeng/fakeit).
|
|
245
267
|
|
|
246
|
-
## Alternatives
|
|
247
|
-
|
|
248
|
-
This gem is inspired by [committee](https://github.com/interagent/committee) (Ruby) and [connexion](https://github.com/zalando/connexion) (Python).
|
|
249
|
-
|
|
250
268
|
## Development
|
|
251
269
|
|
|
252
270
|
Run `bin/setup` to install dependencies.
|
data/benchmarks/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
openapi_first (0.
|
|
4
|
+
openapi_first (0.12.3)
|
|
5
5
|
deep_merge (>= 1.2.1)
|
|
6
6
|
hanami-router (~> 2.0.alpha3)
|
|
7
7
|
hanami-utils (~> 2.0.alpha1)
|
|
@@ -13,7 +13,7 @@ PATH
|
|
|
13
13
|
GEM
|
|
14
14
|
remote: https://rubygems.org/
|
|
15
15
|
specs:
|
|
16
|
-
activesupport (6.0.3.
|
|
16
|
+
activesupport (6.0.3.2)
|
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
18
|
i18n (>= 0.7, < 2)
|
|
19
19
|
minitest (~> 5.1)
|
|
@@ -25,13 +25,13 @@ GEM
|
|
|
25
25
|
benchmark-memory (0.1.2)
|
|
26
26
|
memory_profiler (~> 0.9)
|
|
27
27
|
builder (3.2.4)
|
|
28
|
-
committee (4.
|
|
28
|
+
committee (4.1.0)
|
|
29
29
|
json_schema (~> 0.14, >= 0.14.3)
|
|
30
30
|
openapi_parser (>= 0.11.1)
|
|
31
31
|
rack (>= 1.5)
|
|
32
32
|
concurrent-ruby (1.1.6)
|
|
33
33
|
deep_merge (1.2.1)
|
|
34
|
-
dry-configurable (0.11.
|
|
34
|
+
dry-configurable (0.11.6)
|
|
35
35
|
concurrent-ruby (~> 1.0)
|
|
36
36
|
dry-core (~> 0.4, >= 0.4.7)
|
|
37
37
|
dry-equalizer (~> 0.2)
|
|
@@ -72,9 +72,9 @@ GEM
|
|
|
72
72
|
transproc (~> 1.0)
|
|
73
73
|
hansi (0.2.0)
|
|
74
74
|
hash-deep-merge (0.1.1)
|
|
75
|
-
i18n (1.8.
|
|
75
|
+
i18n (1.8.3)
|
|
76
76
|
concurrent-ruby (~> 1.0)
|
|
77
|
-
json_schema (0.20.
|
|
77
|
+
json_schema (0.20.9)
|
|
78
78
|
json_schemer (0.2.11)
|
|
79
79
|
ecma-re-validator (~> 0.2)
|
|
80
80
|
hana (~> 1.3)
|
|
@@ -83,7 +83,7 @@ GEM
|
|
|
83
83
|
memory_profiler (0.9.14)
|
|
84
84
|
mini_portile2 (2.4.0)
|
|
85
85
|
minitest (5.14.1)
|
|
86
|
-
multi_json (1.
|
|
86
|
+
multi_json (1.15.0)
|
|
87
87
|
mustermann (1.1.1)
|
|
88
88
|
ruby2_keywords (~> 0.0.1)
|
|
89
89
|
mustermann-contrib (1.1.1)
|
|
@@ -91,7 +91,7 @@ GEM
|
|
|
91
91
|
mustermann (= 1.1.1)
|
|
92
92
|
mustermann-grape (1.0.1)
|
|
93
93
|
mustermann (>= 1.0.0)
|
|
94
|
-
nokogiri (1.10.
|
|
94
|
+
nokogiri (1.10.10)
|
|
95
95
|
mini_portile2 (~> 2.4.0)
|
|
96
96
|
oas_parser (0.25.1)
|
|
97
97
|
activesupport (>= 4.0.0)
|
|
@@ -103,12 +103,12 @@ GEM
|
|
|
103
103
|
nokogiri
|
|
104
104
|
openapi_parser (0.11.2)
|
|
105
105
|
public_suffix (4.0.5)
|
|
106
|
-
rack (2.2.
|
|
106
|
+
rack (2.2.3)
|
|
107
107
|
rack-accept (0.4.5)
|
|
108
108
|
rack (>= 0.4)
|
|
109
109
|
rack-protection (2.0.8.1)
|
|
110
110
|
rack
|
|
111
|
-
regexp_parser (1.7.
|
|
111
|
+
regexp_parser (1.7.1)
|
|
112
112
|
ruby2_keywords (0.0.2)
|
|
113
113
|
seg (1.2.0)
|
|
114
114
|
sinatra (2.0.8.1)
|
|
@@ -125,7 +125,7 @@ GEM
|
|
|
125
125
|
tzinfo (1.2.7)
|
|
126
126
|
thread_safe (~> 0.1)
|
|
127
127
|
uri_template (0.7.0)
|
|
128
|
-
zeitwerk (2.3.
|
|
128
|
+
zeitwerk (2.3.1)
|
|
129
129
|
|
|
130
130
|
PLATFORMS
|
|
131
131
|
ruby
|
data/examples/app.rb
CHANGED
|
@@ -13,4 +13,10 @@ module Web
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
oas_path = File.absolute_path('./openapi.yaml', __dir__)
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
App = OpenapiFirst.app(
|
|
18
|
+
oas_path,
|
|
19
|
+
namespace: Web,
|
|
20
|
+
router_raise_error: OpenapiFirst.env == 'test',
|
|
21
|
+
response_validation: OpenapiFirst.env == 'test'
|
|
22
|
+
)
|
data/lib/openapi_first.rb
CHANGED
|
@@ -31,14 +31,39 @@ module OpenapiFirst
|
|
|
31
31
|
Definition.new(parsed)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def self.app(
|
|
34
|
+
def self.app(
|
|
35
|
+
spec,
|
|
36
|
+
namespace:,
|
|
37
|
+
router_raise_error: false,
|
|
38
|
+
request_validation_raise_error: false,
|
|
39
|
+
response_validation: false
|
|
40
|
+
)
|
|
35
41
|
spec = OpenapiFirst.load(spec) if spec.is_a?(String)
|
|
36
|
-
App.new(
|
|
42
|
+
App.new(
|
|
43
|
+
nil,
|
|
44
|
+
spec,
|
|
45
|
+
namespace: namespace,
|
|
46
|
+
router_raise_error: router_raise_error,
|
|
47
|
+
request_validation_raise_error: request_validation_raise_error,
|
|
48
|
+
response_validation: response_validation
|
|
49
|
+
)
|
|
37
50
|
end
|
|
38
51
|
|
|
39
|
-
def self.middleware(
|
|
52
|
+
def self.middleware(
|
|
53
|
+
spec,
|
|
54
|
+
namespace:,
|
|
55
|
+
router_raise_error: false,
|
|
56
|
+
request_validation_raise_error: false,
|
|
57
|
+
response_validation: false
|
|
58
|
+
)
|
|
40
59
|
spec = OpenapiFirst.load(spec) if spec.is_a?(String)
|
|
41
|
-
AppWithOptions.new(
|
|
60
|
+
AppWithOptions.new(
|
|
61
|
+
spec,
|
|
62
|
+
namespace: namespace,
|
|
63
|
+
router_raise_error: router_raise_error,
|
|
64
|
+
request_validation_raise_error: request_validation_raise_error,
|
|
65
|
+
response_validation: response_validation
|
|
66
|
+
)
|
|
42
67
|
end
|
|
43
68
|
|
|
44
69
|
class AppWithOptions
|
|
@@ -54,9 +79,11 @@ module OpenapiFirst
|
|
|
54
79
|
|
|
55
80
|
class Error < StandardError; end
|
|
56
81
|
class NotFoundError < Error; end
|
|
57
|
-
class
|
|
58
|
-
class
|
|
59
|
-
class
|
|
82
|
+
class NotImplementedError < RuntimeError; end
|
|
83
|
+
class ResponseInvalid < Error; end
|
|
84
|
+
class ResponseCodeNotFoundError < ResponseInvalid; end
|
|
85
|
+
class ResponseContentTypeNotFoundError < ResponseInvalid; end
|
|
86
|
+
class ResponseBodyInvalidError < ResponseInvalid; end
|
|
60
87
|
|
|
61
88
|
class RequestInvalidError < Error
|
|
62
89
|
def initialize(serialized_errors)
|
data/lib/openapi_first/app.rb
CHANGED
|
@@ -5,11 +5,19 @@ require 'logger'
|
|
|
5
5
|
|
|
6
6
|
module OpenapiFirst
|
|
7
7
|
class App
|
|
8
|
-
def initialize(
|
|
8
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
9
|
+
parent_app,
|
|
10
|
+
spec,
|
|
11
|
+
namespace:,
|
|
12
|
+
router_raise_error: false,
|
|
13
|
+
request_validation_raise_error: false,
|
|
14
|
+
response_validation: false
|
|
15
|
+
)
|
|
9
16
|
@stack = Rack::Builder.app do
|
|
10
17
|
freeze_app
|
|
11
|
-
use OpenapiFirst::Router, spec: spec, raise_error:
|
|
12
|
-
use OpenapiFirst::RequestValidation, raise_error:
|
|
18
|
+
use OpenapiFirst::Router, spec: spec, raise_error: router_raise_error, parent_app: parent_app
|
|
19
|
+
use OpenapiFirst::RequestValidation, raise_error: request_validation_raise_error
|
|
20
|
+
use OpenapiFirst::ResponseValidation if response_validation
|
|
13
21
|
run OpenapiFirst::Responder.new(
|
|
14
22
|
spec: spec,
|
|
15
23
|
namespace: namespace
|
|
@@ -5,14 +5,10 @@ require_relative 'utils'
|
|
|
5
5
|
module OpenapiFirst
|
|
6
6
|
class FindHandler
|
|
7
7
|
def initialize(spec, namespace)
|
|
8
|
-
@spec = spec
|
|
9
8
|
@namespace = namespace
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def all
|
|
13
|
-
@spec.operations.each_with_object({}) do |operation, hash|
|
|
9
|
+
@handlers = spec.operations.each_with_object({}) do |operation, hash|
|
|
14
10
|
operation_id = operation.operation_id
|
|
15
|
-
handler =
|
|
11
|
+
handler = find_handler(operation_id)
|
|
16
12
|
if handler.nil?
|
|
17
13
|
warn "#{self.class.name} cannot not find handler for '#{operation.operation_id}' (#{operation.method} #{operation.path}). This operation will be ignored." # rubocop:disable Layout/LineLength
|
|
18
14
|
next
|
|
@@ -21,22 +17,17 @@ module OpenapiFirst
|
|
|
21
17
|
end
|
|
22
18
|
end
|
|
23
19
|
|
|
24
|
-
def
|
|
20
|
+
def [](operation_id)
|
|
21
|
+
@handlers[operation_id]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_handler(operation_id)
|
|
25
25
|
name = operation_id.match(/:*(.*)/)&.to_a&.at(1)
|
|
26
26
|
return if name.nil?
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return klass&.method(Utils.underscore(method_name))
|
|
32
|
-
end
|
|
33
|
-
if name.include?('#')
|
|
34
|
-
module_name, klass_name = name.split('#')
|
|
35
|
-
const = find_const(@namespace, module_name)
|
|
36
|
-
klass = find_const(const, klass_name)
|
|
37
|
-
return ->(params, res) { klass.new.call(params, res) } if klass.instance_method(:initialize).arity.zero?
|
|
38
|
-
|
|
39
|
-
return ->(params, res) { klass.new(params.env).call(params, res) }
|
|
28
|
+
catch :halt do
|
|
29
|
+
return find_class_method_handler(name) if name.include?('.')
|
|
30
|
+
return find_instance_method_handler(name) if name.include?('#')
|
|
40
31
|
end
|
|
41
32
|
method_name = Utils.underscore(name)
|
|
42
33
|
return unless @namespace.respond_to?(method_name)
|
|
@@ -44,11 +35,24 @@ module OpenapiFirst
|
|
|
44
35
|
@namespace.method(method_name)
|
|
45
36
|
end
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
def find_class_method_handler(name)
|
|
39
|
+
module_name, method_name = name.split('.')
|
|
40
|
+
klass = find_const(@namespace, module_name)
|
|
41
|
+
klass.method(Utils.underscore(method_name))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_instance_method_handler(name)
|
|
45
|
+
module_name, klass_name = name.split('#')
|
|
46
|
+
const = find_const(@namespace, module_name)
|
|
47
|
+
klass = find_const(const, klass_name)
|
|
48
|
+
return ->(params, res) { klass.new.call(params, res) } if klass.instance_method(:initialize).arity.zero?
|
|
49
|
+
|
|
50
|
+
->(params, res) { klass.new(params.env).call(params, res) }
|
|
51
|
+
end
|
|
48
52
|
|
|
49
53
|
def find_const(parent, name)
|
|
50
54
|
name = Utils.classify(name)
|
|
51
|
-
|
|
55
|
+
throw :halt unless parent.const_defined?(name, false)
|
|
52
56
|
|
|
53
57
|
parent.const_get(name, false)
|
|
54
58
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'forwardable'
|
|
4
|
+
require 'json_schemer'
|
|
4
5
|
require_relative 'utils'
|
|
5
6
|
require_relative 'response_object'
|
|
6
7
|
|
|
@@ -25,6 +26,10 @@ module OpenapiFirst
|
|
|
25
26
|
@parameters_json_schema ||= build_parameters_json_schema
|
|
26
27
|
end
|
|
27
28
|
|
|
29
|
+
def parameters_schema
|
|
30
|
+
@parameters_schema ||= parameters_json_schema && JSONSchemer.schema(parameters_json_schema)
|
|
31
|
+
end
|
|
32
|
+
|
|
28
33
|
def content_type_for(status)
|
|
29
34
|
content = response_for(status)['content']
|
|
30
35
|
content.keys[0] if content
|
|
@@ -34,10 +39,12 @@ module OpenapiFirst
|
|
|
34
39
|
content = response_for(status)['content']
|
|
35
40
|
return if content.nil? || content.empty?
|
|
36
41
|
|
|
42
|
+
raise ResponseInvalid, "Response has no content-type for '#{name}'" unless content_type
|
|
43
|
+
|
|
37
44
|
media_type = content[content_type]
|
|
38
45
|
unless media_type
|
|
39
|
-
message = "Response
|
|
40
|
-
raise
|
|
46
|
+
message = "Response content type not found '#{content_type}' for '#{name}'"
|
|
47
|
+
raise ResponseContentTypeNotFoundError, message
|
|
41
48
|
end
|
|
42
49
|
media_type['schema']
|
|
43
50
|
end
|
|
@@ -45,16 +52,16 @@ module OpenapiFirst
|
|
|
45
52
|
def response_for(status)
|
|
46
53
|
@operation.response_by_code(status.to_s, use_default: true).raw
|
|
47
54
|
rescue OasParser::ResponseCodeNotFound
|
|
48
|
-
message = "Response status code or default not found: #{status} for '#{
|
|
55
|
+
message = "Response status code or default not found: #{status} for '#{name}'"
|
|
49
56
|
raise OpenapiFirst::ResponseCodeNotFoundError, message
|
|
50
57
|
end
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def operation_name
|
|
55
|
-
"#{method.upcase} #{path}"
|
|
59
|
+
def name
|
|
60
|
+
"#{method.upcase} #{path} (#{operation_id})"
|
|
56
61
|
end
|
|
57
62
|
|
|
63
|
+
private
|
|
64
|
+
|
|
58
65
|
def build_parameters_json_schema
|
|
59
66
|
return unless @operation.parameters&.any?
|
|
60
67
|
|
|
@@ -50,32 +50,32 @@ module OpenapiFirst
|
|
|
50
50
|
|
|
51
51
|
parsed_request_body = parse_request_body!(body)
|
|
52
52
|
errors = validate_json_schema(schema, parsed_request_body)
|
|
53
|
-
|
|
53
|
+
halt_with_error(400, serialize_request_body_errors(errors)) if errors.any?
|
|
54
54
|
env[INBOX].merge! env[REQUEST_BODY] = parsed_request_body
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def parse_request_body!(body)
|
|
58
|
-
MultiJson.load(body)
|
|
58
|
+
MultiJson.load(body, symbolize_keys: true)
|
|
59
59
|
rescue MultiJson::ParseError => e
|
|
60
60
|
err = { title: 'Failed to parse body as JSON' }
|
|
61
61
|
err[:detail] = e.cause unless ENV['RACK_ENV'] == 'production'
|
|
62
|
-
|
|
62
|
+
halt_with_error(400, [err])
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def validate_request_content_type!(content_type, operation)
|
|
66
66
|
return if operation.request_body.content[content_type]
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
halt_with_error(415)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def validate_request_body_presence!(body, operation)
|
|
72
72
|
return unless operation.request_body.required && body.empty?
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
halt_with_error(415, 'Request body is required')
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def validate_json_schema(schema, object)
|
|
78
|
-
|
|
78
|
+
schema.validate(Utils.deep_stringify(object))
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def default_error(status, title = Rack::Utils::HTTP_STATUS_CODES[status])
|
|
@@ -85,10 +85,10 @@ module OpenapiFirst
|
|
|
85
85
|
}
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def
|
|
88
|
+
def halt_with_error(status, errors = [default_error(status)])
|
|
89
89
|
raise RequestInvalidError, errors if @raise
|
|
90
90
|
|
|
91
|
-
Rack::Response.new(
|
|
91
|
+
halt Rack::Response.new(
|
|
92
92
|
MultiJson.dump(errors: errors),
|
|
93
93
|
status,
|
|
94
94
|
Rack::CONTENT_TYPE => 'application/vnd.api+json'
|
|
@@ -98,7 +98,8 @@ module OpenapiFirst
|
|
|
98
98
|
def request_body_schema(content_type, operation)
|
|
99
99
|
return unless operation
|
|
100
100
|
|
|
101
|
-
operation.request_body.content[content_type]&.fetch('schema')
|
|
101
|
+
schema = operation.request_body.content[content_type]&.fetch('schema')
|
|
102
|
+
JSONSchemer.schema(schema) if schema
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
def serialize_request_body_errors(validation_errors)
|
|
@@ -116,8 +117,11 @@ module OpenapiFirst
|
|
|
116
117
|
return unless json_schema
|
|
117
118
|
|
|
118
119
|
params = filtered_params(json_schema, params)
|
|
119
|
-
errors =
|
|
120
|
-
|
|
120
|
+
errors = validate_json_schema(
|
|
121
|
+
operation.parameters_schema,
|
|
122
|
+
params
|
|
123
|
+
)
|
|
124
|
+
halt_with_error(400, serialize_query_parameter_errors(errors)) if errors.any?
|
|
121
125
|
env[PARAMETERS] = params
|
|
122
126
|
env[INBOX].merge! params
|
|
123
127
|
end
|
|
@@ -125,7 +129,8 @@ module OpenapiFirst
|
|
|
125
129
|
def filtered_params(json_schema, params)
|
|
126
130
|
json_schema['properties']
|
|
127
131
|
.each_with_object({}) do |key_value, result|
|
|
128
|
-
parameter_name
|
|
132
|
+
parameter_name = key_value[0].to_sym
|
|
133
|
+
schema = key_value[1]
|
|
129
134
|
next unless params.key?(parameter_name)
|
|
130
135
|
|
|
131
136
|
value = params[parameter_name]
|
|
@@ -6,15 +6,15 @@ require_relative 'find_handler'
|
|
|
6
6
|
|
|
7
7
|
module OpenapiFirst
|
|
8
8
|
class Responder
|
|
9
|
-
def initialize(spec:, namespace:)
|
|
10
|
-
@
|
|
9
|
+
def initialize(spec:, namespace:, resolver: FindHandler.new(spec, namespace))
|
|
10
|
+
@resolver = resolver
|
|
11
11
|
@namespace = namespace
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call(env)
|
|
15
15
|
operation = env[OpenapiFirst::OPERATION]
|
|
16
16
|
res = Rack::Response.new
|
|
17
|
-
handler =
|
|
17
|
+
handler = find_handler(operation)
|
|
18
18
|
result = handler.call(env[INBOX], res)
|
|
19
19
|
res.write serialize(result) if result && res.body.empty?
|
|
20
20
|
res[Rack::CONTENT_TYPE] ||= operation.content_type_for(res.status)
|
|
@@ -23,6 +23,13 @@ module OpenapiFirst
|
|
|
23
23
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
|
+
def find_handler(operation)
|
|
27
|
+
handler = @resolver[operation.operation_id]
|
|
28
|
+
raise NotImplementedError, "Could not find handler for #{operation.name}" unless handler
|
|
29
|
+
|
|
30
|
+
handler
|
|
31
|
+
end
|
|
32
|
+
|
|
26
33
|
def serialize(result)
|
|
27
34
|
return result if result.is_a?(String)
|
|
28
35
|
|
|
@@ -17,45 +17,45 @@ module OpenapiFirst
|
|
|
17
17
|
operation = env[OPERATION]
|
|
18
18
|
return @app.call(env) unless operation
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
response = @app.call(env)
|
|
21
|
+
validate(response, operation)
|
|
22
|
+
response
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate(response, operation)
|
|
26
|
+
status, headers, body = response.to_a
|
|
27
|
+
return validate_status_only(operation, status) if status == 204
|
|
28
|
+
|
|
21
29
|
content_type = headers[Rack::CONTENT_TYPE]
|
|
22
30
|
response_schema = operation.response_schema_for(status, content_type)
|
|
23
31
|
validate_response_body(response_schema, body) if response_schema
|
|
24
|
-
|
|
25
|
-
[status, headers, body]
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
private
|
|
29
35
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def error(message)
|
|
35
|
-
{ title: message }
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def error_response(status, errors)
|
|
39
|
-
Rack::Response.new(
|
|
40
|
-
MultiJson.dump(errors: errors),
|
|
41
|
-
status,
|
|
42
|
-
Rack::CONTENT_TYPE => 'application/vnd.api+json'
|
|
43
|
-
).finish
|
|
36
|
+
def validate_status_only(operation, status)
|
|
37
|
+
operation.response_for(status)
|
|
44
38
|
end
|
|
45
39
|
|
|
46
40
|
def validate_response_body(schema, response)
|
|
47
41
|
full_body = +''
|
|
48
42
|
response.each { |chunk| full_body << chunk }
|
|
49
|
-
data = full_body.empty? ? {} :
|
|
43
|
+
data = full_body.empty? ? {} : load_json(full_body)
|
|
50
44
|
errors = JSONSchemer.schema(schema).validate(data).to_a.map do |error|
|
|
51
|
-
|
|
45
|
+
error_message_for(error)
|
|
52
46
|
end
|
|
53
47
|
raise ResponseBodyInvalidError, errors.join(', ') if errors.any?
|
|
54
48
|
end
|
|
55
49
|
|
|
56
|
-
def
|
|
50
|
+
def load_json(string)
|
|
51
|
+
MultiJson.load(string)
|
|
52
|
+
rescue MultiJson::ParseError
|
|
53
|
+
string
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def error_message_for(error)
|
|
57
57
|
err = ValidationFormat.error_details(error)
|
|
58
|
-
[err[:title],
|
|
58
|
+
[err[:title], error['data_pointer'], err[:detail]].compact.join(' ')
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -10,57 +10,13 @@ module OpenapiFirst
|
|
|
10
10
|
def initialize(spec)
|
|
11
11
|
@spec = spec
|
|
12
12
|
@router = Router.new(->(_env) {}, spec: spec, raise_error: true)
|
|
13
|
+
@response_validation = ResponseValidation.new(->(response) { response.to_a })
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def validate(request, response)
|
|
16
|
-
errors = validation_errors(request, response)
|
|
17
|
-
Validation.new(errors || [])
|
|
18
|
-
rescue OpenapiFirst::ResponseCodeNotFoundError, OpenapiFirst::NotFoundError => e
|
|
19
|
-
Validation.new([e.message])
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def validate_operation(request, response)
|
|
23
|
-
errors = validation_errors(request, response)
|
|
24
|
-
Validation.new(errors || [])
|
|
25
|
-
rescue OpenapiFirst::ResponseCodeNotFoundError, OpenapiFirst::NotFoundError => e
|
|
26
|
-
Validation.new([e.message])
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def validation_errors(request, response)
|
|
32
|
-
content = response_for(request, response)&.fetch('content', nil)
|
|
33
|
-
return unless content
|
|
34
|
-
|
|
35
|
-
content_type = content[response.content_type]
|
|
36
|
-
return ["Content type not found: '#{response.content_type}'"] unless content_type
|
|
37
|
-
|
|
38
|
-
response_schema = content_type['schema']
|
|
39
|
-
return unless response_schema
|
|
40
|
-
|
|
41
|
-
response_data = MultiJson.load(response.body)
|
|
42
|
-
validate_json_schema(response_schema, response_data)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def validate_json_schema(schema, data)
|
|
46
|
-
JSONSchemer.schema(schema).validate(data).to_a.map do |error|
|
|
47
|
-
format_error(error)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def format_error(error)
|
|
52
|
-
ValidationFormat.error_details(error)
|
|
53
|
-
.merge!(
|
|
54
|
-
data_pointer: error['data_pointer'],
|
|
55
|
-
schema_pointer: error['schema_pointer']
|
|
56
|
-
)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def response_for(request, response)
|
|
60
17
|
env = request.env.dup
|
|
61
18
|
@router.call(env)
|
|
62
|
-
|
|
63
|
-
operation&.response_for(response.status)
|
|
19
|
+
@response_validation.validate(response, env[OPERATION])
|
|
64
20
|
end
|
|
65
21
|
end
|
|
66
22
|
end
|
data/lib/openapi_first/router.rb
CHANGED
|
@@ -6,58 +6,47 @@ require_relative 'utils'
|
|
|
6
6
|
|
|
7
7
|
module OpenapiFirst
|
|
8
8
|
class Router
|
|
9
|
-
NOT_FOUND = Rack::Response.new('', 404).finish.freeze
|
|
10
|
-
DEFAULT_NOT_FOUND_APP = ->(_env) { NOT_FOUND }
|
|
11
|
-
|
|
12
9
|
def initialize(
|
|
13
10
|
app,
|
|
14
11
|
spec:,
|
|
15
12
|
raise_error: false,
|
|
16
|
-
parent_app: nil
|
|
17
|
-
not_found: nil
|
|
13
|
+
parent_app: nil
|
|
18
14
|
)
|
|
19
15
|
@app = app
|
|
20
16
|
@parent_app = parent_app
|
|
21
17
|
@raise = raise_error
|
|
22
|
-
@failure_app = find_failure_app(not_found)
|
|
23
|
-
if @failure_app.nil?
|
|
24
|
-
raise ArgumentError,
|
|
25
|
-
'not_found must be nil, :continue or must respond to call'
|
|
26
|
-
end
|
|
27
18
|
@filepath = spec.filepath
|
|
28
19
|
@router = build_router(spec.operations)
|
|
29
20
|
end
|
|
30
21
|
|
|
31
22
|
def call(env)
|
|
32
23
|
env[OPERATION] = nil
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
response = call_router(env)
|
|
25
|
+
if env[OPERATION].nil?
|
|
26
|
+
return @parent_app.call(env) if @parent_app # This should only happen if used via OpenapiFirst.middlware
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
req = Rack::Request.new(env)
|
|
38
|
-
msg = "Could not find definition for #{req.request_method} '#{req.path}' in API description #{@filepath}"
|
|
39
|
-
raise NotFoundError, msg
|
|
28
|
+
raise_error(env) if @raise
|
|
40
29
|
end
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@failure_app.call(env)
|
|
30
|
+
response
|
|
44
31
|
end
|
|
45
32
|
|
|
46
|
-
|
|
33
|
+
ORIGINAL_PATH = 'openapi_first.path_info'
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
return DEFAULT_NOT_FOUND_APP if option.nil?
|
|
50
|
-
return @app if option == :continue
|
|
35
|
+
private
|
|
51
36
|
|
|
52
|
-
|
|
37
|
+
def raise_error(env)
|
|
38
|
+
req = Rack::Request.new(env)
|
|
39
|
+
msg = "Could not find definition for #{req.request_method} '#{req.path}' in API description #{@filepath}"
|
|
40
|
+
raise NotFoundError, msg
|
|
53
41
|
end
|
|
54
42
|
|
|
55
|
-
def
|
|
56
|
-
|
|
43
|
+
def call_router(env)
|
|
44
|
+
# Changing and restoring PATH_INFO is needed, because Hanami::Router does not respect existing script_path
|
|
45
|
+
env[ORIGINAL_PATH] = env[Rack::PATH_INFO]
|
|
57
46
|
env[Rack::PATH_INFO] = Rack::Request.new(env).path
|
|
58
|
-
@router.
|
|
47
|
+
@router.call(env)
|
|
59
48
|
ensure
|
|
60
|
-
env[Rack::PATH_INFO] =
|
|
49
|
+
env[Rack::PATH_INFO] = env.delete(ORIGINAL_PATH) if env[ORIGINAL_PATH]
|
|
61
50
|
end
|
|
62
51
|
|
|
63
52
|
def build_router(operations) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
@@ -73,7 +62,8 @@ module OpenapiFirst
|
|
|
73
62
|
normalized_path,
|
|
74
63
|
to: lambda do |env|
|
|
75
64
|
env[OPERATION] = operation
|
|
76
|
-
env[PARAMETERS] =
|
|
65
|
+
env[PARAMETERS] = env['router.params']
|
|
66
|
+
env[Rack::PATH_INFO] = env.delete(ORIGINAL_PATH)
|
|
77
67
|
@app.call(env)
|
|
78
68
|
end
|
|
79
69
|
)
|
|
@@ -5,6 +5,7 @@ module OpenapiFirst
|
|
|
5
5
|
SIMPLE_TYPES = %w[string integer].freeze
|
|
6
6
|
|
|
7
7
|
# rubocop:disable Metrics/MethodLength
|
|
8
|
+
# rubocop:disable Metrics/AbcSize
|
|
8
9
|
def self.error_details(error)
|
|
9
10
|
if error['type'] == 'pattern'
|
|
10
11
|
{
|
|
@@ -23,9 +24,10 @@ module OpenapiFirst
|
|
|
23
24
|
elsif error['schema'] == false
|
|
24
25
|
{ title: 'unknown fields are not allowed' }
|
|
25
26
|
else
|
|
26
|
-
{ title:
|
|
27
|
+
{ title: "is not valid: #{error['data'].inspect}" }
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
# rubocop:enable Metrics/MethodLength
|
|
31
|
+
# rubocop:enable Metrics/AbcSize
|
|
30
32
|
end
|
|
31
33
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openapi_first
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Haller
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-07-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: deep_merge
|
|
@@ -236,9 +236,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
236
236
|
version: '0'
|
|
237
237
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
238
|
requirements:
|
|
239
|
-
- - "
|
|
239
|
+
- - ">="
|
|
240
240
|
- !ruby/object:Gem::Version
|
|
241
|
-
version:
|
|
241
|
+
version: '0'
|
|
242
242
|
requirements: []
|
|
243
243
|
rubygems_version: 3.1.2
|
|
244
244
|
signing_key:
|