hanami-api 0.2.0 → 0.3.0
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 +3 -2
- data/.rubocop.yml +6 -2
- data/CHANGELOG.md +11 -0
- data/Gemfile +1 -1
- data/LICENSE.md +22 -0
- data/README.md +266 -21
- data/hanami-api.gemspec +6 -4
- data/lib/hanami/api/block/context.rb +31 -7
- data/lib/hanami/api/dsl.rb +14 -1
- data/lib/hanami/api/middleware.rb +3 -4
- data/lib/hanami/api/router.rb +17 -2
- data/lib/hanami/api/version.rb +1 -1
- data/lib/hanami/api.rb +77 -26
- metadata +13 -13
- data/lib/hanami/api/middleware/app.rb +0 -41
- data/lib/hanami/api/middleware/node.rb +0 -61
- data/lib/hanami/api/middleware/trie.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11eac74d32637ac1f12ee6d7a774a80501167f46b5a51f6a66f04f4518d82476
|
4
|
+
data.tar.gz: b7b1801a85993adcd839e5ce9d1b460e137ebbbf2a8913af2fb583bf765c7196
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 011cbdb50d4372d08d14d7c037599e3f0c3a6ff3838518702272969bfabf0f1692ee9a8bbbfe1f128b4bcc63d2ef6935ec01e330d78b731e264e3f9be62d9030
|
7
|
+
data.tar.gz: 49bec19e9eaf598b31935e02bd45dfbb2cb949f54beee482210f8a33f50b4eb5ab333915fd9d600c34773faaa4f6a8d480966192077f3428bfc7bb4a65f1a37c
|
data/.github/workflows/ci.yml
CHANGED
@@ -12,7 +12,7 @@ name: ci
|
|
12
12
|
- ".rubocop.yml"
|
13
13
|
pull_request:
|
14
14
|
branches:
|
15
|
-
-
|
15
|
+
- main
|
16
16
|
create:
|
17
17
|
|
18
18
|
jobs:
|
@@ -22,8 +22,9 @@ jobs:
|
|
22
22
|
fail-fast: false
|
23
23
|
matrix:
|
24
24
|
ruby:
|
25
|
+
- "3.2"
|
26
|
+
- "3.1"
|
25
27
|
- "3.0"
|
26
|
-
- "2.7"
|
27
28
|
env:
|
28
29
|
CODACY_RUN_LOCAL: true
|
29
30
|
CODACY_PROJECT_TOKEN: ${{secrets.CODACY_PROJECT_TOKEN}}
|
data/.rubocop.yml
CHANGED
@@ -5,6 +5,10 @@
|
|
5
5
|
# * https://github.com/bbatsov/ruby-style-guide
|
6
6
|
# * https://rubocop.readthedocs.io/
|
7
7
|
inherit_from:
|
8
|
-
- https://raw.githubusercontent.com/hanami/devtools/
|
8
|
+
- https://raw.githubusercontent.com/hanami/devtools/main/.rubocop.yml
|
9
9
|
AllCops:
|
10
|
-
TargetRubyVersion:
|
10
|
+
TargetRubyVersion: 3.0
|
11
|
+
|
12
|
+
Lint/EmptyBlock:
|
13
|
+
Exclude:
|
14
|
+
- "./spec/integration/hanami/api/block_spec.rb"
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
# Hanami::API
|
2
2
|
Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
3
3
|
|
4
|
+
## v0.3.0 - 2022-12-25
|
5
|
+
|
6
|
+
### Added
|
7
|
+
- [Luca Guidi] Official support for Ruby 3.1 & 3.2
|
8
|
+
- [Thomas Jachmann] Streamed responses
|
9
|
+
- [Luca Guidi] Introduce `Hanami::API.helpers` to define helper methods to be used in route blocks
|
10
|
+
- [Luca Guidi] Introduce `Hanami::API#to_inspect` to inspect app routes
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- [Luca Guidi] Drop support for Ruby 2.7
|
14
|
+
|
4
15
|
## v0.2.0 - 2021-01-05
|
5
16
|
### Added
|
6
17
|
- [Luca Guidi] Official support for Ruby: MRI 3.0
|
data/Gemfile
CHANGED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright © 2014-2022 - Hanami Team
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,7 +1,53 @@
|
|
1
1
|
# Hanami::API
|
2
2
|
|
3
3
|
Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
4
|
-
|
4
|
+
|
5
|
+
## Version
|
6
|
+
|
7
|
+
**This branch contains the code for `hanami-api` 0.3.x.**
|
8
|
+
|
9
|
+
## Status
|
10
|
+
|
11
|
+
[![Gem Version](https://badge.fury.io/rb/hanami-api.svg)](https://badge.fury.io/rb/hanami-api)
|
12
|
+
[![CI](https://github.com/hanami/api/workflows/ci/badge.svg?branch=main)](https://github.com/hanami/api/actions?query=workflow%3Aci+branch%3Amain)
|
13
|
+
[![Test Coverage](https://codecov.io/gh/hanami/api/branch/main/graph/badge.svg)](https://codecov.io/gh/hanami/api)
|
14
|
+
[![Depfu](https://badges.depfu.com/badges/a8545fb67cf32a2c75b6227bc0821027/overview.svg)](https://depfu.com/github/hanami/api?project=Bundler)
|
15
|
+
[![Inline Docs](http://inch-ci.org/github/hanami/api.svg)](http://inch-ci.org/github/hanami/api)
|
16
|
+
|
17
|
+
## Contact
|
18
|
+
|
19
|
+
* Home page: http://hanamirb.org
|
20
|
+
* Mailing List: http://hanamirb.org/mailing-list
|
21
|
+
* API Doc: http://rdoc.info/gems/hanami-api
|
22
|
+
* Bugs/Issues: https://github.com/hanami/api/issues
|
23
|
+
* Support: http://stackoverflow.com/questions/tagged/hanami
|
24
|
+
* Chat: http://chat.hanamirb.org
|
25
|
+
|
26
|
+
## Rubies
|
27
|
+
|
28
|
+
__Hanami::API__ supports Ruby (MRI) 3.0+
|
29
|
+
|
30
|
+
## Installation
|
31
|
+
|
32
|
+
Add these lines to your application's `Gemfile`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem "hanami-api"
|
36
|
+
gem "puma" # or "webrick", or "thin", "falcon"
|
37
|
+
```
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
|
41
|
+
```shell
|
42
|
+
$ bundle install
|
43
|
+
```
|
44
|
+
|
45
|
+
Or install it yourself as:
|
46
|
+
|
47
|
+
```shell
|
48
|
+
$ gem install hanami-api
|
49
|
+
```
|
50
|
+
|
5
51
|
* [Performance](#performance)
|
6
52
|
+ [Runtime](#runtime)
|
7
53
|
+ [Memory](#memory)
|
@@ -13,9 +59,12 @@ Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
|
13
59
|
- [Rack endpoint](#rack-endpoint)
|
14
60
|
- [Block endpoint](#block-endpoint)
|
15
61
|
* [String (body)](#string-body)
|
62
|
+
* [Enumerator (body)](#enumerator-body)
|
16
63
|
* [Integer (status code)](#integer-status-code)
|
17
64
|
* [Integer, String (status code, body)](#integer-string-status-code-body)
|
65
|
+
* [Integer, Enumerator (status code, body)](#integer-enumerator-status-code-body)
|
18
66
|
* [Integer, Hash, String (status code, headers, body)](#integer-hash-string-status-code-headers-body)
|
67
|
+
* [Integer, Hash, Enumerator (status code, headers, body)](#integer-hash-enumerator-status-code-headers-body)
|
19
68
|
+ [Block context](#block-context)
|
20
69
|
- [env](#env)
|
21
70
|
- [status](#status)
|
@@ -27,31 +76,14 @@ Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
|
27
76
|
- [back](#back)
|
28
77
|
- [json](#json)
|
29
78
|
+ [Scope](#scope)
|
79
|
+
+ [Helpers](#helpers)
|
30
80
|
+ [Rack Middleware](#rack-middleware)
|
81
|
+
+ [Streamed Responses](#streamed-responses)
|
31
82
|
+ [Body Parsers](#body-parsers)
|
83
|
+
* [Testing](#testing)
|
32
84
|
* [Development](#development)
|
33
85
|
* [Contributing](#contributing)
|
34
86
|
|
35
|
-
## Installation
|
36
|
-
|
37
|
-
Add this line to your application's `Gemfile`:
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
gem "hanami-api"
|
41
|
-
```
|
42
|
-
|
43
|
-
And then execute:
|
44
|
-
|
45
|
-
```shell
|
46
|
-
$ bundle install
|
47
|
-
```
|
48
|
-
|
49
|
-
Or install it yourself as:
|
50
|
-
|
51
|
-
```shell
|
52
|
-
$ gem install hanami-api
|
53
|
-
```
|
54
|
-
|
55
87
|
## Performance
|
56
88
|
|
57
89
|
Benchmark against an app with 10,000 routes, hitting the 10,000th to measure the worst case scenario.
|
@@ -178,6 +210,16 @@ end
|
|
178
210
|
|
179
211
|
It will return `[200, {}, ["Hello, world"]]`
|
180
212
|
|
213
|
+
##### Enumerator (body)
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
get "/" do
|
217
|
+
Enumerator.new { ... }
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
It will return `[200, {}, Enumerator]`, see [Streamed Responses](#streamed-responses)
|
222
|
+
|
181
223
|
##### Integer (status code)
|
182
224
|
|
183
225
|
```ruby
|
@@ -198,6 +240,16 @@ end
|
|
198
240
|
|
199
241
|
It will return `[401, {}, ["You shall not pass"]]`
|
200
242
|
|
243
|
+
##### Integer, Enumerator (status code, body)
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
get "/" do
|
247
|
+
[401, Enumerator.new { ... }]
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
It will return `[401, {}, Enumerator]`, see [Streamed Responses](#streamed-responses)
|
252
|
+
|
201
253
|
##### Integer, Hash, String (status code, headers, body)
|
202
254
|
|
203
255
|
```ruby
|
@@ -208,6 +260,16 @@ end
|
|
208
260
|
|
209
261
|
It will return `[401, {"X-Custom-Header" => "foo"}, ["You shall not pass"]]`
|
210
262
|
|
263
|
+
##### Integer, Hash, Enumerator (status code, headers, body)
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
get "/" do
|
267
|
+
[401, {"X-Custom-Header" => "foo"}, Enumerator.new { ... }]
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
It will return `[401, {"X-Custom-Header" => "foo"}, Enumerator]`, see [Streamed Responses](#streamed-responses)
|
272
|
+
|
211
273
|
### Block context
|
212
274
|
|
213
275
|
When using the block syntax there is a rich API to use.
|
@@ -273,6 +335,14 @@ get "/" do
|
|
273
335
|
end
|
274
336
|
```
|
275
337
|
|
338
|
+
Set HTTP response body using a [Streamed Response](#streamed-responses)
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
get "/" do
|
342
|
+
body Enumerator.new { ... }
|
343
|
+
end
|
344
|
+
```
|
345
|
+
|
276
346
|
#### params
|
277
347
|
|
278
348
|
Access params for current request
|
@@ -308,6 +378,14 @@ end
|
|
308
378
|
|
309
379
|
It sets a Rack response: `[401, {}, ["You shall not pass"]]`
|
310
380
|
|
381
|
+
You can also use a [Streamed Response](#streamed-responses) here
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
get "/authenticate" do
|
385
|
+
halt(401, Enumerator.new { ... })
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
311
389
|
#### redirect
|
312
390
|
|
313
391
|
Redirects request and immediately halts it
|
@@ -364,6 +442,15 @@ get "/user/:id" do
|
|
364
442
|
end
|
365
443
|
```
|
366
444
|
|
445
|
+
If you want a [Streamed Response](#streamed-responses)
|
446
|
+
|
447
|
+
```ruby
|
448
|
+
get "/users" do
|
449
|
+
users = Enumerator.new { ... }
|
450
|
+
json(users)
|
451
|
+
end
|
452
|
+
```
|
453
|
+
|
367
454
|
### Scope
|
368
455
|
|
369
456
|
Prefixing routes is possible with routing scopes:
|
@@ -378,6 +465,58 @@ end
|
|
378
465
|
|
379
466
|
It will generate a route with `"/api/v1/users"` as path.
|
380
467
|
|
468
|
+
### Helpers
|
469
|
+
|
470
|
+
Define helper methods available within the block context.
|
471
|
+
Helper methods have access to default utilities available in block context (e.g. `#halt`).
|
472
|
+
|
473
|
+
Helpers can be defined inline by passing a block to the `.helpers` method:
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
require "hanami/api"
|
477
|
+
|
478
|
+
class MyAPI < Hanami::API
|
479
|
+
helpers do
|
480
|
+
def redirect_to_root
|
481
|
+
# redirect method is provided by Hanami::API block context
|
482
|
+
redirect "/"
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
root { "Hello, World" }
|
487
|
+
|
488
|
+
get "/legacy" do
|
489
|
+
redirect_to_root
|
490
|
+
end
|
491
|
+
end
|
492
|
+
```
|
493
|
+
|
494
|
+
Alternatively, `.helpers` accepts a module.
|
495
|
+
|
496
|
+
```ruby
|
497
|
+
require "hanami/api"
|
498
|
+
|
499
|
+
class MyAPI < Hanami::API
|
500
|
+
module Authentication
|
501
|
+
private
|
502
|
+
|
503
|
+
def unauthorized
|
504
|
+
halt(401)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
helpers(Authentication)
|
509
|
+
|
510
|
+
root { "Hello, World" }
|
511
|
+
|
512
|
+
get "/secrets" do
|
513
|
+
unauthorized
|
514
|
+
end
|
515
|
+
end
|
516
|
+
```
|
517
|
+
|
518
|
+
You can use `.helpers` multiple times in the same app.
|
519
|
+
|
381
520
|
### Rack Middleware
|
382
521
|
|
383
522
|
To mount a Rack middleware it's possible with `.use`
|
@@ -412,6 +551,56 @@ it's part of the top level scope. `ApiAuthentication` it's used for all the API
|
|
412
551
|
versions, because it's defined in the `"api"` scope. `ApiV1Deprecation` is used
|
413
552
|
only by the routes in `"v1"` scope, but not by `"v2"`.
|
414
553
|
|
554
|
+
### Streamed Responses
|
555
|
+
|
556
|
+
When the work to be done by the server takes time, it may be a good idea to
|
557
|
+
stream your response. For this, you just use an `Enumerator` anywhere you would
|
558
|
+
normally use a `String` as body or another `Object` as JSON response. Here's an
|
559
|
+
example of streaming JSON data:
|
560
|
+
|
561
|
+
```ruby
|
562
|
+
scope "stream" do
|
563
|
+
use ::Rack::Chunked
|
564
|
+
|
565
|
+
get "/data" do
|
566
|
+
Enumerator.new do |yielder|
|
567
|
+
data = %w[a b c]
|
568
|
+
data.each do |item|
|
569
|
+
yielder << item
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
get "/to_enum" do
|
575
|
+
%w[a b c].to_enum
|
576
|
+
end
|
577
|
+
|
578
|
+
get "/json" do
|
579
|
+
result = Enumerator.new do |yielder|
|
580
|
+
data = %w[a b c]
|
581
|
+
data.each do |item|
|
582
|
+
yielder << item
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
json(result)
|
587
|
+
end
|
588
|
+
end
|
589
|
+
```
|
590
|
+
|
591
|
+
Note:
|
592
|
+
|
593
|
+
* Returning an `Enumerator` will also work without `Rack::Chunked`, it just
|
594
|
+
won't stream but return the whole body at the end instead.
|
595
|
+
* Data pushed to `yielder` MUST be a `String`.
|
596
|
+
* Streaming does not work with WEBrick as it buffers its response. We recommend
|
597
|
+
using `puma`, though you may find success with other servers.
|
598
|
+
* To manual test this feature use a web browser or cURL:
|
599
|
+
|
600
|
+
```shell
|
601
|
+
$ curl --raw -i http://localhost:2300/stream/data
|
602
|
+
```
|
603
|
+
|
415
604
|
### Body Parsers
|
416
605
|
|
417
606
|
Rack ignores request bodies unless they come from a form submission.
|
@@ -426,6 +615,59 @@ require "hanami/middleware/body_parser"
|
|
426
615
|
use Hanami::Middleware::BodyParser, :json
|
427
616
|
```
|
428
617
|
|
618
|
+
## Testing
|
619
|
+
|
620
|
+
## Unit testing
|
621
|
+
You can unit test your `Hanami::API` app by passing a `env` hash to your app's `#call` method.
|
622
|
+
|
623
|
+
The keys that (based on the Rack standard) `Hanami::API` uses for routing are:
|
624
|
+
* `PATH_INFO`
|
625
|
+
* `REQUEST_METHOD`
|
626
|
+
|
627
|
+
|
628
|
+
For example, a spec for the basic app in the [Usage section](https://github.com/hanami/api#usage) could be:
|
629
|
+
|
630
|
+
```ruby
|
631
|
+
require "my_project/app"
|
632
|
+
|
633
|
+
RSpec.describe App do
|
634
|
+
describe "#call" do
|
635
|
+
it "returns successfully" do
|
636
|
+
response = subject.call({"PATH_INFO" => "/", "REQUEST_METHOD" => "GET"})
|
637
|
+
expect(response).to eq([200, {}, ["Hello, world"]])
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
```
|
642
|
+
|
643
|
+
## Integration testing
|
644
|
+
Add this line to your application's `Gemfile`:
|
645
|
+
|
646
|
+
```ruby
|
647
|
+
gem "rack-test", group: :test
|
648
|
+
```
|
649
|
+
|
650
|
+
In a test, load `Rack::Test`:
|
651
|
+
|
652
|
+
```ruby
|
653
|
+
require "rack/test"
|
654
|
+
```
|
655
|
+
|
656
|
+
and then, inside your spec/test, include its helper methods:
|
657
|
+
|
658
|
+
```ruby
|
659
|
+
include Rack::Test::Methods
|
660
|
+
```
|
661
|
+
|
662
|
+
Then you can use its methods like `get` and `last_response`, e.g.:
|
663
|
+
|
664
|
+
```ruby
|
665
|
+
it "returns the status 200" do
|
666
|
+
get "/"
|
667
|
+
expect(last_response.status).to eq 200
|
668
|
+
end
|
669
|
+
```
|
670
|
+
|
429
671
|
## Development
|
430
672
|
|
431
673
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -436,3 +678,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
436
678
|
|
437
679
|
Bug reports and pull requests are welcome on GitHub at https://github.com/hanami/api.
|
438
680
|
|
681
|
+
## Copyright
|
682
|
+
|
683
|
+
Copyright © 2014-2022 Hanami Team – Released under MIT License.
|
data/hanami-api.gemspec
CHANGED
@@ -11,13 +11,14 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "Hanami API"
|
12
12
|
spec.description = "Extremely fast and lightweight HTTP API"
|
13
13
|
spec.homepage = "http://rubygems.org"
|
14
|
-
spec.
|
14
|
+
spec.licenses = ["MIT"]
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
15
16
|
|
16
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
18
|
|
18
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
19
20
|
spec.metadata["source_code_uri"] = "https://github.com/hanami/api"
|
20
|
-
spec.metadata["changelog_uri"] = "https://github.com/hanami/api/blob/
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/hanami/api/blob/main/CHANGELOG.md"
|
21
22
|
|
22
23
|
# Specify which files should be added to the gem when it is released.
|
23
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -28,11 +29,12 @@ Gem::Specification.new do |spec|
|
|
28
29
|
spec.bindir = "exe"
|
29
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
31
|
spec.require_paths = ["lib"]
|
32
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
31
33
|
|
32
|
-
spec.add_dependency "hanami-router", "~> 2.0
|
34
|
+
spec.add_dependency "hanami-router", "~> 2.0"
|
33
35
|
|
34
36
|
spec.add_development_dependency "rake", "~> 13.0"
|
35
37
|
spec.add_development_dependency "rspec", "~> 3.8"
|
36
|
-
spec.add_development_dependency "rubocop", "~> 0
|
38
|
+
spec.add_development_dependency "rubocop", "~> 1.0"
|
37
39
|
spec.add_development_dependency "yard", "~> 0.9"
|
38
40
|
end
|
@@ -124,7 +124,12 @@ module Hanami
|
|
124
124
|
# end
|
125
125
|
def json(object, mime = "application/json")
|
126
126
|
headers["Content-Type"] = mime
|
127
|
-
|
127
|
+
case object
|
128
|
+
in Enumerator => collection
|
129
|
+
json_enum(collection)
|
130
|
+
else
|
131
|
+
JSON.generate(object)
|
132
|
+
end
|
128
133
|
end
|
129
134
|
|
130
135
|
# @since 0.1.0
|
@@ -132,10 +137,11 @@ module Hanami
|
|
132
137
|
#
|
133
138
|
def call
|
134
139
|
case caught
|
135
|
-
|
140
|
+
in String => body
|
136
141
|
[status, headers, [body]]
|
137
|
-
|
138
|
-
|
142
|
+
in Enumerator => body
|
143
|
+
[status, headers, body]
|
144
|
+
in Integer => status
|
139
145
|
#
|
140
146
|
# NOTE: It must use `self.body` so it will pick the method defined above.
|
141
147
|
#
|
@@ -144,12 +150,16 @@ module Hanami
|
|
144
150
|
# When that happens, the body that was manually set is ignored,
|
145
151
|
# which results in a bug.
|
146
152
|
[status, headers, [self.body || http_status(status)]]
|
147
|
-
|
148
|
-
in [Integer => status, String => body]
|
153
|
+
in [Integer => status, String => body]
|
149
154
|
[status, headers, [body]]
|
150
|
-
|
155
|
+
in [Integer => status, Enumerator => body]
|
156
|
+
[status, headers, body]
|
157
|
+
in [Integer => status, Hash => caught_headers, String => body]
|
151
158
|
headers.merge!(caught_headers)
|
152
159
|
[status, headers, [body]]
|
160
|
+
in [Integer => status, Hash => caught_headers, Enumerator => body]
|
161
|
+
headers.merge!(caught_headers)
|
162
|
+
[status, headers, body]
|
153
163
|
end
|
154
164
|
end
|
155
165
|
|
@@ -168,6 +178,20 @@ module Hanami
|
|
168
178
|
def http_status(code)
|
169
179
|
Rack::Utils::HTTP_STATUS_CODES.fetch(code)
|
170
180
|
end
|
181
|
+
|
182
|
+
# @since x.x.x
|
183
|
+
# @api private
|
184
|
+
def json_enum(collection)
|
185
|
+
Enumerator.new do |yielder|
|
186
|
+
yielder << "["
|
187
|
+
collection.each_with_index do |item, i|
|
188
|
+
yielder << "," if i.positive?
|
189
|
+
yielder << JSON.generate(item)
|
190
|
+
end
|
191
|
+
ensure
|
192
|
+
yielder << "]"
|
193
|
+
end
|
194
|
+
end
|
171
195
|
end
|
172
196
|
end
|
173
197
|
end
|
data/lib/hanami/api/dsl.rb
CHANGED
@@ -57,7 +57,10 @@ module Hanami
|
|
57
57
|
super
|
58
58
|
|
59
59
|
app.class_eval do
|
60
|
-
|
60
|
+
klass = Block::Context.dup
|
61
|
+
app.const_set(:BlockContext, klass)
|
62
|
+
|
63
|
+
@router = Router.new(block_context: klass)
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -117,6 +120,16 @@ module Hanami
|
|
117
120
|
def call(env)
|
118
121
|
@app.call(env)
|
119
122
|
end
|
123
|
+
|
124
|
+
# Printable routes
|
125
|
+
#
|
126
|
+
# @return [String] printable routes
|
127
|
+
#
|
128
|
+
# @since x.x.x
|
129
|
+
# @api public
|
130
|
+
def to_inspect
|
131
|
+
@app.to_inspect
|
132
|
+
end
|
120
133
|
end
|
121
134
|
end
|
122
135
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "hanami/middleware/app"
|
4
|
+
|
3
5
|
module Hanami
|
4
6
|
class API
|
5
7
|
# Hanami::API middleware stack
|
@@ -7,9 +9,6 @@ module Hanami
|
|
7
9
|
# @since 0.1.0
|
8
10
|
# @api private
|
9
11
|
module Middleware
|
10
|
-
require "hanami/api/middleware/app"
|
11
|
-
require "hanami/api/middleware/trie"
|
12
|
-
|
13
12
|
# Middleware stack
|
14
13
|
#
|
15
14
|
# @since 0.1.0
|
@@ -44,7 +43,7 @@ module Hanami
|
|
44
43
|
mapping = to_hash
|
45
44
|
return app if mapping.empty?
|
46
45
|
|
47
|
-
App.new(app, mapping)
|
46
|
+
Hanami::Middleware::App.new(app, mapping)
|
48
47
|
end
|
49
48
|
|
50
49
|
private
|
data/lib/hanami/api/router.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "hanami/router"
|
4
|
+
require "hanami/router/inspector"
|
4
5
|
require "hanami/api/block/context"
|
5
6
|
|
6
7
|
module Hanami
|
7
8
|
class API
|
8
9
|
# @since 0.1.0
|
9
10
|
class Router < ::Hanami::Router
|
11
|
+
# @since x.x.x
|
12
|
+
# @api private
|
13
|
+
attr_reader :inspector
|
14
|
+
|
10
15
|
# @since 0.1.0
|
11
16
|
# @api private
|
12
|
-
def initialize(block_context: Block::Context, **kwargs)
|
13
|
-
super(block_context: block_context, **kwargs)
|
17
|
+
def initialize(block_context: Block::Context, inspector: Inspector.new, **kwargs)
|
18
|
+
super(block_context: block_context, inspector: inspector, **kwargs)
|
14
19
|
@stack = Middleware::Stack.new(@path_prefix.to_s)
|
15
20
|
end
|
16
21
|
|
@@ -34,6 +39,16 @@ module Hanami
|
|
34
39
|
def to_rack_app
|
35
40
|
@stack.finalize(self)
|
36
41
|
end
|
42
|
+
|
43
|
+
# Returns formatted routes
|
44
|
+
#
|
45
|
+
# @return [String] formatted routes
|
46
|
+
#
|
47
|
+
# @since x.x.x
|
48
|
+
# @api private
|
49
|
+
def to_inspect
|
50
|
+
@inspector.call
|
51
|
+
end
|
37
52
|
end
|
38
53
|
end
|
39
54
|
end
|
data/lib/hanami/api/version.rb
CHANGED
data/lib/hanami/api.rb
CHANGED
@@ -20,6 +20,57 @@ module Hanami
|
|
20
20
|
app.include(DSL::InstanceMethods)
|
21
21
|
end
|
22
22
|
|
23
|
+
# Defines helper methods available within the block context.
|
24
|
+
# Helper methods have access to default utilities available in block
|
25
|
+
# context (e.g. `#halt`).
|
26
|
+
#
|
27
|
+
# @param mod [Module] optional module to include in block context
|
28
|
+
# @param blk [Proc] inline helper definitions
|
29
|
+
#
|
30
|
+
# @since x.x.x
|
31
|
+
#
|
32
|
+
# @example Inline helpers definition
|
33
|
+
# require "hanami/api"
|
34
|
+
#
|
35
|
+
# class MyAPI < Hanami::API
|
36
|
+
# helpers do
|
37
|
+
# def redirect_to_root
|
38
|
+
# # redirect method is provided by Hanami::API block context
|
39
|
+
# redirect "/"
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# root { "Hello, World" }
|
44
|
+
#
|
45
|
+
# get "/legacy" do
|
46
|
+
# redirect_to_root
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @example Module helpers definition
|
51
|
+
# require "hanami/api"
|
52
|
+
#
|
53
|
+
# class MyAPI < Hanami::API
|
54
|
+
# module Authentication
|
55
|
+
# private
|
56
|
+
#
|
57
|
+
# def unauthorized
|
58
|
+
# halt(401)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# helpers(Authentication)
|
63
|
+
#
|
64
|
+
# root { "Hello, World" }
|
65
|
+
#
|
66
|
+
# get "/secrets" do
|
67
|
+
# unauthorized
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
def self.helpers(mod = nil, &blk)
|
71
|
+
const_get(:BlockContext).include(mod || Module.new(&blk))
|
72
|
+
end
|
73
|
+
|
23
74
|
# Defines a named root route (a GET route for "/")
|
24
75
|
#
|
25
76
|
# @param to [#call] the Rack endpoint
|
@@ -44,8 +95,8 @@ module Hanami
|
|
44
95
|
# "Hello from Hanami!"
|
45
96
|
# end
|
46
97
|
# end
|
47
|
-
def self.root(
|
48
|
-
@router.root(
|
98
|
+
def self.root(...)
|
99
|
+
@router.root(...)
|
49
100
|
end
|
50
101
|
|
51
102
|
# Defines a route that accepts GET requests for the given path.
|
@@ -81,8 +132,8 @@ module Hanami
|
|
81
132
|
# class MyAPI < Hanami::API
|
82
133
|
# get "/users/:id", to: ->(*) { [200, {}, ["OK"]] }, id: /\d+/
|
83
134
|
# end
|
84
|
-
def self.get(
|
85
|
-
@router.get(
|
135
|
+
def self.get(...)
|
136
|
+
@router.get(...)
|
86
137
|
end
|
87
138
|
|
88
139
|
# Defines a route that accepts POST requests for the given path.
|
@@ -96,8 +147,8 @@ module Hanami
|
|
96
147
|
# @since 0.1.0
|
97
148
|
#
|
98
149
|
# @see .get
|
99
|
-
def self.post(
|
100
|
-
@router.post(
|
150
|
+
def self.post(...)
|
151
|
+
@router.post(...)
|
101
152
|
end
|
102
153
|
|
103
154
|
# Defines a route that accepts PATCH requests for the given path.
|
@@ -111,8 +162,8 @@ module Hanami
|
|
111
162
|
# @since 0.1.0
|
112
163
|
#
|
113
164
|
# @see .get
|
114
|
-
def self.patch(
|
115
|
-
@router.patch(
|
165
|
+
def self.patch(...)
|
166
|
+
@router.patch(...)
|
116
167
|
end
|
117
168
|
|
118
169
|
# Defines a route that accepts PUT requests for the given path.
|
@@ -126,8 +177,8 @@ module Hanami
|
|
126
177
|
# @since 0.1.0
|
127
178
|
#
|
128
179
|
# @see .get
|
129
|
-
def self.put(
|
130
|
-
@router.put(
|
180
|
+
def self.put(...)
|
181
|
+
@router.put(...)
|
131
182
|
end
|
132
183
|
|
133
184
|
# Defines a route that accepts DELETE requests for the given path.
|
@@ -141,8 +192,8 @@ module Hanami
|
|
141
192
|
# @since 0.1.0
|
142
193
|
#
|
143
194
|
# @see .get
|
144
|
-
def self.delete(
|
145
|
-
@router.delete(
|
195
|
+
def self.delete(...)
|
196
|
+
@router.delete(...)
|
146
197
|
end
|
147
198
|
|
148
199
|
# Defines a route that accepts TRACE requests for the given path.
|
@@ -156,8 +207,8 @@ module Hanami
|
|
156
207
|
# @since 0.1.0
|
157
208
|
#
|
158
209
|
# @see .get
|
159
|
-
def self.trace(
|
160
|
-
@router.trace(
|
210
|
+
def self.trace(...)
|
211
|
+
@router.trace(...)
|
161
212
|
end
|
162
213
|
|
163
214
|
# Defines a route that accepts OPTIONS requests for the given path.
|
@@ -171,8 +222,8 @@ module Hanami
|
|
171
222
|
# @since 0.1.0
|
172
223
|
#
|
173
224
|
# @see .get
|
174
|
-
def self.options(
|
175
|
-
@router.options(
|
225
|
+
def self.options(...)
|
226
|
+
@router.options(...)
|
176
227
|
end
|
177
228
|
|
178
229
|
# Defines a route that accepts LINK requests for the given path.
|
@@ -186,8 +237,8 @@ module Hanami
|
|
186
237
|
# @since 0.1.0
|
187
238
|
#
|
188
239
|
# @see .get
|
189
|
-
def self.link(
|
190
|
-
@router.link(
|
240
|
+
def self.link(...)
|
241
|
+
@router.link(...)
|
191
242
|
end
|
192
243
|
|
193
244
|
# Defines a route that accepts UNLINK requests for the given path.
|
@@ -201,8 +252,8 @@ module Hanami
|
|
201
252
|
# @since 0.1.0
|
202
253
|
#
|
203
254
|
# @see .get
|
204
|
-
def self.unlink(
|
205
|
-
@router.unlink(
|
255
|
+
def self.unlink(...)
|
256
|
+
@router.unlink(...)
|
206
257
|
end
|
207
258
|
|
208
259
|
# Defines a route that redirects the incoming request to another path.
|
@@ -215,8 +266,8 @@ module Hanami
|
|
215
266
|
# @since 0.1.0
|
216
267
|
#
|
217
268
|
# @see .get
|
218
|
-
def self.redirect(
|
219
|
-
@router.redirect(
|
269
|
+
def self.redirect(...)
|
270
|
+
@router.redirect(...)
|
220
271
|
end
|
221
272
|
|
222
273
|
# Defines a routing scope. Routes defined in the context of a scope,
|
@@ -239,8 +290,8 @@ module Hanami
|
|
239
290
|
# end
|
240
291
|
#
|
241
292
|
# # It generates a route with a path `/v1/users`
|
242
|
-
def self.scope(
|
243
|
-
@router.scope(
|
293
|
+
def self.scope(...)
|
294
|
+
@router.scope(...)
|
244
295
|
end
|
245
296
|
|
246
297
|
# Mount a Rack application at the specified path.
|
@@ -264,8 +315,8 @@ module Hanami
|
|
264
315
|
# class MyAPI < Hanami::API
|
265
316
|
# mount MyRackApp.new, at: "/foo"
|
266
317
|
# end
|
267
|
-
def self.mount(
|
268
|
-
@router.mount(
|
318
|
+
def self.mount(...)
|
319
|
+
@router.mount(...)
|
269
320
|
end
|
270
321
|
|
271
322
|
# Use a Rack middleware
|
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.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-12-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hanami-router
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.0
|
19
|
+
version: '2.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.0
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0
|
61
|
+
version: '1.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0
|
68
|
+
version: '1.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: yard
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -93,6 +93,7 @@ files:
|
|
93
93
|
- ".rubocop.yml"
|
94
94
|
- CHANGELOG.md
|
95
95
|
- Gemfile
|
96
|
+
- LICENSE.md
|
96
97
|
- README.md
|
97
98
|
- Rakefile
|
98
99
|
- bin/console
|
@@ -103,18 +104,17 @@ files:
|
|
103
104
|
- lib/hanami/api/dsl.rb
|
104
105
|
- lib/hanami/api/error.rb
|
105
106
|
- lib/hanami/api/middleware.rb
|
106
|
-
- lib/hanami/api/middleware/app.rb
|
107
|
-
- lib/hanami/api/middleware/node.rb
|
108
|
-
- lib/hanami/api/middleware/trie.rb
|
109
107
|
- lib/hanami/api/router.rb
|
110
108
|
- lib/hanami/api/version.rb
|
111
109
|
homepage: http://rubygems.org
|
112
|
-
licenses:
|
110
|
+
licenses:
|
111
|
+
- MIT
|
113
112
|
metadata:
|
114
113
|
allowed_push_host: https://rubygems.org
|
115
114
|
homepage_uri: http://rubygems.org
|
116
115
|
source_code_uri: https://github.com/hanami/api
|
117
|
-
changelog_uri: https://github.com/hanami/api/blob/
|
116
|
+
changelog_uri: https://github.com/hanami/api/blob/main/CHANGELOG.md
|
117
|
+
rubygems_mfa_required: 'true'
|
118
118
|
post_install_message:
|
119
119
|
rdoc_options: []
|
120
120
|
require_paths:
|
@@ -123,14 +123,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
123
|
requirements:
|
124
124
|
- - ">="
|
125
125
|
- !ruby/object:Gem::Version
|
126
|
-
version:
|
126
|
+
version: 3.0.0
|
127
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
requirements: []
|
133
|
-
rubygems_version: 3.
|
133
|
+
rubygems_version: 3.4.1
|
134
134
|
signing_key:
|
135
135
|
specification_version: 4
|
136
136
|
summary: Hanami API
|
@@ -1,41 +0,0 @@
|
|
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
|
@@ -1,61 +0,0 @@
|
|
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
|
@@ -1,71 +0,0 @@
|
|
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
|