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