hanami-api 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|