openapi_first 1.0.0.beta5 → 1.0.0.beta6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -1
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +2 -1
  5. data/Gemfile.lock +6 -9
  6. data/Gemfile.rack2 +15 -0
  7. data/lib/openapi_first/{body_parser_middleware.rb → body_parser.rb} +3 -15
  8. data/lib/openapi_first/definition/cookie_parameters.rb +12 -0
  9. data/lib/openapi_first/definition/has_content.rb +37 -0
  10. data/lib/openapi_first/definition/header_parameters.rb +12 -0
  11. data/lib/openapi_first/definition/operation.rb +103 -0
  12. data/lib/openapi_first/definition/parameters.rb +47 -0
  13. data/lib/openapi_first/definition/path_item.rb +23 -0
  14. data/lib/openapi_first/definition/path_parameters.rb +13 -0
  15. data/lib/openapi_first/definition/query_parameters.rb +12 -0
  16. data/lib/openapi_first/definition/request_body.rb +32 -0
  17. data/lib/openapi_first/definition/response.rb +37 -0
  18. data/lib/openapi_first/{json_schema → definition/schema}/result.rb +1 -1
  19. data/lib/openapi_first/{json_schema.rb → definition/schema.rb} +2 -2
  20. data/lib/openapi_first/definition.rb +26 -6
  21. data/lib/openapi_first/error_response.rb +2 -0
  22. data/lib/openapi_first/request_body_validator.rb +17 -21
  23. data/lib/openapi_first/request_validation.rb +34 -30
  24. data/lib/openapi_first/response_validation.rb +31 -11
  25. data/lib/openapi_first/router.rb +19 -53
  26. data/lib/openapi_first/version.rb +1 -1
  27. data/openapi_first.gemspec +7 -4
  28. metadata +32 -52
  29. data/.rspec +0 -3
  30. data/.rubocop.yml +0 -14
  31. data/Rakefile +0 -15
  32. data/benchmarks/Gemfile +0 -16
  33. data/benchmarks/Gemfile.lock +0 -142
  34. data/benchmarks/README.md +0 -29
  35. data/benchmarks/apps/committee_with_hanami_api.ru +0 -26
  36. data/benchmarks/apps/committee_with_response_validation.ru +0 -29
  37. data/benchmarks/apps/committee_with_sinatra.ru +0 -31
  38. data/benchmarks/apps/grape.ru +0 -21
  39. data/benchmarks/apps/hanami_api.ru +0 -21
  40. data/benchmarks/apps/hanami_router.ru +0 -14
  41. data/benchmarks/apps/openapi.yaml +0 -268
  42. data/benchmarks/apps/openapi_first_with_hanami_api.ru +0 -24
  43. data/benchmarks/apps/openapi_first_with_plain_rack.ru +0 -32
  44. data/benchmarks/apps/openapi_first_with_response_validation.ru +0 -25
  45. data/benchmarks/apps/openapi_first_with_sinatra.ru +0 -29
  46. data/benchmarks/apps/roda.ru +0 -27
  47. data/benchmarks/apps/sinatra.ru +0 -26
  48. data/benchmarks/apps/syro.ru +0 -25
  49. data/benchmarks/benchmark-wrk.sh +0 -3
  50. data/benchmarks/benchmarks.rb +0 -48
  51. data/benchmarks/post.lua +0 -3
  52. data/bin/console +0 -15
  53. data/bin/setup +0 -8
  54. data/examples/README.md +0 -13
  55. data/examples/app.rb +0 -18
  56. data/examples/config.ru +0 -7
  57. data/examples/openapi.yaml +0 -29
  58. data/lib/openapi_first/operation.rb +0 -170
  59. data/lib/openapi_first/string_keyed_hash.rb +0 -20
