openapi_first 0.11.0.alpha → 0.12.1

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 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)