openapi_first 0.6.1 → 0.6.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/CHANGELOG.md +14 -3
- data/Gemfile.lock +21 -17
- data/README.md +63 -106
- data/benchmarks/Gemfile +13 -0
- data/benchmarks/Gemfile.lock +136 -0
- data/benchmarks/apps/committee.ru +30 -0
- data/benchmarks/apps/grape.ru +21 -0
- data/benchmarks/apps/hanami_router.ru +14 -0
- data/benchmarks/apps/openapi.yaml +86 -0
- data/benchmarks/apps/openapi_first.ru +22 -0
- data/benchmarks/apps/openapi_first_request_validation_only.ru +31 -0
- data/benchmarks/apps/openapi_first_resolve_only.ru +23 -0
- data/benchmarks/apps/sinatra.ru +26 -0
- data/benchmarks/apps/syro.ru +25 -0
- data/benchmarks/benchmarks.rb +46 -0
- data/lib/openapi_first.rb +8 -2
- data/lib/openapi_first/error_response_method.rb +1 -1
- data/lib/openapi_first/operation_resolver.rb +21 -13
- data/lib/openapi_first/query_parameter_schemas.rb +29 -0
- data/lib/openapi_first/query_parameter_validation.rb +7 -16
- data/lib/openapi_first/router.rb +3 -1
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +1 -1
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c39942d9c5860a92b211d51852ad5633dd573f2a244d85631d2c2c4990e607c
|
4
|
+
data.tar.gz: 8bbfd7a2cdf632338117d689dd770561d5f4a903223800c9a6829e556ab1c26a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58ab052d0d464da3f37224c943a9da8c990eb6f75b13306b223d1a6d4e36f55588df3e5f2106ca2659a44a048604cd011da50d6b11417efb8b45f05762ab23e4
|
7
|
+
data.tar.gz: 53ba1d5a794cd1561e812c37cc426e6d282bbfd6c7bb5cce547be7eda1d90c342d4b6cdfc490e1b0765b43ba7fba126abea8ae413c0744c444c2c2aae725b2aa
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,21 @@
|
|
1
|
-
#
|
1
|
+
# Changelog
|
2
2
|
|
3
|
-
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
|
6
|
+
## 0.6.3
|
7
|
+
|
8
|
+
- Add option to parse only certain paths from OAS file
|
9
|
+
|
10
|
+
## 0.6.2
|
11
|
+
|
12
|
+
- Add support to map operationIds like `things#index` or `web.things_index`
|
13
|
+
|
14
|
+
## 0.6.1
|
4
15
|
|
5
16
|
- Make ResponseValidator errors easier to read
|
6
17
|
|
7
|
-
|
18
|
+
## 0.6.0
|
8
19
|
|
9
20
|
- Set the content-type based on the OpenAPI description [#29](https://github.com/ahx/openapi-first/pull/29)
|
10
21
|
- Add CHANGELOG 📝
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
openapi_first (0.6.
|
4
|
+
openapi_first (0.6.3)
|
5
5
|
json_schemer (~> 0.2)
|
6
6
|
multi_json (~> 1.13)
|
7
7
|
oas_parser (~> 0.19)
|
@@ -10,13 +10,14 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
activesupport (
|
13
|
+
activesupport (6.0.0)
|
14
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
15
|
i18n (>= 0.7, < 2)
|
16
16
|
minitest (~> 5.1)
|
17
17
|
tzinfo (~> 1.1)
|
18
|
-
|
19
|
-
|
18
|
+
zeitwerk (~> 2.1, >= 2.1.8)
|
19
|
+
addressable (2.7.0)
|
20
|
+
public_suffix (>= 2.0.2, < 5.0)
|
20
21
|
ast (2.4.0)
|
21
22
|
builder (3.2.3)
|
22
23
|
coderay (1.1.2)
|
@@ -27,29 +28,31 @@ GEM
|
|
27
28
|
regexp_parser (~> 1.2)
|
28
29
|
hana (1.3.5)
|
29
30
|
hansi (0.2.0)
|
30
|
-
|
31
|
+
hash-deep-merge (0.1.1)
|
32
|
+
i18n (1.7.0)
|
31
33
|
concurrent-ruby (~> 1.0)
|
32
34
|
jaro_winkler (1.5.3)
|
33
|
-
json_schemer (0.2.
|
34
|
-
ecma-re-validator (~> 0.2
|
35
|
-
hana (~> 1.3
|
36
|
-
regexp_parser (~> 1.
|
37
|
-
uri_template (~> 0.7
|
35
|
+
json_schemer (0.2.7)
|
36
|
+
ecma-re-validator (~> 0.2)
|
37
|
+
hana (~> 1.3)
|
38
|
+
regexp_parser (~> 1.5)
|
39
|
+
uri_template (~> 0.7)
|
38
40
|
method_source (0.9.2)
|
39
41
|
mini_portile2 (2.4.0)
|
40
|
-
minitest (5.
|
41
|
-
multi_json (1.
|
42
|
+
minitest (5.12.2)
|
43
|
+
multi_json (1.14.1)
|
42
44
|
mustermann (1.0.3)
|
43
45
|
mustermann-contrib (1.0.3)
|
44
46
|
hansi (~> 0.2.0)
|
45
47
|
mustermann (= 1.0.3)
|
46
|
-
nokogiri (1.10.
|
48
|
+
nokogiri (1.10.4)
|
47
49
|
mini_portile2 (~> 2.4.0)
|
48
|
-
oas_parser (0.
|
50
|
+
oas_parser (0.22.2)
|
49
51
|
activesupport (>= 4.0.0)
|
50
52
|
addressable (~> 2.3)
|
51
53
|
builder (~> 3.2.3)
|
52
54
|
deep_merge (~> 1.2.1)
|
55
|
+
hash-deep-merge
|
53
56
|
mustermann-contrib (~> 1.0.3s)
|
54
57
|
nokogiri
|
55
58
|
parallel (1.17.0)
|
@@ -58,13 +61,13 @@ GEM
|
|
58
61
|
pry (0.12.2)
|
59
62
|
coderay (~> 1.1.0)
|
60
63
|
method_source (~> 0.9.0)
|
61
|
-
public_suffix (
|
64
|
+
public_suffix (4.0.1)
|
62
65
|
rack (2.0.7)
|
63
66
|
rack-test (1.1.0)
|
64
67
|
rack (>= 1.0, < 3)
|
65
68
|
rainbow (3.0.0)
|
66
69
|
rake (10.5.0)
|
67
|
-
regexp_parser (1.
|
70
|
+
regexp_parser (1.6.0)
|
68
71
|
rspec (3.8.0)
|
69
72
|
rspec-core (~> 3.8.0)
|
70
73
|
rspec-expectations (~> 3.8.0)
|
@@ -91,6 +94,7 @@ GEM
|
|
91
94
|
thread_safe (~> 0.1)
|
92
95
|
unicode-display_width (1.6.0)
|
93
96
|
uri_template (0.7.0)
|
97
|
+
zeitwerk (2.2.0)
|
94
98
|
|
95
99
|
PLATFORMS
|
96
100
|
ruby
|
@@ -105,4 +109,4 @@ DEPENDENCIES
|
|
105
109
|
rubocop
|
106
110
|
|
107
111
|
BUNDLED WITH
|
108
|
-
2.0.
|
112
|
+
2.0.2
|
data/README.md
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
# OpenapiFirst
|
2
2
|
|
3
|
-
OpenapiFirst helps to implement HTTP APIs based on an [OpenApi](https://www.openapis.org/) API description. The idea is that you create an API description first, then add minimal code about your business logic (some call this "
|
3
|
+
OpenapiFirst helps to implement HTTP APIs based on an [OpenApi](https://www.openapis.org/) API description. The idea is that you create an API description first, then add minimal code about your business logic (some call this "handler") and be done.
|
4
4
|
|
5
5
|
## TL;DR
|
6
6
|
|
7
|
+
Start with writing an OpenAPI file that describes the API, which you are about to write. Use a [validator](http://speccy.io/) to make sure the file is valid.
|
8
|
+
|
9
|
+
We recommend saving the file as `openapi/openapi.yaml`.
|
10
|
+
|
11
|
+
Now implement your API:
|
12
|
+
|
7
13
|
```ruby
|
8
14
|
module Pets
|
9
15
|
def self.find_pet(params, res)
|
@@ -21,7 +27,7 @@ run OpenapiFirst.app('./openapi/openapi.yaml', namespace: Pets)
|
|
21
27
|
|
22
28
|
The above will:
|
23
29
|
|
24
|
-
- Validate the request and respond with 400 if the request does not match
|
30
|
+
- Validate the request and respond with 400 if the request does not match with your API description
|
25
31
|
- Map the request to a method call `Pets.find_pet` based on the `operationId` in the API description
|
26
32
|
- Set the response content type according to your spec (here with the default status code `200`)
|
27
33
|
|
@@ -31,6 +37,17 @@ Resolver functions (`find_pet`) are called with two arguments:
|
|
31
37
|
- `res` - Holds a Rack::Response that you can modify if needed
|
32
38
|
If you want to access to plain Rack env you can call `params.env`.
|
33
39
|
|
40
|
+
You can also use the provided Rack middlewares to auto-implement only certain aspects of the request-response flow like query parameter or request body parameter validation based on your OpenAPI file. Read on to learn how.
|
41
|
+
|
42
|
+
### Handling only certain paths
|
43
|
+
|
44
|
+
You can filter the URIs that should be handled by pass ing `only` to `OpenapiFirst.load`:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
spec = OpenapiFirst.load './openapi/openapi.yaml', only: '/pets'.method(:==)
|
48
|
+
run OpenapiFirst.app(spec, namespace: Pets)
|
49
|
+
```
|
50
|
+
|
34
51
|
### Usage as Rack middleware
|
35
52
|
|
36
53
|
```ruby
|
@@ -45,15 +62,6 @@ When using the middleware, all requests that are not part of the API description
|
|
45
62
|
|
46
63
|
See [example](examples)
|
47
64
|
|
48
|
-
## Missing features
|
49
|
-
|
50
|
-
See [issues](https://github.com/ahx/openapi_first/issues).
|
51
|
-
|
52
|
-
## Start
|
53
|
-
|
54
|
-
Start with writing an OpenAPI file that describes the API, which you are about to write. Use a [validator](http://speccy.io/) to make sure the file is valid.
|
55
|
-
|
56
|
-
We recommend saving the file as `openapi/openapi.yaml`.
|
57
65
|
|
58
66
|
## Installation
|
59
67
|
|
@@ -65,76 +73,15 @@ gem 'openapi_first'
|
|
65
73
|
|
66
74
|
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
67
75
|
|
68
|
-
## Testing
|
69
|
-
|
70
|
-
OpenapiFirst offers tools to help testing your app against your API description.
|
71
|
-
|
72
|
-
### Response validation
|
73
|
-
|
74
|
-
Response validation is to make sure your app responds as described in your API description. You usually do this in your tests using [rack-test](https://github.com/rack-test/rack-test).
|
75
|
-
|
76
|
-
```ruby
|
77
|
-
# In your test:
|
78
|
-
require 'openapi_first/response_validator'
|
79
|
-
spec = OpenapiFirst.load('petstore.yaml')
|
80
|
-
validator = OpenapiFirst::ResponseValidator.new(spec)
|
81
|
-
validator.validate(last_request, last_response).errors? # => true or false
|
82
|
-
```
|
83
|
-
|
84
|
-
TODO: Add RSpec matcher (via extra rubygem)
|
85
|
-
|
86
|
-
### Coverage
|
87
|
-
|
88
|
-
(This is a bit experimental. Please try it out and give feedback.)
|
89
|
-
|
90
|
-
`OpenapiFirst::Coverage` helps you make sure, that you have called all endpoints of your OAS file when running tests via `rack-test`.
|
91
|
-
|
92
|
-
```ruby
|
93
|
-
# In your test (rspec example):
|
94
|
-
require 'openapi_first/coverage'
|
95
|
-
|
96
|
-
describe MyApp do
|
97
|
-
include Rack::Test::Methods
|
98
|
-
|
99
|
-
before(:all) do
|
100
|
-
spec = OpenapiFirst.load('petstore.yaml')
|
101
|
-
@app_wrapper = OpenapiFirst::Coverage.new(MyApp, spec)
|
102
|
-
end
|
103
|
-
|
104
|
-
after(:all) do
|
105
|
-
message = "The following paths have not been called yet: #{@app_wrapper.to_be_called}"
|
106
|
-
expect(@app_wrapper.to_be_called).to be_empty
|
107
|
-
end
|
108
|
-
|
109
|
-
# Overwrite `#app` to make rack-test call the wrapped app
|
110
|
-
def app
|
111
|
-
@app_wrapper
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'does things' do
|
115
|
-
get '/i/my/stuff'
|
116
|
-
# …
|
117
|
-
end
|
118
|
-
end
|
119
|
-
```
|
120
|
-
|
121
|
-
## Mocking
|
122
|
-
|
123
|
-
Mocking is currently out of scope. Try https://github.com/JustinFeng/fakeit or something else.
|
124
|
-
|
125
|
-
## Alternatives
|
126
|
-
|
127
|
-
This gem is inspired by [committee](https://github.com/interagent/committee), which has much more features like response stubs or support for Hyper-Schema or OpenAPI 2.
|
128
|
-
|
129
76
|
## How it works
|
130
77
|
|
131
|
-
OpenapiFirst offers Rack middlewares to auto-implement different aspects
|
78
|
+
OpenapiFirst offers Rack middlewares to auto-implement different aspects for request handling:
|
132
79
|
|
133
80
|
- Query parameter validation
|
134
81
|
- Request body validation
|
135
82
|
- Mapping request to a function call
|
136
83
|
|
137
|
-
It starts
|
84
|
+
It starts by adding a router middleware:
|
138
85
|
|
139
86
|
```ruby
|
140
87
|
spec = OpenapiFirst.load('petstore.yaml')
|
@@ -163,7 +110,7 @@ content-type: "application/vnd.api+json"
|
|
163
110
|
}
|
164
111
|
```
|
165
112
|
|
166
|
-
|
113
|
+
## Query parameter validation
|
167
114
|
|
168
115
|
```ruby
|
169
116
|
use OpenapiFirst::QueryParameterValidation
|
@@ -181,11 +128,11 @@ If you want to forbid nested query parameters you will need to use `additionalPr
|
|
181
128
|
|
182
129
|
OpenapiFirst does not support parameters set to `explode: false` and treats nested query parameters (`filter[foo]=bar`) like [`style: deepObject`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#style-values).
|
183
130
|
|
184
|
-
|
131
|
+
## Header, Cookie, Path parameter validation
|
185
132
|
|
186
133
|
tbd.
|
187
134
|
|
188
|
-
|
135
|
+
## Request Body validation
|
189
136
|
|
190
137
|
```ruby
|
191
138
|
# Add the middleware:
|
@@ -197,14 +144,34 @@ This will add the parsed request body to `env[OpenapiFirst::REQUEST_BODY]`.
|
|
197
144
|
|
198
145
|
OpenAPI request (and response) body validation is based on [JSON Schema](http://json-schema.org/).
|
199
146
|
|
200
|
-
|
147
|
+
## Mapping the request to a method call
|
148
|
+
|
149
|
+
OpenapiFirst uses a `OperationResolver` middleware to map the HTTP request to a method call.
|
150
|
+
|
151
|
+
The resolver function is found via the [`operationId`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object) attribute in your API description like this:
|
152
|
+
|
153
|
+
- `create_pet` will map to `MyApi.create_pet(params, response)`
|
154
|
+
- `some_things.create` will map to `MyApi::SomeThings.create(params, response)`
|
155
|
+
- `pets#create` will map to `MyApi::Pets::Create.new.call(params, response)` (like [Hanami::Router](https://github.com/hanami/router#controllers))
|
156
|
+
|
157
|
+
These handler methods are called with two arguments:
|
201
158
|
|
202
|
-
|
159
|
+
- `params` - Holds the parsed request body, filtered query params and path parameters
|
160
|
+
- `res` - Holds a Rack::Response that you can modify if needed
|
161
|
+
|
162
|
+
You can call `params.env` to access the Rack env (just like in [Hanami actions](https://guides.hanamirb.org/actions/parameters/))
|
163
|
+
|
164
|
+
There are two ways to set the response body:
|
165
|
+
|
166
|
+
- Calling `res.write "things"` (see [Rack::Response](https://www.rubydoc.info/github/rack/rack/Rack/Response))
|
167
|
+
- Returning a value from the function (see example above) (this will always converted to JSON)
|
168
|
+
|
169
|
+
### Adding the middleware
|
203
170
|
|
204
171
|
```ruby
|
205
172
|
# Define some methods
|
206
173
|
module MyApi
|
207
|
-
def create_pet(params, res)
|
174
|
+
def self.create_pet(params, res)
|
208
175
|
res.status = 201
|
209
176
|
{
|
210
177
|
id: '1',
|
@@ -225,40 +192,20 @@ run OpenapiFirst::OperationResolver, namespace: Pets
|
|
225
192
|
# POST /pets, { name: 'Oscar' }
|
226
193
|
```
|
227
194
|
|
228
|
-
|
195
|
+
## Response validation
|
229
196
|
|
230
|
-
|
231
|
-
|
232
|
-
- `params` - Holds the parsed request body, filtered query params and path parameters
|
233
|
-
- `res` - Holds a Rack::Response that you can modify if needed
|
234
|
-
|
235
|
-
You can call `params.env` to access the Rack env (just like in [Hanami actions](https://guides.hanamirb.org/actions/parameters/))
|
236
|
-
|
237
|
-
There are two ways to set the response body:
|
238
|
-
|
239
|
-
- Calling `res.write "things"` (see [Rack::Response](https://www.rubydoc.info/github/rack/rack/Rack/Response))
|
240
|
-
- Returning a value from the function (see example above) (this will always converted to JSON)
|
241
|
-
|
242
|
-
## Testing
|
243
|
-
|
244
|
-
OpenapiFirst offers tools to help testing your app.
|
245
|
-
|
246
|
-
### Response validation
|
247
|
-
|
248
|
-
Response validation is to make sure your app responds as described in your OpenAPI spec. You usually do this in your tests using [rack-test](https://github.com/rack-test/rack-test).
|
197
|
+
Response validation is useful to make sure your app responds as described in your API description. You usually do this in your tests using [rack-test](https://github.com/rack-test/rack-test).
|
249
198
|
|
250
199
|
```ruby
|
251
|
-
# In your test:
|
252
|
-
require 'openapi_first
|
200
|
+
# In your test (rspec example):
|
201
|
+
require 'openapi_first'
|
253
202
|
spec = OpenapiFirst.load('petstore.yaml')
|
254
203
|
validator = OpenapiFirst::ResponseValidator.new(spec)
|
255
204
|
|
256
205
|
expect(validator.validate(last_request, last_response).errors).to be_empty
|
257
206
|
```
|
258
207
|
|
259
|
-
|
260
|
-
|
261
|
-
### Coverage
|
208
|
+
## Coverage
|
262
209
|
|
263
210
|
(This is a bit experimental. Please try it out and give feedback.)
|
264
211
|
|
@@ -307,6 +254,16 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
307
254
|
|
308
255
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
309
256
|
|
257
|
+
### Run benchmarks
|
258
|
+
|
259
|
+
```sh
|
260
|
+
cd benchmarks
|
261
|
+
bundle
|
262
|
+
bundle exec ruby benchmarks.rb
|
263
|
+
```
|
264
|
+
|
310
265
|
## Contributing
|
311
266
|
|
312
|
-
|
267
|
+
If you have a question or an idea or found a bug don't hesitate to [create an issue on GitHub](https://github.com/ahx/openapi_first/issues).
|
268
|
+
|
269
|
+
Pull requests are very welcome as well, of course. Feel free to create a "draft" pull request early on, even if your change is still work in progress. 🤗
|
data/benchmarks/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
gem 'benchmark-ips'
|
6
|
+
gem 'benchmark-memory'
|
7
|
+
gem 'committee'
|
8
|
+
gem 'grape'
|
9
|
+
gem 'hanami-router', '~> 2.0.0.alpha1'
|
10
|
+
gem 'multi_json'
|
11
|
+
gem 'openapi_first', path: '../'
|
12
|
+
gem 'sinatra'
|
13
|
+
gem 'syro'
|
@@ -0,0 +1,136 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
openapi_first (0.6.3)
|
5
|
+
json_schemer (~> 0.2)
|
6
|
+
multi_json (~> 1.13)
|
7
|
+
oas_parser (~> 0.19)
|
8
|
+
rack (~> 2)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activesupport (6.0.0)
|
14
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
|
+
i18n (>= 0.7, < 2)
|
16
|
+
minitest (~> 5.1)
|
17
|
+
tzinfo (~> 1.1)
|
18
|
+
zeitwerk (~> 2.1, >= 2.1.8)
|
19
|
+
addressable (2.7.0)
|
20
|
+
public_suffix (>= 2.0.2, < 5.0)
|
21
|
+
axiom-types (0.1.1)
|
22
|
+
descendants_tracker (~> 0.0.4)
|
23
|
+
ice_nine (~> 0.11.0)
|
24
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
25
|
+
benchmark-ips (2.7.2)
|
26
|
+
benchmark-memory (0.1.2)
|
27
|
+
memory_profiler (~> 0.9)
|
28
|
+
builder (3.2.3)
|
29
|
+
coercible (1.0.0)
|
30
|
+
descendants_tracker (~> 0.0.1)
|
31
|
+
committee (3.2.1)
|
32
|
+
json_schema (~> 0.14, >= 0.14.3)
|
33
|
+
openapi_parser (>= 0.6.1)
|
34
|
+
rack (>= 1.5)
|
35
|
+
concurrent-ruby (1.1.5)
|
36
|
+
deep_merge (1.2.1)
|
37
|
+
descendants_tracker (0.0.4)
|
38
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
39
|
+
dry-inflector (0.2.0)
|
40
|
+
ecma-re-validator (0.2.0)
|
41
|
+
regexp_parser (~> 1.2)
|
42
|
+
equalizer (0.0.11)
|
43
|
+
grape (1.2.4)
|
44
|
+
activesupport
|
45
|
+
builder
|
46
|
+
mustermann-grape (~> 1.0.0)
|
47
|
+
rack (>= 1.3.0)
|
48
|
+
rack-accept
|
49
|
+
virtus (>= 1.0.0)
|
50
|
+
hana (1.3.5)
|
51
|
+
hanami-router (2.0.0.alpha1)
|
52
|
+
dry-inflector (~> 0.1)
|
53
|
+
hanami-utils (~> 2.0.alpha)
|
54
|
+
mustermann (~> 1.0)
|
55
|
+
mustermann-contrib (~> 1.0)
|
56
|
+
rack (~> 2.0)
|
57
|
+
hanami-utils (2.0.0.alpha1)
|
58
|
+
concurrent-ruby (~> 1.0)
|
59
|
+
transproc (~> 1.0)
|
60
|
+
hansi (0.2.0)
|
61
|
+
hash-deep-merge (0.1.1)
|
62
|
+
i18n (1.7.0)
|
63
|
+
concurrent-ruby (~> 1.0)
|
64
|
+
ice_nine (0.11.2)
|
65
|
+
json_schema (0.20.8)
|
66
|
+
json_schemer (0.2.7)
|
67
|
+
ecma-re-validator (~> 0.2)
|
68
|
+
hana (~> 1.3)
|
69
|
+
regexp_parser (~> 1.5)
|
70
|
+
uri_template (~> 0.7)
|
71
|
+
memory_profiler (0.9.14)
|
72
|
+
mini_portile2 (2.4.0)
|
73
|
+
minitest (5.12.2)
|
74
|
+
multi_json (1.14.1)
|
75
|
+
mustermann (1.0.3)
|
76
|
+
mustermann-contrib (1.0.3)
|
77
|
+
hansi (~> 0.2.0)
|
78
|
+
mustermann (= 1.0.3)
|
79
|
+
mustermann-grape (1.0.0)
|
80
|
+
mustermann (~> 1.0.0)
|
81
|
+
nokogiri (1.10.4)
|
82
|
+
mini_portile2 (~> 2.4.0)
|
83
|
+
oas_parser (0.22.2)
|
84
|
+
activesupport (>= 4.0.0)
|
85
|
+
addressable (~> 2.3)
|
86
|
+
builder (~> 3.2.3)
|
87
|
+
deep_merge (~> 1.2.1)
|
88
|
+
hash-deep-merge
|
89
|
+
mustermann-contrib (~> 1.0.3s)
|
90
|
+
nokogiri
|
91
|
+
openapi_parser (0.6.1)
|
92
|
+
public_suffix (4.0.1)
|
93
|
+
rack (2.0.7)
|
94
|
+
rack-accept (0.4.5)
|
95
|
+
rack (>= 0.4)
|
96
|
+
rack-protection (2.0.7)
|
97
|
+
rack
|
98
|
+
regexp_parser (1.6.0)
|
99
|
+
seg (1.2.0)
|
100
|
+
sinatra (2.0.7)
|
101
|
+
mustermann (~> 1.0)
|
102
|
+
rack (~> 2.0)
|
103
|
+
rack-protection (= 2.0.7)
|
104
|
+
tilt (~> 2.0)
|
105
|
+
syro (3.1.1)
|
106
|
+
rack (>= 1.6.0)
|
107
|
+
seg
|
108
|
+
thread_safe (0.3.6)
|
109
|
+
tilt (2.0.10)
|
110
|
+
transproc (1.1.0)
|
111
|
+
tzinfo (1.2.5)
|
112
|
+
thread_safe (~> 0.1)
|
113
|
+
uri_template (0.7.0)
|
114
|
+
virtus (1.0.5)
|
115
|
+
axiom-types (~> 0.1)
|
116
|
+
coercible (~> 1.0)
|
117
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
118
|
+
equalizer (~> 0.0, >= 0.0.9)
|
119
|
+
zeitwerk (2.2.0)
|
120
|
+
|
121
|
+
PLATFORMS
|
122
|
+
ruby
|
123
|
+
|
124
|
+
DEPENDENCIES
|
125
|
+
benchmark-ips
|
126
|
+
benchmark-memory
|
127
|
+
committee
|
128
|
+
grape
|
129
|
+
hanami-router (~> 2.0.0.alpha1)
|
130
|
+
multi_json
|
131
|
+
openapi_first!
|
132
|
+
sinatra
|
133
|
+
syro
|
134
|
+
|
135
|
+
BUNDLED WITH
|
136
|
+
2.0.2
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'committee'
|
4
|
+
require 'syro'
|
5
|
+
require 'multi_json'
|
6
|
+
|
7
|
+
app = Syro.new do
|
8
|
+
on 'hello' do
|
9
|
+
on :id do
|
10
|
+
get do
|
11
|
+
res.json MultiJson.dump(hello: 'world', id: inbox[:id])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
get do
|
16
|
+
res.json [MultiJson.dump(hello: 'world')]
|
17
|
+
end
|
18
|
+
|
19
|
+
post do
|
20
|
+
res.status = 201
|
21
|
+
res.json MultiJson.dump(hello: 'world')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
use Committee::Middleware::RequestValidation,
|
27
|
+
schema_path: './apps/openapi.yaml',
|
28
|
+
coerce_date_times: false
|
29
|
+
|
30
|
+
run app
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'grape'
|
4
|
+
|
5
|
+
class GrapeExample < Grape::API
|
6
|
+
format :json
|
7
|
+
|
8
|
+
get :hello do
|
9
|
+
[{ hello: 'world' }]
|
10
|
+
end
|
11
|
+
|
12
|
+
post :hello do
|
13
|
+
{ hello: 'world' }
|
14
|
+
end
|
15
|
+
|
16
|
+
get 'hello/:id' do
|
17
|
+
{ hello: 'world', id: params[:id] }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
run GrapeExample
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hanami/router'
|
4
|
+
require 'multi_json'
|
5
|
+
|
6
|
+
app = Hanami::Router.new do
|
7
|
+
get '/hello', to: ->(_env) { [200, {}, [MultiJson.dump(hello: 'world')]] }
|
8
|
+
get '/hello/:id', to: lambda { |env|
|
9
|
+
[200, {}, [MultiJson.dump(hello: 'world', id: env['router.params'][:id])]]
|
10
|
+
}
|
11
|
+
post '/hello', to: ->(_env) { [201, {}, [MultiJson.dump(hello: 'world')]] }
|
12
|
+
end
|
13
|
+
|
14
|
+
run app
|
@@ -0,0 +1,86 @@
|
|
1
|
+
openapi: 3.0.0
|
2
|
+
info:
|
3
|
+
title: "API"
|
4
|
+
version: "1.0.0"
|
5
|
+
contact:
|
6
|
+
name: Contact Name
|
7
|
+
email: contact@example.com
|
8
|
+
url: https://example.com/
|
9
|
+
tags:
|
10
|
+
- name: Metadata
|
11
|
+
description: Metadata related requests
|
12
|
+
paths:
|
13
|
+
/hello/{id}:
|
14
|
+
parameters:
|
15
|
+
- name: id
|
16
|
+
description: ID of the thing to get
|
17
|
+
in: path
|
18
|
+
required: true
|
19
|
+
schema:
|
20
|
+
type: string
|
21
|
+
get:
|
22
|
+
operationId: find_thing
|
23
|
+
description: Get one thing
|
24
|
+
tags: ["Metadata"]
|
25
|
+
responses:
|
26
|
+
"200":
|
27
|
+
description: OK
|
28
|
+
content:
|
29
|
+
application/json:
|
30
|
+
schema:
|
31
|
+
type: array
|
32
|
+
items:
|
33
|
+
type: object
|
34
|
+
required: [hello, id]
|
35
|
+
properties:
|
36
|
+
hello:
|
37
|
+
type: string
|
38
|
+
id:
|
39
|
+
type: string
|
40
|
+
/hello:
|
41
|
+
get:
|
42
|
+
operationId: find_things
|
43
|
+
description: Get multiple things
|
44
|
+
tags: ["Metadata"]
|
45
|
+
parameters:
|
46
|
+
- name: filter
|
47
|
+
description: filter things
|
48
|
+
in: query
|
49
|
+
required: false
|
50
|
+
schema:
|
51
|
+
type: object
|
52
|
+
required: [id]
|
53
|
+
properties:
|
54
|
+
id:
|
55
|
+
type: string
|
56
|
+
description: Comma separated list of thing-IDs
|
57
|
+
|
58
|
+
responses:
|
59
|
+
"200":
|
60
|
+
description: OK
|
61
|
+
content:
|
62
|
+
application/json:
|
63
|
+
schema:
|
64
|
+
type: object
|
65
|
+
required: [hello]
|
66
|
+
properties:
|
67
|
+
hello:
|
68
|
+
type: string
|
69
|
+
default:
|
70
|
+
description: Error response
|
71
|
+
|
72
|
+
post:
|
73
|
+
operationId: create_thing
|
74
|
+
description: Create a thing
|
75
|
+
tags: ["Metadata"]
|
76
|
+
responses:
|
77
|
+
"201":
|
78
|
+
description: OK
|
79
|
+
content:
|
80
|
+
application/json:
|
81
|
+
schema:
|
82
|
+
type: object
|
83
|
+
required: [hello]
|
84
|
+
properties:
|
85
|
+
hello:
|
86
|
+
type: string
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'multi_json'
|
4
|
+
require 'openapi_first'
|
5
|
+
|
6
|
+
namespace = Module.new do
|
7
|
+
def self.find_thing(params, _res)
|
8
|
+
{ hello: 'world', id: params.fetch('id') }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.find_things(_params, _res)
|
12
|
+
[{ hello: 'world' }]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create_thing(_params, res)
|
16
|
+
res.status = 201
|
17
|
+
{ hello: 'world' }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
oas_path = File.absolute_path('./openapi.yaml', __dir__)
|
22
|
+
run OpenapiFirst.app(oas_path, namespace: namespace)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openapi_first'
|
4
|
+
require 'syro'
|
5
|
+
require 'multi_json'
|
6
|
+
|
7
|
+
app = Syro.new do
|
8
|
+
on 'hello' do
|
9
|
+
on :id do
|
10
|
+
get do
|
11
|
+
res.json MultiJson.dump(hello: 'world', id: inbox[:id])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
get do
|
16
|
+
res.json [MultiJson.dump(hello: 'world')]
|
17
|
+
end
|
18
|
+
|
19
|
+
post do
|
20
|
+
res.status = 201
|
21
|
+
res.json MultiJson.dump(hello: 'world')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
spec = OpenapiFirst.load(File.absolute_path('./openapi.yaml', __dir__))
|
27
|
+
use OpenapiFirst::Router, spec: spec
|
28
|
+
use OpenapiFirst::QueryParameterValidation
|
29
|
+
use OpenapiFirst::RequestBodyValidation
|
30
|
+
|
31
|
+
run app
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'multi_json'
|
4
|
+
require 'openapi_first'
|
5
|
+
|
6
|
+
namespace = Module.new do
|
7
|
+
def self.find_thing(params, _res)
|
8
|
+
{ hello: 'world', id: params.fetch('id') }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.find_things(_params, _res)
|
12
|
+
[{ hello: 'world' }]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create_thing(_params, res)
|
16
|
+
res.status = 201
|
17
|
+
{ hello: 'world' }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
spec = OpenapiFirst.load(File.absolute_path('./openapi.yaml', __dir__))
|
22
|
+
use OpenapiFirst::Router, spec: spec
|
23
|
+
run OpenapiFirst::OperationResolver.new(namespace: namespace)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'multi_json'
|
4
|
+
require 'sinatra/base'
|
5
|
+
|
6
|
+
class SinatraExample < Sinatra::Base
|
7
|
+
set :environment, :production
|
8
|
+
|
9
|
+
get '/hello/:id' do
|
10
|
+
content_type :json
|
11
|
+
MultiJson.dump(hello: 'world', id: params.fetch('id'))
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/hello' do
|
15
|
+
content_type :json
|
16
|
+
[MultiJson.dump(hello: 'world')]
|
17
|
+
end
|
18
|
+
|
19
|
+
post '/hello' do
|
20
|
+
content_type :json
|
21
|
+
status 201
|
22
|
+
MultiJson.dump(hello: 'world')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
run SinatraExample
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'syro'
|
4
|
+
require 'multi_json'
|
5
|
+
|
6
|
+
app = Syro.new do
|
7
|
+
on 'hello' do
|
8
|
+
on :id do
|
9
|
+
get do
|
10
|
+
res.json MultiJson.dump(hello: 'world', id: inbox[:id])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
get do
|
15
|
+
res.json [MultiJson.dump(hello: 'world')]
|
16
|
+
end
|
17
|
+
|
18
|
+
post do
|
19
|
+
res.status = 201
|
20
|
+
res.json MultiJson.dump(hello: 'world')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
run app
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark/ips'
|
4
|
+
require 'benchmark/memory'
|
5
|
+
require 'rack'
|
6
|
+
ENV['RACK_ENV'] = 'production'
|
7
|
+
|
8
|
+
examples = [
|
9
|
+
[Rack::MockRequest.env_for('/hello'), 200],
|
10
|
+
[Rack::MockRequest.env_for('/unknown'), 404],
|
11
|
+
[Rack::MockRequest.env_for('/hello', method: 'POST'), 201],
|
12
|
+
[Rack::MockRequest.env_for('/hello/1'), 200],
|
13
|
+
[Rack::MockRequest.env_for('/hello/123'), 200],
|
14
|
+
[Rack::MockRequest.env_for('/hello?filter[id]=1,2'), 200]
|
15
|
+
]
|
16
|
+
|
17
|
+
apps = Dir['./apps/*.ru'].each_with_object({}) do |config, hash|
|
18
|
+
hash[config] = Rack::Builder.parse_file(config).first
|
19
|
+
end
|
20
|
+
apps.freeze
|
21
|
+
|
22
|
+
Benchmark.ips do |x|
|
23
|
+
apps.each do |config, app|
|
24
|
+
x.report(config) do
|
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
|
31
|
+
end
|
32
|
+
x.compare!
|
33
|
+
end
|
34
|
+
|
35
|
+
Benchmark.memory do |x|
|
36
|
+
apps.each do |config, app|
|
37
|
+
x.report(config) do
|
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
|
44
|
+
end
|
45
|
+
x.compare!
|
46
|
+
end
|
data/lib/openapi_first.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'yaml'
|
3
4
|
require 'oas_parser'
|
4
5
|
require 'openapi_first/definition'
|
5
6
|
require 'openapi_first/version'
|
6
7
|
require 'openapi_first/router'
|
7
8
|
require 'openapi_first/query_parameter_validation'
|
8
9
|
require 'openapi_first/request_body_validation'
|
10
|
+
require 'openapi_first/response_validator'
|
9
11
|
require 'openapi_first/operation_resolver'
|
10
12
|
require 'openapi_first/app'
|
11
13
|
|
@@ -15,8 +17,12 @@ module OpenapiFirst
|
|
15
17
|
REQUEST_BODY = 'openapi_first.parsed_request_body'
|
16
18
|
QUERY_PARAMS = 'openapi_first.query_params'
|
17
19
|
|
18
|
-
def self.load(spec_path)
|
19
|
-
|
20
|
+
def self.load(spec_path, only: nil)
|
21
|
+
content = YAML.load_file(spec_path)
|
22
|
+
raw = OasParser::Parser.new(spec_path, content).resolve
|
23
|
+
raw['paths'].filter!(&->(key, _) { only.call(key) }) if only
|
24
|
+
parsed = OasParser::Definition.new(raw, spec_path)
|
25
|
+
Definition.new(parsed)
|
20
26
|
end
|
21
27
|
|
22
28
|
def self.app(spec, namespace:)
|
@@ -4,23 +4,40 @@ require 'rack'
|
|
4
4
|
|
5
5
|
module OpenapiFirst
|
6
6
|
class OperationResolver
|
7
|
-
|
7
|
+
NOT_FOUND = Rack::Response.new('', 404).finish.freeze
|
8
|
+
DEFAULT_APP = ->(_env) { NOT_FOUND }
|
8
9
|
|
9
10
|
def initialize(app = DEFAULT_APP, namespace:)
|
10
11
|
@app = app
|
11
12
|
@namespace = namespace
|
12
13
|
end
|
13
14
|
|
14
|
-
def call(env)
|
15
|
+
def call(env) # rubocop:disable Metrics/AbcSize
|
15
16
|
operation = env[OpenapiFirst::OPERATION]
|
16
17
|
return @app.call(env) unless operation
|
17
18
|
|
18
19
|
operation_id = operation.operation_id
|
19
20
|
res = Rack::Response.new
|
20
|
-
|
21
|
+
params = build_params(env)
|
22
|
+
handler = find_handler(operation_id)
|
23
|
+
result = handler.call(params, res)
|
21
24
|
res.write MultiJson.dump(result) if result && res.body.empty?
|
22
25
|
res[Rack::CONTENT_TYPE] ||= find_content_type(operation, res.status)
|
23
|
-
res
|
26
|
+
res.finish
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_handler(operation_id)
|
30
|
+
if operation_id.include?('.')
|
31
|
+
module_name, method_name = operation_id.split('.')
|
32
|
+
return @namespace.const_get(module_name.camelize).method(method_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
if operation_id.include?('#')
|
36
|
+
module_name, class_name = operation_id.split('#')
|
37
|
+
return @namespace.const_get(module_name.camelize)
|
38
|
+
.const_get(class_name.camelize).new
|
39
|
+
end
|
40
|
+
@namespace.method(operation_id)
|
24
41
|
end
|
25
42
|
|
26
43
|
private
|
@@ -32,15 +49,6 @@ module OpenapiFirst
|
|
32
49
|
content.keys[0] if content
|
33
50
|
end
|
34
51
|
|
35
|
-
def call_operation_method(operation_id, env, res)
|
36
|
-
target = @namespace
|
37
|
-
methods = operation_id.split('.')
|
38
|
-
final = methods.pop
|
39
|
-
methods.each { |m| target = target.send(m) }
|
40
|
-
params = build_params(env)
|
41
|
-
target.send(final, params, res)
|
42
|
-
end
|
43
|
-
|
44
52
|
def build_params(env)
|
45
53
|
sources = [
|
46
54
|
env[PATH_PARAMS],
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
class QueryParameterSchemas
|
5
|
+
def initialize(allow_additional_parameters:)
|
6
|
+
@additional_properties = allow_additional_parameters
|
7
|
+
end
|
8
|
+
|
9
|
+
def find(operation)
|
10
|
+
build_parameter_schema(operation)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def build_parameter_schema(operation)
|
16
|
+
return unless operation&.query_parameters&.any?
|
17
|
+
|
18
|
+
operation.query_parameters.each_with_object(
|
19
|
+
'type' => 'object',
|
20
|
+
'required' => [],
|
21
|
+
'additionalProperties' => @additional_properties,
|
22
|
+
'properties' => {}
|
23
|
+
) do |parameter, schema|
|
24
|
+
schema['required'] << parameter.name if parameter.required
|
25
|
+
schema['properties'][parameter.name] = parameter.schema
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -5,6 +5,7 @@ require 'json_schemer'
|
|
5
5
|
require 'multi_json'
|
6
6
|
require_relative 'validation_format'
|
7
7
|
require_relative 'error_response_method'
|
8
|
+
require_relative 'query_parameter_schemas'
|
8
9
|
|
9
10
|
module OpenapiFirst
|
10
11
|
class QueryParameterValidation
|
@@ -12,14 +13,18 @@ module OpenapiFirst
|
|
12
13
|
|
13
14
|
def initialize(app, allow_additional_parameters: false)
|
14
15
|
@app = app
|
16
|
+
@schemas = QueryParameterSchemas.new(
|
17
|
+
allow_additional_parameters: allow_additional_parameters
|
18
|
+
)
|
15
19
|
@additional_properties = allow_additional_parameters
|
16
20
|
end
|
17
21
|
|
18
22
|
def call(env)
|
19
23
|
req = Rack::Request.new(env)
|
20
|
-
|
21
|
-
|
24
|
+
operation = env[OpenapiFirst::OPERATION]
|
25
|
+
schema = operation && @schemas.find(operation)
|
22
26
|
if schema
|
27
|
+
params = req.params
|
23
28
|
errors = schema && JSONSchemer.schema(schema).validate(params)
|
24
29
|
return error_response(400, serialize_errors(errors)) if errors&.any?
|
25
30
|
|
@@ -38,20 +43,6 @@ module OpenapiFirst
|
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
41
|
-
def parameter_schema(operation)
|
42
|
-
return unless operation&.query_parameters&.any?
|
43
|
-
|
44
|
-
operation.query_parameters.each_with_object(
|
45
|
-
'type' => 'object',
|
46
|
-
'required' => [],
|
47
|
-
'additionalProperties' => @additional_properties,
|
48
|
-
'properties' => {}
|
49
|
-
) do |parameter, schema|
|
50
|
-
schema['required'] << parameter.name if parameter.required
|
51
|
-
schema['properties'][parameter.name] = parameter.schema
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
46
|
def serialize_errors(validation_errors)
|
56
47
|
validation_errors.map do |error|
|
57
48
|
{
|
data/lib/openapi_first/router.rb
CHANGED
@@ -7,6 +7,8 @@ require 'mustermann/template'
|
|
7
7
|
|
8
8
|
module OpenapiFirst
|
9
9
|
class Router
|
10
|
+
NOT_FOUND = Rack::Response.new('', 404).finish.freeze
|
11
|
+
|
10
12
|
def initialize(app, spec:, allow_unknown_operation: false)
|
11
13
|
@app = app
|
12
14
|
@spec = spec
|
@@ -20,7 +22,7 @@ module OpenapiFirst
|
|
20
22
|
env[PATH_PARAMS] = path_params if path_params
|
21
23
|
return @app.call(env) if operation || @allow_unknown_operation
|
22
24
|
|
23
|
-
|
25
|
+
NOT_FOUND
|
24
26
|
end
|
25
27
|
|
26
28
|
def find_path_params(operation, req)
|
data/openapi_first.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.email = ['andreas.haller@posteo.de']
|
12
12
|
spec.licenses = ['MIT']
|
13
13
|
|
14
|
-
spec.summary = 'Implement REST APIs based on
|
14
|
+
spec.summary = 'Implement REST APIs based on OpenApi.'
|
15
15
|
spec.homepage = 'https://github.com/ahx/openapi_first'
|
16
16
|
|
17
17
|
if spec.respond_to?(:metadata)
|
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.6.
|
4
|
+
version: 0.6.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: 2019-
|
11
|
+
date: 2019-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json_schemer
|
@@ -139,6 +139,18 @@ files:
|
|
139
139
|
- Gemfile.lock
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
|
+
- benchmarks/Gemfile
|
143
|
+
- benchmarks/Gemfile.lock
|
144
|
+
- benchmarks/apps/committee.ru
|
145
|
+
- benchmarks/apps/grape.ru
|
146
|
+
- benchmarks/apps/hanami_router.ru
|
147
|
+
- benchmarks/apps/openapi.yaml
|
148
|
+
- benchmarks/apps/openapi_first.ru
|
149
|
+
- benchmarks/apps/openapi_first_request_validation_only.ru
|
150
|
+
- benchmarks/apps/openapi_first_resolve_only.ru
|
151
|
+
- benchmarks/apps/sinatra.ru
|
152
|
+
- benchmarks/apps/syro.ru
|
153
|
+
- benchmarks/benchmarks.rb
|
142
154
|
- bin/console
|
143
155
|
- bin/setup
|
144
156
|
- examples/README.md
|
@@ -151,6 +163,7 @@ files:
|
|
151
163
|
- lib/openapi_first/definition.rb
|
152
164
|
- lib/openapi_first/error_response_method.rb
|
153
165
|
- lib/openapi_first/operation_resolver.rb
|
166
|
+
- lib/openapi_first/query_parameter_schemas.rb
|
154
167
|
- lib/openapi_first/query_parameter_validation.rb
|
155
168
|
- lib/openapi_first/request_body_validation.rb
|
156
169
|
- lib/openapi_first/response_validator.rb
|
@@ -182,5 +195,5 @@ requirements: []
|
|
182
195
|
rubygems_version: 3.0.3
|
183
196
|
signing_key:
|
184
197
|
specification_version: 4
|
185
|
-
summary: Implement REST APIs based on
|
198
|
+
summary: Implement REST APIs based on OpenApi.
|
186
199
|
test_files: []
|