data/benchmarks/README.md DELETED
@@ -1,29 +0,0 @@
1
- # How to run these bechmarks
2
-
3
- ## Setup
4
-
5
- ```bash
6
- cd benchmarks
7
- bundle install
8
- ```
9
-
10
- ## Run Ruby benchmarks
11
-
12
- This compares ips and memory usage for all apps defined in /apps
13
-
14
- ```bash
15
- bundle exec ruby benchmarks.rb
16
- ```
17
-
18
- ## Run benchmark using [wrk](https://github.com/wg/wrk)
19
-
20
- 1. Start the example app
21
- Example: openapi_first
22
- ```bash
23
- bundle exec puma apps/openapi_first_with_response_validation.ru
24
- ```
25
-
26
- 2. Run wrk
27
- ```bash
28
- ./benchmark-wrk.sh
29
- ```
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'committee'
5
- require 'hanami/api'
6
-
7
- app = Class.new(Hanami::API) do
8
- get '/hello/:id' do
9
- json(hello: 'world', id: params.fetch(:id))
10
- end
11
-
12
- get '/hello' do
13
- json([{ hello: 'world' }])
14
- end
15
-
16
- post '/hello' do
17
- status 201
18
- json(hello: 'world')
19
- end
20
- end.new
21
-
22
- use Committee::Middleware::RequestValidation,
23
- schema_path: File.absolute_path('./openapi.yaml', __dir__),
24
- parse_response_by_content_type: true
25
-
26
- run app
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'committee'
5
- require 'hanami/api'
6
-
7
- app = Class.new(Hanami::API) do
8
- get '/hello/:id' do
9
- json(hello: 'world', id: params.fetch(:id))
10
- end
11
-
12
- get '/hello' do
13
- json([{ hello: 'world' }])
14
- end
15
-
16
- post '/hello' do
17
- status 201
18
- json(hello: 'world')
19
- end
20
- end.new
21
-
22
- use Committee::Middleware::RequestValidation,
23
- schema_path: File.absolute_path('./openapi.yaml', __dir__),
24
- parse_response_by_content_type: true
25
-
26
- use Committee::Middleware::ResponseValidation,
27
- schema_path: File.absolute_path('./openapi.yaml', __dir__)
28
-
29
- run app
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'committee'
5
- require 'sinatra'
6
-
7
- class SinatraWithCommiteeExample < Sinatra::Base
8
- set :environment, :production
9
-
10
- get '/hello/:id' do
11
- content_type :json
12
- MultiJson.dump(hello: 'world', id: params.fetch('id'))
13
- end
14
-
15
- get '/hello' do
16
- content_type :json
17
- MultiJson.dump([{ hello: 'world' }])
18
- end
19
-
20
- post '/hello' do
21
- content_type :json
22
- status 201
23
- MultiJson.dump(hello: 'world')
24
- end
25
- end
26
-
27
- use Committee::Middleware::RequestValidation,
28
- schema_path: File.absolute_path('./openapi.yaml', __dir__),
29
- parse_response_by_content_type: true
30
-
31
- run SinatraWithCommiteeExample
@@ -1,21 +0,0 @@
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
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'hanami/api'
5
-
6
- app = Class.new(Hanami::API) do
7
- get '/hello/:id' do
8
- json(hello: 'world', id: params.fetch(:id))
9
- end
10
-
11
- get '/hello' do
12
- json([{ hello: 'world' }])
13
- end
14
-
15
- post '/hello' do
16
- status 201
17
- json(hello: 'world')
18
- end
19
- end.new
20
-
21
- run app
@@ -1,14 +0,0 @@
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
@@ -1,268 +0,0 @@
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: object
32
- required: [hello, id]
33
- properties:
34
- hello:
35
- type: string
36
- id:
37
- type: string
38
- /hello:
39
- get:
40
- operationId: find_things
41
- description: Get multiple things
42
- tags: ["Metadata"]
43
- parameters:
44
- - name: filter
45
- description: filter things
46
- in: query
47
- required: false
48
- schema:
49
- type: object
50
- required: [id]
51
- properties:
52
- id:
53
- type: string
54
- description: Comma separated list of thing-IDs
55
-
56
- responses:
57
- "200":
58
- description: OK
59
- content:
60
- application/json:
61
- schema:
62
- type: array
63
- items:
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
- requestBody:
77
- content:
78
- application/json:
79
- schema:
80
- type: object
81
- required:
82
- - say
83
- properties:
84
- say:
85
- type: string
86
- responses:
87
- "201":
88
- description: OK
89
- content:
90
- application/json:
91
- schema:
92
- type: object
93
- required: [hello]
94
- properties:
95
- hello:
96
- type: string
97
- /pets:
98
- get:
99
- description: |
100
- Returns all pets from the system that the user has access to
101
- Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.
102
-
103
- Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.
104
- operationId: find_pets
105
- parameters:
106
- - name: tags
107
- in: query
108
- description: tags to filter by
109
- required: false
110
- style: form
111
- schema:
112
- type: array
113
- items:
114
- type: string
115
- - name: limit
116
- in: query
117
- description: maximum number of results to return
118
- required: false
119
- schema:
120
- type: integer
121
- format: int32
122
- responses:
123
- '200':
124
- description: pet response
125
- content:
126
- application/json:
127
- schema:
128
- type: array
129
- items:
130
- $ref: '#/components/schemas/Pet'
131
- default:
132
- description: unexpected error
133
- content:
134
- application/json:
135
- schema:
136
- $ref: '#/components/schemas/Error'
137
- post:
138
- description: Creates a new pet in the store. Duplicates are allowed
139
- operationId: create_pet
140
- requestBody:
141
- description: Pet to add to the store
142
- required: true
143
- content:
144
- application/json:
145
- schema:
146
- $ref: '#/components/schemas/NewPet'
147
- responses:
148
- '200':
149
- description: pet response
150
- content:
151
- application/json:
152
- schema:
153
- $ref: '#/components/schemas/Pet'
154
- default:
155
- description: unexpected error
156
- content:
157
- application/json:
158
- schema:
159
- $ref: '#/components/schemas/Error'
160
- /pets/{id}:
161
- parameters:
162
- - name: id
163
- in: path
164
- description: ID of pet to fetch
165
- required: true
166
- schema:
167
- type: integer
168
- format: int64
169
- get:
170
- description: Returns a user based on a single ID, if the user does not have access to the pet
171
- operationId: find_pet
172
- responses:
173
- '200':
174
- description: pet response
175
- content:
176
- application/json:
177
- schema:
178
- $ref: '#/components/schemas/Pet'
179
- default:
180
- description: unexpected error
181
- content:
182
- application/json:
183
- schema:
184
- $ref: '#/components/schemas/Error'
185
- delete:
186
- description: deletes a single pet based on the ID supplied
187
- operationId: delete_pet
188
- parameters:
189
- - name: id
190
- in: path
191
- description: ID of pet to delete
192
- required: true
193
- schema:
194
- type: integer
195
- format: int64
196
- responses:
197
- '204':
198
- description: pet deleted
199
- default:
200
- description: unexpected error
201
- content:
202
- application/json:
203
- schema:
204
- $ref: '#/components/schemas/Error'
205
- patch:
206
- description: Updates a pet
207
- operationId: update_pet
208
- requestBody:
209
- description: Changes
210
- required: false
211
- content:
212
- application/json:
213
- schema:
214
- $ref: '#/components/schemas/NewPet'
215
- responses:
216
- '200':
217
- description: pet response
218
- content:
219
- application/json:
220
- schema:
221
- $ref: '#/components/schemas/Pet'
222
- default:
223
- description: unexpected error
224
- content:
225
- application/json:
226
- schema:
227
- $ref: '#/components/schemas/Error'
228
-
229
- components:
230
- schemas:
231
- Pet:
232
- allOf:
233
- - $ref: '#/components/schemas/NewPet'
234
- - required:
235
- - id
236
- properties:
237
- id:
238
- type: integer
239
- format: int64
240
-
241
- NewPet:
242
- required:
243
- - type
244
- - attributes
245
- properties:
246
- type:
247
- type: string
248
- enum:
249
- - pet
250
- - plant
251
- attributes:
252
- additionalProperties: false
253
- type: object
254
- required: [name]
255
- properties:
256
- name:
257
- type: string
258
-
259
- Error:
260
- required:
261
- - code
262
- - message
263
- properties:
264
- code:
265
- type: integer
266
- format: int32
267
- message:
268
- type: string
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'openapi_first'
5
- require 'hanami/api'
6
-
7
- app = Class.new(Hanami::API) do
8
- get '/hello/:id' do
9
- json(hello: 'world', id: params.fetch(:id))
10
- end
11
-
12
- get '/hello' do
13
- json([{ hello: 'world' }])
14
- end
15
-
16
- post '/hello' do
17
- status 201
18
- json(hello: 'world')
19
- end
20
- end.new
21
-
22
- use OpenapiFirst::RequestValidation, spec: File.absolute_path('./openapi.yaml', __dir__)
23
-
24
- run app
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'openapi_first'
5
-
6
- app = Rack::Builder.new do
7
- use OpenapiFirst::RequestValidation, spec: File.expand_path('./openapi.yaml', __dir__)
8
-
9
- handlers = {
10
- 'find_thing' => lambda do |env|
11
- params = env[OpenapiFirst::PARAMS]
12
- body = MultiJson.dump(hello: 'world', id: params.fetch('id'))
13
- [200, { 'Content-Type' => 'application/json' }, [body]]
14
- end,
15
- 'find_things' => lambda do |_env|
16
- body = MultiJson.dump(hello: 'world')
17
- [200, { 'Content-Type' => 'application/json' }, [body]]
18
- end,
19
- 'create_thing' => lambda do |_env|
20
- body = MultiJson.dump(hello: 'world')
21
- [201, { 'Content-Type' => 'application/json' }, [body]]
22
- end
23
- }
24
-
25
- not_found = ->(_env) { [404, {}, []] }
26
-
27
- run(lambda do |env|
28
- handlers.fetch(env[OpenapiFirst::OPERATION]&.operation_id, not_found).call(env)
29
- end)
30
- end
31
-
32
- run app
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'openapi_first'
5
- require 'hanami/api'
6
-
7
- app = Class.new(Hanami::API) do
8
- get '/hello/:id' do
9
- json(hello: 'world', id: params.fetch(:id))
10
- end
11
-
12
- get '/hello' do
13
- json([{ hello: 'world' }])
14
- end
15
-
16
- post '/hello' do
17
- status 201
18
- json(hello: 'world')
19
- end
20
- end.new
21
-
22
- use OpenapiFirst::RequestValidation, spec: File.absolute_path('./openapi.yaml', __dir__)
23
- use OpenapiFirst::ResponseValidation
24
-
25
- run app
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require 'openapi_first'
5
- require 'sinatra'
6
-
7
- class SinatraWithOpenapiFirstExample < Sinatra::Base
8
- set :environment, :production
9
-
10
- get '/hello/:id' do
11
- content_type :json
12
- MultiJson.dump(hello: 'world', id: params.fetch('id'))
13
- end
14
-
15
- get '/hello' do
16
- content_type :json
17
- MultiJson.dump([{ hello: 'world' }])
18
- end
19
-
20
- post '/hello' do
21
- content_type :json
22
- status 201
23
- MultiJson.dump(hello: 'world')
24
- end
25
- end
26
-
27
- use OpenapiFirst::RequestValidation, spec: File.absolute_path('./openapi.yaml', __dir__)
28
-
29
- run SinatraWithOpenapiFirstExample
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'roda'
4
- require 'multi_json'
5
-
6
- class App < Roda
7
- route do |r|
8
- r.on 'hello' do
9
- r.on :id do
10
- r.get do
11
- MultiJson.dump({ hello: 'world', id: r.params[:id] })
12
- end
13
- end
14
-
15
- r.get do
16
- MultiJson.dump([{ hello: 'world' }])
17
- end
18
-
19
- r.post do
20
- response.status = 201
21
- MultiJson.dump({ hello: 'world' })
22
- end
23
- end
24
- end
25
- end
26
-
27
- run App.freeze.app
@@ -1,26 +0,0 @@
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
@@ -1,25 +0,0 @@
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({ hello: 'world', id: inbox[:id] })
11
- end
12
- end
13
-
14
- get do
15
- res.json([{ hello: 'world' }])
16
- end
17
-
18
- post do
19
- res.status = 201
20
- res.json({ hello: 'world' })
21
- end
22
- end
23
- end
24
-
25
- run app
@@ -1,3 +0,0 @@
1
- #!/bin/sh
2
-
3
- wrk -t12 -c400 -d10s --latency -s post.lua http://localhost:9292/hello
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'benchmark/ips'
4
- require 'benchmark/memory'
5
- require 'rack'
6
- require 'json'
7
- ENV['RACK_ENV'] = 'production'
8
-
9
- examples = [
10
- [Rack::MockRequest.env_for('/hello'), 200],
11
- [Rack::MockRequest.env_for('/unknown'), 404],
12
- [
13
- Rack::MockRequest.env_for('/hello', method: 'POST', input: JSON.dump({ say: 'hi!' }),
14
- 'CONTENT_TYPE' => 'application/json'), 201
15
- ],
16
- [Rack::MockRequest.env_for('/hello/1'), 200],
17
- [Rack::MockRequest.env_for('/hello/123'), 200],
18
- [Rack::MockRequest.env_for('/hello?filter[id]=1,2'), 200]
19
- ]
20
-
21
- glob = ARGV[0] || './apps/*.ru'
22
- apps = Dir[glob].each_with_object({}) do |config, hash|
23
- hash[config] = Rack::Builder.parse_file(config).first
24
- end
25
- apps.freeze
26
-
27
- bench = lambda do |app|
28
- examples.each do |example|
29
- env, expected_status = example
30
- 100.times { app.call(env) }
31
- response = app.call(env)
32
- raise "expected status #{expected_status}, but was #{response[0]}" unless response[0] == expected_status
33
- end
34
- end
35
-
36
- Benchmark.ips do |x|
37
- apps.each do |config, app|
38
- x.report(config) { bench.call(app) }
39
- end
40
- x.compare!
41
- end
42
-
43
- Benchmark.memory do |x|
44
- apps.each do |config, app|
45
- x.report(config) { bench.call(app) }
46
- end
47
- x.compare!
48
- end