flon 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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +12 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +212 -0
- data/Rakefile +11 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/flon.gemspec +37 -0
- data/lib/flon.rb +12 -0
- data/lib/flon/api.rb +85 -0
- data/lib/flon/dsl.rb +90 -0
- data/lib/flon/router.rb +90 -0
- data/lib/flon/version.rb +6 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dcd15fb29f7a61dc56644751c6b3f3f6a6181e148a386e71b2b9d5197048f3dd
|
4
|
+
data.tar.gz: f12632af7ac2b14a6ba79a77fcb8cdc0d6905de5ad47bb59bc73a5d5b39d3089
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c552c94ec88849cdfb0d6e68b584609154f7ff73f9d486ee95860bca47416f2bb15327a6f97890810c5c8de2cb9a0e75c8503985d000448115e9d2e518e66bd7
|
7
|
+
data.tar.gz: 9051e7d9f471a94b78a3731e2996eee2e61c9d7f10babc144873314a1d4cbc8cd6b2b1d1036ae99da34fd412b56411e0681d2872e91b731b3996fe914628723f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- README.md LICENSE.txt
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 unleashy
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
# Flon
|
2
|
+
|
3
|
+
Flon, the API maker that uses JSON. It focuses on not doing any magic: it’s
|
4
|
+
just a thin and convenient wrapper over Rack, with a hopefully obvious DSL.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'flon'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install flon
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Basic usage
|
25
|
+
|
26
|
+
To use Flon, simply make a plain class extending `Flon::DSL`. This will put the
|
27
|
+
router DSL in your class scope.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'flon'
|
31
|
+
|
32
|
+
class MyApi
|
33
|
+
extend Flon::DSL
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@names = []
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace '/names' do
|
40
|
+
get '/'
|
41
|
+
attr_reader :names
|
42
|
+
|
43
|
+
post '/new'
|
44
|
+
def add_name(_params, body)
|
45
|
+
@names << body
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace '/:index' do
|
49
|
+
get '/'
|
50
|
+
def name_by_index(params)
|
51
|
+
@names[params[:index]]
|
52
|
+
end
|
53
|
+
|
54
|
+
put '/edit'
|
55
|
+
def change_name(params, body)
|
56
|
+
@names[params[:index]] = body
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
You can then create a `Flon::Api` using this:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
api = Flon::Api.new(MyApi.new)
|
67
|
+
```
|
68
|
+
|
69
|
+
Note how you initialise the `MyApi` normally—you can use this for dependency
|
70
|
+
injection without a headache.
|
71
|
+
|
72
|
+
`Flon::Api` is a bog-standard Rack app, so you can just use it in `config.ru`,
|
73
|
+
or whatever method you use.
|
74
|
+
|
75
|
+
### Routing
|
76
|
+
|
77
|
+
The DSL defines methods for routing based on every HTTP request method, except
|
78
|
+
HEAD, TRACE, OPTIONS, and CONNECT. HEAD is handled by GET routes but the body
|
79
|
+
is discarded.
|
80
|
+
|
81
|
+
When you call a routing method, the next method you define will be bound to
|
82
|
+
that route.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
get '/route'
|
86
|
+
def get_route
|
87
|
+
# matches GET /route and HEAD /route
|
88
|
+
end
|
89
|
+
|
90
|
+
post '/route'
|
91
|
+
def post_route
|
92
|
+
# matches POST /route
|
93
|
+
end
|
94
|
+
|
95
|
+
put '/route'
|
96
|
+
def put_route
|
97
|
+
# matches PUT /route
|
98
|
+
end
|
99
|
+
|
100
|
+
delete '/route'
|
101
|
+
def delete_route
|
102
|
+
# matches DELETE /route
|
103
|
+
end
|
104
|
+
|
105
|
+
patch '/route'
|
106
|
+
def patch_route
|
107
|
+
# matches PATCH /route
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
Each routing DSL method takes a single route pattern as a `String`. The route
|
112
|
+
pattern is the exact same as [Sinatra’s](https://github.com/sinatra/mustermann/blob/master/mustermann/README.md#-sinatra-pattern).
|
113
|
+
|
114
|
+
The bound method is called when an HTTP request matches that route with that
|
115
|
+
HTTP method.
|
116
|
+
|
117
|
+
The method will receive two arguments, in that order:
|
118
|
+
|
119
|
+
1. A parameters hash, containing the query string parameters and the route
|
120
|
+
parameters;
|
121
|
+
2. A body object, containing the HTTP request’s body parsed with `JSON.parse`.
|
122
|
+
For GETs, HEADs, and DELETEs, this will be `nil`.
|
123
|
+
|
124
|
+
#### Namespaces
|
125
|
+
|
126
|
+
You can also **namespace** routes:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
namespace '/users' do
|
130
|
+
get '/'
|
131
|
+
attr_reader :users # GET /users
|
132
|
+
|
133
|
+
post '/new'
|
134
|
+
def add_user(_params, user) # /users/new
|
135
|
+
@users << user
|
136
|
+
end
|
137
|
+
|
138
|
+
namespace '/:id' do
|
139
|
+
get
|
140
|
+
def user_by_id(params) # GET /users/:id
|
141
|
+
@users[params[:id]]
|
142
|
+
end
|
143
|
+
|
144
|
+
put '/edit'
|
145
|
+
def edit_user(params, body) # PUT /users/:id/edit
|
146
|
+
@users[params[:id]] = body
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
Namespaces are entirely “virtual”, that is, they just concatenate their route
|
153
|
+
patterns with their children, so they’re just there for DRYness.
|
154
|
+
|
155
|
+
### Versioning
|
156
|
+
|
157
|
+
The `version` method applies a namespace throughout every route:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
version '/v1'
|
161
|
+
|
162
|
+
get '/yes'
|
163
|
+
def yes
|
164
|
+
'yes'
|
165
|
+
end
|
166
|
+
|
167
|
+
get '/no'
|
168
|
+
def no
|
169
|
+
'no'
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
Going to `/yes` will give you a 404, but going to `/v1/yes` will give you
|
174
|
+
`"yes"`. The same thing happens with the `/no` route.
|
175
|
+
|
176
|
+
In reality, `version` is just an alias for `namespace`.
|
177
|
+
|
178
|
+
### Responding
|
179
|
+
|
180
|
+
Whatever is returned by the route’s method is `#to_json`’d and sent back. If
|
181
|
+
you want to return a custom status code, return a `Flon::Response` object,
|
182
|
+
which takes the status code as the first argument and the body as the second
|
183
|
+
argument of its constructor:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
get '/admin'
|
187
|
+
def admin
|
188
|
+
Flon::Response.new(403, 'No. Go away.')
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
## Development
|
193
|
+
|
194
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
195
|
+
`rake` to run rubocop and the tests. You can also run `bin/console` for an
|
196
|
+
interactive prompt that will allow you to experiment.
|
197
|
+
|
198
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
199
|
+
release a new version, update the version number in `version.rb`, and then run
|
200
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
201
|
+
git commits and tags, and push the `.gem` file to
|
202
|
+
[rubygems.org](https://rubygems.org).
|
203
|
+
|
204
|
+
## Contributing
|
205
|
+
|
206
|
+
Bug reports and pull requests are welcome on GitHub at
|
207
|
+
[https://github.com/unleashy/flon](https://github.com/unleashy/flon).
|
208
|
+
|
209
|
+
## License
|
210
|
+
|
211
|
+
The gem is available as open source under the terms of the
|
212
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'flon'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/flon.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'flon/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'flon'
|
9
|
+
spec.version = Flon::VERSION
|
10
|
+
spec.authors = ['unleashy']
|
11
|
+
spec.email = ['unleashy@users.noreply.github.com']
|
12
|
+
|
13
|
+
spec.summary = 'Flon, the API maker that uses JSON'
|
14
|
+
spec.homepage = 'https://github.com/unleashy/flon'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = 'exe'
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
|
29
|
+
spec.add_dependency 'mustermann', '~> 1.0'
|
30
|
+
spec.add_dependency 'rack', '~> 2.0'
|
31
|
+
|
32
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
33
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
35
|
+
spec.add_development_dependency 'rubocop', '>= 0.74.0'
|
36
|
+
spec.add_development_dependency 'simplecov', '>= 0.17.0'
|
37
|
+
end
|
data/lib/flon.rb
ADDED
data/lib/flon/api.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Flon
|
7
|
+
# Represents a response, with an integer status and a body that will be
|
8
|
+
# #to_json'd.
|
9
|
+
#
|
10
|
+
# @!attribute [r] status
|
11
|
+
# @return [Integer] the status to use when responding
|
12
|
+
#
|
13
|
+
# @!attribute [r] body
|
14
|
+
# @return [#to_json] the object to respond with that shall be #to_json'd
|
15
|
+
Response = Struct.new(:status, :body)
|
16
|
+
|
17
|
+
# This class is Flon's Rack application.
|
18
|
+
class Api
|
19
|
+
# Creates a new {Api} object with the given API.
|
20
|
+
#
|
21
|
+
# @param [Object] api the API to use; its class must respond to #router and
|
22
|
+
# the result must be a {Router}.
|
23
|
+
# @return [Api] the newly constructed object
|
24
|
+
def initialize(api)
|
25
|
+
raise ArgumentError, "given API's class does not respond to #routes" unless valid_api?(api)
|
26
|
+
|
27
|
+
@api = api
|
28
|
+
@router = api.class.router
|
29
|
+
end
|
30
|
+
|
31
|
+
# Implements the Rack interface.
|
32
|
+
#
|
33
|
+
# @param [Hash] env a rack environment
|
34
|
+
# @return [Array] a rack-suitable response
|
35
|
+
def call(env)
|
36
|
+
request = Rack::Request.new(env)
|
37
|
+
response = dispatch(request)
|
38
|
+
[response.status, { 'Content-Type' => 'application/json' }, [response.body]]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def dispatch(request)
|
44
|
+
method = http_method_to_symbol(request.request_method)
|
45
|
+
path = request.path_info
|
46
|
+
|
47
|
+
match = @router.match(method, path)
|
48
|
+
case match
|
49
|
+
when :bad_path then Response.new(404, '"404 Not Found"')
|
50
|
+
when :bad_method then Response.new(405, '"405 Method Not Allowed"')
|
51
|
+
else call_action(request.params, match, receives_body?(method) ? request.body.read : nil)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def call_action(params, match, body)
|
56
|
+
params = params.transform_keys(&:to_sym).merge(match.params)
|
57
|
+
responsify(call_respect_arity(match.action.bind(@api), params, body))
|
58
|
+
end
|
59
|
+
|
60
|
+
def responsify(result)
|
61
|
+
if result.is_a?(Response)
|
62
|
+
result.tap { |it| it.body = it.body.to_json }
|
63
|
+
else
|
64
|
+
Response.new(200, result.to_json)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def call_respect_arity(method, *args)
|
69
|
+
arity = method.arity
|
70
|
+
arity.negative? ? method.call(*args) : method.call(*args[0...arity])
|
71
|
+
end
|
72
|
+
|
73
|
+
def http_method_to_symbol(http_method)
|
74
|
+
http_method.downcase.to_sym
|
75
|
+
end
|
76
|
+
|
77
|
+
def receives_body?(http_method)
|
78
|
+
http_method != :get && http_method != :delete
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid_api?(api)
|
82
|
+
api.class.respond_to?(:router) && api.class.router.is_a?(Flon::Router)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/flon/dsl.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'flon/router'
|
4
|
+
|
5
|
+
module Flon
|
6
|
+
# This module holds Flon's DSL. You should extend it in your class.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class BreadApi
|
10
|
+
# extend Flon::DSL
|
11
|
+
#
|
12
|
+
# get '/bread'
|
13
|
+
# def bread
|
14
|
+
# 'bread.'
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
module DSL
|
18
|
+
# @!group Routing
|
19
|
+
|
20
|
+
# @!method get(path)
|
21
|
+
# Creates a GET route from path, bound to the next defined method.
|
22
|
+
# @param [String] path the path to bind to
|
23
|
+
# @return [void]
|
24
|
+
# @!method post(path)
|
25
|
+
# Creates a POST route from path, bound to the next defined method.
|
26
|
+
# @param [String] path the path to bind to
|
27
|
+
# @return [void]
|
28
|
+
# @!method put(path)
|
29
|
+
# Creates a PUT route from path, bound to the next defined method.
|
30
|
+
# @param [String] path the path to bind to
|
31
|
+
# @return [void]
|
32
|
+
# @!method delete(path)
|
33
|
+
# Creates a DELETE route from path, bound to the next defined method.
|
34
|
+
# @param [String] path the path to bind to
|
35
|
+
# @return [void]
|
36
|
+
# @!method patch(path)
|
37
|
+
# Creates a PATCH route from path, bound to the next defined method.
|
38
|
+
# @param [String] path the path to bind to
|
39
|
+
# @return [void]
|
40
|
+
%i[get post put delete patch].each do |http_method|
|
41
|
+
define_method(http_method) do |path|
|
42
|
+
@current_route = [http_method, namespaces.join + path]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a namespace of the given path. If a block is given, the namespace
|
47
|
+
# is local to the block. If not, it is applied to the rest of the API. You
|
48
|
+
# may not call this method without a block twice.
|
49
|
+
#
|
50
|
+
# @param [String] path the path to use
|
51
|
+
# @return [void]
|
52
|
+
def namespace(path)
|
53
|
+
if block_given?
|
54
|
+
namespaces.push(path)
|
55
|
+
yield
|
56
|
+
namespaces.pop
|
57
|
+
else
|
58
|
+
unless namespaces.empty?
|
59
|
+
raise Flon::Error, 'cannot declare a nested global namespace or declare a global namespace twice'
|
60
|
+
end
|
61
|
+
|
62
|
+
namespaces.push(path)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias version namespace
|
67
|
+
|
68
|
+
# @!endgroup
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
def router
|
72
|
+
@router ||= Flon::Router.new
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def namespaces
|
77
|
+
@namespaces ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
# @api private
|
81
|
+
def method_added(name)
|
82
|
+
return unless @current_route
|
83
|
+
|
84
|
+
http_method, path = @current_route
|
85
|
+
router.add_route(http_method, path, instance_method(name))
|
86
|
+
|
87
|
+
@current_route = nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/flon/router.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mustermann/sinatra'
|
4
|
+
|
5
|
+
module Flon
|
6
|
+
# Handles a set of routes mapped to actions. Note that the Router API is
|
7
|
+
# mainly for private Flon use.
|
8
|
+
class Router
|
9
|
+
# A route's match is encapsulated in this struct. {#action} has the action
|
10
|
+
# associated with the route matched against and {#params} has the parameters
|
11
|
+
# hash.
|
12
|
+
#
|
13
|
+
# @!attribute [r] action
|
14
|
+
# @return the route's action
|
15
|
+
#
|
16
|
+
# @!attribute [r] params
|
17
|
+
# @return [Hash{Symbol => Object}] the route's bound parameters
|
18
|
+
Match = Struct.new(:action, :params)
|
19
|
+
|
20
|
+
# The HTTP methods as symbols that this router will handle.
|
21
|
+
VALID_HTTP_METHODS = %i[get post put delete patch].freeze
|
22
|
+
|
23
|
+
# @return [Array<Mustermann::Sinatra, Hash{Symbol => Object}>] the current route set
|
24
|
+
attr_reader :routes
|
25
|
+
|
26
|
+
# Creates a new Router with an empty route set.
|
27
|
+
# @return [Router] the newly constructed object
|
28
|
+
def initialize
|
29
|
+
@routes = []
|
30
|
+
end
|
31
|
+
|
32
|
+
# Adds a route to this router's route set.
|
33
|
+
#
|
34
|
+
# @param [Symbol] method the method to use
|
35
|
+
# @param [String] path the path to use
|
36
|
+
# @param [Object] action the action to use
|
37
|
+
# @return [self] this {Router}
|
38
|
+
# @raise [ArgumentError] if method is not a valid HTTP method or the given
|
39
|
+
# route is already registered
|
40
|
+
def add_route(method, path, action) # rubocop:disable Metrics/MethodLength
|
41
|
+
raise ArgumentError, "#{method} is not a valid HTTP method" unless valid_method?(method)
|
42
|
+
raise ArgumentError, "route #{method.upcase} #{path} is already registered" if routing_to?(method, path)
|
43
|
+
|
44
|
+
route = @routes.find { |it| it[0].to_s == path }
|
45
|
+
if route
|
46
|
+
route[1][method] = action
|
47
|
+
else
|
48
|
+
route = [Mustermann::Sinatra.new(path), { method => action }]
|
49
|
+
@routes << route
|
50
|
+
end
|
51
|
+
|
52
|
+
route[1][:head] = action if method == :get
|
53
|
+
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Matches a method and path against this router's route set.
|
58
|
+
#
|
59
|
+
# @param [Symbol] method the HTTP method to match against
|
60
|
+
# @param [String] path the path to match against
|
61
|
+
# @return [RouteMatch, :bad_path, :bad_method] if successful, a RouteMatch;
|
62
|
+
# if not, :bad_method if the path exists but not bound to that HTTP
|
63
|
+
# method, :bad_path otherwise
|
64
|
+
def match(method, path)
|
65
|
+
@routes.each do |route|
|
66
|
+
params = route[0].params(path)
|
67
|
+
next unless params
|
68
|
+
|
69
|
+
action = route[1][method]
|
70
|
+
return :bad_method unless action
|
71
|
+
|
72
|
+
return Match.new(action, params.transform_keys(&:to_sym))
|
73
|
+
end
|
74
|
+
|
75
|
+
:bad_path
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Boolean] true if this router has a route with that method and
|
79
|
+
# path in its route set, false otherwise
|
80
|
+
def routing_to?(method, path)
|
81
|
+
@routes.any? { |it| it[0].to_s == path && it[1].include?(method) }
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def valid_method?(method)
|
87
|
+
VALID_HTTP_METHODS.include?(method)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/flon/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- unleashy
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-09-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mustermann
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.74.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.74.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.17.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.17.0
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- unleashy@users.noreply.github.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
121
|
+
- ".travis.yml"
|
122
|
+
- ".yardopts"
|
123
|
+
- Gemfile
|
124
|
+
- LICENSE.txt
|
125
|
+
- README.md
|
126
|
+
- Rakefile
|
127
|
+
- bin/console
|
128
|
+
- bin/setup
|
129
|
+
- flon.gemspec
|
130
|
+
- lib/flon.rb
|
131
|
+
- lib/flon/api.rb
|
132
|
+
- lib/flon/dsl.rb
|
133
|
+
- lib/flon/router.rb
|
134
|
+
- lib/flon/version.rb
|
135
|
+
homepage: https://github.com/unleashy/flon
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata:
|
139
|
+
homepage_uri: https://github.com/unleashy/flon
|
140
|
+
source_code_uri: https://github.com/unleashy/flon
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.7.6
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Flon, the API maker that uses JSON
|
161
|
+
test_files: []
|