hanami-api 0.0.0 → 0.1.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 +5 -5
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -2
- data/README.md +353 -10
- data/Rakefile +12 -1
- data/bin/console +1 -0
- data/hanami-api.gemspec +22 -12
- data/lib/hanami/api.rb +341 -3
- data/lib/hanami/api/block/context.rb +161 -0
- data/lib/hanami/api/error.rb +9 -0
- data/lib/hanami/api/middleware.rb +93 -0
- data/lib/hanami/api/router.rb +41 -0
- data/lib/hanami/api/version.rb +5 -2
- metadata +50 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b78d8afabe0c9e3a00e3d809866366d1961786fab864788d37bb0b4edc0b67c8
|
4
|
+
data.tar.gz: 7df6b562a2721f299b9c2a307f546c4e79d72f500b67032ed434907bc880dc09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb8c28fded8d1518c02d286d57d56afff643caab1da5c11e0f357b6dea72e79a4788be00b11169599b692d74fc867244516220785932e3b30895b72077375a77
|
7
|
+
data.tar.gz: 82db2bdc48a9416f884d22fb7d3fe9d3bb7f6dab1632416ca0939e6ed66c87f4a4fc81e2a7a0d86da18a9e8a3e418d189a375a78ca8e63b2a7177e7e9a5bb91b
|
data/.gitignore
CHANGED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Please keep AllCops, Bundler, Layout, Style, Metrics groups and then order cops
|
2
|
+
# alphabetically
|
3
|
+
#
|
4
|
+
# References:
|
5
|
+
# * https://github.com/bbatsov/ruby-style-guide
|
6
|
+
# * https://rubocop.readthedocs.io/
|
7
|
+
inherit_from:
|
8
|
+
- https://raw.githubusercontent.com/hanami/devtools/master/.rubocop-unstable.yml
|
9
|
+
AllCops:
|
10
|
+
TargetRubyVersion: 2.7
|
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
source "https://rubygems.org"
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
gem "byebug", require: false
|
7
|
+
gem "hanami-router", git: "https://github.com/hanami/router.git", branch: "feature/tree-rewrite"
|
data/README.md
CHANGED
@@ -1,28 +1,371 @@
|
|
1
|
-
# Hanami::
|
1
|
+
# Hanami::API
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
|
-
Add this line to your application's Gemfile
|
7
|
+
Add this line to your application's `Gemfile`:
|
10
8
|
|
11
9
|
```ruby
|
12
|
-
gem
|
10
|
+
gem "hanami-api"
|
13
11
|
```
|
14
12
|
|
15
13
|
And then execute:
|
16
14
|
|
17
|
-
|
15
|
+
```shell
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
18
|
|
19
19
|
Or install it yourself as:
|
20
20
|
|
21
|
-
|
21
|
+
```shell
|
22
|
+
$ gem install hanami-api
|
23
|
+
```
|
24
|
+
|
25
|
+
## Performance
|
26
|
+
|
27
|
+
Benchmark against an app with 10,000 routes, hitting the 10,000th to measure the worst case scenario.
|
28
|
+
Based on [`jeremyevans/r10k`](https://github.com/jeremyevans/r10k), `Hanami::API` scores first for speed, and second for memory footprint.
|
29
|
+
|
30
|
+
### Runtime
|
31
|
+
|
32
|
+
Runtime to complete 20,000 requests (lower is better).
|
33
|
+
|
34
|
+
| Framework | Seconds to complete |
|
35
|
+
|------------|---------------------|
|
36
|
+
| hanami-api | 0.11628299998119473 |
|
37
|
+
| watts | 0.23525599995628 |
|
38
|
+
| roda | 0.348202999914065 |
|
39
|
+
| syro | 0.355627000099048 |
|
40
|
+
| rack-app | 0.6226229998283088 |
|
41
|
+
| cuba | 1.2913489998318255 |
|
42
|
+
| rails | 17.04722599987872 |
|
43
|
+
| synfeld | 171.83788800006732 |
|
44
|
+
| sinatra | 197.47695700009353 |
|
45
|
+
|
46
|
+
### Memory
|
47
|
+
|
48
|
+
Memory footprint for 10,000 routes app (lower is better).
|
49
|
+
|
50
|
+
| Framework | Bytes |
|
51
|
+
|------------|--------|
|
52
|
+
| roda | 47252 |
|
53
|
+
| hanami-api | 53988 |
|
54
|
+
| cuba | 55420 |
|
55
|
+
| syro | 60256 |
|
56
|
+
| rack-app | 82976 |
|
57
|
+
| watts | 84956 |
|
58
|
+
| sinatra | 124980 |
|
59
|
+
| rails | 143048 |
|
60
|
+
| synfeld | 172680 |
|
22
61
|
|
23
62
|
## Usage
|
24
63
|
|
25
|
-
|
64
|
+
Create `config.ru` at the root of your project:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
# frozen_string_literal: true
|
68
|
+
|
69
|
+
require "bundler/setup"
|
70
|
+
require "hanami/api"
|
71
|
+
|
72
|
+
class App < Hanami::API
|
73
|
+
get "/" do
|
74
|
+
"Hello, world"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
run App.new
|
79
|
+
```
|
80
|
+
|
81
|
+
Start the Rack server with `bundle exec rackup`
|
82
|
+
|
83
|
+
### Routes
|
84
|
+
|
85
|
+
A route is a combination of three elements:
|
86
|
+
|
87
|
+
* HTTP method (e.g. `get`)
|
88
|
+
* Path (e.g. `"/"`)
|
89
|
+
* Endpoint (e.g. `MyEndpoint.new`)
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
get "/", to: MyEndpoint.new
|
93
|
+
```
|
94
|
+
|
95
|
+
### HTTP methods
|
96
|
+
|
97
|
+
`Hanami::API` supports the following HTTP methods:
|
98
|
+
|
99
|
+
* `get`
|
100
|
+
* `head`
|
101
|
+
* `post`
|
102
|
+
* `patch`
|
103
|
+
* `put`
|
104
|
+
* `options`
|
105
|
+
* `trace`
|
106
|
+
* `link`
|
107
|
+
* `unlink`
|
108
|
+
|
109
|
+
### Endpoints
|
110
|
+
|
111
|
+
`Hanami::API` supports two kind of endpoints: block and Rack.
|
112
|
+
|
113
|
+
#### Rack endpoint
|
114
|
+
|
115
|
+
The framework is compatible with Rack. Any Rack endpoint, can be passed to the route:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
get "/", to: MyRackEndpoint.new
|
119
|
+
```
|
120
|
+
|
121
|
+
#### Block endpoint
|
122
|
+
|
123
|
+
A block passed to the route definition is named a block endpoint.
|
124
|
+
The returning value will compose the Rack response. It can be:
|
125
|
+
|
126
|
+
##### String
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
get "/" do
|
130
|
+
"Hello, world"
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
It will return `[200, {}, ["Hello, world"]]`
|
135
|
+
|
136
|
+
##### Integer
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
get "/" do
|
140
|
+
418
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
It will return `[418, {}, ["I'm a teapot"]]`
|
145
|
+
|
146
|
+
##### Integer, String
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
get "/" do
|
150
|
+
[401, "You shall not pass"]
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
It will return `[401, {}, ["You shall not pass"]]`
|
155
|
+
|
156
|
+
##### Integer, Hash, String
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
get "/" do
|
160
|
+
[401, {"X-Custom-Header" => "foo"}, "You shall not pass"]
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
It will return `[401, {"X-Custom-Header" => "foo"}, ["You shall not pass"]]`
|
165
|
+
|
166
|
+
### Block context
|
167
|
+
|
168
|
+
When using the block syntax there is a rich API to use.
|
169
|
+
|
170
|
+
#### env
|
171
|
+
|
172
|
+
The `#env` method exposes the Rack environment for the current request
|
173
|
+
|
174
|
+
#### status
|
175
|
+
|
176
|
+
Get HTTP status
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
get "/" do
|
180
|
+
puts status
|
181
|
+
# => 200
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
Set HTTP status
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
get "/" do
|
189
|
+
status(201)
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
#### headers
|
194
|
+
|
195
|
+
Get HTTP response headers
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
get "/" do
|
199
|
+
puts headers
|
200
|
+
# => {}
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
Set HTTP status
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
get "/" do
|
208
|
+
headers["X-My-Header"] = "OK"
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
#### body
|
213
|
+
|
214
|
+
Get HTTP response body
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
get "/" do
|
218
|
+
puts body
|
219
|
+
# => nil
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
Get HTTP response body
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
get "/" do
|
227
|
+
body "Hello, world"
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
#### params
|
232
|
+
|
233
|
+
Access params for current request
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
get "/" do
|
237
|
+
id = params[:id]
|
238
|
+
# ...
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
#### halt
|
243
|
+
|
244
|
+
Halts the flow of the block and immediately returns with the current HTTP status
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
get "/authenticate" do
|
248
|
+
halt(401)
|
249
|
+
|
250
|
+
# this code will never be reached
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
It sets a Rack response: `[401, {}, ["Unauthorized"]]`
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
get "/authenticate" do
|
258
|
+
halt(401, "You shall not pass")
|
259
|
+
|
260
|
+
# this code will never be reached
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
It sets a Rack response: `[401, {}, ["You shall not pass"]]`
|
265
|
+
|
266
|
+
#### redirect
|
267
|
+
|
268
|
+
Redirects request and immediately halts it
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
get "/legacy" do
|
272
|
+
redirect "/dashboard"
|
273
|
+
|
274
|
+
# this code will never be reached
|
275
|
+
end
|
276
|
+
```
|
277
|
+
|
278
|
+
It sets a Rack response: `[301, {"Location" => "/new"}, ["Moved Permanently"]]`
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
get "/legacy" do
|
282
|
+
redirect "/dashboard", 302
|
283
|
+
|
284
|
+
# this code will never be reached
|
285
|
+
end
|
286
|
+
```
|
287
|
+
|
288
|
+
It sets a Rack response: `[302, {"Location" => "/new"}, ["Moved"]]`
|
289
|
+
|
290
|
+
#### back
|
291
|
+
|
292
|
+
Utility for redirect back using HTTP request header `HTTP_REFERER`
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
get "/authenticate" do
|
296
|
+
if authenticate(env)
|
297
|
+
redirect back
|
298
|
+
else
|
299
|
+
# ...
|
300
|
+
end
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
304
|
+
#### json
|
305
|
+
|
306
|
+
Sets a JSON response for the given object
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
get "/user/:id" do
|
310
|
+
user = UserRepository.new.find(params[:id])
|
311
|
+
json(user)
|
312
|
+
end
|
313
|
+
```
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
get "/user/:id" do
|
317
|
+
user = UserRepository.new.find(params[:id])
|
318
|
+
json(user, "application/vnd.api+json")
|
319
|
+
end
|
320
|
+
```
|
321
|
+
|
322
|
+
### Scope
|
323
|
+
|
324
|
+
Prefixing routes is possible with routing scopes:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
scope "api" do
|
328
|
+
scope "v1" do
|
329
|
+
get "/users", to: Actions::V1::Users::Index.new
|
330
|
+
end
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
It will generate a route with `"/api/v1/users"` as path.
|
335
|
+
|
336
|
+
### Rack Middleware
|
337
|
+
|
338
|
+
To mount a Rack middleware it's possible with `.use`
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
# frozen_string_literal: true
|
342
|
+
|
343
|
+
require "bundler/setup"
|
344
|
+
require "hanami/api"
|
345
|
+
|
346
|
+
class App < Hanami::API
|
347
|
+
use ElapsedTime
|
348
|
+
|
349
|
+
scope "api" do
|
350
|
+
use ApiAuthentication
|
351
|
+
|
352
|
+
scope "v1" do
|
353
|
+
use ApiV1Deprecation
|
354
|
+
end
|
355
|
+
|
356
|
+
scope "v2" do
|
357
|
+
# ...
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
```
|
362
|
+
|
363
|
+
Middleware are inherited from top level scope.
|
364
|
+
|
365
|
+
In the example above, `ElapsedTime` is used for each incoming request because
|
366
|
+
it's part of the top level scope. `ApiAuthentication` it's used for all the API
|
367
|
+
versions, because it's defined in the `"api"` scope. `ApiV1Deprecation` is used
|
368
|
+
only by the routes in `"v1"` scope, but not by `"v2"`.
|
26
369
|
|
27
370
|
## Development
|
28
371
|
|
@@ -32,5 +375,5 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
375
|
|
33
376
|
## Contributing
|
34
377
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
378
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/hanami/api.
|
36
379
|
|
data/Rakefile
CHANGED
@@ -1,2 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake"
|
1
4
|
require "bundler/gem_tasks"
|
2
|
-
|
5
|
+
require "rspec/core/rake_task"
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
8
|
+
file_list = FileList["spec/**/*_spec.rb"]
|
9
|
+
|
10
|
+
task.pattern = file_list
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: "spec"
|
data/bin/console
CHANGED
data/hanami-api.gemspec
CHANGED
@@ -1,27 +1,37 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
require 'hanami/api/version'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/hanami/api/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = "hanami-api"
|
8
|
-
spec.version = Hanami::
|
7
|
+
spec.version = Hanami::API::VERSION
|
9
8
|
spec.authors = ["Luca Guidi"]
|
10
9
|
spec.email = ["me@lucaguidi.com"]
|
11
10
|
|
12
11
|
spec.summary = "Hanami API"
|
13
|
-
spec.description = "
|
14
|
-
spec.homepage = "http://
|
12
|
+
spec.description = "Extremely fast and lightweight HTTP API"
|
13
|
+
spec.homepage = "http://rubygems.org"
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
15
17
|
|
16
|
-
spec.metadata[
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/hanami/api"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/hanami/api/blob/master/CHANGELOG.md"
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
26
|
end
|
27
|
+
|
21
28
|
spec.bindir = "exe"
|
22
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
30
|
spec.require_paths = ["lib"]
|
24
31
|
|
25
|
-
spec.
|
26
|
-
|
32
|
+
spec.add_dependency "hanami-router", "~> 2.0.alpha"
|
33
|
+
|
34
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.8"
|
36
|
+
spec.add_development_dependency "rubocop", "~> 0.79"
|
27
37
|
end
|
data/lib/hanami/api.rb
CHANGED
@@ -1,7 +1,345 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hanami
|
4
|
-
|
5
|
-
|
4
|
+
# Hanami::API
|
5
|
+
#
|
6
|
+
# @since 0.1.0
|
7
|
+
class API
|
8
|
+
require "hanami/api/version"
|
9
|
+
require "hanami/api/error"
|
10
|
+
require "hanami/api/router"
|
11
|
+
require "hanami/api/middleware"
|
12
|
+
|
13
|
+
# @since 0.1.0
|
14
|
+
# @api private
|
15
|
+
def self.inherited(app)
|
16
|
+
super
|
17
|
+
|
18
|
+
app.class_eval do
|
19
|
+
@routes = []
|
20
|
+
@stack = Middleware::Stack.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# @since 0.1.0
|
26
|
+
# @api private
|
27
|
+
attr_reader :routes
|
28
|
+
|
29
|
+
# @since 0.1.0
|
30
|
+
# @api private
|
31
|
+
attr_reader :stack
|
32
|
+
end
|
33
|
+
|
34
|
+
# Defines a named root route (a GET route for "/")
|
35
|
+
#
|
36
|
+
# @param to [#call] the Rack endpoint
|
37
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
38
|
+
#
|
39
|
+
# @since 0.1.0
|
40
|
+
#
|
41
|
+
# @see .get
|
42
|
+
#
|
43
|
+
# @example Proc endpoint
|
44
|
+
# require "hanami/router"
|
45
|
+
#
|
46
|
+
# router = Hanami::Router.new do
|
47
|
+
# root to: ->(env) { [200, {}, ["Hello from Hanami!"]] }
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @example Block endpoint
|
51
|
+
# require "hanami/router"
|
52
|
+
#
|
53
|
+
# router = Hanami::Router.new do
|
54
|
+
# root do
|
55
|
+
# "Hello from Hanami!"
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
def self.root(*args, **kwargs, &blk)
|
59
|
+
@routes << [:root, args, kwargs, blk]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Defines a route that accepts GET requests for the given path.
|
63
|
+
# It also defines a route to accept HEAD requests.
|
64
|
+
#
|
65
|
+
# @param path [String] the relative URL to be matched
|
66
|
+
# @param to [#call] the Rack endpoint
|
67
|
+
# @param as [Symbol] a unique name for the route
|
68
|
+
# @param constraints [Hash] a set of constraints for path variables
|
69
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
70
|
+
#
|
71
|
+
# @since 0.1.0
|
72
|
+
#
|
73
|
+
# @example Proc endpoint
|
74
|
+
# require "hanami/api"
|
75
|
+
#
|
76
|
+
# class MyAPI < Hanami::API
|
77
|
+
# get "/", to: ->(*) { [200, {}, ["OK"]] }
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @example Block endpoint
|
81
|
+
# require "hanami/api"
|
82
|
+
#
|
83
|
+
# class MyAPI < Hanami::API
|
84
|
+
# get "/" do
|
85
|
+
# "OK"
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# @example Constraints
|
90
|
+
# require "hanami/api"
|
91
|
+
#
|
92
|
+
# class MyAPI < Hanami::API
|
93
|
+
# get "/users/:id", to: ->(*) { [200, {}, ["OK"]] }, id: /\d+/
|
94
|
+
# end
|
95
|
+
def self.get(*args, **kwargs, &blk)
|
96
|
+
@routes << [:get, args, kwargs, blk]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Defines a route that accepts POST requests for the given path.
|
100
|
+
#
|
101
|
+
# @param path [String] the relative URL to be matched
|
102
|
+
# @param to [#call] the Rack endpoint
|
103
|
+
# @param as [Symbol] a unique name for the route
|
104
|
+
# @param constraints [Hash] a set of constraints for path variables
|
105
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
106
|
+
#
|
107
|
+
# @since 0.1.0
|
108
|
+
#
|
109
|
+
# @see .get
|
110
|
+
def self.post(*args, **kwargs, &blk)
|
111
|
+
@routes << [:post, args, kwargs, blk]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Defines a route that accepts PATCH requests for the given path.
|
115
|
+
#
|
116
|
+
# @param path [String] the relative URL to be matched
|
117
|
+
# @param to [#call] the Rack endpoint
|
118
|
+
# @param as [Symbol] a unique name for the route
|
119
|
+
# @param constraints [Hash] a set of constraints for path variables
|
120
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
121
|
+
#
|
122
|
+
# @since 0.1.0
|
123
|
+
#
|
124
|
+
# @see .get
|
125
|
+
def self.patch(*args, **kwargs, &blk)
|
126
|
+
@routes << [:patch, args, kwargs, blk]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Defines a route that accepts PUT requests for the given path.
|
130
|
+
#
|
131
|
+
# @param path [String] the relative URL to be matched
|
132
|
+
# @param to [#call] the Rack endpoint
|
133
|
+
# @param as [Symbol] a unique name for the route
|
134
|
+
# @param constraints [Hash] a set of constraints for path variables
|
135
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
136
|
+
#
|
137
|
+
# @since 0.1.0
|
138
|
+
#
|
139
|
+
# @see .get
|
140
|
+
def self.put(*args, **kwargs, &blk)
|
141
|
+
@routes << [:put, args, kwargs, blk]
|
142
|
+
end
|
143
|
+
|
144
|
+
# Defines a route that accepts DELETE requests for the given path.
|
145
|
+
#
|
146
|
+
# @param path [String] the relative URL to be matched
|
147
|
+
# @param to [#call] the Rack endpoint
|
148
|
+
# @param as [Symbol] a unique name for the route
|
149
|
+
# @param constraints [Hash] a set of constraints for path variables
|
150
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
151
|
+
#
|
152
|
+
# @since 0.1.0
|
153
|
+
#
|
154
|
+
# @see .get
|
155
|
+
def self.delete(*args, **kwargs, &blk)
|
156
|
+
@routes << [:delete, args, kwargs, blk]
|
157
|
+
end
|
158
|
+
|
159
|
+
# Defines a route that accepts TRACE requests for the given path.
|
160
|
+
#
|
161
|
+
# @param path [String] the relative URL to be matched
|
162
|
+
# @param to [#call] the Rack endpoint
|
163
|
+
# @param as [Symbol] a unique name for the route
|
164
|
+
# @param constraints [Hash] a set of constraints for path variables
|
165
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
166
|
+
#
|
167
|
+
# @since 0.1.0
|
168
|
+
#
|
169
|
+
# @see .get
|
170
|
+
def self.trace(*args, **kwargs, &blk)
|
171
|
+
@routes << [:trace, args, kwargs, blk]
|
172
|
+
end
|
173
|
+
|
174
|
+
# Defines a route that accepts OPTIONS requests for the given path.
|
175
|
+
#
|
176
|
+
# @param path [String] the relative URL to be matched
|
177
|
+
# @param to [#call] the Rack endpoint
|
178
|
+
# @param as [Symbol] a unique name for the route
|
179
|
+
# @param constraints [Hash] a set of constraints for path variables
|
180
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
181
|
+
#
|
182
|
+
# @since 0.1.0
|
183
|
+
#
|
184
|
+
# @see .get
|
185
|
+
def self.options(*args, **kwargs, &blk)
|
186
|
+
@routes << [:options, args, kwargs, blk]
|
187
|
+
end
|
188
|
+
|
189
|
+
# Defines a route that accepts LINK requests for the given path.
|
190
|
+
#
|
191
|
+
# @param path [String] the relative URL to be matched
|
192
|
+
# @param to [#call] the Rack endpoint
|
193
|
+
# @param as [Symbol] a unique name for the route
|
194
|
+
# @param constraints [Hash] a set of constraints for path variables
|
195
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
196
|
+
#
|
197
|
+
# @since 0.1.0
|
198
|
+
#
|
199
|
+
# @see .get
|
200
|
+
def self.link(*args, **kwargs, &blk)
|
201
|
+
@routes << [:link, args, kwargs, blk]
|
202
|
+
end
|
203
|
+
|
204
|
+
# Defines a route that accepts UNLINK requests for the given path.
|
205
|
+
#
|
206
|
+
# @param path [String] the relative URL to be matched
|
207
|
+
# @param to [#call] the Rack endpoint
|
208
|
+
# @param as [Symbol] a unique name for the route
|
209
|
+
# @param constraints [Hash] a set of constraints for path variables
|
210
|
+
# @param blk [Proc] the anonymous proc to be used as endpoint for the route
|
211
|
+
#
|
212
|
+
# @since 0.1.0
|
213
|
+
#
|
214
|
+
# @see .get
|
215
|
+
def self.unlink(*args, **kwargs, &blk)
|
216
|
+
@routes << [:unlink, args, kwargs, blk]
|
217
|
+
end
|
218
|
+
|
219
|
+
# Defines a route that redirects the incoming request to another path.
|
220
|
+
#
|
221
|
+
# @param path [String] the relative URL to be matched
|
222
|
+
# @param to [#call] the Rack endpoint
|
223
|
+
# @param as [Symbol] a unique name for the route
|
224
|
+
# @param code [Integer] a HTTP status code to use for the redirect
|
225
|
+
#
|
226
|
+
# @since 0.1.0
|
227
|
+
#
|
228
|
+
# @see .get
|
229
|
+
def self.redirect(*args, **kwargs, &blk)
|
230
|
+
@routes << [:redirect, args, kwargs, blk]
|
231
|
+
end
|
232
|
+
|
233
|
+
# Defines a routing scope. Routes defined in the context of a scope,
|
234
|
+
# inherit the given path as path prefix and as a named routes prefix.
|
235
|
+
#
|
236
|
+
# @param path [String] the scope path to be used as a path prefix
|
237
|
+
# @param blk [Proc] the routes definitions withing the scope
|
238
|
+
#
|
239
|
+
# @since 0.1.0
|
240
|
+
#
|
241
|
+
# @see #path
|
242
|
+
#
|
243
|
+
# @example
|
244
|
+
# require "hanami/api"
|
245
|
+
#
|
246
|
+
# class MyAPI < Hanami::API
|
247
|
+
# scope "v1" do
|
248
|
+
# get "/users", to: ->(*) { ... }, as: :users
|
249
|
+
# end
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
# # It generates a route with a path `/v1/users`
|
253
|
+
def self.scope(*args, **kwargs, &blk)
|
254
|
+
@routes << [:scope, args, kwargs, blk]
|
255
|
+
end
|
256
|
+
|
257
|
+
# Mount a Rack application at the specified path.
|
258
|
+
# All the requests starting with the specified path, will be forwarded to
|
259
|
+
# the given application.
|
260
|
+
#
|
261
|
+
# All the other methods (eg `#get`) support callable objects, but they
|
262
|
+
# restrict the range of the acceptable HTTP verb. Mounting an application
|
263
|
+
# with #mount doesn't apply this kind of restriction at the router level,
|
264
|
+
# but let the application to decide.
|
265
|
+
#
|
266
|
+
# @param app [#call] a class or an object that responds to #call
|
267
|
+
# @param at [String] the relative path where to mount the app
|
268
|
+
# @param constraints [Hash] a set of constraints for path variables
|
269
|
+
#
|
270
|
+
# @since 0.1.0
|
271
|
+
#
|
272
|
+
# @example
|
273
|
+
# require "hanami/api"
|
274
|
+
#
|
275
|
+
# class MyAPI < Hanami::API
|
276
|
+
# mount MyRackApp.new, at: "/foo"
|
277
|
+
# end
|
278
|
+
def self.mount(*args, **kwargs, &blk)
|
279
|
+
@routes << [:mount, args, kwargs, blk]
|
280
|
+
end
|
281
|
+
|
282
|
+
# Use a Rack middleware
|
283
|
+
#
|
284
|
+
# @param middleware [Class,#call] a Rack middleware
|
285
|
+
# @param args [Array<Object>] an optional array of arguments for Rack middleware
|
286
|
+
# @param blk [Block] an optional block to pass to the Rack middleware
|
287
|
+
#
|
288
|
+
# @since 0.1.0
|
289
|
+
#
|
290
|
+
# @example
|
291
|
+
# require "hanami/api"
|
292
|
+
#
|
293
|
+
# class MyAPI < Hanami::API
|
294
|
+
# use MyRackMiddleware
|
295
|
+
# end
|
296
|
+
def self.use(middleware, *args, &blk)
|
297
|
+
@stack.use(middleware, args, &blk)
|
298
|
+
end
|
299
|
+
|
300
|
+
# @since 0.1.0
|
301
|
+
def initialize(routes: self.class.routes, stack: self.class.stack)
|
302
|
+
@stack = stack
|
303
|
+
@router = Router.new(stack: @stack) do
|
304
|
+
routes.each do |method_name, args, kwargs, blk|
|
305
|
+
send(method_name, *args, **kwargs, &blk)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
freeze
|
310
|
+
end
|
311
|
+
|
312
|
+
# @since 0.1.0
|
313
|
+
def freeze
|
314
|
+
@app = @stack.finalize(@router)
|
315
|
+
@url_helpers = @router.url_helpers
|
316
|
+
@router.remove_instance_variable(:@url_helpers)
|
317
|
+
remove_instance_variable(:@stack)
|
318
|
+
remove_instance_variable(:@router)
|
319
|
+
@url_helpers.freeze
|
320
|
+
@app.freeze
|
321
|
+
super
|
322
|
+
end
|
323
|
+
|
324
|
+
# @since 0.1.0
|
325
|
+
def call(env)
|
326
|
+
@app.call(env)
|
327
|
+
end
|
328
|
+
|
329
|
+
# TODO: verify if needed here on in block context
|
330
|
+
#
|
331
|
+
# @since 0.1.0
|
332
|
+
# @api private
|
333
|
+
def path(name, variables = {})
|
334
|
+
@url_helpers.path(name, variables)
|
335
|
+
end
|
336
|
+
|
337
|
+
# TODO: verify if needed here on in block context
|
338
|
+
#
|
339
|
+
# @since 0.1.0
|
340
|
+
# @api private
|
341
|
+
def url(name, variables = {})
|
342
|
+
@url_helpers.url(name, variables)
|
343
|
+
end
|
6
344
|
end
|
7
345
|
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/router/block"
|
4
|
+
require "rack/utils"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
class API
|
9
|
+
module Block
|
10
|
+
class Context < Hanami::Router::Block::Context
|
11
|
+
# @overload body
|
12
|
+
# Gets the current HTTP response body
|
13
|
+
# @return [String] the HTTP body
|
14
|
+
# @overload body(value)
|
15
|
+
# Sets the HTTP body
|
16
|
+
# @param value [String] the HTTP response body
|
17
|
+
#
|
18
|
+
# @since 0.1.0
|
19
|
+
def body(value = nil)
|
20
|
+
if value
|
21
|
+
@body = value
|
22
|
+
else
|
23
|
+
@body
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Halts the flow of the block and immediately returns with the current
|
28
|
+
# HTTP status
|
29
|
+
#
|
30
|
+
# @param status [Integer] a valid HTTP status code
|
31
|
+
# @param body [String] an optional HTTP response body
|
32
|
+
#
|
33
|
+
# @example HTTP Status
|
34
|
+
# get "/authenticate" do
|
35
|
+
# halt(401)
|
36
|
+
#
|
37
|
+
# # this code will never be reached
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # It sets a Rack response: [401, {}, ["Unauthorized"]]
|
41
|
+
#
|
42
|
+
# @example HTTP Status and body
|
43
|
+
# get "/authenticate" do
|
44
|
+
# halt(401, "You shall not pass")
|
45
|
+
#
|
46
|
+
# # this code will never be reached
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # It sets a Rack response: [401, {}, ["You shall not pass"]]
|
50
|
+
#
|
51
|
+
# @since 0.1.0
|
52
|
+
def halt(status, body = nil)
|
53
|
+
body ||= http_status(status)
|
54
|
+
throw :halt, [status, body]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Redirects request and immediately halts it
|
58
|
+
#
|
59
|
+
# @param url [String] the destination URL
|
60
|
+
# @param status [Integer] an optional HTTP code for the redirect
|
61
|
+
#
|
62
|
+
# @see #halt
|
63
|
+
#
|
64
|
+
# @since 0.1.0
|
65
|
+
#
|
66
|
+
# @example URL
|
67
|
+
# get "/legacy" do
|
68
|
+
# redirect "/dashboard"
|
69
|
+
#
|
70
|
+
# # this code will never be reached
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# # It sets a Rack response: [301, {"Location" => "/new"}, ["Moved Permanently"]]
|
74
|
+
#
|
75
|
+
# @example URL and HTTP status
|
76
|
+
# get "/legacy" do
|
77
|
+
# redirect "/dashboard", 302
|
78
|
+
#
|
79
|
+
# # this code will never be reached
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# # It sets a Rack response: [302, {"Location" => "/new"}, ["Moved"]]
|
83
|
+
def redirect(url, status = 301)
|
84
|
+
headers["Location"] = url
|
85
|
+
halt(status)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Utility for redirect back using HTTP request header `HTTP_REFERER`
|
89
|
+
#
|
90
|
+
# @since 0.1.0
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# get "/authenticate" do
|
94
|
+
# if authenticate(env)
|
95
|
+
# redirect back
|
96
|
+
# else
|
97
|
+
# # ...
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
def back
|
101
|
+
env["HTTP_REFERER"] || "/"
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sets a JSON response for the given object
|
105
|
+
#
|
106
|
+
# @param object [Object] a JSON serializable object
|
107
|
+
# @param mime [String] optional MIME type to set for the response
|
108
|
+
#
|
109
|
+
# @since 0.1.0
|
110
|
+
#
|
111
|
+
# @example JSON serializable object
|
112
|
+
# get "/user/:id" do
|
113
|
+
# user = UserRepository.new.find(params[:id])
|
114
|
+
# json(user)
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# @example JSON serializable object and custom MIME type
|
118
|
+
# get "/user/:id" do
|
119
|
+
# user = UserRepository.new.find(params[:id])
|
120
|
+
# json(user, "application/vnd.api+json")
|
121
|
+
# end
|
122
|
+
def json(object, mime = "application/json")
|
123
|
+
headers["Content-Type"] = mime
|
124
|
+
JSON.generate(object)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @since 0.1.0
|
128
|
+
# @api private
|
129
|
+
def call
|
130
|
+
case caught
|
131
|
+
in String => body
|
132
|
+
[status, headers, [body]]
|
133
|
+
in Integer => status
|
134
|
+
[status, headers, [self.body || http_status(status)]]
|
135
|
+
in [Integer, String] => response
|
136
|
+
[response[0], headers, [response[1]]]
|
137
|
+
in [Integer, Hash, String] => response
|
138
|
+
headers.merge!(response[1])
|
139
|
+
[response[0], headers, [response[2]]]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# @since 0.1.0
|
146
|
+
# @api private
|
147
|
+
def caught
|
148
|
+
catch :halt do
|
149
|
+
instance_exec(&@blk)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @since 0.1.0
|
154
|
+
# @api private
|
155
|
+
def http_status(code)
|
156
|
+
Rack::Utils::HTTP_STATUS_CODES.fetch(code)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/builder"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class API
|
7
|
+
# Hanami::API middleware stack
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
# @api private
|
11
|
+
module Middleware
|
12
|
+
# Middleware stack
|
13
|
+
#
|
14
|
+
# @since 0.1.0
|
15
|
+
# @api private
|
16
|
+
class Stack
|
17
|
+
# @since 0.1.0
|
18
|
+
# @api private
|
19
|
+
ROOT_PREFIX = "/"
|
20
|
+
private_constant :ROOT_PREFIX
|
21
|
+
|
22
|
+
# @since 0.1.0
|
23
|
+
# @api private
|
24
|
+
def initialize
|
25
|
+
@prefix = ROOT_PREFIX
|
26
|
+
@stack = Hash.new { |hash, key| hash[key] = [] }
|
27
|
+
end
|
28
|
+
|
29
|
+
# @since 0.1.0
|
30
|
+
# @api private
|
31
|
+
def use(middleware, args, &blk)
|
32
|
+
@stack[@prefix].push([middleware, args, blk])
|
33
|
+
end
|
34
|
+
|
35
|
+
# @since 0.1.0
|
36
|
+
# @api private
|
37
|
+
def with(path)
|
38
|
+
prefix = @prefix
|
39
|
+
@prefix = path
|
40
|
+
yield
|
41
|
+
ensure
|
42
|
+
@prefix = prefix
|
43
|
+
end
|
44
|
+
|
45
|
+
# @since 0.1.0
|
46
|
+
# @api private
|
47
|
+
def finalize(app) # rubocop:disable Metrics/MethodLength
|
48
|
+
uniq!
|
49
|
+
return app if @stack.empty?
|
50
|
+
|
51
|
+
s = self
|
52
|
+
|
53
|
+
Rack::Builder.new do
|
54
|
+
s.each do |prefix, stack|
|
55
|
+
s.mapped(self, prefix) do
|
56
|
+
stack.each do |middleware, args, blk|
|
57
|
+
use(middleware, *args, &blk)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
run app
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @since 0.1.0
|
67
|
+
# @api private
|
68
|
+
def each(&blk)
|
69
|
+
uniq!
|
70
|
+
@stack.each(&blk)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @since 0.1.0
|
74
|
+
# @api private
|
75
|
+
def mapped(builder, prefix, &blk)
|
76
|
+
if prefix == ROOT_PREFIX
|
77
|
+
builder.instance_eval(&blk)
|
78
|
+
else
|
79
|
+
builder.map(prefix, &blk)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @since 0.1.0
|
86
|
+
# @api private
|
87
|
+
def uniq!
|
88
|
+
@stack.each_value(&:uniq!)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hanami/router"
|
4
|
+
require "hanami/api/block/context"
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
class API
|
8
|
+
# @since 0.1.0
|
9
|
+
class Router < ::Hanami::Router
|
10
|
+
# @since 0.1.0
|
11
|
+
# @api private
|
12
|
+
def initialize(stack:, **kwargs, &blk)
|
13
|
+
@stack = stack
|
14
|
+
super(block_context: Block::Context, **kwargs, &blk)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @since 0.1.0
|
18
|
+
# @api private
|
19
|
+
def freeze
|
20
|
+
return self if frozen?
|
21
|
+
|
22
|
+
remove_instance_variable(:@stack)
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# @since 0.1.0
|
27
|
+
# @api private
|
28
|
+
def use(middleware, *args, &blk)
|
29
|
+
@stack.use(middleware, args, &blk)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @since 0.1.0
|
33
|
+
# @api private
|
34
|
+
def scope(*args, **kwargs, &blk)
|
35
|
+
@stack.with(args.first) do
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/hanami/api/version.rb
CHANGED
metadata
CHANGED
@@ -1,44 +1,72 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.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: 2020-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: hanami-router
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
19
|
+
version: 2.0.alpha
|
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:
|
26
|
+
version: 2.0.alpha
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.8'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
|
54
|
+
version: '3.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.79'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.79'
|
69
|
+
description: Extremely fast and lightweight HTTP API
|
42
70
|
email:
|
43
71
|
- me@lucaguidi.com
|
44
72
|
executables: []
|
@@ -46,6 +74,9 @@ extensions: []
|
|
46
74
|
extra_rdoc_files: []
|
47
75
|
files:
|
48
76
|
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".rubocop.yml"
|
79
|
+
- CHANGELOG.md
|
49
80
|
- Gemfile
|
50
81
|
- README.md
|
51
82
|
- Rakefile
|
@@ -53,11 +84,18 @@ files:
|
|
53
84
|
- bin/setup
|
54
85
|
- hanami-api.gemspec
|
55
86
|
- lib/hanami/api.rb
|
87
|
+
- lib/hanami/api/block/context.rb
|
88
|
+
- lib/hanami/api/error.rb
|
89
|
+
- lib/hanami/api/middleware.rb
|
90
|
+
- lib/hanami/api/router.rb
|
56
91
|
- lib/hanami/api/version.rb
|
57
|
-
homepage: http://
|
92
|
+
homepage: http://rubygems.org
|
58
93
|
licenses: []
|
59
94
|
metadata:
|
60
95
|
allowed_push_host: https://rubygems.org
|
96
|
+
homepage_uri: http://rubygems.org
|
97
|
+
source_code_uri: https://github.com/hanami/api
|
98
|
+
changelog_uri: https://github.com/hanami/api/blob/master/CHANGELOG.md
|
61
99
|
post_install_message:
|
62
100
|
rdoc_options: []
|
63
101
|
require_paths:
|
@@ -66,15 +104,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
104
|
requirements:
|
67
105
|
- - ">="
|
68
106
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
107
|
+
version: 2.7.0
|
70
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
109
|
requirements:
|
72
110
|
- - ">="
|
73
111
|
- !ruby/object:Gem::Version
|
74
112
|
version: '0'
|
75
113
|
requirements: []
|
76
|
-
|
77
|
-
rubygems_version: 2.6.11
|
114
|
+
rubygems_version: 3.1.2
|
78
115
|
signing_key:
|
79
116
|
specification_version: 4
|
80
117
|
summary: Hanami API
|