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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50a431b59d4568cede6b8a110ada173691c698517231299795ee22fc8f54115a
4
- data.tar.gz: be49aa45dd7b95ee0ec2149b4ec6476c78a42a0e46204c321e5350368bee2d54
3
+ metadata.gz: e7c0190540a81a93de98accc25eac31681aa77997282744083639e75e032a65b
4
+ data.tar.gz: '08c6b1f8bee0682a75672e77e8831d30fe15a466ad72cdd6ea901c93b4c0361d'
5
5
  SHA512:
6
- metadata.gz: b6b29dab24a951716e9fb73a7f852a4acd3de577cce82008cd329906ef77d73c4cff182face7adb1d36e9ecb9ea170d1655accb9cdc3bba682a818c3c81000b0
7
- data.tar.gz: d0ad9c915cb4e637dce9ff4f0e6a8bc35cf705c684f69f38d9abbc8edada4b8aef0d2ba226cfae187f9d80da3a229e0f9493ac78bddb0045206ed816cbfe022e
6
+ metadata.gz: cb1e60e214237e73304f5e4abc7611b6516072b7610e2bacfe9040d9fc91299e786fbfc64c695702aca891981a3eb993d0f8a2f75007fa2637f659e5ab4a635c
7
+ data.tar.gz: ab281285bda7e922497885c3751eaa9e34419ba2a8f9a20b2b9dc9bf693d9f2248ed998347527430485edf123d715dbb7932d516f5f2af22aae95910f05e7d58
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+ - Add support for query parameters named `"some[thing]"` ([issue](https://github.com/ahx/openapi_first/issues/40))
5
+
3
6
  ## 0.9.0
4
7
  - Make request validation usable standalone
5
8
 
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openapi_first (0.9.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.2.2)
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.5)
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.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.1)
87
- rspec-support (~> 3.9.1)
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.2)
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 a method that returns data and implements your business logic and be done.
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
- Now implement your API:
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
- - `OpenapiFirst::Router` finds the operation for the current request and finds a handler (if namespace option is given)
43
- - `OpenapiFirst::RequestValidation` validates the request and returns 400 if it's invalid
44
- - `OpenapiFirst::OperationResolver` calls the handler
58
+ ### Handlers
45
59
 
46
- Instead of using `OpenapiFirst.app` you can use these middlwares by itself.
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.)
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- openapi_first (0.9.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.2.2)
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.7.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.5)
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
- ) do |parameter, schema|
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
@@ -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.9.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-04-24 00:00:00.000000000 Z
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