openapi_first 0.13.3 → 0.14.0
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 +3 -2
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +58 -57
- data/README.md +12 -8
- data/benchmarks/Gemfile.lock +49 -52
- data/benchmarks/apps/committee.ru +3 -1
- data/benchmarks/benchmarks.rb +11 -14
- data/lib/openapi_first.rb +9 -4
- data/lib/openapi_first/app.rb +3 -5
- data/lib/openapi_first/{find_handler.rb → default_operation_resolver.rb} +5 -11
- data/lib/openapi_first/definition.rb +8 -3
- data/lib/openapi_first/operation.rb +43 -18
- data/lib/openapi_first/request_validation.rb +2 -2
- data/lib/openapi_first/responder.rb +4 -4
- data/lib/openapi_first/router.rb +5 -2
- data/lib/openapi_first/validation_format.rb +5 -0
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +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: 4f1e3ae677c078e5e4b64641c76c40a66994edb8599d9001e3673458eb83338d
|
|
4
|
+
data.tar.gz: 6e394c9017964515be4e8970899fe697480600753ca6e81db78d34faed7c982b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e41290229c5e5bc7d65179783b6b1ec0403ab987433c5b65535c10d3fee00a42440626f26f2a8ffd02d69a63b64684780165cc4398baf873842e20731570a076
|
|
7
|
+
data.tar.gz: b93594502a2dff93be5b0bff277f49431c40cae4a77a263acfac14370c855dbd424ac36156b832ec2f2ef1f892e44c0c7aaf84d6b567d772220da0ec991357a5
|
data/.rubocop.yml
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
AllCops:
|
|
2
2
|
TargetRubyVersion: 2.6
|
|
3
3
|
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
4
5
|
Style/Documentation:
|
|
5
6
|
Enabled: false
|
|
6
7
|
Style/ExponentialNotation:
|
|
7
8
|
Enabled: true
|
|
8
9
|
Metrics/BlockLength:
|
|
9
10
|
Exclude:
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
- "spec/**/*.rb"
|
|
12
|
+
- "*.gemspec"
|
|
12
13
|
Metrics/MethodLength:
|
|
13
14
|
Max: 20
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
- Handle custom x-handler field in the API description to find a handler method not based on operationId
|
|
5
|
+
- Add `resolver` option to provide a custom resolver to find a handler method
|
|
6
|
+
|
|
3
7
|
## 0.13.3
|
|
4
8
|
- Better error message if string does not match format
|
|
9
|
+
- readOnly and writeOnly just works when used inside allOf
|
|
5
10
|
|
|
6
11
|
## 0.13.2
|
|
7
12
|
- Return indicator (`source: { parameter: 'list/1' }`) in error response body when array item in query parameter is invalid
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
openapi_first (0.
|
|
4
|
+
openapi_first (0.14.0)
|
|
5
5
|
deep_merge (>= 1.2.1)
|
|
6
6
|
hanami-router (~> 2.0.alpha3)
|
|
7
7
|
hanami-utils (~> 2.0.alpha1)
|
|
@@ -13,51 +13,53 @@ PATH
|
|
|
13
13
|
GEM
|
|
14
14
|
remote: https://rubygems.org/
|
|
15
15
|
specs:
|
|
16
|
-
activesupport (6.
|
|
16
|
+
activesupport (6.1.4)
|
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
|
-
i18n (>=
|
|
19
|
-
minitest (
|
|
20
|
-
tzinfo (~>
|
|
21
|
-
zeitwerk (~> 2.
|
|
22
|
-
addressable (2.
|
|
18
|
+
i18n (>= 1.6, < 2)
|
|
19
|
+
minitest (>= 5.1)
|
|
20
|
+
tzinfo (~> 2.0)
|
|
21
|
+
zeitwerk (~> 2.3)
|
|
22
|
+
addressable (2.8.0)
|
|
23
23
|
public_suffix (>= 2.0.2, < 5.0)
|
|
24
|
-
ast (2.4.
|
|
24
|
+
ast (2.4.2)
|
|
25
25
|
builder (3.2.4)
|
|
26
26
|
coderay (1.1.3)
|
|
27
|
-
concurrent-ruby (1.1.
|
|
27
|
+
concurrent-ruby (1.1.9)
|
|
28
28
|
deep_merge (1.2.1)
|
|
29
29
|
diff-lcs (1.4.4)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
dry-transformer (0.1.1)
|
|
31
|
+
ecma-re-validator (0.3.0)
|
|
32
|
+
regexp_parser (~> 2.0)
|
|
33
|
+
hana (1.3.7)
|
|
34
|
+
hanami-router (2.0.0.alpha5)
|
|
34
35
|
mustermann (~> 1.0)
|
|
35
36
|
mustermann-contrib (~> 1.0)
|
|
36
37
|
rack (~> 2.0)
|
|
37
|
-
hanami-utils (2.0.0.
|
|
38
|
+
hanami-utils (2.0.0.alpha2)
|
|
38
39
|
concurrent-ruby (~> 1.0)
|
|
39
|
-
|
|
40
|
+
dry-transformer (~> 0.1)
|
|
40
41
|
hansi (0.2.0)
|
|
41
42
|
hash-deep-merge (0.1.1)
|
|
42
|
-
i18n (1.8.
|
|
43
|
+
i18n (1.8.10)
|
|
43
44
|
concurrent-ruby (~> 1.0)
|
|
44
|
-
json_schemer (0.2.
|
|
45
|
-
ecma-re-validator (~> 0.
|
|
45
|
+
json_schemer (0.2.18)
|
|
46
|
+
ecma-re-validator (~> 0.3)
|
|
46
47
|
hana (~> 1.3)
|
|
47
|
-
regexp_parser (~>
|
|
48
|
+
regexp_parser (~> 2.0)
|
|
48
49
|
uri_template (~> 0.7)
|
|
49
50
|
method_source (1.0.0)
|
|
50
|
-
mini_portile2 (2.
|
|
51
|
-
minitest (5.14.
|
|
51
|
+
mini_portile2 (2.6.1)
|
|
52
|
+
minitest (5.14.4)
|
|
52
53
|
multi_json (1.15.0)
|
|
53
54
|
mustermann (1.1.1)
|
|
54
55
|
ruby2_keywords (~> 0.0.1)
|
|
55
56
|
mustermann-contrib (1.1.1)
|
|
56
57
|
hansi (~> 0.2.0)
|
|
57
58
|
mustermann (= 1.1.1)
|
|
58
|
-
nokogiri (1.
|
|
59
|
-
mini_portile2 (~> 2.
|
|
60
|
-
|
|
59
|
+
nokogiri (1.12.2)
|
|
60
|
+
mini_portile2 (~> 2.6.1)
|
|
61
|
+
racc (~> 1.4)
|
|
62
|
+
oas_parser (0.25.4)
|
|
61
63
|
activesupport (>= 4.0.0)
|
|
62
64
|
addressable (~> 2.3)
|
|
63
65
|
builder (~> 3.2.3)
|
|
@@ -65,53 +67,52 @@ GEM
|
|
|
65
67
|
hash-deep-merge
|
|
66
68
|
mustermann-contrib (~> 1.1.1)
|
|
67
69
|
nokogiri
|
|
68
|
-
parallel (1.
|
|
69
|
-
parser (
|
|
70
|
+
parallel (1.20.1)
|
|
71
|
+
parser (3.0.2.0)
|
|
70
72
|
ast (~> 2.4.1)
|
|
71
|
-
pry (0.
|
|
73
|
+
pry (0.14.1)
|
|
72
74
|
coderay (~> 1.1)
|
|
73
75
|
method_source (~> 1.0)
|
|
74
76
|
public_suffix (4.0.6)
|
|
77
|
+
racc (1.5.2)
|
|
75
78
|
rack (2.2.3)
|
|
76
79
|
rack-test (1.1.0)
|
|
77
80
|
rack (>= 1.0, < 3)
|
|
78
81
|
rainbow (3.0.0)
|
|
79
|
-
rake (13.0.
|
|
80
|
-
regexp_parser (1.
|
|
81
|
-
rexml (3.2.
|
|
82
|
-
rspec (3.
|
|
83
|
-
rspec-core (~> 3.
|
|
84
|
-
rspec-expectations (~> 3.
|
|
85
|
-
rspec-mocks (~> 3.
|
|
86
|
-
rspec-core (3.
|
|
87
|
-
rspec-support (~> 3.
|
|
88
|
-
rspec-expectations (3.
|
|
82
|
+
rake (13.0.6)
|
|
83
|
+
regexp_parser (2.1.1)
|
|
84
|
+
rexml (3.2.5)
|
|
85
|
+
rspec (3.10.0)
|
|
86
|
+
rspec-core (~> 3.10.0)
|
|
87
|
+
rspec-expectations (~> 3.10.0)
|
|
88
|
+
rspec-mocks (~> 3.10.0)
|
|
89
|
+
rspec-core (3.10.1)
|
|
90
|
+
rspec-support (~> 3.10.0)
|
|
91
|
+
rspec-expectations (3.10.1)
|
|
89
92
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
90
|
-
rspec-support (~> 3.
|
|
91
|
-
rspec-mocks (3.
|
|
93
|
+
rspec-support (~> 3.10.0)
|
|
94
|
+
rspec-mocks (3.10.2)
|
|
92
95
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
93
|
-
rspec-support (~> 3.
|
|
94
|
-
rspec-support (3.
|
|
95
|
-
rubocop (
|
|
96
|
+
rspec-support (~> 3.10.0)
|
|
97
|
+
rspec-support (3.10.2)
|
|
98
|
+
rubocop (1.18.4)
|
|
96
99
|
parallel (~> 1.10)
|
|
97
|
-
parser (>=
|
|
100
|
+
parser (>= 3.0.0.0)
|
|
98
101
|
rainbow (>= 2.2.2, < 4.0)
|
|
99
|
-
regexp_parser (>= 1.8)
|
|
102
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
100
103
|
rexml
|
|
101
|
-
rubocop-ast (>=
|
|
104
|
+
rubocop-ast (>= 1.8.0, < 2.0)
|
|
102
105
|
ruby-progressbar (~> 1.7)
|
|
103
|
-
unicode-display_width (>= 1.4.0, <
|
|
104
|
-
rubocop-ast (
|
|
105
|
-
parser (>=
|
|
106
|
-
ruby-progressbar (1.
|
|
107
|
-
ruby2_keywords (0.0.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
thread_safe (~> 0.1)
|
|
112
|
-
unicode-display_width (1.7.0)
|
|
106
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
107
|
+
rubocop-ast (1.9.0)
|
|
108
|
+
parser (>= 3.0.1.1)
|
|
109
|
+
ruby-progressbar (1.11.0)
|
|
110
|
+
ruby2_keywords (0.0.5)
|
|
111
|
+
tzinfo (2.0.4)
|
|
112
|
+
concurrent-ruby (~> 1.0)
|
|
113
|
+
unicode-display_width (2.0.0)
|
|
113
114
|
uri_template (0.7.0)
|
|
114
|
-
zeitwerk (2.4.
|
|
115
|
+
zeitwerk (2.4.2)
|
|
115
116
|
|
|
116
117
|
PLATFORMS
|
|
117
118
|
ruby
|
|
@@ -126,4 +127,4 @@ DEPENDENCIES
|
|
|
126
127
|
rubocop
|
|
127
128
|
|
|
128
129
|
BUNDLED WITH
|
|
129
|
-
2.
|
|
130
|
+
2.2.3
|
data/README.md
CHANGED
|
@@ -29,9 +29,9 @@ You always have to add this middleware first in order to make the other middlewa
|
|
|
29
29
|
use OpenapiFirst::Router, spec: OpenapiFirst.load('./openapi/openapi.yaml')
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
This middleware adds `env[OpenapiFirst::OPERATION]` which holds an Operation object that responds to
|
|
32
|
+
This middleware adds `env[OpenapiFirst::OPERATION]` which holds an Operation object that responds to `#operation_id`, `#path` (and `#[]` to access raw fields).
|
|
33
33
|
|
|
34
|
-
Options and
|
|
34
|
+
### Options and defaults
|
|
35
35
|
|
|
36
36
|
| Name | Possible values | Description | Default
|
|
37
37
|
|:---|---|---|---|
|
|
@@ -48,7 +48,7 @@ use OpenapiFirst::RequestValidation
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
Options and
|
|
51
|
+
### Options and defaults
|
|
52
52
|
|
|
53
53
|
| Name | Possible values | Description | Default
|
|
54
54
|
|:---|---|---|---|
|
|
@@ -109,16 +109,19 @@ Response validation fails if response body includes a property with `writeOnly:
|
|
|
109
109
|
|
|
110
110
|
This Rack endpoint maps the HTTP request to a method call based on the [operationId](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object) in your API description and calls it. Responder also adds a content-type to the response.
|
|
111
111
|
|
|
112
|
-
Currently there are no customization options for this part. Please [share your ideas](#contributing) on how to best meet your needs and preferred style.
|
|
113
|
-
|
|
114
112
|
```ruby
|
|
115
113
|
run OpenapiFirst::Responder, spec: OpenapiFirst.load('./openapi/openapi.yaml')
|
|
116
114
|
```
|
|
117
115
|
|
|
116
|
+
### Options
|
|
118
117
|
| Name | Description
|
|
119
118
|
|:---|---|
|
|
120
|
-
|
|
121
|
-
| `
|
|
119
|
+
| `namespace:` | Optional. A class or module where to find the handler method. |
|
|
120
|
+
| `resolver:` | Optional. An object that responds to `#call(operation)` and returns a (handler)[#handler]. By default this is an instance of [DefaultOperationResolver](#OpenapiFirst::DefaultOperationResolver) |
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
### OpenapiFirst::DefaultOperationResolver
|
|
124
|
+
This is the default way to look up a handler method for an operation. Handlers are always looked up in a namespace module that needs to be specified.
|
|
122
125
|
|
|
123
126
|
It works like this:
|
|
124
127
|
|
|
@@ -176,7 +179,7 @@ The above will use the mentioned Rack middlewares to:
|
|
|
176
179
|
- Map the request to a method call `Pets.find_pet` based on the `operationId` in the API description
|
|
177
180
|
- Set the response content type according to your spec (here with the default status code `200`)
|
|
178
181
|
|
|
179
|
-
### Options and
|
|
182
|
+
### Options and defaults
|
|
180
183
|
|
|
181
184
|
| Name | Possible values | Description | Default
|
|
182
185
|
|:---|---|---|---|
|
|
@@ -185,6 +188,7 @@ The above will use the mentioned Rack middlewares to:
|
|
|
185
188
|
| `response_validation:` | `true`, `false` | If set to true it raises an exception if the response is invalid. This is useful during testing. | `false`
|
|
186
189
|
| `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`
|
|
187
190
|
| `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`
|
|
191
|
+
| `resolver:` | | Option to customize finding the [handler](#handler) method for an operation. See [OpenapiFirst::Responder](#OpenapiFirst::Responder) for details.
|
|
188
192
|
|
|
189
193
|
|
|
190
194
|
Handler functions (`find_pet`) are called with two arguments:
|
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.14.0)
|
|
5
5
|
deep_merge (>= 1.2.1)
|
|
6
6
|
hanami-router (~> 2.0.alpha3)
|
|
7
7
|
hanami-utils (~> 2.0.alpha1)
|
|
@@ -13,78 +13,75 @@ PATH
|
|
|
13
13
|
GEM
|
|
14
14
|
remote: https://rubygems.org/
|
|
15
15
|
specs:
|
|
16
|
-
activesupport (6.
|
|
16
|
+
activesupport (6.1.4)
|
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
|
-
i18n (>=
|
|
19
|
-
minitest (
|
|
20
|
-
tzinfo (~>
|
|
21
|
-
zeitwerk (~> 2.
|
|
22
|
-
addressable (2.
|
|
18
|
+
i18n (>= 1.6, < 2)
|
|
19
|
+
minitest (>= 5.1)
|
|
20
|
+
tzinfo (~> 2.0)
|
|
21
|
+
zeitwerk (~> 2.3)
|
|
22
|
+
addressable (2.8.0)
|
|
23
23
|
public_suffix (>= 2.0.2, < 5.0)
|
|
24
|
-
benchmark-ips (2.
|
|
24
|
+
benchmark-ips (2.9.1)
|
|
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.4.0)
|
|
29
29
|
json_schema (~> 0.14, >= 0.14.3)
|
|
30
|
-
openapi_parser (>= 0.11.1)
|
|
30
|
+
openapi_parser (>= 0.11.1, < 1.0)
|
|
31
31
|
rack (>= 1.5)
|
|
32
|
-
concurrent-ruby (1.1.
|
|
32
|
+
concurrent-ruby (1.1.9)
|
|
33
33
|
deep_merge (1.2.1)
|
|
34
|
-
dry-configurable (0.
|
|
34
|
+
dry-configurable (0.12.1)
|
|
35
35
|
concurrent-ruby (~> 1.0)
|
|
36
|
-
dry-core (~> 0.
|
|
37
|
-
|
|
38
|
-
dry-container (0.7.2)
|
|
36
|
+
dry-core (~> 0.5, >= 0.5.0)
|
|
37
|
+
dry-container (0.8.0)
|
|
39
38
|
concurrent-ruby (~> 1.0)
|
|
40
39
|
dry-configurable (~> 0.1, >= 0.1.3)
|
|
41
|
-
dry-core (0.
|
|
40
|
+
dry-core (0.7.1)
|
|
42
41
|
concurrent-ruby (~> 1.0)
|
|
43
|
-
dry-
|
|
44
|
-
dry-
|
|
45
|
-
dry-logic (1.0.8)
|
|
42
|
+
dry-inflector (0.2.1)
|
|
43
|
+
dry-logic (1.2.0)
|
|
46
44
|
concurrent-ruby (~> 1.0)
|
|
47
|
-
dry-core (~> 0.
|
|
48
|
-
|
|
49
|
-
dry-types (1.
|
|
45
|
+
dry-core (~> 0.5, >= 0.5)
|
|
46
|
+
dry-transformer (0.1.1)
|
|
47
|
+
dry-types (1.5.1)
|
|
50
48
|
concurrent-ruby (~> 1.0)
|
|
51
49
|
dry-container (~> 0.3)
|
|
52
|
-
dry-core (~> 0.
|
|
53
|
-
dry-equalizer (~> 0.3)
|
|
50
|
+
dry-core (~> 0.5, >= 0.5)
|
|
54
51
|
dry-inflector (~> 0.1, >= 0.1.2)
|
|
55
52
|
dry-logic (~> 1.0, >= 1.0.2)
|
|
56
|
-
ecma-re-validator (0.
|
|
57
|
-
regexp_parser (~>
|
|
58
|
-
grape (1.5.
|
|
53
|
+
ecma-re-validator (0.3.0)
|
|
54
|
+
regexp_parser (~> 2.0)
|
|
55
|
+
grape (1.5.3)
|
|
59
56
|
activesupport
|
|
60
57
|
builder
|
|
61
58
|
dry-types (>= 1.1)
|
|
62
59
|
mustermann-grape (~> 1.0.0)
|
|
63
60
|
rack (>= 1.3.0)
|
|
64
61
|
rack-accept
|
|
65
|
-
hana (1.3.
|
|
66
|
-
hanami-api (0.
|
|
62
|
+
hana (1.3.7)
|
|
63
|
+
hanami-api (0.2.0)
|
|
67
64
|
hanami-router (~> 2.0.alpha)
|
|
68
|
-
hanami-router (2.0.0.
|
|
65
|
+
hanami-router (2.0.0.alpha5)
|
|
69
66
|
mustermann (~> 1.0)
|
|
70
67
|
mustermann-contrib (~> 1.0)
|
|
71
68
|
rack (~> 2.0)
|
|
72
|
-
hanami-utils (2.0.0.
|
|
69
|
+
hanami-utils (2.0.0.alpha2)
|
|
73
70
|
concurrent-ruby (~> 1.0)
|
|
74
|
-
|
|
71
|
+
dry-transformer (~> 0.1)
|
|
75
72
|
hansi (0.2.0)
|
|
76
73
|
hash-deep-merge (0.1.1)
|
|
77
|
-
i18n (1.8.
|
|
74
|
+
i18n (1.8.10)
|
|
78
75
|
concurrent-ruby (~> 1.0)
|
|
79
|
-
json_schema (0.
|
|
80
|
-
json_schemer (0.2.
|
|
81
|
-
ecma-re-validator (~> 0.
|
|
76
|
+
json_schema (0.21.0)
|
|
77
|
+
json_schemer (0.2.18)
|
|
78
|
+
ecma-re-validator (~> 0.3)
|
|
82
79
|
hana (~> 1.3)
|
|
83
|
-
regexp_parser (~>
|
|
80
|
+
regexp_parser (~> 2.0)
|
|
84
81
|
uri_template (~> 0.7)
|
|
85
82
|
memory_profiler (0.9.14)
|
|
86
|
-
mini_portile2 (2.
|
|
87
|
-
minitest (5.14.
|
|
83
|
+
mini_portile2 (2.6.1)
|
|
84
|
+
minitest (5.14.4)
|
|
88
85
|
multi_json (1.15.0)
|
|
89
86
|
mustermann (1.1.1)
|
|
90
87
|
ruby2_keywords (~> 0.0.1)
|
|
@@ -93,9 +90,10 @@ GEM
|
|
|
93
90
|
mustermann (= 1.1.1)
|
|
94
91
|
mustermann-grape (1.0.1)
|
|
95
92
|
mustermann (>= 1.0.0)
|
|
96
|
-
nokogiri (1.
|
|
97
|
-
mini_portile2 (~> 2.
|
|
98
|
-
|
|
93
|
+
nokogiri (1.12.2)
|
|
94
|
+
mini_portile2 (~> 2.6.1)
|
|
95
|
+
racc (~> 1.4)
|
|
96
|
+
oas_parser (0.25.4)
|
|
99
97
|
activesupport (>= 4.0.0)
|
|
100
98
|
addressable (~> 2.3)
|
|
101
99
|
builder (~> 3.2.3)
|
|
@@ -103,31 +101,30 @@ GEM
|
|
|
103
101
|
hash-deep-merge
|
|
104
102
|
mustermann-contrib (~> 1.1.1)
|
|
105
103
|
nokogiri
|
|
106
|
-
openapi_parser (0.
|
|
104
|
+
openapi_parser (0.14.1)
|
|
107
105
|
public_suffix (4.0.6)
|
|
106
|
+
racc (1.5.2)
|
|
108
107
|
rack (2.2.3)
|
|
109
108
|
rack-accept (0.4.5)
|
|
110
109
|
rack (>= 0.4)
|
|
111
110
|
rack-protection (2.1.0)
|
|
112
111
|
rack
|
|
113
|
-
regexp_parser (1.
|
|
114
|
-
ruby2_keywords (0.0.
|
|
112
|
+
regexp_parser (2.1.1)
|
|
113
|
+
ruby2_keywords (0.0.5)
|
|
115
114
|
seg (1.2.0)
|
|
116
115
|
sinatra (2.1.0)
|
|
117
116
|
mustermann (~> 1.0)
|
|
118
117
|
rack (~> 2.2)
|
|
119
118
|
rack-protection (= 2.1.0)
|
|
120
119
|
tilt (~> 2.0)
|
|
121
|
-
syro (3.2.
|
|
120
|
+
syro (3.2.1)
|
|
122
121
|
rack (>= 1.6.0)
|
|
123
122
|
seg
|
|
124
|
-
thread_safe (0.3.6)
|
|
125
123
|
tilt (2.0.10)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
thread_safe (~> 0.1)
|
|
124
|
+
tzinfo (2.0.4)
|
|
125
|
+
concurrent-ruby (~> 1.0)
|
|
129
126
|
uri_template (0.7.0)
|
|
130
|
-
zeitwerk (2.4.
|
|
127
|
+
zeitwerk (2.4.2)
|
|
131
128
|
|
|
132
129
|
PLATFORMS
|
|
133
130
|
ruby
|
|
@@ -145,4 +142,4 @@ DEPENDENCIES
|
|
|
145
142
|
syro
|
|
146
143
|
|
|
147
144
|
BUNDLED WITH
|
|
148
|
-
2.
|
|
145
|
+
2.2.3
|
|
@@ -19,6 +19,8 @@ app = Class.new(Hanami::API) do
|
|
|
19
19
|
end
|
|
20
20
|
end.new
|
|
21
21
|
|
|
22
|
-
use Committee::Middleware::RequestValidation,
|
|
22
|
+
use Committee::Middleware::RequestValidation,
|
|
23
|
+
schema_path: './apps/openapi.yaml',
|
|
24
|
+
parse_response_by_content_type: true
|
|
23
25
|
|
|
24
26
|
run app
|
data/benchmarks/benchmarks.rb
CHANGED
|
@@ -19,28 +19,25 @@ apps = Dir['./apps/*.ru'].each_with_object({}) do |config, hash|
|
|
|
19
19
|
end
|
|
20
20
|
apps.freeze
|
|
21
21
|
|
|
22
|
+
bench = lambda do |app|
|
|
23
|
+
examples.each do |example|
|
|
24
|
+
env, expected_status = example
|
|
25
|
+
100.times { app.call(env) }
|
|
26
|
+
response = app.call(env)
|
|
27
|
+
raise unless response[0] == expected_status
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
22
31
|
Benchmark.ips do |x|
|
|
23
32
|
apps.each do |config, app|
|
|
24
|
-
x.report(config)
|
|
25
|
-
examples.each do |example|
|
|
26
|
-
env, expected_status = example
|
|
27
|
-
response = app.call(env)
|
|
28
|
-
raise unless response[0] == expected_status
|
|
29
|
-
end
|
|
30
|
-
end
|
|
33
|
+
x.report(config) { bench.call(app) }
|
|
31
34
|
end
|
|
32
35
|
x.compare!
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
Benchmark.memory do |x|
|
|
36
39
|
apps.each do |config, app|
|
|
37
|
-
x.report(config)
|
|
38
|
-
examples.each do |example|
|
|
39
|
-
env, expected_status = example
|
|
40
|
-
response = app.call(env)
|
|
41
|
-
raise unless response[0] == expected_status
|
|
42
|
-
end
|
|
43
|
-
end
|
|
40
|
+
x.report(config) { bench.call(app) }
|
|
44
41
|
end
|
|
45
42
|
x.compare!
|
|
46
43
|
end
|
data/lib/openapi_first.rb
CHANGED
|
@@ -25,10 +25,9 @@ module OpenapiFirst
|
|
|
25
25
|
|
|
26
26
|
def self.load(spec_path, only: nil)
|
|
27
27
|
content = YAML.load_file(spec_path)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Definition.new(parsed)
|
|
28
|
+
resolved = OasParser::Parser.new(spec_path, content).resolve
|
|
29
|
+
resolved['paths'].filter!(&->(key, _) { only.call(key) }) if only
|
|
30
|
+
Definition.new(resolved, spec_path)
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
def self.app(
|
|
@@ -78,11 +77,17 @@ module OpenapiFirst
|
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
class Error < StandardError; end
|
|
80
|
+
|
|
81
81
|
class NotFoundError < Error; end
|
|
82
|
+
|
|
82
83
|
class NotImplementedError < RuntimeError; end
|
|
84
|
+
|
|
83
85
|
class ResponseInvalid < Error; end
|
|
86
|
+
|
|
84
87
|
class ResponseCodeNotFoundError < ResponseInvalid; end
|
|
88
|
+
|
|
85
89
|
class ResponseContentTypeNotFoundError < ResponseInvalid; end
|
|
90
|
+
|
|
86
91
|
class ResponseBodyInvalidError < ResponseInvalid; end
|
|
87
92
|
|
|
88
93
|
class RequestInvalidError < Error
|
data/lib/openapi_first/app.rb
CHANGED
|
@@ -11,17 +11,15 @@ module OpenapiFirst
|
|
|
11
11
|
namespace:,
|
|
12
12
|
router_raise_error: false,
|
|
13
13
|
request_validation_raise_error: false,
|
|
14
|
-
response_validation: false
|
|
14
|
+
response_validation: false,
|
|
15
|
+
resolver: nil
|
|
15
16
|
)
|
|
16
17
|
@stack = Rack::Builder.app do
|
|
17
18
|
freeze_app
|
|
18
19
|
use OpenapiFirst::Router, spec: spec, raise_error: router_raise_error, parent_app: parent_app
|
|
19
20
|
use OpenapiFirst::RequestValidation, raise_error: request_validation_raise_error
|
|
20
21
|
use OpenapiFirst::ResponseValidation if response_validation
|
|
21
|
-
run OpenapiFirst::Responder.new(
|
|
22
|
-
spec: spec,
|
|
23
|
-
namespace: namespace
|
|
24
|
-
)
|
|
22
|
+
run OpenapiFirst::Responder.new(namespace: namespace, resolver: resolver)
|
|
25
23
|
end
|
|
26
24
|
end
|
|
27
25
|
|
|
@@ -3,20 +3,14 @@
|
|
|
3
3
|
require_relative 'utils'
|
|
4
4
|
|
|
5
5
|
module OpenapiFirst
|
|
6
|
-
class
|
|
7
|
-
def initialize(
|
|
6
|
+
class DefaultOperationResolver
|
|
7
|
+
def initialize(namespace)
|
|
8
8
|
@namespace = namespace
|
|
9
|
-
@handlers =
|
|
10
|
-
operation_id = operation.operation_id
|
|
11
|
-
handler = find_handler(operation_id)
|
|
12
|
-
next if handler.nil?
|
|
13
|
-
|
|
14
|
-
hash[operation_id] = handler
|
|
15
|
-
end
|
|
9
|
+
@handlers = {}
|
|
16
10
|
end
|
|
17
11
|
|
|
18
|
-
def
|
|
19
|
-
@handlers[
|
|
12
|
+
def call(operation)
|
|
13
|
+
@handlers[operation.name] ||= find_handler(operation['x-handler'] || operation['operationId'])
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
def find_handler(operation_id)
|
|
@@ -6,9 +6,14 @@ module OpenapiFirst
|
|
|
6
6
|
class Definition
|
|
7
7
|
attr_reader :filepath, :operations
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@filepath =
|
|
11
|
-
|
|
9
|
+
def initialize(resolved, filepath)
|
|
10
|
+
@filepath = filepath
|
|
11
|
+
methods = %w[get head post put patch delete trace options]
|
|
12
|
+
@operations = resolved['paths'].flat_map do |path, path_item|
|
|
13
|
+
path_item.slice(*methods).map do |request_method, _operation_object|
|
|
14
|
+
Operation.new(path, request_method, path_item)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
end
|
|
14
19
|
end
|
|
@@ -7,23 +7,25 @@ require_relative 'utils'
|
|
|
7
7
|
require_relative 'response_object'
|
|
8
8
|
|
|
9
9
|
module OpenapiFirst
|
|
10
|
-
class Operation
|
|
10
|
+
class Operation # rubocop:disable Metrics/ClassLength
|
|
11
11
|
extend Forwardable
|
|
12
|
-
def_delegators
|
|
13
|
-
:
|
|
14
|
-
:
|
|
15
|
-
:request_body,
|
|
16
|
-
:operation_id
|
|
12
|
+
def_delegators :operation_object,
|
|
13
|
+
:[],
|
|
14
|
+
:dig
|
|
17
15
|
|
|
18
16
|
WRITE_METHODS = Set.new(%w[post put patch delete]).freeze
|
|
19
17
|
private_constant :WRITE_METHODS
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
attr_reader :path, :method
|
|
20
|
+
|
|
21
|
+
def initialize(path, request_method, path_item_object)
|
|
22
|
+
@path = path
|
|
23
|
+
@method = request_method
|
|
24
|
+
@path_item_object = path_item_object
|
|
23
25
|
end
|
|
24
26
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
+
def operation_id
|
|
28
|
+
operation_object['operationId']
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
def read?
|
|
@@ -34,6 +36,10 @@ module OpenapiFirst
|
|
|
34
36
|
WRITE_METHODS.include?(method)
|
|
35
37
|
end
|
|
36
38
|
|
|
39
|
+
def request_body
|
|
40
|
+
operation_object['requestBody']
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
def parameters_schema
|
|
38
44
|
@parameters_schema ||= begin
|
|
39
45
|
parameters_json_schema = build_parameters_json_schema
|
|
@@ -53,6 +59,7 @@ module OpenapiFirst
|
|
|
53
59
|
raise ResponseInvalid, "Response has no content-type for '#{name}'" unless content_type
|
|
54
60
|
|
|
55
61
|
media_type = find_content_for_content_type(content, content_type)
|
|
62
|
+
|
|
56
63
|
unless media_type
|
|
57
64
|
message = "Response content type not found '#{content_type}' for '#{name}'"
|
|
58
65
|
raise ResponseContentTypeNotFoundError, message
|
|
@@ -62,7 +69,7 @@ module OpenapiFirst
|
|
|
62
69
|
end
|
|
63
70
|
|
|
64
71
|
def request_body_schema(request_content_type)
|
|
65
|
-
content =
|
|
72
|
+
content = operation_object.dig('requestBody', 'content')
|
|
66
73
|
media_type = find_content_for_content_type(content, request_content_type)
|
|
67
74
|
schema = media_type&.fetch('schema', nil)
|
|
68
75
|
return unless schema
|
|
@@ -71,8 +78,9 @@ module OpenapiFirst
|
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
def response_for(status)
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
response_content = response_by_code(status)
|
|
82
|
+
return response_content if response_content
|
|
83
|
+
|
|
76
84
|
message = "Response status code or default not found: #{status} for '#{name}'"
|
|
77
85
|
raise OpenapiFirst::ResponseCodeNotFoundError, message
|
|
78
86
|
end
|
|
@@ -83,6 +91,15 @@ module OpenapiFirst
|
|
|
83
91
|
|
|
84
92
|
private
|
|
85
93
|
|
|
94
|
+
def response_by_code(status)
|
|
95
|
+
operation_object.dig('responses', status.to_s) ||
|
|
96
|
+
operation_object.dig('responses', 'default')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def operation_object
|
|
100
|
+
@path_item_object[method]
|
|
101
|
+
end
|
|
102
|
+
|
|
86
103
|
def find_content_for_content_type(content, request_content_type)
|
|
87
104
|
content.fetch(request_content_type) do |_|
|
|
88
105
|
type = request_content_type.split(';')[0]
|
|
@@ -91,24 +108,32 @@ module OpenapiFirst
|
|
|
91
108
|
end
|
|
92
109
|
|
|
93
110
|
def build_parameters_json_schema
|
|
94
|
-
|
|
111
|
+
parameters = all_parameters
|
|
112
|
+
return unless parameters&.any?
|
|
95
113
|
|
|
96
|
-
|
|
97
|
-
params = Rack::Utils.parse_nested_query(parameter
|
|
114
|
+
parameters.each_with_object(new_node) do |parameter, schema|
|
|
115
|
+
params = Rack::Utils.parse_nested_query(parameter['name'])
|
|
98
116
|
generate_schema(schema, params, parameter)
|
|
99
117
|
end
|
|
100
118
|
end
|
|
101
119
|
|
|
120
|
+
def all_parameters
|
|
121
|
+
parameters = @path_item_object['parameters'] || []
|
|
122
|
+
parameters_on_operation = operation_object['parameters']
|
|
123
|
+
parameters.concat(parameters_on_operation) if parameters_on_operation
|
|
124
|
+
parameters
|
|
125
|
+
end
|
|
126
|
+
|
|
102
127
|
def generate_schema(schema, params, parameter)
|
|
103
128
|
required = Set.new(schema['required'])
|
|
104
129
|
params.each do |key, value|
|
|
105
|
-
required << key if parameter
|
|
130
|
+
required << key if parameter['required']
|
|
106
131
|
if value.is_a? Hash
|
|
107
132
|
property_schema = new_node
|
|
108
133
|
generate_schema(property_schema, value, parameter)
|
|
109
134
|
Utils.deep_merge!(schema['properties'], { key => property_schema })
|
|
110
135
|
else
|
|
111
|
-
schema['properties'][key] = parameter
|
|
136
|
+
schema['properties'][key] = parameter['schema']
|
|
112
137
|
end
|
|
113
138
|
end
|
|
114
139
|
schema['required'] = required.to_a
|
|
@@ -63,13 +63,13 @@ module OpenapiFirst
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def validate_request_content_type!(content_type, operation)
|
|
66
|
-
return if operation.request_body.content
|
|
66
|
+
return if operation.request_body.dig('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
|
-
return unless operation.request_body
|
|
72
|
+
return unless operation.request_body['required'] && body.empty?
|
|
73
73
|
|
|
74
74
|
halt_with_error(415, 'Request body is required')
|
|
75
75
|
end
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rack'
|
|
4
4
|
require_relative 'inbox'
|
|
5
|
-
require_relative '
|
|
5
|
+
require_relative 'default_operation_resolver'
|
|
6
6
|
|
|
7
7
|
module OpenapiFirst
|
|
8
8
|
class Responder
|
|
9
|
-
def initialize(
|
|
10
|
-
@resolver = resolver
|
|
9
|
+
def initialize(namespace: nil, resolver: nil)
|
|
10
|
+
@resolver = resolver || DefaultOperationResolver.new(namespace)
|
|
11
11
|
@namespace = namespace
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -24,7 +24,7 @@ module OpenapiFirst
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
26
|
def find_handler(operation)
|
|
27
|
-
handler = @resolver
|
|
27
|
+
handler = @resolver.call(operation)
|
|
28
28
|
raise NotImplementedError, "Could not find handler for #{operation.name}" unless handler
|
|
29
29
|
|
|
30
30
|
handler
|
data/lib/openapi_first/router.rb
CHANGED
|
@@ -40,7 +40,10 @@ module OpenapiFirst
|
|
|
40
40
|
|
|
41
41
|
def raise_error(env)
|
|
42
42
|
req = Rack::Request.new(env)
|
|
43
|
-
msg =
|
|
43
|
+
msg =
|
|
44
|
+
"Could not find definition for #{req.request_method} '#{
|
|
45
|
+
req.path
|
|
46
|
+
}' in API description #{@filepath}"
|
|
44
47
|
raise NotFoundError, msg
|
|
45
48
|
end
|
|
46
49
|
|
|
@@ -54,7 +57,7 @@ module OpenapiFirst
|
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
def build_router(operations) # rubocop:disable Metrics/AbcSize
|
|
57
|
-
router = Hanami::Router.new
|
|
60
|
+
router = Hanami::Router.new
|
|
58
61
|
operations.each do |operation|
|
|
59
62
|
normalized_path = operation.path.gsub('{', ':').gsub('}', '')
|
|
60
63
|
if operation.operation_id.nil?
|
|
@@ -19,6 +19,11 @@ module OpenapiFirst
|
|
|
19
19
|
title: "has not a valid #{error.dig('schema', 'format')} format",
|
|
20
20
|
detail: "#{error['data'].inspect} is not a valid #{error.dig('schema', 'format')} format"
|
|
21
21
|
}
|
|
22
|
+
elsif error['type'] == 'enum'
|
|
23
|
+
{
|
|
24
|
+
title: "value #{error['data'].inspect} is not defined in enum",
|
|
25
|
+
detail: "value can be one of #{error.dig('schema', 'enum')&.join(', ')}"
|
|
26
|
+
}
|
|
22
27
|
elsif error['type'] == 'required'
|
|
23
28
|
missing_keys = error['details']['missing_keys']
|
|
24
29
|
{
|
data/openapi_first.gemspec
CHANGED
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.metadata['changelog_uri'] = 'https://github.com/ahx/openapi_first/blob/master/CHANGELOG.md'
|
|
21
21
|
else
|
|
22
22
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
23
|
-
|
|
23
|
+
'public gem pushes.'
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
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.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Haller
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: deep_merge
|
|
@@ -203,8 +203,8 @@ files:
|
|
|
203
203
|
- lib/openapi_first.rb
|
|
204
204
|
- lib/openapi_first/app.rb
|
|
205
205
|
- lib/openapi_first/coverage.rb
|
|
206
|
+
- lib/openapi_first/default_operation_resolver.rb
|
|
206
207
|
- lib/openapi_first/definition.rb
|
|
207
|
-
- lib/openapi_first/find_handler.rb
|
|
208
208
|
- lib/openapi_first/inbox.rb
|
|
209
209
|
- lib/openapi_first/operation.rb
|
|
210
210
|
- lib/openapi_first/request_validation.rb
|
|
@@ -242,7 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
242
242
|
- !ruby/object:Gem::Version
|
|
243
243
|
version: '0'
|
|
244
244
|
requirements: []
|
|
245
|
-
rubygems_version: 3.
|
|
245
|
+
rubygems_version: 3.2.3
|
|
246
246
|
signing_key:
|
|
247
247
|
specification_version: 4
|
|
248
248
|
summary: Implement REST APIs based on OpenApi.
|