openapi_first 0.11.0.alpha → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c16372f591f02e7332d7d010c0a91e239e38592f41bf2eb5d88d7777058f66fd
4
- data.tar.gz: e02ab1188d3cd8bc64ece82381316f4d7e8012c784e21d1ae2bb0b16bc24b141
3
+ metadata.gz: 6968ab6df070c86d592d8d09bdc68d637287adf0cf090eac5b6f35cddbc918f8
4
+ data.tar.gz: 6d41f5d082f8f5882fa2c57fe509bc54aae6d3b9ef6c3d16cd41340c6d58d1c6
5
5
  SHA512:
6
- metadata.gz: 8d8f44debbe60e787de68246b822669991ba6d1aac3721cbe68f04ee51cffb99ea02b280be0ba3e6e28d0bf11a26b565d2e23792fb08f451462ab36990026970
7
- data.tar.gz: 2545816e97f18afde6947c61893b81a2606fa5e27262d037f3fd1475430acf7c1ffa2bf5db70b6d06316cd6524380ac47290b656ae457200e29e8384da860dc9
6
+ metadata.gz: 76f33d42b144e17b291883c8994562f79db35e9e575e098acb49434a2fe4142975781f31297bd9833d309cde8da87d4f59d873290487cb5bb4224c49306fca0e
7
+ data.tar.gz: 7aad49f9f315e923205c96b92166553a43d29bb5a45fcab4a359a72fd7484536c8099f22dafea5a973aac427aaebf3b37f7832dd0f28af9bc1e39d4aafb9bb6f
@@ -16,6 +16,12 @@ Lint/DeprecatedOpenSSLConstant:
16
16
  Enabled: true
17
17
  Lint/RaiseException:
18
18
  Enabled: true
19
+ Lint/MixedRegexpCaptureTypes:
20
+ Enabled: true
21
+ Style/RedundantRegexpCharacterClass:
22
+ Enabled: true
23
+ Style/RedundantRegexpEscape:
24
+ Enabled: true
19
25
  Style/SlicingWithRange:
20
26
  Enabled: true
21
27
  Lint/StructNewOverride:
@@ -1,6 +1,24 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.12.1
4
+ - Fix response when handler returns 404 or 405
5
+ - Don't validate the response content if status is 205 (no content)
6
+
7
+ ## 0.12.0
8
+ - Change `ResponseValidator` to raise an exception if it found a problem
9
+ - Params have symbolized keys now
10
+ - Remove `not_found` option from Router. Return 405 if HTTP verb is not allowed (via Hanami::Router)
11
+ - Add `raise_error` option to OpenapiFirst.app (false by default)
12
+ - Add ResponseValidation to OpenapiFirst.app if raise_error option is true
13
+ - Rename `raise` option to `raise_error`
14
+ - Add `raise_error` option to RequestValidation middleware
15
+ - Raise error if handler could not be found by Responder
16
+ - Add `Operation#name` that returns a human readable name for an operation
17
+
18
+ ## 0.11.0
19
+ - Raise error if you forgot to add the Router middleware
20
+ - Make OpenapiFirst.app raise an error in test env when request path is not specified
21
+ - Rename OperationResolver to Responder
4
22
  - Add ResponseValidation middleware that validates the response body
5
23
  - Add `raise` option to Router middleware to raise an error if request could not be found in the API description similar to committee's raise option.
6
24
  - Move namespace option from Router to OperationResolver
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openapi_first (0.11.0.alpha)
4
+ openapi_first (0.12.1)
5
5
  deep_merge (>= 1.2.1)
6
6
  hanami-router (~> 2.0.alpha3)
7
7
  hanami-utils (~> 2.0.alpha1)
@@ -21,9 +21,9 @@ 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.0)
24
+ ast (2.4.1)
25
25
  builder (3.2.4)
26
- coderay (1.1.2)
26
+ coderay (1.1.3)
27
27
  concurrent-ruby (1.1.6)
28
28
  deep_merge (1.2.1)
29
29
  diff-lcs (1.3)
