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 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