hanami-api 0.1.0 → 0.1.1
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/.github/workflows/ci.yml +43 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -1
- data/README.md +58 -15
- data/lib/hanami/api.rb +20 -31
- data/lib/hanami/api/block/context.rb +17 -0
- data/lib/hanami/api/middleware.rb +26 -55
- data/lib/hanami/api/middleware/app.rb +41 -0
- data/lib/hanami/api/middleware/node.rb +61 -0
- data/lib/hanami/api/middleware/trie.rb +71 -0
- data/lib/hanami/api/router.rb +7 -9
- data/lib/hanami/api/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33a0b691f992906407c5d0c6bdf4c844d6b7ff4deb420b88f1e73c45768f210f
|
4
|
+
data.tar.gz: 01a7c8f0972e5ea9a1eff88c81d5966a9c22192c99ca9e3416cfdba9c9fdbc38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a40490671a64ebdf57939a470de03045f29224859453465aa5ed60d39c19d2d8f2d342f8cd7116a90ae6e754b761b609f0cabc6d1ed7daac3df991ac0c25b2ce
|
7
|
+
data.tar.gz: f8791ad3eaa0be2b0e114f124d5e193b5a112ea3a4282c01ebc21a5c860c37e367484ade01f44392962853c6126511f30f9fc32829ebb078e2a86b1a47583358
|
@@ -0,0 +1,43 @@
|
|
1
|
+
name: ci
|
2
|
+
|
3
|
+
"on":
|
4
|
+
push:
|
5
|
+
paths:
|
6
|
+
- ".github/workflows/ci.yml"
|
7
|
+
- "lib/**"
|
8
|
+
- "*.gemspec"
|
9
|
+
- "spec/**"
|
10
|
+
- "Rakefile"
|
11
|
+
- "Gemfile"
|
12
|
+
- ".rubocop.yml"
|
13
|
+
pull_request:
|
14
|
+
branches:
|
15
|
+
- master
|
16
|
+
create:
|
17
|
+
|
18
|
+
jobs:
|
19
|
+
tests:
|
20
|
+
runs-on: ubuntu-latest
|
21
|
+
strategy:
|
22
|
+
fail-fast: false
|
23
|
+
matrix:
|
24
|
+
ruby:
|
25
|
+
- "2.7"
|
26
|
+
env:
|
27
|
+
CODACY_RUN_LOCAL: true
|
28
|
+
CODACY_PROJECT_TOKEN: ${{secrets.CODACY_PROJECT_TOKEN}}
|
29
|
+
steps:
|
30
|
+
- uses: actions/checkout@v1
|
31
|
+
- name: Install package dependencies
|
32
|
+
run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
|
33
|
+
- name: Set up Ruby
|
34
|
+
uses: ruby/setup-ruby@v1
|
35
|
+
with:
|
36
|
+
ruby-version: ${{matrix.ruby}}
|
37
|
+
- name: Install latest bundler
|
38
|
+
run: |
|
39
|
+
gem install bundler --no-document
|
40
|
+
- name: Bundle install
|
41
|
+
run: bundle install --jobs 4 --retry 3
|
42
|
+
- name: Run all tests
|
43
|
+
run: bundle exec rake
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
# Hanami::API
|
2
2
|
Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
3
3
|
|
4
|
+
## v0.1.1 - 2020-05-20
|
5
|
+
### Fixed
|
6
|
+
- [Luca Guidi] Ensure Rack middleware to be mounted in scopes without a leading slash
|
7
|
+
- [Luca Guidi] Ensure nested scopes to use the given middleware stack
|
8
|
+
- [Luca Guidi] Ensure nested scopes to inherit middleware from outer scopes
|
9
|
+
|
4
10
|
## v0.1.0 - 2020-02-19
|
5
11
|
### Added
|
12
|
+
- [Luca Guidi] Allow to use Rack middleware with scope visibility
|
13
|
+
- [Luca Guidi] Block syntax: introduced `json` to render JSON response body
|
14
|
+
- [Luca Guidi] Block syntax: introduced `redirect` to perform HTTP redirect
|
15
|
+
- [Luca Guidi] Block syntax: introduced `halt` to interrupt the execution flow and return a HTTP status and body
|
16
|
+
- [Luca Guidi] Block syntax: introduced `status`, `headers`, `body` that act both as getters and setters for the response values
|
17
|
+
- [Luca Guidi] Block syntax: introduced `params` getter
|
6
18
|
- [Luca Guidi] Introduced `Hanami::API` superclass
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,35 @@
|
|
1
1
|
# Hanami::API
|
2
2
|
|
3
3
|
Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
4
|
+
* [Installation](#installation)
|
5
|
+
* [Performance](#performance)
|
6
|
+
+ [Runtime](#runtime)
|
7
|
+
+ [Memory](#memory)
|
8
|
+
+ [Requests per second](#requests-per-second)
|
9
|
+
* [Usage](#usage)
|
10
|
+
+ [Routes](#routes)
|
11
|
+
+ [HTTP methods](#http-methods)
|
12
|
+
+ [Endpoints](#endpoints)
|
13
|
+
- [Rack endpoint](#rack-endpoint)
|
14
|
+
- [Block endpoint](#block-endpoint)
|
15
|
+
* [String (body)](#string-body)
|
16
|
+
* [Integer (status code)](#integer-status-code)
|
17
|
+
* [Integer, String (status code, body)](#integer-string-status-code-body)
|
18
|
+
* [Integer, Hash, String (status code, headers, body)](#integer-hash-string-status-code-headers-body)
|
19
|
+
+ [Block context](#block-context)
|
20
|
+
- [env](#env)
|
21
|
+
- [status](#status)
|
22
|
+
- [headers](#headers)
|
23
|
+
- [body](#body)
|
24
|
+
- [params](#params)
|
25
|
+
- [halt](#halt)
|
26
|
+
- [redirect](#redirect)
|
27
|
+
- [back](#back)
|
28
|
+
- [json](#json)
|
29
|
+
+ [Scope](#scope)
|
30
|
+
+ [Rack Middleware](#rack-middleware)
|
31
|
+
* [Development](#development)
|
32
|
+
* [Contributing](#contributing)
|
4
33
|
|
5
34
|
## Installation
|
6
35
|
|
@@ -33,15 +62,14 @@ Runtime to complete 20,000 requests (lower is better).
|
|
33
62
|
|
34
63
|
| Framework | Seconds to complete |
|
35
64
|
|------------|---------------------|
|
36
|
-
| hanami-api | 0.
|
37
|
-
| watts | 0.
|
38
|
-
| roda | 0.
|
39
|
-
| syro | 0.
|
40
|
-
| rack-app | 0.
|
41
|
-
| cuba | 1.
|
42
|
-
| rails | 17.
|
43
|
-
|
|
44
|
-
| sinatra | 197.47695700009353 |
|
65
|
+
| hanami-api | 0.116 |
|
66
|
+
| watts | 0.235 |
|
67
|
+
| roda | 0.348 |
|
68
|
+
| syro | 0.356 |
|
69
|
+
| rack-app | 0.623 |
|
70
|
+
| cuba | 1.291 |
|
71
|
+
| rails | 17.047 |
|
72
|
+
| sinatra | 197.477 |
|
45
73
|
|
46
74
|
### Memory
|
47
75
|
|
@@ -57,7 +85,22 @@ Memory footprint for 10,000 routes app (lower is better).
|
|
57
85
|
| watts | 84956 |
|
58
86
|
| sinatra | 124980 |
|
59
87
|
| rails | 143048 |
|
60
|
-
|
88
|
+
|
89
|
+
### Requests per second
|
90
|
+
|
91
|
+
For this benchmark there are two apps for each framework: one with the root route, and one with 10,000 routes.
|
92
|
+
Requests per second hitting the 1st (and only route) and the 10,000th route to measure the best and worst case scenario (higher is better).
|
93
|
+
|
94
|
+
| Framework | 1st route | 10,000th route |
|
95
|
+
|------------|-----------|----------------|
|
96
|
+
| hanami-api | 14719.95 | 14290.20 |
|
97
|
+
| watts | 13912.31 | 12609.68 |
|
98
|
+
| roda | 13965.20 | 11051.27 |
|
99
|
+
| syro | 13079.12 | 10689.51 |
|
100
|
+
| rack-app | 10274.01 | 10306.46 |
|
101
|
+
| cuba | 13061.82 | 7084.33 |
|
102
|
+
| rails | 1345.27 | 303.06 |
|
103
|
+
| sinatra | 5038.74 | 28.14 |
|
61
104
|
|
62
105
|
## Usage
|
63
106
|
|
@@ -123,7 +166,7 @@ get "/", to: MyRackEndpoint.new
|
|
123
166
|
A block passed to the route definition is named a block endpoint.
|
124
167
|
The returning value will compose the Rack response. It can be:
|
125
168
|
|
126
|
-
##### String
|
169
|
+
##### String (body)
|
127
170
|
|
128
171
|
```ruby
|
129
172
|
get "/" do
|
@@ -133,7 +176,7 @@ end
|
|
133
176
|
|
134
177
|
It will return `[200, {}, ["Hello, world"]]`
|
135
178
|
|
136
|
-
##### Integer
|
179
|
+
##### Integer (status code)
|
137
180
|
|
138
181
|
```ruby
|
139
182
|
get "/" do
|
@@ -143,7 +186,7 @@ end
|
|
143
186
|
|
144
187
|
It will return `[418, {}, ["I'm a teapot"]]`
|
145
188
|
|
146
|
-
##### Integer, String
|
189
|
+
##### Integer, String (status code, body)
|
147
190
|
|
148
191
|
```ruby
|
149
192
|
get "/" do
|
@@ -153,7 +196,7 @@ end
|
|
153
196
|
|
154
197
|
It will return `[401, {}, ["You shall not pass"]]`
|
155
198
|
|
156
|
-
##### Integer, Hash, String
|
199
|
+
##### Integer, Hash, String (status code, headers, body)
|
157
200
|
|
158
201
|
```ruby
|
159
202
|
get "/" do
|
@@ -220,7 +263,7 @@ get "/" do
|
|
220
263
|
end
|
221
264
|
```
|
222
265
|
|
223
|
-
|
266
|
+
Set HTTP response body
|
224
267
|
|
225
268
|
```ruby
|
226
269
|
get "/" do
|
data/lib/hanami/api.rb
CHANGED
@@ -16,19 +16,14 @@ module Hanami
|
|
16
16
|
super
|
17
17
|
|
18
18
|
app.class_eval do
|
19
|
-
@
|
20
|
-
@stack = Middleware::Stack.new
|
19
|
+
@router = Router.new
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
23
|
class << self
|
25
|
-
# @since 0.1.
|
24
|
+
# @since 0.1.1
|
26
25
|
# @api private
|
27
|
-
attr_reader :
|
28
|
-
|
29
|
-
# @since 0.1.0
|
30
|
-
# @api private
|
31
|
-
attr_reader :stack
|
26
|
+
attr_reader :router
|
32
27
|
end
|
33
28
|
|
34
29
|
# Defines a named root route (a GET route for "/")
|
@@ -56,7 +51,7 @@ module Hanami
|
|
56
51
|
# end
|
57
52
|
# end
|
58
53
|
def self.root(*args, **kwargs, &blk)
|
59
|
-
@
|
54
|
+
@router.root(*args, **kwargs, &blk)
|
60
55
|
end
|
61
56
|
|
62
57
|
# Defines a route that accepts GET requests for the given path.
|
@@ -93,7 +88,7 @@ module Hanami
|
|
93
88
|
# get "/users/:id", to: ->(*) { [200, {}, ["OK"]] }, id: /\d+/
|
94
89
|
# end
|
95
90
|
def self.get(*args, **kwargs, &blk)
|
96
|
-
@
|
91
|
+
@router.get(*args, **kwargs, &blk)
|
97
92
|
end
|
98
93
|
|
99
94
|
# Defines a route that accepts POST requests for the given path.
|
@@ -108,7 +103,7 @@ module Hanami
|
|
108
103
|
#
|
109
104
|
# @see .get
|
110
105
|
def self.post(*args, **kwargs, &blk)
|
111
|
-
@
|
106
|
+
@router.post(*args, **kwargs, &blk)
|
112
107
|
end
|
113
108
|
|
114
109
|
# Defines a route that accepts PATCH requests for the given path.
|
@@ -123,7 +118,7 @@ module Hanami
|
|
123
118
|
#
|
124
119
|
# @see .get
|
125
120
|
def self.patch(*args, **kwargs, &blk)
|
126
|
-
@
|
121
|
+
@router.patch(*args, **kwargs, &blk)
|
127
122
|
end
|
128
123
|
|
129
124
|
# Defines a route that accepts PUT requests for the given path.
|
@@ -138,7 +133,7 @@ module Hanami
|
|
138
133
|
#
|
139
134
|
# @see .get
|
140
135
|
def self.put(*args, **kwargs, &blk)
|
141
|
-
@
|
136
|
+
@router.put(*args, **kwargs, &blk)
|
142
137
|
end
|
143
138
|
|
144
139
|
# Defines a route that accepts DELETE requests for the given path.
|
@@ -153,7 +148,7 @@ module Hanami
|
|
153
148
|
#
|
154
149
|
# @see .get
|
155
150
|
def self.delete(*args, **kwargs, &blk)
|
156
|
-
@
|
151
|
+
@router.delete(*args, **kwargs, &blk)
|
157
152
|
end
|
158
153
|
|
159
154
|
# Defines a route that accepts TRACE requests for the given path.
|
@@ -168,7 +163,7 @@ module Hanami
|
|
168
163
|
#
|
169
164
|
# @see .get
|
170
165
|
def self.trace(*args, **kwargs, &blk)
|
171
|
-
@
|
166
|
+
@router.trace(*args, **kwargs, &blk)
|
172
167
|
end
|
173
168
|
|
174
169
|
# Defines a route that accepts OPTIONS requests for the given path.
|
@@ -183,7 +178,7 @@ module Hanami
|
|
183
178
|
#
|
184
179
|
# @see .get
|
185
180
|
def self.options(*args, **kwargs, &blk)
|
186
|
-
@
|
181
|
+
@router.options(*args, **kwargs, &blk)
|
187
182
|
end
|
188
183
|
|
189
184
|
# Defines a route that accepts LINK requests for the given path.
|
@@ -198,7 +193,7 @@ module Hanami
|
|
198
193
|
#
|
199
194
|
# @see .get
|
200
195
|
def self.link(*args, **kwargs, &blk)
|
201
|
-
@
|
196
|
+
@router.link(*args, **kwargs, &blk)
|
202
197
|
end
|
203
198
|
|
204
199
|
# Defines a route that accepts UNLINK requests for the given path.
|
@@ -213,7 +208,7 @@ module Hanami
|
|
213
208
|
#
|
214
209
|
# @see .get
|
215
210
|
def self.unlink(*args, **kwargs, &blk)
|
216
|
-
@
|
211
|
+
@router.unlink(*args, **kwargs, &blk)
|
217
212
|
end
|
218
213
|
|
219
214
|
# Defines a route that redirects the incoming request to another path.
|
@@ -227,7 +222,7 @@ module Hanami
|
|
227
222
|
#
|
228
223
|
# @see .get
|
229
224
|
def self.redirect(*args, **kwargs, &blk)
|
230
|
-
@
|
225
|
+
@router.redirect(*args, **kwargs, &blk)
|
231
226
|
end
|
232
227
|
|
233
228
|
# Defines a routing scope. Routes defined in the context of a scope,
|
@@ -251,7 +246,7 @@ module Hanami
|
|
251
246
|
#
|
252
247
|
# # It generates a route with a path `/v1/users`
|
253
248
|
def self.scope(*args, **kwargs, &blk)
|
254
|
-
@
|
249
|
+
@router.scope(*args, **kwargs, &blk)
|
255
250
|
end
|
256
251
|
|
257
252
|
# Mount a Rack application at the specified path.
|
@@ -276,7 +271,7 @@ module Hanami
|
|
276
271
|
# mount MyRackApp.new, at: "/foo"
|
277
272
|
# end
|
278
273
|
def self.mount(*args, **kwargs, &blk)
|
279
|
-
@
|
274
|
+
@router.mount(*args, **kwargs, &blk)
|
280
275
|
end
|
281
276
|
|
282
277
|
# Use a Rack middleware
|
@@ -294,27 +289,21 @@ module Hanami
|
|
294
289
|
# use MyRackMiddleware
|
295
290
|
# end
|
296
291
|
def self.use(middleware, *args, &blk)
|
297
|
-
@
|
292
|
+
@router.use(middleware, *args, &blk)
|
298
293
|
end
|
299
294
|
|
300
295
|
# @since 0.1.0
|
301
|
-
def initialize(
|
302
|
-
@
|
303
|
-
@router = Router.new(stack: @stack) do
|
304
|
-
routes.each do |method_name, args, kwargs, blk|
|
305
|
-
send(method_name, *args, **kwargs, &blk)
|
306
|
-
end
|
307
|
-
end
|
296
|
+
def initialize(router: self.class.router)
|
297
|
+
@router = router
|
308
298
|
|
309
299
|
freeze
|
310
300
|
end
|
311
301
|
|
312
302
|
# @since 0.1.0
|
313
303
|
def freeze
|
314
|
-
@app = @
|
304
|
+
@app = @router.to_rack_app
|
315
305
|
@url_helpers = @router.url_helpers
|
316
306
|
@router.remove_instance_variable(:@url_helpers)
|
317
|
-
remove_instance_variable(:@stack)
|
318
307
|
remove_instance_variable(:@router)
|
319
308
|
@url_helpers.freeze
|
320
309
|
@app.freeze
|
@@ -7,6 +7,9 @@ require "json"
|
|
7
7
|
module Hanami
|
8
8
|
class API
|
9
9
|
module Block
|
10
|
+
# Execution context for Block syntax
|
11
|
+
#
|
12
|
+
# @since 0.1.0
|
10
13
|
class Context < Hanami::Router::Block::Context
|
11
14
|
# @overload body
|
12
15
|
# Gets the current HTTP response body
|
@@ -126,12 +129,24 @@ module Hanami
|
|
126
129
|
|
127
130
|
# @since 0.1.0
|
128
131
|
# @api private
|
132
|
+
#
|
133
|
+
# rubocop:disable Metrics/AbcSize
|
134
|
+
# rubocop:disable Metrics/MethodLength
|
129
135
|
def call
|
130
136
|
case caught
|
131
137
|
in String => body
|
132
138
|
[status, headers, [body]]
|
133
139
|
in Integer => status
|
140
|
+
# rubocop:disable Style/RedundantSelf
|
141
|
+
#
|
142
|
+
# NOTE: It must use `self.body` so it will pick the method defined above.
|
143
|
+
#
|
144
|
+
# If `self` isn't enforced, Ruby will try to bind `body` to
|
145
|
+
# the current pattern matching context.
|
146
|
+
# When that happens, the body that was manually set is ignored,
|
147
|
+
# which results in a bug.
|
134
148
|
[status, headers, [self.body || http_status(status)]]
|
149
|
+
# rubocop:enable Style/RedundantSelf
|
135
150
|
in [Integer, String] => response
|
136
151
|
[response[0], headers, [response[1]]]
|
137
152
|
in [Integer, Hash, String] => response
|
@@ -139,6 +154,8 @@ module Hanami
|
|
139
154
|
[response[0], headers, [response[2]]]
|
140
155
|
end
|
141
156
|
end
|
157
|
+
# rubocop:enable Metrics/MethodLength
|
158
|
+
# rubocop:enable Metrics/AbcSize
|
142
159
|
|
143
160
|
private
|
144
161
|
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack/builder"
|
4
|
-
|
5
3
|
module Hanami
|
6
4
|
class API
|
7
5
|
# Hanami::API middleware stack
|
@@ -9,6 +7,9 @@ module Hanami
|
|
9
7
|
# @since 0.1.0
|
10
8
|
# @api private
|
11
9
|
module Middleware
|
10
|
+
require "hanami/api/middleware/app"
|
11
|
+
require "hanami/api/middleware/trie"
|
12
|
+
|
12
13
|
# Middleware stack
|
13
14
|
#
|
14
15
|
# @since 0.1.0
|
@@ -16,76 +17,46 @@ module Hanami
|
|
16
17
|
class Stack
|
17
18
|
# @since 0.1.0
|
18
19
|
# @api private
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# @since 0.1.0
|
23
|
-
# @api private
|
24
|
-
def initialize
|
25
|
-
@prefix = ROOT_PREFIX
|
26
|
-
@stack = Hash.new { |hash, key| hash[key] = [] }
|
27
|
-
end
|
28
|
-
|
29
|
-
# @since 0.1.0
|
30
|
-
# @api private
|
31
|
-
def use(middleware, args, &blk)
|
32
|
-
@stack[@prefix].push([middleware, args, blk])
|
20
|
+
def initialize(prefix)
|
21
|
+
@prefix = prefix
|
22
|
+
@stack = {}
|
33
23
|
end
|
34
24
|
|
35
25
|
# @since 0.1.0
|
36
26
|
# @api private
|
37
|
-
def
|
38
|
-
prefix
|
39
|
-
@
|
40
|
-
|
41
|
-
ensure
|
42
|
-
@prefix = prefix
|
27
|
+
def use(path, middleware, *args, &blk)
|
28
|
+
# FIXME: test with prefix when Hanami::API.settings and prefix will be supported
|
29
|
+
@stack[path] ||= []
|
30
|
+
@stack[path].push([middleware, args, blk])
|
43
31
|
end
|
44
32
|
|
45
|
-
# @since 0.1.
|
33
|
+
# @since 0.1.1
|
46
34
|
# @api private
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
s = self
|
52
|
-
|
53
|
-
Rack::Builder.new do
|
54
|
-
s.each do |prefix, stack|
|
55
|
-
s.mapped(self, prefix) do
|
56
|
-
stack.each do |middleware, args, blk|
|
57
|
-
use(middleware, *args, &blk)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
run app
|
62
|
-
end
|
35
|
+
def to_hash
|
36
|
+
@stack.each_with_object({}) do |(path, _), result|
|
37
|
+
result[path] = stack_for(path)
|
63
38
|
end
|
64
39
|
end
|
65
40
|
|
66
|
-
# @since 0.1.
|
41
|
+
# @since 0.1.1
|
67
42
|
# @api private
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
end
|
43
|
+
def finalize(app)
|
44
|
+
mapping = to_hash
|
45
|
+
return app if mapping.empty?
|
72
46
|
|
73
|
-
|
74
|
-
# @api private
|
75
|
-
def mapped(builder, prefix, &blk)
|
76
|
-
if prefix == ROOT_PREFIX
|
77
|
-
builder.instance_eval(&blk)
|
78
|
-
else
|
79
|
-
builder.map(prefix, &blk)
|
80
|
-
end
|
47
|
+
App.new(app, mapping)
|
81
48
|
end
|
82
49
|
|
83
50
|
private
|
84
51
|
|
85
|
-
# @since 0.1.
|
52
|
+
# @since 0.1.1
|
86
53
|
# @api private
|
87
|
-
def
|
88
|
-
@stack.
|
54
|
+
def stack_for(current_path)
|
55
|
+
@stack.each_with_object([]) do |(path, stack), result|
|
56
|
+
next unless current_path.start_with?(path)
|
57
|
+
|
58
|
+
result.push(stack)
|
59
|
+
end.flatten(1)
|
89
60
|
end
|
90
61
|
end
|
91
62
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/builder"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class API
|
7
|
+
module Middleware
|
8
|
+
# Hanami::API middleware stack
|
9
|
+
#
|
10
|
+
# @since 0.1.1
|
11
|
+
# @api private
|
12
|
+
class App
|
13
|
+
# @since 0.1.1
|
14
|
+
# @api private
|
15
|
+
def initialize(app, mapping)
|
16
|
+
@trie = Hanami::API::Middleware::Trie.new(app)
|
17
|
+
|
18
|
+
mapping.each do |path, stack|
|
19
|
+
builder = Rack::Builder.new
|
20
|
+
|
21
|
+
stack.each do |middleware, args, blk|
|
22
|
+
builder.use(middleware, *args, &blk)
|
23
|
+
end
|
24
|
+
|
25
|
+
builder.run(app)
|
26
|
+
|
27
|
+
@trie.add(path, builder.to_app.freeze)
|
28
|
+
end
|
29
|
+
|
30
|
+
@trie.freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
# @since 0.1.1
|
34
|
+
# @api private
|
35
|
+
def call(env)
|
36
|
+
@trie.find(env["PATH_INFO"]).call(env)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
class API
|
5
|
+
module Middleware
|
6
|
+
# Trie node to register scopes with custom Rack middleware
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.1.1
|
10
|
+
class Node
|
11
|
+
# @api private
|
12
|
+
# @since 0.1.1
|
13
|
+
attr_reader :app
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
# @since 0.1.1
|
17
|
+
def initialize
|
18
|
+
@app = nil
|
19
|
+
@children = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
# @since 0.1.1
|
24
|
+
def freeze
|
25
|
+
@children.each(&:freeze)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
# @since 0.1.1
|
31
|
+
def put(segment)
|
32
|
+
@children[segment] ||= self.class.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
# @since 0.1.1
|
37
|
+
def get(segment)
|
38
|
+
@children.fetch(segment) { self if leaf? }
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
# @since 0.1.1
|
43
|
+
def app!(app)
|
44
|
+
@app = app
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
# @since 0.1.1
|
49
|
+
def app?
|
50
|
+
@app
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
# @since 0.1.1
|
55
|
+
def leaf?
|
56
|
+
@children.empty?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/api/middleware/node"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class API
|
7
|
+
module Middleware
|
8
|
+
# Trie to register scopes with custom Rack middleware
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.1.1
|
12
|
+
class Trie
|
13
|
+
# @api private
|
14
|
+
# @since 0.1.1
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
@root = Node.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
# @since 0.1.1
|
22
|
+
def freeze
|
23
|
+
@root.freeze
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
# @since 0.1.1
|
29
|
+
def add(path, app)
|
30
|
+
node = @root
|
31
|
+
for_each_segment(path) do |segment|
|
32
|
+
node = node.put(segment)
|
33
|
+
end
|
34
|
+
|
35
|
+
node.app!(app)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
# @since 0.1.1
|
40
|
+
def find(path)
|
41
|
+
node = @root
|
42
|
+
|
43
|
+
for_each_segment(path) do |segment|
|
44
|
+
break unless node
|
45
|
+
|
46
|
+
node = node.get(segment)
|
47
|
+
end
|
48
|
+
|
49
|
+
return node.app if node&.app?
|
50
|
+
|
51
|
+
@root.app || @app
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @since 0.1.1
|
56
|
+
def empty?
|
57
|
+
@root.leaf?
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
# @since 0.1.1
|
64
|
+
def for_each_segment(path, &blk)
|
65
|
+
_, *segments = path.split(/\//)
|
66
|
+
segments.each(&blk)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/hanami/api/router.rb
CHANGED
@@ -9,9 +9,9 @@ module Hanami
|
|
9
9
|
class Router < ::Hanami::Router
|
10
10
|
# @since 0.1.0
|
11
11
|
# @api private
|
12
|
-
def initialize(
|
13
|
-
|
14
|
-
|
12
|
+
def initialize(block_context: Block::Context, **kwargs)
|
13
|
+
super(block_context: block_context, **kwargs)
|
14
|
+
@stack = Middleware::Stack.new(@path_prefix.to_s)
|
15
15
|
end
|
16
16
|
|
17
17
|
# @since 0.1.0
|
@@ -26,15 +26,13 @@ module Hanami
|
|
26
26
|
# @since 0.1.0
|
27
27
|
# @api private
|
28
28
|
def use(middleware, *args, &blk)
|
29
|
-
@stack.use(middleware, args, &blk)
|
29
|
+
@stack.use(@path_prefix.to_s, middleware, *args, &blk)
|
30
30
|
end
|
31
31
|
|
32
|
-
# @since 0.1.
|
32
|
+
# @since 0.1.1
|
33
33
|
# @api private
|
34
|
-
def
|
35
|
-
@stack.
|
36
|
-
super
|
37
|
-
end
|
34
|
+
def to_rack_app
|
35
|
+
@stack.finalize(self)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
data/lib/hanami/api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hanami-router
|
@@ -73,6 +73,7 @@ executables: []
|
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
|
+
- ".github/workflows/ci.yml"
|
76
77
|
- ".gitignore"
|
77
78
|
- ".rspec"
|
78
79
|
- ".rubocop.yml"
|
@@ -87,6 +88,9 @@ files:
|
|
87
88
|
- lib/hanami/api/block/context.rb
|
88
89
|
- lib/hanami/api/error.rb
|
89
90
|
- lib/hanami/api/middleware.rb
|
91
|
+
- lib/hanami/api/middleware/app.rb
|
92
|
+
- lib/hanami/api/middleware/node.rb
|
93
|
+
- lib/hanami/api/middleware/trie.rb
|
90
94
|
- lib/hanami/api/router.rb
|
91
95
|
- lib/hanami/api/version.rb
|
92
96
|
homepage: http://rubygems.org
|
@@ -111,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
115
|
- !ruby/object:Gem::Version
|
112
116
|
version: '0'
|
113
117
|
requirements: []
|
114
|
-
rubygems_version: 3.1.
|
118
|
+
rubygems_version: 3.1.3
|
115
119
|
signing_key:
|
116
120
|
specification_version: 4
|
117
121
|
summary: Hanami API
|