hanami-api 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +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
|
+
[](https://badge.fury.io/rb/hanami-api)
|
12
|
+
[](https://github.com/hanami/api/actions?query=workflow%3Aci+branch%3Amain)
|
13
|
+
[](https://codecov.io/gh/hanami/api)
|
14
|
+
[](https://depfu.com/github/hanami/api?project=Bundler)
|
15
|
+
[](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
|