openapi_first 2.0.0 → 2.0.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 +11 -1
- data/README.md +62 -65
- data/lib/openapi_first/configuration.rb +1 -1
- data/lib/openapi_first/middlewares/request_validation.rb +8 -5
- data/lib/openapi_first/router/path_template.rb +2 -2
- data/lib/openapi_first/test.rb +14 -5
- data/lib/openapi_first/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 600de21e1747c012275bf68c2d577296a8d82bb158cb4d73949b5b97167e655d
|
4
|
+
data.tar.gz: 86bcde27440b46fba91662fc7e6b86ac05d899da927fefc3e9d45a41a6d7fac9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee729f6f6fedf5d6e9b82b82bdf86493a0a7ea6274e20b60e2cdd84eda2ce086cf366249f61a28016374355d11ab32654a970a418cf5af24a0b187d31b967618
|
7
|
+
data.tar.gz: 125cc38e91e489f30c922f38056c69f9a6d501f3748ee098f2977d8fc6dc7f5f43f77264991f98564cb60ff2af19912ae85537c57d25c92604f8f6ee8766a749
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,17 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
-
## 2.0
|
5
|
+
## 2.0.3
|
6
|
+
|
7
|
+
- Request validation middleware now accepts `error_response: false` do disable rendering a response. This is useful if you just want to collect metrics (via hooks) during a migration phase.
|
8
|
+
|
9
|
+
## 2.0.2
|
10
|
+
|
11
|
+
- Fix setting custom error response (thanks @gobijan)
|
12
|
+
|
13
|
+
## 2.0.1 (Janked)
|
14
|
+
|
15
|
+
## 2.0.0
|
6
16
|
|
7
17
|
### New Features
|
8
18
|
- Test Assertions! 📋 You can now use `assert_api_conform` for contract testing in your rack-test / Rails integration tests. See Readme for details.
|
data/README.md
CHANGED
@@ -2,17 +2,20 @@
|
|
2
2
|
|
3
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
4
|
|
5
|
+
[](https://github.com/ahx/openapi_first/actions/workflows/ruby.yml)
|
6
|
+
[](https://github.com/ahx/openapi_first/blob/codeql/.github/workflows/codeql.yml)
|
7
|
+
|
5
8
|
## Contents
|
6
9
|
|
7
10
|
<!-- TOC -->
|
8
11
|
|
12
|
+
- [Manual use](#manual-use)
|
13
|
+
- [Validate request](#validate-request)
|
14
|
+
- [Validate response](#validate-response)
|
9
15
|
- [Rack Middlewares](#rack-middlewares)
|
10
16
|
- [Request validation](#request-validation)
|
11
17
|
- [Response validation](#response-validation)
|
12
18
|
- [Test assertions](#test-assertions)
|
13
|
-
- [Manual use](#manual-use)
|
14
|
-
- [Validate request](#validate-request)
|
15
|
-
- [Validate response](#validate-response)
|
16
19
|
- [Framework integration](#framework-integration)
|
17
20
|
- [Configuration](#configuration)
|
18
21
|
- [Hooks](#hooks)
|
@@ -23,6 +26,61 @@ OpenapiFirst helps to implement HTTP APIs based on an [OpenAPI](https://www.open
|
|
23
26
|
|
24
27
|
<!-- /TOC -->
|
25
28
|
|
29
|
+
## Manual use
|
30
|
+
|
31
|
+
Load the API description:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'openapi_first'
|
35
|
+
|
36
|
+
definition = OpenapiFirst.load('openapi.yaml')
|
37
|
+
```
|
38
|
+
|
39
|
+
### Validate request
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
validated_request = definition.validate_request(rack_request)
|
43
|
+
|
44
|
+
# Inspect the request and access parsed parameters
|
45
|
+
validated_request.valid?
|
46
|
+
validated_request.invalid?
|
47
|
+
validated_request.error # => Failure object or nil
|
48
|
+
validated_request.parsed_body # => The parsed request body (Hash)
|
49
|
+
validated_request.parsed_query # A Hash of query parameters that are defined in the API description, parsed exactly as described.
|
50
|
+
validated_request.parsed_path_parameters
|
51
|
+
validated_request.parsed_headers
|
52
|
+
validated_request.parsed_cookies
|
53
|
+
validated_request.parsed_params # Merged parsed path, query parameters and request body
|
54
|
+
# Access the Openapi 3 Operation Object Hash
|
55
|
+
validated_request.operation['x-foo']
|
56
|
+
validated_request.operation['operationId'] => "getStuff"
|
57
|
+
# or the whole request definition
|
58
|
+
validated_request.request_definition.path # => "/pets/{petId}"
|
59
|
+
validated_request.request_definition.operation_id # => "showPetById"
|
60
|
+
|
61
|
+
# Or you can raise an exception if validation fails:
|
62
|
+
definition.validate_request(rack_request, raise_error: true) # Raises OpenapiFirst::RequestInvalidError or OpenapiFirst::NotFoundError if request is invalid
|
63
|
+
```
|
64
|
+
|
65
|
+
### Validate response
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
validated_response = definition.validate_response(rack_request, rack_response)
|
69
|
+
|
70
|
+
# Inspect the response and access parsed parameters and
|
71
|
+
validated_response.valid?
|
72
|
+
validated_response.invalid?
|
73
|
+
validated_response.error # => Failure object or nil
|
74
|
+
validated_response.status # => 200
|
75
|
+
validated_response.parsed_body
|
76
|
+
validated_response.parsed_headers
|
77
|
+
|
78
|
+
# Or you can raise an exception if validation fails:
|
79
|
+
definition.validate_response(rack_request,rack_response, raise_error: true) # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError
|
80
|
+
```
|
81
|
+
|
82
|
+
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
83
|
+
|
26
84
|
## Rack Middlewares
|
27
85
|
|
28
86
|
### Request validation
|
@@ -39,7 +97,7 @@ use OpenapiFirst::Middlewares::RequestValidation, spec: 'openapi.yaml'
|
|
39
97
|
| :---------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
40
98
|
| `spec:` | | The path to the spec file or spec loaded via `OpenapiFirst.load` |
|
41
99
|
| `raise_error:` | `false` (default), `true` | If set to true the middleware raises `OpenapiFirst::RequestInvalidError` or `OpenapiFirst::NotFoundError` instead of returning 4xx. |
|
42
|
-
| `error_response:` | `:default` (default), `:jsonapi`, Your implementation of `ErrorResponse` |
|
100
|
+
| `error_response:` | `:default` (default), `:jsonapi`, Your implementation of `ErrorResponse` or `false` to disable responding |
|
43
101
|
|
44
102
|
#### Error responses
|
45
103
|
|
@@ -174,67 +232,6 @@ class TripsApiTest < ActionDispatch::IntegrationTest
|
|
174
232
|
end
|
175
233
|
```
|
176
234
|
|
177
|
-
## Manual use
|
178
|
-
|
179
|
-
Load the API description:
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
require 'openapi_first'
|
183
|
-
|
184
|
-
definition = OpenapiFirst.load('openapi.yaml')
|
185
|
-
```
|
186
|
-
|
187
|
-
### Validate request
|
188
|
-
|
189
|
-
```ruby
|
190
|
-
# Find and validate request
|
191
|
-
rack_request = Rack::Request.new(env)
|
192
|
-
validated_request = definition.validate_request(rack_request)
|
193
|
-
# Or raise an exception if validation fails:
|
194
|
-
definition.validate_request(rack_request, raise_error: true) # Raises OpenapiFirst::RequestInvalidError or OpenapiFirst::NotFoundError if request is invalid
|
195
|
-
|
196
|
-
# Inspect the request and access parsed parameters
|
197
|
-
validated_request.known? # Is the request defined in the API description?
|
198
|
-
validated_request.valid? # => true / false
|
199
|
-
validated_request.invalid? # => true / false
|
200
|
-
validated_request.error # => Failure object if request is invalid
|
201
|
-
validated_request.parsed_params # Merged parsed path, query parameters and request body
|
202
|
-
validated_request.parsed_body
|
203
|
-
validated_request.parsed_path_parameters # => { "pet_id" => 42 }
|
204
|
-
validated_request.parsed_headers
|
205
|
-
validated_request.parsed_cookies
|
206
|
-
validated_request.parsed_query
|
207
|
-
|
208
|
-
# Access the Openapi 3 Operation Object Hash
|
209
|
-
validated_request.operation['x-foo']
|
210
|
-
validated_request.operation['operationId']
|
211
|
-
# or the whole request definition
|
212
|
-
validated_request.request_definition.path # => "/pets/{petId}"
|
213
|
-
validated_request.request_definition.operation_id # => "showPetById"
|
214
|
-
```
|
215
|
-
|
216
|
-
### Validate response
|
217
|
-
|
218
|
-
```ruby
|
219
|
-
# Find and validate the response
|
220
|
-
rack_response = Rack::Response[*app.call(env)]
|
221
|
-
validated_response = definition.validate_response(rack_request, rack_response)
|
222
|
-
|
223
|
-
# Raise an exception if validation fails:
|
224
|
-
definition.validate_response(rack_request,rack_response, raise_error: true) # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError
|
225
|
-
|
226
|
-
# Inspect the response and access parsed parameters and
|
227
|
-
response.known? # Is the response defined in the API description?
|
228
|
-
response.valid? # => true / false
|
229
|
-
response.invalid? # => true / false
|
230
|
-
response.error # => Failure object if response is invalid
|
231
|
-
response.status # => 200
|
232
|
-
response.parsed_body
|
233
|
-
response.parsed_headers
|
234
|
-
```
|
235
|
-
|
236
|
-
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
237
|
-
|
238
235
|
## Configuration
|
239
236
|
|
240
237
|
You can configure default options globally:
|
@@ -10,11 +10,13 @@ module OpenapiFirst
|
|
10
10
|
# :raise_error A Boolean indicating whether to raise an error if validation fails.
|
11
11
|
# default: false
|
12
12
|
# :error_response The Class to use for error responses.
|
13
|
+
# This can be a Symbol-name of an registered error response (:default, :jsonapi)
|
14
|
+
# or it can be set to false to disable returning a response.
|
13
15
|
# default: OpenapiFirst::Plugins::Default::ErrorResponse (Config.default_options.error_response)
|
14
16
|
def initialize(app, options = {})
|
15
17
|
@app = app
|
16
18
|
@raise = options.fetch(:raise_error, OpenapiFirst.configuration.request_validation_raise_error)
|
17
|
-
@error_response_class =
|
19
|
+
@error_response_class = error_response_option(options[:error_response])
|
18
20
|
|
19
21
|
spec = options.fetch(:spec)
|
20
22
|
raise "You have to pass spec: when initializing #{self.class}" unless spec
|
@@ -29,17 +31,18 @@ module OpenapiFirst
|
|
29
31
|
validated = @definition.validate_request(Rack::Request.new(env), raise_error: @raise)
|
30
32
|
env[REQUEST] = validated
|
31
33
|
failure = validated.error
|
32
|
-
return @error_response_class.new(failure:).render if failure
|
34
|
+
return @error_response_class.new(failure:).render if failure && @error_response_class
|
33
35
|
|
34
36
|
@app.call(env)
|
35
37
|
end
|
36
38
|
|
37
39
|
private
|
38
40
|
|
39
|
-
def
|
40
|
-
return
|
41
|
+
def error_response_option(value)
|
42
|
+
return if value == false
|
43
|
+
return OpenapiFirst.find_error_response(value) if value.is_a?(Symbol)
|
41
44
|
|
42
|
-
|
45
|
+
value || OpenapiFirst.configuration.request_validation_error_response
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|
@@ -5,8 +5,8 @@ module OpenapiFirst
|
|
5
5
|
# @visibility private
|
6
6
|
class PathTemplate
|
7
7
|
# See also https://spec.openapis.org/oas/v3.1.0#path-templating
|
8
|
-
TEMPLATE_EXPRESSION = /(\{[^}]+\})/
|
9
|
-
TEMPLATE_EXPRESSION_NAME = /\{([^}]+)\}/
|
8
|
+
TEMPLATE_EXPRESSION = /(\{[^{}]+\})/
|
9
|
+
TEMPLATE_EXPRESSION_NAME = /\{([^{}]+)\}/
|
10
10
|
ALLOWED_PARAMETER_CHARACTERS = %r{([^/?#]+)}
|
11
11
|
|
12
12
|
def self.template?(string)
|
data/lib/openapi_first/test.rb
CHANGED
@@ -5,15 +5,24 @@ require_relative 'test/methods'
|
|
5
5
|
module OpenapiFirst
|
6
6
|
# Test integration
|
7
7
|
module Test
|
8
|
+
class NotRegisteredError < StandardError; end
|
9
|
+
|
10
|
+
DEFINITIONS = {} # rubocop:disable Style/MutableConstant
|
11
|
+
|
12
|
+
def self.definitions = DEFINITIONS
|
13
|
+
|
8
14
|
def self.register(path, as: :default)
|
9
|
-
|
10
|
-
@registry[as] = OpenapiFirst.load(path)
|
15
|
+
definitions[as] = OpenapiFirst.load(path)
|
11
16
|
end
|
12
17
|
|
13
18
|
def self.[](api)
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
definitions.fetch(api) do
|
20
|
+
option = api == :default ? '' : ", as: #{api.inspect}"
|
21
|
+
raise(NotRegisteredError,
|
22
|
+
"API description '#{api.inspect}' not found." \
|
23
|
+
"Please call OpenapiFirst::Test.register('myopenapi.yaml'#{option}) " \
|
24
|
+
'once before calling assert_api_conform.')
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
19
28
|
end
|
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: 2.0.
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hana
|