@@ -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.2)
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)
@@ -66,7 +66,7 @@ GEM
66
66
  mustermann-contrib (~> 1.1.1)
67
67
  nokogiri
68
68
  parallel (1.19.1)
69
- parser (2.7.1.2)
69
+ parser (2.7.1.3)
70
70
  ast (~> 2.4.0)
71
71
  pry (0.13.1)
72
72
  coderay (~> 1.1)
@@ -77,7 +77,7 @@ GEM
77
77
  rack (>= 1.0, < 3)
78
78
  rainbow (3.0.0)
79
79
  rake (13.0.1)
80
- regexp_parser (1.7.0)
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,10 +92,11 @@ 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.84.0)
95
+ rubocop (0.85.1)
96
96
  parallel (~> 1.10)
97
97
  parser (>= 2.7.0.1)
98
98
  rainbow (>= 2.2.2, < 4.0)
99
+ regexp_parser (>= 1.7)
99
100
  rexml
100
101
  rubocop-ast (>= 0.0.3)
101
102
  ruby-progressbar (~> 1.7)
data/README.md CHANGED
@@ -1,83 +1,124 @@
1
1
  # OpenapiFirst
2
2
 
3
+ [![Join the chat at https://gitter.im/openapi_first/community](https://badges.gitter.im/openapi_first/community.svg)](https://gitter.im/openapi_first/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
+
3
5
  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 code that returns data and implements your business logic and be done.
4
6
 
5
- Start with writing an OpenAPI file that describes the API, which you are about to write. Use a [validator](https://github.com/stoplightio/spectral/) to make sure the file is valid.
7
+ Start with writing an OpenAPI file that describes the API, which you are about to implement. Use a [validator](https://github.com/stoplightio/spectral/) to make sure the file is valid.
8
+
9
+ You can use OpenapiFirst via its [Rack middlewares](#rack-middlewares) or in [standalone mode](#standalone-usage).
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).
6
16
 
7
17
  ## Rack middlewares
8
18
  OpenapiFirst consists of these Rack middlewares:
9
19
 
10
- - `OpenapiFirst::Router` – Finds the operation for the current request or returns 404 if no operation was found. This can be customized.
11
- - `OpenapiFirst::RequestValidation` – Validates the request against the API description and returns 400 if the request is invalid.
12
- - `OpenapiFirst::OperationResolver` calls the [handler](#handlers) found for the operation.
20
+ - [`OpenapiFirst::Router`](#OpenapiFirst::Router) – Finds the OpenAPI operation for the current request or returns 404 if no operation was found. This can be customized.
21
+ - [`OpenapiFirst::RequestValidation`](#OpenapiFirst::RequestValidation) – Validates the request against the API description and returns 400 if the request is invalid.
22
+ - [`OpenapiFirst::Responder`](#OpenapiFirst::Responder) calls the [handler](#handlers) found for the operation.
23
+ - [`OpenapiFirst::ResponseValidation`](#OpenapiFirst::ResponseValidation) Validates the response and raises an exception if the response body is invalid.
13
24
 
14
25
  ## OpenapiFirst::Router
26
+ You always have to add this middleware first in order to make the other middlewares work.
27
+
28
+ ```ruby
29
+ use OpenapiFirst::Router, spec: OpenapiFirst.load('./openapi/openapi.yaml')
30
+ ```
31
+
32
+ This middleware adds `env[OpenapiFirst::OPERATION]` which holds an Operation object that responds to `operation_id` and `path`.
33
+
15
34
  Options and their defaults:
16
35
 
17
36
  | Name | Possible values | Description | Default
18
37
  |:---|---|---|---|
19
- | `not_found:` |`nil`, `:continue`, `Proc`| Specifies what to do if 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)
20
- | `raise:` |`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)
38
+ |`spec:`| | The spec loaded via `OpenapiFirst.load` ||
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)
21
40
 
41
+ ## OpenapiFirst::RequestValidation
22
42
 
23
- ## Usage within your Rack webframework
24
- If you just want to use the request validation part without any handlers you can use the rack middlewares standalone:
43
+ This middleware returns a 400 status code with a body that describes the error if the request is not valid.
25
44
 
26
45
  ```ruby
27
- use OpenapiFirst::Router, spec: OpenapiFirst.load('./openapi/openapi.yaml')
28
46
  use OpenapiFirst::RequestValidation
29
47
  ```
30
48
 
31
- ### Rack env variables
32
- These variables will available in your rack env:
33
49
 
34
- - `env[OpenapiFirst::OPERATION]` - Holds an Operation object that responsed about `operation_id` and `path`. This is useful for introspection.
35
- - `env[OpenapiFirst::INBOX]`. Holds the (filtered) path and query parameters and the parsed request body.
50
+ Options and their defaults:
36
51
 
52
+ | Name | Possible values | Description | Default
53
+ |:---|---|---|---|
54
+ | `raise_error:` |`false`, `true` | If set to true the middleware raises `OpenapiFirst::RequestInvalidError` instead of returning 4xx. | `false` (don't raise an exception)
37
55
 
38
- ## Standalone usage
39
- You can implement your API in conveniently with just OpenapiFirst.
56
+ The error responses conform with [JSON:API](https://jsonapi.org).
40
57
 
41
- ```ruby
42
- module Pets
43
- def self.find_pet(params, res)
58
+ Here's an example response body for a missing query parameter "search":
59
+
60
+ ```json
61
+ http-status: 400
62
+ content-type: "application/vnd.api+json"
63
+
64
+ {
65
+ "errors": [
44
66
  {
45
- id: params['id'],
46
- name: 'Oscar'
67
+ "title": "is missing",
68
+ "source": {
69
+ "parameter": "search"
70
+ }
47
71
  }
48
- end
49
- end
50
-
51
- # In config.ru:
52
- require 'openapi_first'
53
- run OpenapiFirst.app('./openapi/openapi.yaml', namespace: Pets)
72
+ ]
73
+ }
54
74
  ```
55
75
 
56
- The above will use the mentioned Rack middlewares to:
76
+ This middleware adds `env[OpenapiFirst::INBOX]` which holds the (filtered) path and query parameters and the parsed request body.
57
77
 
58
- - Validate the request and respond with 400 if the request does not match with your API description
59
- - Map the request to a method call `Pets.find_pet` based on the `operationId` in the API description
60
- - Set the response content type according to your spec (here with the default status code `200`)
78
+ ### Parameter validation
61
79
 
62
- Handler functions (`find_pet`) are called with two arguments:
80
+ The middleware filteres all top-level query parameters and paths parameters and tries to convert numeric values. Meaning, if you have an `:something_id` path with `type: integer`, it will try convert the value to an integer.
81
+ Note that is currently does not convert date, date-time or time formats and that conversion is currently done only for path and query parameters, but not for the request body. It just works with a parameter with `name: filter[age]`.
63
82
 
64
- - `params` - Holds the parsed request body, filtered query params and path parameters
65
- - `res` - Holds a Rack::Response that you can modify if needed
66
- If you want to access to plain Rack env you can call `params.env`.
83
+ If you want to forbid _nested_ query parameters you will need to use [`additionalProperties: false`](https://json-schema.org/understanding-json-schema/reference/object.html#properties) in your query parameter JSON schema.
67
84
 
68
- ### Handlers
85
+ _OpenapiFirst always treats query parameters like [`style: deepObject`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#style-values), **but** it just works with nested objects (`filter[foo][bar]=baz`) (see [this discussion](https://github.com/OAI/OpenAPI-Specification/issues/1706))._
86
+
87
+ ### Request body validation
88
+
89
+ The middleware will return a status `415` if the requests content type does not match or `400` if the request body is invalid.
90
+ This will also add the parsed request body to `env[OpenapiFirst::REQUEST_BODY]`.
91
+
92
+ ### Header, Cookie, Path parameter validation
93
+
94
+ tbd.
69
95
 
70
- OpenapiFirst 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 via the `OperationResolver` middleware.
96
+ ## OpenapiFirst::Responder
97
+
98
+ 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.
99
+
100
+ Currently there are no customization options for this part. Please [share your ideas](#contributing) on how to best meet your needs and preferred style.
101
+
102
+ ```ruby
103
+ run OpenapiFirst::Responder, spec: OpenapiFirst.load('./openapi/openapi.yaml')
104
+ ```
105
+
106
+ | Name | Description
107
+ |:---|---|
108
+ |`spec:`| The spec loaded via `OpenapiFirst.load` |
109
+ | `namespace:` | A class or module where to find the handler method. |
71
110
 
72
111
  It works like this:
73
112
 
74
- - "create_pet" or "createPet" or "create pet" calls `MyApi.create_pet(params, response)`
113
+ - An operationId "create_pet" or "createPet" or "create pet" calls `MyApi.create_pet(params, response)`
75
114
  - "some_things.create" calls: `MyApi::SomeThings.create(params, response)`
76
115
  - "pets#create" calls: `MyApi::Pets::Create.new.call(params, response)` If `MyApi::Pets::Create.new` accepts an argument, it will pass the rack `env`.
77
116
 
117
+ ### Handlers
118
+
78
119
  These handler methods are called with two arguments:
79
120
 
80
- - `params` - Holds the parsed request body, filtered query params and path parameters
121
+ - `params` - Holds the parsed request body, filtered query params and path parameters (same as `env[OpenapiFirst::INBOX]`)
81
122
  - `res` - Holds a Rack::Response that you can modify if needed
82
123
 
83
124
  You can call `params.env` to access the Rack env (just like in [Hanami actions](https://guides.hanamirb.org/actions/parameters/))
@@ -85,75 +126,70 @@ You can call `params.env` to access the Rack env (just like in [Hanami actions](
85
126
  There are two ways to set the response body:
86
127
 
87
128
  - Calling `res.write "things"` (see [Rack::Response](https://www.rubydoc.info/github/rack/rack/Rack/Response))
88
- - Returning a value from the function (see example above) (this will always converted to JSON)
129
+ - Returning a value which will get converted to JSON
89
130
 
90
- ### If your API description does not contain all endpoints
131
+ ## OpenapiFirst::ResponseValidation
132
+ This middleware is especially useful when testing. It *always* raises an error if the response is not valid.
91
133
 
92
134
  ```ruby
93
- run OpenapiFirst.middleware('./openapi/openapi.yaml', namespace: Pets)
135
+ use OpenapiFirst::ResponseValidation if ENV['RACK_ENV'] == 'test'
94
136
  ```
95
137
 
96
- Here all requests that are not part of the API description will be passed to the next app.
97
-
98
- ### Try it out
99
-
100
- See [examples](examples).
101
-
102
- ## Installation
103
-
104
- Add this line to your application's Gemfile:
138
+ ## Standalone usage
139
+ Instead of composing these middlewares yourself you can use `OpenapiFirst.app`.
105
140
 
106
141
  ```ruby
107
- gem 'openapi_first'
108
- ```
142
+ module Pets
143
+ def self.find_pet(params, res)
144
+ {
145
+ id: params[:id],
146
+ name: 'Oscar'
147
+ }
148
+ end
149
+ end
109
150
 
110
- OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
151
+ # In config.ru:
152
+ require 'openapi_first'
153
+ run OpenapiFirst.app('./openapi/openapi.yaml', namespace: Pets)
154
+ ```
111
155
 
112
- ## Request validation
156
+ The above will use the mentioned Rack middlewares to:
113
157
 
114
- If the request is not valid, these middlewares return a 400 status code with a body that describes the error.
158
+ - Validate the request and respond with 400 if the request does not match with your API description
159
+ - Map the request to a method call `Pets.find_pet` based on the `operationId` in the API description
160
+ - Set the response content type according to your spec (here with the default status code `200`)
115
161
 
116
- The error responses conform with [JSON:API](https://jsonapi.org).
162
+ Handler functions (`find_pet`) are called with two arguments:
117
163
 
118
- Here's an example response body for a missing query parameter "search":
164
+ - `params` - Holds the parsed request body, filtered query params and path parameters
165
+ - `res` - Holds a Rack::Response that you can modify if needed
166
+ If you want to access to plain Rack env you can call `params.env`.
119
167
 
120
- ```json
121
- http-status: 400
122
- content-type: "application/vnd.api+json"
168
+ ## If your API description does not contain all endpoints
123
169
 
124
- {
125
- "errors": [
126
- {
127
- "title": "is missing",
128
- "source": {
129
- "parameter": "search"
130
- }
131
- }
132
- ]
133
- }
170
+ ```ruby
171
+ run OpenapiFirst.middleware('./openapi/openapi.yaml', namespace: Pets)
134
172
  ```
135
173
 
136
- ### Parameter validation
137
-
138
- The middleware filteres all top-level query parameters and paths parameters and tries to convert numeric values. Meaning, if you have an `:something_id` path with `type: integer`, it will try convert the value to an integer.
139
- Note that is currently does not convert date, date-time or time formats and that conversion is currently on done for path and query parameters, but not for request bodies.
174
+ Here all requests that are not part of the API description will be passed to the next app.
140
175
 
141
- If you want to forbid _nested_ query parameters you will need to use [`additionalProperties: false`](https://json-schema.org/understanding-json-schema/reference/object.html#properties) in your query parameter JSON schema.
176
+ ## Try it out
142
177
 
143
- _OpenapiFirst always treats query parameters like [`style: deepObject`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#style-values), **but** it just works with nested objects (`filter[foo][bar]=baz`) (see [this discussion](https://github.com/OAI/OpenAPI-Specification/issues/1706))._
178
+ See [examples](examples).
144
179
 
145
- ### Request body validation
180
+ ## Installation
146
181
 
147
- The middleware will return a `415` if the requests content type does not match or `400` if the request body is invalid.
148
- This will add the parsed request body to `env[OpenapiFirst::REQUEST_BODY]`.
182
+ Add this line to your application's Gemfile:
149
183
 
150
- ### Header, Cookie, Path parameter validation
184
+ ```ruby
185
+ gem 'openapi_first'
186
+ ```
151
187
 
152
- tbd.
188
+ OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
153
189
 
154
- ## Response validation
190
+ ## Manual response validation
155
191
 
156
- 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).
192
+ 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.
157
193
 
158
194
  ```ruby
159
195
  # In your test (rspec example):
@@ -161,7 +197,8 @@ require 'openapi_first'
161
197
  spec = OpenapiFirst.load('petstore.yaml')
162
198
  validator = OpenapiFirst::ResponseValidator.new(spec)
163
199
 
164
- expect(validator.validate(last_request, last_response).errors).to be_empty
200
+ # This will raise an exception if it found an error
201
+ validator.validate(last_request, last_response)
165
202
  ```
166
203
 
167
204
  ## Handling only certain paths
@@ -212,10 +249,6 @@ end
212
249
 
213
250
  Out of scope. Use [Prism](https://github.com/stoplightio/prism) or [fakeit](https://github.com/JustinFeng/fakeit).
214
251
 
215
- ## Alternatives
216
-
217
- This gem is inspired by [committee](https://github.com/interagent/committee) (Ruby) and [connexion](https://github.com/zalando/connexion) (Python).
218
-
219
252
  ## Development
220
253
 
221
254
  Run `bin/setup` to install dependencies.
@@ -234,6 +267,6 @@ bundle exec ruby benchmarks.rb
234
267
 
235
268
  ## Contributing
236
269
 
237
- 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).
270
+ 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) or [reach out via chat](https://gitter.im/openapi_first/community).
238
271
 
239
272
  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. 🤗
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- openapi_first (0.11.0.alpha)
4
+ openapi_first (0.12.1)
5
5
  deep_merge (>= 1.2.1)
6
- hanami-router (~> 2.0.alpha2)
6
+ hanami-router (~> 2.0.alpha3)
7
7
  hanami-utils (~> 2.0.alpha1)
8
8
  json_schemer (~> 0.2)
9
9
  multi_json (~> 1.14)
@@ -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.2)
75
+ i18n (1.8.3)
76
76
  concurrent-ruby (~> 1.0)
77
- json_schema (0.20.8)
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)
@@ -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.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.0)
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)