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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b78d8afabe0c9e3a00e3d809866366d1961786fab864788d37bb0b4edc0b67c8
4
- data.tar.gz: 7df6b562a2721f299b9c2a307f546c4e79d72f500b67032ed434907bc880dc09
3
+ metadata.gz: 33a0b691f992906407c5d0c6bdf4c844d6b7ff4deb420b88f1e73c45768f210f
4
+ data.tar.gz: 01a7c8f0972e5ea9a1eff88c81d5966a9c22192c99ca9e3416cfdba9c9fdbc38
5
5
  SHA512:
6
- metadata.gz: bb8c28fded8d1518c02d286d57d56afff643caab1da5c11e0f357b6dea72e79a4788be00b11169599b692d74fc867244516220785932e3b30895b72077375a77
7
- data.tar.gz: 82db2bdc48a9416f884d22fb7d3fe9d3bb7f6dab1632416ca0939e6ed66c87f4a4fc81e2a7a0d86da18a9e8a3e418d189a375a78ca8e63b2a7177e7e9a5bb91b
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
@@ -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
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "byebug", require: false
7
- gem "hanami-router", git: "https://github.com/hanami/router.git", branch: "feature/tree-rewrite"
7
+ gem "hanami-router", git: "https://github.com/hanami/router.git", branch: "unstable"
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.11628299998119473 |
37
- | watts | 0.23525599995628 |
38
- | roda | 0.348202999914065 |
39
- | syro | 0.355627000099048 |
40
- | rack-app | 0.6226229998283088 |
41
- | cuba | 1.2913489998318255 |
42
- | rails | 17.04722599987872 |
43
- | synfeld | 171.83788800006732 |
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
- | synfeld | 172680 |
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
- Get HTTP response body
266
+ Set HTTP response body
224
267
 
225
268
  ```ruby
226
269
  get "/" do
@@ -16,19 +16,14 @@ module Hanami
16
16
  super
17
17
 
18
18
  app.class_eval do
19
- @routes = []
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.0
24
+ # @since 0.1.1
26
25
  # @api private
27
- attr_reader :routes
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
- @routes << [:root, args, kwargs, blk]
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
- @routes << [:get, args, kwargs, blk]
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
- @routes << [:post, args, kwargs, blk]
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
- @routes << [:patch, args, kwargs, blk]
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
- @routes << [:put, args, kwargs, blk]
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
- @routes << [:delete, args, kwargs, blk]
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
- @routes << [:trace, args, kwargs, blk]
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
- @routes << [:options, args, kwargs, blk]
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
- @routes << [:link, args, kwargs, blk]
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
- @routes << [:unlink, args, kwargs, blk]
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
- @routes << [:redirect, args, kwargs, blk]
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
- @routes << [:scope, args, kwargs, blk]
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
- @routes << [:mount, args, kwargs, blk]
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
- @stack.use(middleware, args, &blk)
292
+ @router.use(middleware, *args, &blk)
298
293
  end
299
294
 
300
295
  # @since 0.1.0
301
- def initialize(routes: self.class.routes, stack: self.class.stack)
302
- @stack = stack
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 = @stack.finalize(@router)
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
- ROOT_PREFIX = "/"
20
- private_constant :ROOT_PREFIX
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 with(path)
38
- prefix = @prefix
39
- @prefix = path
40
- yield
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.0
33
+ # @since 0.1.1
46
34
  # @api private
47
- def finalize(app) # rubocop:disable Metrics/MethodLength
48
- uniq!
49
- return app if @stack.empty?
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.0
41
+ # @since 0.1.1
67
42
  # @api private
68
- def each(&blk)
69
- uniq!
70
- @stack.each(&blk)
71
- end
43
+ def finalize(app)
44
+ mapping = to_hash
45
+ return app if mapping.empty?
72
46
 
73
- # @since 0.1.0
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.0
52
+ # @since 0.1.1
86
53
  # @api private
87
- def uniq!
88
- @stack.each_value(&:uniq!)
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
@@ -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(stack:, **kwargs, &blk)
13
- @stack = stack
14
- super(block_context: Block::Context, **kwargs, &blk)
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.0
32
+ # @since 0.1.1
33
33
  # @api private
34
- def scope(*args, **kwargs, &blk)
35
- @stack.with(args.first) do
36
- super
37
- end
34
+ def to_rack_app
35
+ @stack.finalize(self)
38
36
  end
39
37
  end
40
38
  end
@@ -3,6 +3,6 @@
3
3
  module Hanami
4
4
  class API
5
5
  # @since 0.1.0
6
- VERSION = "0.1.0"
6
+ VERSION = "0.1.1"
7
7
  end
8
8
  end
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.0
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-02-19 00:00:00.000000000 Z
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.2
118
+ rubygems_version: 3.1.3
115
119
  signing_key:
116
120
  specification_version: 4
117
121
  summary: Hanami API