openapi_first 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|