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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2c7e4f7ca1d651ea24fdefb1455358b4c52682c0d1de57658ff7a8fe6253ec4
4
- data.tar.gz: d900b1c2455f57e16a5f0f2044fcecd1c79f76bec3fc42fbfea13942bfa75dc5
3
+ metadata.gz: 11eac74d32637ac1f12ee6d7a774a80501167f46b5a51f6a66f04f4518d82476
4
+ data.tar.gz: b7b1801a85993adcd839e5ce9d1b460e137ebbbf2a8913af2fb583bf765c7196
5
5
  SHA512:
6
- metadata.gz: c919a7b1a5f354e99aa2a34cc0189abed167e6bac9b7ba5c96da0329473b08fbfa8e0ac31913462443d874dcd99cad99db6fddf1fb9acd16254964851c0b065f
7
- data.tar.gz: 160ecda952bb09ccf58f7d205f5626c80811ec3bad13e092d60d9fae64c71983b3b2121bf3c2f52eadb558d19b691269fad0e00b0cd80229e7c83128998033f1
6
+ metadata.gz: 011cbdb50d4372d08d14d7c037599e3f0c3a6ff3838518702272969bfabf0f1692ee9a8bbbfe1f128b4bcc63d2ef6935ec01e330d78b731e264e3f9be62d9030
7
+ data.tar.gz: 49bec19e9eaf598b31935e02bd45dfbb2cb949f54beee482210f8a33f50b4eb5ab333915fd9d600c34773faaa4f6a8d480966192077f3428bfc7bb4a65f1a37c
@@ -12,7 +12,7 @@ name: ci
12
12
  - ".rubocop.yml"
13
13
  pull_request:
14
14
  branches:
15
- - master
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/master/.rubocop-unstable.yml
8
+ - https://raw.githubusercontent.com/hanami/devtools/main/.rubocop.yml
9
9
  AllCops:
10
- TargetRubyVersion: 2.7
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
@@ -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: "unstable"
7
+ gem "hanami-router", git: "https://github.com/hanami/router.git", branch: "main"
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
- * [Installation](#installation)
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.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
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/master/CHANGELOG.md"
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.alpha"
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.86"
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
- JSON.generate(object)
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
- in String => body
140
+ in String => body
136
141
  [status, headers, [body]]
137
- in Integer => status
138
- # rubocop:disable Style/RedundantSelf
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
- # rubocop:enable Style/RedundantSelf
148
- in [Integer => status, String => body]
153
+ in [Integer => status, String => body]
149
154
  [status, headers, [body]]
150
- in [Integer => status, Hash => caught_headers, String => body]
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
@@ -57,7 +57,10 @@ module Hanami
57
57
  super
58
58
 
59
59
  app.class_eval do
60
- @router = Router.new
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
@@ -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
@@ -3,6 +3,6 @@
3
3
  module Hanami
4
4
  class API
5
5
  # @since 0.1.0
6
- VERSION = "0.2.0"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  end
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(*args, **kwargs, &blk)
48
- @router.root(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
85
- @router.get(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
100
- @router.post(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
115
- @router.patch(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
130
- @router.put(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
145
- @router.delete(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
160
- @router.trace(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
175
- @router.options(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
190
- @router.link(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
205
- @router.unlink(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
219
- @router.redirect(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
243
- @router.scope(*args, **kwargs, &blk)
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(*args, **kwargs, &blk)
268
- @router.mount(*args, **kwargs, &blk)
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.2.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: 2021-01-05 00:00:00.000000000 Z
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.alpha
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.alpha
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.86'
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.86'
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/master/CHANGELOG.md
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: 2.7.0
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.2.4
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