openapi_first 0.9.0 → 0.10.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/CHANGELOG.md +3 -0
- data/Gemfile.lock +9 -8
- data/README.md +49 -55
- data/benchmarks/Gemfile.lock +6 -5
- data/lib/openapi_first/operation.rb +23 -5
- data/lib/openapi_first/utils.rb +5 -0
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7c0190540a81a93de98accc25eac31681aa77997282744083639e75e032a65b
|
4
|
+
data.tar.gz: '08c6b1f8bee0682a75672e77e8831d30fe15a466ad72cdd6ea901c93b4c0361d'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb1e60e214237e73304f5e4abc7611b6516072b7610e2bacfe9040d9fc91299e786fbfc64c695702aca891981a3eb993d0f8a2f75007fa2637f659e5ab4a635c
|
7
|
+
data.tar.gz: ab281285bda7e922497885c3751eaa9e34419ba2a8f9a20b2b9dc9bf693d9f2248ed998347527430485edf123d715dbb7932d516f5f2af22aae95910f05e7d58
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
openapi_first (0.
|
4
|
+
openapi_first (0.10.0)
|
5
|
+
deep_merge (>= 1.2.1)
|
5
6
|
hanami-router (~> 2.0.alpha2)
|
6
7
|
hanami-utils (~> 2.0.alpha1)
|
7
8
|
json_schemer (~> 0.2)
|
@@ -12,12 +13,12 @@ PATH
|
|
12
13
|
GEM
|
13
14
|
remote: https://rubygems.org/
|
14
15
|
specs:
|
15
|
-
activesupport (6.0.
|
16
|
+
activesupport (6.0.3)
|
16
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
18
|
i18n (>= 0.7, < 2)
|
18
19
|
minitest (~> 5.1)
|
19
20
|
tzinfo (~> 1.1)
|
20
|
-
zeitwerk (~> 2.2)
|
21
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
21
22
|
addressable (2.7.0)
|
22
23
|
public_suffix (>= 2.0.2, < 5.0)
|
23
24
|
ast (2.4.0)
|
@@ -28,7 +29,7 @@ GEM
|
|
28
29
|
diff-lcs (1.3)
|
29
30
|
ecma-re-validator (0.2.1)
|
30
31
|
regexp_parser (~> 1.2)
|
31
|
-
hana (1.3.
|
32
|
+
hana (1.3.6)
|
32
33
|
hanami-router (2.0.0.alpha2)
|
33
34
|
mustermann (~> 1.0)
|
34
35
|
mustermann-contrib (~> 1.0)
|
@@ -66,7 +67,7 @@ GEM
|
|
66
67
|
mustermann-contrib (~> 1.1.1)
|
67
68
|
nokogiri
|
68
69
|
parallel (1.19.1)
|
69
|
-
parser (2.7.1.
|
70
|
+
parser (2.7.1.2)
|
70
71
|
ast (~> 2.4.0)
|
71
72
|
pry (0.13.1)
|
72
73
|
coderay (~> 1.1)
|
@@ -83,15 +84,15 @@ GEM
|
|
83
84
|
rspec-core (~> 3.9.0)
|
84
85
|
rspec-expectations (~> 3.9.0)
|
85
86
|
rspec-mocks (~> 3.9.0)
|
86
|
-
rspec-core (3.9.
|
87
|
-
rspec-support (~> 3.9.
|
87
|
+
rspec-core (3.9.2)
|
88
|
+
rspec-support (~> 3.9.3)
|
88
89
|
rspec-expectations (3.9.1)
|
89
90
|
diff-lcs (>= 1.2.0, < 2.0)
|
90
91
|
rspec-support (~> 3.9.0)
|
91
92
|
rspec-mocks (3.9.1)
|
92
93
|
diff-lcs (>= 1.2.0, < 2.0)
|
93
94
|
rspec-support (~> 3.9.0)
|
94
|
-
rspec-support (3.9.
|
95
|
+
rspec-support (3.9.3)
|
95
96
|
rubocop (0.82.0)
|
96
97
|
jaro_winkler (~> 1.5.1)
|
97
98
|
parallel (~> 1.10)
|
data/README.md
CHANGED
@@ -1,13 +1,32 @@
|
|
1
1
|
# OpenapiFirst
|
2
2
|
|
3
|
-
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
|
4
|
-
|
5
|
-
## TL;DR
|
3
|
+
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.
|
6
4
|
|
7
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.
|
8
|
-
In the following examples, the OpenAPI file is named `openapi/openapi.yaml`.
|
9
6
|
|
10
|
-
|
7
|
+
## Rack middlewares
|
8
|
+
OpenapiFirst consists of these Rack middlewares:
|
9
|
+
|
10
|
+
- `OpenapiFirst::Router` finds the operation for the current request or returns 404 if no operation was found.
|
11
|
+
- `OpenapiFirst::RequestValidation` validates the request against the found operation and returns 400 if the request is invalid.
|
12
|
+
- `OpenapiFirst::OperationResolver` calls the [handler](#handlers) found for the operation.
|
13
|
+
|
14
|
+
## Usage within your Rack webframework
|
15
|
+
If you just want to use the request validation part without any handlers you can use the rack middlewares standalone:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
use OpenapiFirst::Router, spec: OpenapiFirst.load('./openapi/openapi.yaml')
|
19
|
+
use OpenapiFirst::RequestValidation
|
20
|
+
```
|
21
|
+
|
22
|
+
### Rack env variables
|
23
|
+
These variables will available in your rack env:
|
24
|
+
|
25
|
+
- `env[OpenapiFirst::OPERATION]` - Holds an Operation object that responsed about `operation_id` and `path`. This is useful for introspection.
|
26
|
+
- `env[OpenapiFirst::INBOX]`. Holds the (filtered) path and query parameters and the parsed request body.
|
27
|
+
|
28
|
+
## Standalone usage
|
29
|
+
You can implement your API in conveniently with just OpenapiFirst.
|
11
30
|
|
12
31
|
```ruby
|
13
32
|
module Pets
|
@@ -24,7 +43,7 @@ require 'openapi_first'
|
|
24
43
|
run OpenapiFirst.app('./openapi/openapi.yaml', namespace: Pets)
|
25
44
|
```
|
26
45
|
|
27
|
-
The above will:
|
46
|
+
The above will use the mentioned Rack middlewares to:
|
28
47
|
|
29
48
|
- Validate the request and respond with 400 if the request does not match with your API description
|
30
49
|
- Map the request to a method call `Pets.find_pet` based on the `operationId` in the API description
|
@@ -35,48 +54,10 @@ Handler functions (`find_pet`) are called with two arguments:
|
|
35
54
|
- `params` - Holds the parsed request body, filtered query params and path parameters
|
36
55
|
- `res` - Holds a Rack::Response that you can modify if needed
|
37
56
|
If you want to access to plain Rack env you can call `params.env`.
|
38
|
-
|
39
|
-
## Rack middlewares
|
40
|
-
OpenapiFirst consists of these Rack middlewares:
|
41
57
|
|
42
|
-
|
43
|
-
- `OpenapiFirst::RequestValidation` validates the request and returns 400 if it's invalid
|
44
|
-
- `OpenapiFirst::OperationResolver` calls the handler
|
58
|
+
### Handlers
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
## Usage within your Rack webframework
|
49
|
-
If you just want to use the request validation part without any handlers you can use the rack middlewares standalone and don't need to pass a `namespace` option:
|
50
|
-
|
51
|
-
```ruby
|
52
|
-
use OpenapiFirst::Router, spec: OpenapiFirst.load('./openapi/openapi.yaml')
|
53
|
-
use OpenapiFirst::RequestValidation
|
54
|
-
```
|
55
|
-
|
56
|
-
### Rack env variables
|
57
|
-
These variables will available in your rack env:
|
58
|
-
|
59
|
-
- `env[OpenapiFirst::OPERATION]` - Holds an Operation object that responsed about `operation_id` and `path`. This is useful for introspection.
|
60
|
-
- `env[OpenapiFirst::INBOX]`. Holds the (filtered) path and query parameters and the response body.
|
61
|
-
|
62
|
-
## Try it out
|
63
|
-
|
64
|
-
See [examples](examples).
|
65
|
-
|
66
|
-
|
67
|
-
## Installation
|
68
|
-
|
69
|
-
Add this line to your application's Gemfile:
|
70
|
-
|
71
|
-
```ruby
|
72
|
-
gem 'openapi_first'
|
73
|
-
```
|
74
|
-
|
75
|
-
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
76
|
-
|
77
|
-
## Handlers
|
78
|
-
|
79
|
-
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.
|
60
|
+
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.
|
80
61
|
|
81
62
|
It works like this:
|
82
63
|
|
@@ -96,6 +77,28 @@ There are two ways to set the response body:
|
|
96
77
|
- Calling `res.write "things"` (see [Rack::Response](https://www.rubydoc.info/github/rack/rack/Rack/Response))
|
97
78
|
- Returning a value from the function (see example above) (this will always converted to JSON)
|
98
79
|
|
80
|
+
### If your API description does not contain all endpoints
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
run OpenapiFirst.middleware('./openapi/openapi.yaml', namespace: Pets)
|
84
|
+
```
|
85
|
+
|
86
|
+
Here all requests that are not part of the API description will be passed to the next app.
|
87
|
+
|
88
|
+
### Try it out
|
89
|
+
|
90
|
+
See [examples](examples).
|
91
|
+
|
92
|
+
## Installation
|
93
|
+
|
94
|
+
Add this line to your application's Gemfile:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
gem 'openapi_first'
|
98
|
+
```
|
99
|
+
|
100
|
+
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
101
|
+
|
99
102
|
## Request validation
|
100
103
|
|
101
104
|
If the request is not valid, these middlewares return a 400 status code with a body that describes the error.
|
@@ -151,14 +154,6 @@ validator = OpenapiFirst::ResponseValidator.new(spec)
|
|
151
154
|
expect(validator.validate(last_request, last_response).errors).to be_empty
|
152
155
|
```
|
153
156
|
|
154
|
-
## If your API description does not contain all endpoints
|
155
|
-
|
156
|
-
```ruby
|
157
|
-
run OpenapiFirst.middleware('./openapi/openapi.yaml', namespace: Pets)
|
158
|
-
```
|
159
|
-
|
160
|
-
Here all requests that are not part of the API description will be passed to the next app.
|
161
|
-
|
162
157
|
## Handling only certain paths
|
163
158
|
|
164
159
|
You can filter the URIs that should be handled by passing `only` to `OpenapiFirst.load`:
|
@@ -168,7 +163,6 @@ spec = OpenapiFirst.load './openapi/openapi.yaml', only: '/pets'.method(:==)
|
|
168
163
|
run OpenapiFirst.app(spec, namespace: Pets)
|
169
164
|
```
|
170
165
|
|
171
|
-
|
172
166
|
## Coverage
|
173
167
|
|
174
168
|
(This is a bit experimental. Please try it out and give feedback.)
|
data/benchmarks/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
openapi_first (0.
|
4
|
+
openapi_first (0.10.0)
|
5
|
+
deep_merge (>= 1.2.1)
|
5
6
|
hanami-router (~> 2.0.alpha2)
|
6
7
|
hanami-utils (~> 2.0.alpha1)
|
7
8
|
json_schemer (~> 0.2)
|
@@ -12,15 +13,15 @@ PATH
|
|
12
13
|
GEM
|
13
14
|
remote: https://rubygems.org/
|
14
15
|
specs:
|
15
|
-
activesupport (6.0.
|
16
|
+
activesupport (6.0.3)
|
16
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
18
|
i18n (>= 0.7, < 2)
|
18
19
|
minitest (~> 5.1)
|
19
20
|
tzinfo (~> 1.1)
|
20
|
-
zeitwerk (~> 2.2)
|
21
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
21
22
|
addressable (2.7.0)
|
22
23
|
public_suffix (>= 2.0.2, < 5.0)
|
23
|
-
benchmark-ips (2.
|
24
|
+
benchmark-ips (2.8.2)
|
24
25
|
benchmark-memory (0.1.2)
|
25
26
|
memory_profiler (~> 0.9)
|
26
27
|
builder (3.2.4)
|
@@ -61,7 +62,7 @@ GEM
|
|
61
62
|
mustermann-grape (~> 1.0.0)
|
62
63
|
rack (>= 1.3.0)
|
63
64
|
rack-accept
|
64
|
-
hana (1.3.
|
65
|
+
hana (1.3.6)
|
65
66
|
hanami-router (2.0.0.alpha2)
|
66
67
|
mustermann (~> 1.0)
|
67
68
|
mustermann-contrib (~> 1.0)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
|
+
require_relative 'utils'
|
4
5
|
|
5
6
|
module OpenapiFirst
|
6
7
|
class Operation
|
@@ -39,14 +40,31 @@ module OpenapiFirst
|
|
39
40
|
def build_parameters_json_schema
|
40
41
|
return unless @operation.parameters&.any?
|
41
42
|
|
42
|
-
@operation.parameters.each_with_object(
|
43
|
+
@operation.parameters.each_with_object(new_node) do |parameter, schema|
|
44
|
+
params = Rack::Utils.parse_nested_query(parameter.name)
|
45
|
+
generate_schema(schema, params, parameter)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_schema(schema, params, parameter)
|
50
|
+
params.each do |key, value|
|
51
|
+
schema['required'] << key if parameter.required
|
52
|
+
if value.is_a? Hash
|
53
|
+
property_schema = new_node
|
54
|
+
generate_schema(property_schema, value, parameter)
|
55
|
+
Utils.deep_merge!(schema['properties'], { key => property_schema })
|
56
|
+
else
|
57
|
+
schema['properties'][key] = parameter.schema
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def new_node
|
63
|
+
{
|
43
64
|
'type' => 'object',
|
44
65
|
'required' => [],
|
45
66
|
'properties' => {}
|
46
|
-
|
47
|
-
schema['required'] << parameter.name if parameter.required
|
48
|
-
schema['properties'][parameter.name] = parameter.schema
|
49
|
-
end
|
67
|
+
}
|
50
68
|
end
|
51
69
|
end
|
52
70
|
end
|
data/lib/openapi_first/utils.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'hanami/utils/string'
|
4
|
+
require 'deep_merge/core'
|
4
5
|
|
5
6
|
module OpenapiFirst
|
6
7
|
module Utils
|
8
|
+
def self.deep_merge!(dest, source)
|
9
|
+
DeepMerge.deep_merge!(source, dest)
|
10
|
+
end
|
11
|
+
|
7
12
|
def self.underscore(string)
|
8
13
|
Hanami::Utils::String.underscore(string)
|
9
14
|
end
|
data/openapi_first.gemspec
CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.bindir = 'exe'
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
|
+
spec.add_dependency 'deep_merge', '>= 1.2.1'
|
35
36
|
spec.add_dependency 'hanami-router', '~> 2.0.alpha2'
|
36
37
|
spec.add_dependency 'hanami-utils', '~> 2.0.alpha1'
|
37
38
|
spec.add_dependency 'json_schemer', '~> 0.2'
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: deep_merge
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: hanami-router
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|