openapi_first 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/openapi_first/body_parser.rb +3 -1
- data/lib/openapi_first/configuration.rb +3 -1
- data/lib/openapi_first/definition/operation.rb +65 -5
- data/lib/openapi_first/definition/path_item.rb +1 -0
- data/lib/openapi_first/definition/request_body.rb +1 -0
- data/lib/openapi_first/definition/response.rb +7 -0
- data/lib/openapi_first/definition.rb +43 -3
- data/lib/openapi_first/error_response.rb +1 -1
- data/lib/openapi_first/errors.rb +6 -0
- data/lib/openapi_first/failure.rb +28 -4
- data/lib/openapi_first/middlewares/request_validation.rb +2 -5
- data/lib/openapi_first/middlewares/response_validation.rb +1 -4
- data/lib/openapi_first/plugins/default/error_response.rb +4 -4
- data/lib/openapi_first/plugins/default.rb +1 -1
- data/lib/openapi_first/plugins/jsonapi/error_response.rb +3 -2
- data/lib/openapi_first/plugins/jsonapi.rb +1 -1
- data/lib/openapi_first/plugins.rb +1 -0
- data/lib/openapi_first/request_validation/request_body_validator.rb +1 -1
- data/lib/openapi_first/request_validation/validator.rb +1 -0
- data/lib/openapi_first/response_validation/validator.rb +1 -0
- data/lib/openapi_first/runtime_request.rb +63 -3
- data/lib/openapi_first/runtime_response.rb +43 -4
- data/lib/openapi_first/schema/validation_error.rb +2 -0
- data/lib/openapi_first/schema/validation_result.rb +2 -7
- data/lib/openapi_first/schema.rb +1 -0
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +21 -3
- metadata +6 -17
- data/.github/CODEOWNERS +0 -1
- data/.github/workflows/ruby.yml +0 -13
- data/.gitignore +0 -11
- data/CHANGELOG.md +0 -274
- data/Gemfile +0 -18
- data/Gemfile.lock +0 -170
- data/Gemfile.rack2 +0 -15
- data/Gemfile.rack2.lock +0 -99
- data/LICENSE.txt +0 -21
- data/README.md +0 -225
- data/openapi_first.gemspec +0 -47
data/README.md
DELETED
@@ -1,225 +0,0 @@
|
|
1
|
-
# openapi_first
|
2
|
-
|
3
|
-
OpenapiFirst helps to implement HTTP APIs based on an [OpenAPI](https://www.openapis.org/) API description. It supports OpenAPI 3.0 and 3.1. It offers request and response validation and it ensures that your implementation follows exactly the API description.
|
4
|
-
|
5
|
-
## Contents
|
6
|
-
|
7
|
-
<!-- TOC -->
|
8
|
-
|
9
|
-
- [Manual use](#manual-use)
|
10
|
-
- [Rack Middlewares](#rack-middlewares)
|
11
|
-
- [Request validation](#request-validation)
|
12
|
-
- [Response validation](#response-validation)
|
13
|
-
- [Configuration](#configuration)
|
14
|
-
- [Development](#development)
|
15
|
-
- [Benchmarks](#benchmarks)
|
16
|
-
- [Contributing](#contributing)
|
17
|
-
|
18
|
-
<!-- /TOC -->
|
19
|
-
|
20
|
-
## Manual use
|
21
|
-
|
22
|
-
Load the API description:
|
23
|
-
|
24
|
-
```ruby
|
25
|
-
require 'openapi_first'
|
26
|
-
|
27
|
-
definition = OpenapiFirst.load('petstore.yaml')
|
28
|
-
```
|
29
|
-
|
30
|
-
Validate request / response:
|
31
|
-
|
32
|
-
```ruby
|
33
|
-
|
34
|
-
# Find the request
|
35
|
-
rack_request = Rack::Request.new(env) # GET /pets/42
|
36
|
-
request = definition.request(rack_request)
|
37
|
-
|
38
|
-
# Inspect the request and access parsed parameters
|
39
|
-
request.known? # Is the request defined in the API description?
|
40
|
-
request.content_type
|
41
|
-
request.body # alias: parsed_body
|
42
|
-
request.path_parameters # => { "pet_id" => 42 }
|
43
|
-
request.query_parameters # alias: query
|
44
|
-
request.params # Merged path and query parameters
|
45
|
-
request.headers
|
46
|
-
request.cookies
|
47
|
-
request.request_method # => "get"
|
48
|
-
request.path # => "/pets/42"
|
49
|
-
request.path_definition # => "/pets/{pet_id}"
|
50
|
-
|
51
|
-
# Validate the request
|
52
|
-
request.validate # Returns OpenapiFirst:::Failure if validation fails
|
53
|
-
request.validate! # Raises OpenapiFirst::RequestInvalidError or OpenapiFirst::NotFoundError if validation fails
|
54
|
-
|
55
|
-
# Find the response
|
56
|
-
rack_response = Rack::Response[*app.call(env)]
|
57
|
-
response = request.response(rack_response) # or definition.response(rack_request, rack_response)
|
58
|
-
|
59
|
-
# Inspect the response
|
60
|
-
response.known? # Is the response defined in the API description?
|
61
|
-
response.status # => 200
|
62
|
-
response.content_type
|
63
|
-
response.body
|
64
|
-
request.headers # parsed response headers
|
65
|
-
|
66
|
-
# Validate response
|
67
|
-
response.validate # Returns OpenapiFirst::Failure if validation fails
|
68
|
-
response.validate! # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError if validation fails
|
69
|
-
```
|
70
|
-
|
71
|
-
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
72
|
-
|
73
|
-
## Rack Middlewares
|
74
|
-
|
75
|
-
All middlewares add a _request_ object to the current Rack env at `env[OpenapiFirst::REQUEST]`), which is in an instance of `OpenapiFirst::RuntimeRequest` that responds to `.params`, `.parsed_body` etc.
|
76
|
-
|
77
|
-
This gives you access to the converted request parameters and body exaclty as described in your API description instead of relying on Rack alone to parse the request. This only includes query parameters that are defined in the API description. It supports every [`style` and `explode` value as described](https://spec.openapis.org/oas/latest.html#style-examples) in the OpenAPI 3.0 and 3.1 specs.
|
78
|
-
|
79
|
-
### Request validation
|
80
|
-
|
81
|
-
The request validation middleware returns a 4xx if the request is invalid or not defined in the API description.
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
use OpenapiFirst::Middlewares::RequestValidation, spec: 'openapi.yaml'
|
85
|
-
```
|
86
|
-
|
87
|
-
#### Options
|
88
|
-
|
89
|
-
| Name | Possible values | Description |
|
90
|
-
| :---------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
91
|
-
| `spec:` | | The path to the spec file or spec loaded via `OpenapiFirst.load` |
|
92
|
-
| `raise_error:` | `false` (default), `true` | If set to true the middleware raises `OpenapiFirst::RequestInvalidError` or `OpenapiFirst::NotFoundError` instead of returning 4xx. |
|
93
|
-
| `error_response:` | `:default` (default), `:jsonapi`, Your implementation of `ErrorResponse` |
|
94
|
-
|
95
|
-
Here in an example response body about an invalid request body. See also [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457).
|
96
|
-
|
97
|
-
```json
|
98
|
-
http-status: 400
|
99
|
-
content-type: "application/problem+json"
|
100
|
-
|
101
|
-
{
|
102
|
-
"title": "Bad Request Body",
|
103
|
-
"status": 400,
|
104
|
-
"errors": [
|
105
|
-
{
|
106
|
-
"message": "value at `/data/name` is not a string",
|
107
|
-
"pointer": "/data/name",
|
108
|
-
"code": "string"
|
109
|
-
},
|
110
|
-
{
|
111
|
-
"message": "number at `/data/numberOfLegs` is less than: 2",
|
112
|
-
"pointer": "/data/numberOfLegs",
|
113
|
-
"code": "minimum"
|
114
|
-
},
|
115
|
-
{
|
116
|
-
"message": "object at `/data` is missing required properties: mandatory",
|
117
|
-
"pointer": "/data",
|
118
|
-
"code": "required"
|
119
|
-
}
|
120
|
-
]
|
121
|
-
}
|
122
|
-
```
|
123
|
-
|
124
|
-
openapi_first offers a [JSON:API](https://jsonapi.org/) error response as well:
|
125
|
-
|
126
|
-
```ruby
|
127
|
-
use OpenapiFirst::Middlewares::RequestValidation, spec: 'openapi.yaml, error_response: :jsonapi'
|
128
|
-
```
|
129
|
-
|
130
|
-
Here is an example error response:
|
131
|
-
|
132
|
-
```json
|
133
|
-
// http-status: 400
|
134
|
-
// content-type: "application/vnd.api+json"
|
135
|
-
|
136
|
-
{
|
137
|
-
"errors": [
|
138
|
-
{
|
139
|
-
"status": "400",
|
140
|
-
"source": {
|
141
|
-
"pointer": "/data/name"
|
142
|
-
},
|
143
|
-
"title": "value at `/data/name` is not a string",
|
144
|
-
"code": "string"
|
145
|
-
},
|
146
|
-
{
|
147
|
-
"status": "400",
|
148
|
-
"source": {
|
149
|
-
"pointer": "/data/numberOfLegs"
|
150
|
-
},
|
151
|
-
"title": "number at `/data/numberOfLegs` is less than: 2",
|
152
|
-
"code": "minimum"
|
153
|
-
},
|
154
|
-
{
|
155
|
-
"status": "400",
|
156
|
-
"source": {
|
157
|
-
"pointer": "/data"
|
158
|
-
},
|
159
|
-
"title": "object at `/data` is missing required properties: mandatory",
|
160
|
-
"code": "required"
|
161
|
-
}
|
162
|
-
]
|
163
|
-
}
|
164
|
-
```
|
165
|
-
|
166
|
-
You can build your own custom error response with `error_response: MyCustomClass` that implements `OpenapiFirst::ErrorResponse`.
|
167
|
-
|
168
|
-
#### readOnly / writeOnly properties
|
169
|
-
|
170
|
-
Request validation fails if request includes a property with `readOnly: true`.
|
171
|
-
|
172
|
-
Response validation fails if response body includes a property with `writeOnly: true`.
|
173
|
-
|
174
|
-
### Response validation
|
175
|
-
|
176
|
-
This middleware is especially useful when testing. It _always_ raises an error if the response is not valid.
|
177
|
-
|
178
|
-
```ruby
|
179
|
-
use OpenapiFirst::Middlewares::ResponseValidation, spec: 'openapi.yaml' if ENV['RACK_ENV'] == 'test'
|
180
|
-
```
|
181
|
-
|
182
|
-
#### Options
|
183
|
-
|
184
|
-
| Name | Possible values | Description |
|
185
|
-
| :------ | --------------- | ---------------------------------------------------------------- |
|
186
|
-
| `spec:` | | The path to the spec file or spec loaded via `OpenapiFirst.load` |
|
187
|
-
|
188
|
-
## Configuration
|
189
|
-
|
190
|
-
You can configure default options globally:
|
191
|
-
|
192
|
-
```ruby
|
193
|
-
OpenapiFirst.configure do |config|
|
194
|
-
# Specify which plugin is used to render error responses returned by the request validation middleware (defaults to :default)
|
195
|
-
config.request_validation_error_response = :jsonapi
|
196
|
-
# Configure if the response validation middleware should raise an exception (defaults to false)
|
197
|
-
config.request_validation_raise_error = true
|
198
|
-
end
|
199
|
-
```
|
200
|
-
|
201
|
-
## Development
|
202
|
-
|
203
|
-
Run `bin/setup` to install dependencies.
|
204
|
-
|
205
|
-
See `bundle exec rake` to run the linter and the tests.
|
206
|
-
|
207
|
-
Run `bundle exec rspec` to run the tests only.
|
208
|
-
|
209
|
-
### Benchmarks
|
210
|
-
|
211
|
-
[Results](https://gist.github.com/ahx/e6ffced58bd2e8d5baffb2f4d2c1f823)
|
212
|
-
|
213
|
-
Run benchmarks:
|
214
|
-
|
215
|
-
```sh
|
216
|
-
cd benchmarks
|
217
|
-
bundle
|
218
|
-
bundle exec ruby benchmarks.rb
|
219
|
-
```
|
220
|
-
|
221
|
-
### Contributing
|
222
|
-
|
223
|
-
If you have a question or an idea or found a bug don't hesitate to [create an issue](https://github.com/ahx/openapi_first/issues) or [start a discussion](https://github.com/ahx/openapi_first/discussions).
|
224
|
-
|
225
|
-
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/openapi_first.gemspec
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
lib = File.expand_path('lib', __dir__)
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require 'openapi_first/version'
|
6
|
-
|
7
|
-
Gem::Specification.new do |spec|
|
8
|
-
spec.name = 'openapi_first'
|
9
|
-
spec.version = OpenapiFirst::VERSION
|
10
|
-
spec.authors = ['Andreas Haller']
|
11
|
-
spec.email = ['andreas.haller@posteo.de']
|
12
|
-
spec.licenses = ['MIT']
|
13
|
-
|
14
|
-
spec.summary = 'Implement REST APIs based on OpenApi 3.x'
|
15
|
-
spec.homepage = 'https://github.com/ahx/openapi_first'
|
16
|
-
|
17
|
-
if spec.respond_to?(:metadata)
|
18
|
-
spec.metadata['https://github.com/ahx/openapi_first'] = spec.homepage
|
19
|
-
spec.metadata['source_code_uri'] = 'https://github.com/ahx/openapi_first'
|
20
|
-
spec.metadata['changelog_uri'] = 'https://github.com/ahx/openapi_first/blob/main/CHANGELOG.md'
|
21
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
22
|
-
else
|
23
|
-
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
|
-
'public gem pushes.'
|
25
|
-
end
|
26
|
-
|
27
|
-
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
28
|
-
`git ls-files -z`
|
29
|
-
.split("\x0")
|
30
|
-
.reject { |f| f.match(%r{^(test|spec|features|benchmarks|examples|bin)/}) }
|
31
|
-
.reject do |f|
|
32
|
-
%w[Dockerfile Jenkinsfile .tool-versions CODEOWNERS .rspec .rubocop.yml .tool-versions
|
33
|
-
Rakefile].include?(f)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
spec.bindir = 'exe'
|
37
|
-
spec.require_paths = ['lib']
|
38
|
-
|
39
|
-
spec.required_ruby_version = '>= 3.1.1'
|
40
|
-
|
41
|
-
spec.add_runtime_dependency 'json_refs', '~> 0.1', '>= 0.1.7'
|
42
|
-
spec.add_runtime_dependency 'json_schemer', '~> 2.1.0'
|
43
|
-
spec.add_runtime_dependency 'multi_json', '~> 1.15'
|
44
|
-
spec.add_runtime_dependency 'mustermann-contrib', '~> 3.0.0'
|
45
|
-
spec.add_runtime_dependency 'openapi_parameters', '>= 0.3.2', '< 2.0'
|
46
|
-
spec.add_runtime_dependency 'rack', '>= 2.2', '< 4.0'
|
47
|
-
end
|