flon 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +57 -30
- data/lib/flon/api.rb +59 -18
- data/lib/flon/dsl.rb +1 -1
- data/lib/flon/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dadeb3b4469a7ee38098b5e24548f525a32fa08d2577a8f67a64175f34d965e3
|
4
|
+
data.tar.gz: fe9c3d073b64a8bb443f1ce9b87d33889cb3c2750d255df3979b05563b3bfb42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15663df435d9955f82ddc991955537919007a7e0c220dd380d435f8ba8cfc1593352155a5f7eed8dcb74efa2632ef295bda620dd8fc68e5d1beaa50f17f0c2a8
|
7
|
+
data.tar.gz: f10055e8e158a2a9a7f9c72c51a469d408558c9bec5c46d4ceee7a9c59b8af50dbdd359dc7b5fe008b69e86e86f1e2b58ca8348ada6f93c4ad1b735250b513c8
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Flon
|
2
2
|
|
3
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.
|
4
|
+
just a thin and convenient wrapper over Rack, with a hopefully obvious DSL.
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
|
@@ -13,11 +13,15 @@ gem 'flon'
|
|
13
13
|
|
14
14
|
And then execute:
|
15
15
|
|
16
|
-
|
16
|
+
```sh
|
17
|
+
bundle
|
18
|
+
```
|
17
19
|
|
18
20
|
Or install it yourself as:
|
19
21
|
|
20
|
-
|
22
|
+
```sh
|
23
|
+
gem install flon
|
24
|
+
```
|
21
25
|
|
22
26
|
## Usage
|
23
27
|
|
@@ -29,7 +33,7 @@ router DSL in your class scope.
|
|
29
33
|
```ruby
|
30
34
|
require 'flon'
|
31
35
|
|
32
|
-
class
|
36
|
+
class MyAPI
|
33
37
|
extend Flon::DSL
|
34
38
|
|
35
39
|
def initialize
|
@@ -41,35 +45,35 @@ class MyApi
|
|
41
45
|
attr_reader :names
|
42
46
|
|
43
47
|
post '/new'
|
44
|
-
def add_name(
|
45
|
-
@names << body
|
48
|
+
def add_name(request)
|
49
|
+
@names << request.body
|
46
50
|
end
|
47
51
|
|
48
52
|
namespace '/:index' do
|
49
53
|
get '/'
|
50
|
-
def name_by_index(
|
51
|
-
@names[params[:index].to_i]
|
54
|
+
def name_by_index(request)
|
55
|
+
@names[request.params[:index].to_i]
|
52
56
|
end
|
53
57
|
|
54
58
|
put '/edit'
|
55
|
-
def change_name(
|
56
|
-
@names[params[:index].to_i] = body
|
59
|
+
def change_name(request)
|
60
|
+
@names[request.params[:index].to_i] = request.body
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
60
64
|
end
|
61
65
|
```
|
62
66
|
|
63
|
-
You can then create a `Flon::
|
67
|
+
You can then create a `Flon::API` using this:
|
64
68
|
|
65
69
|
```ruby
|
66
|
-
api = Flon::
|
70
|
+
api = Flon::API.new(MyAPI.new)
|
67
71
|
```
|
68
72
|
|
69
|
-
Note how you initialise
|
73
|
+
Note how you initialise everything normally—you can use this for dependency
|
70
74
|
injection without a headache.
|
71
75
|
|
72
|
-
`Flon::
|
76
|
+
`Flon::API` is a bog-standard Rack app, so you can just use it in `config.ru`,
|
73
77
|
or whatever method you use.
|
74
78
|
|
75
79
|
### Routing
|
@@ -114,12 +118,35 @@ pattern is the exact same as [Sinatra’s](https://github.com/sinatra/mustermann
|
|
114
118
|
The bound method is called when an HTTP request matches that route with that
|
115
119
|
HTTP method.
|
116
120
|
|
117
|
-
The method will receive
|
121
|
+
The method will receive one argument, the `Flon::Request` object describing the
|
122
|
+
HTTP request:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
request.body # Gets the body; returns a String, or nil if no body
|
126
|
+
|
127
|
+
request.headers # Gets the headers; returns a Hash with String keys and
|
128
|
+
# String values
|
129
|
+
|
130
|
+
request.method # Gets the HTTP method; returns a Symbol with the name of
|
131
|
+
# the method lowercased
|
132
|
+
|
133
|
+
request.params # Gets the route and query parameters, merged, giving
|
134
|
+
# priority to route parameters; returns a Hash with Symbol
|
135
|
+
# keys
|
118
136
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
137
|
+
request.path # Gets the path; returns a String
|
138
|
+
|
139
|
+
request.query_params # Gets the query parameters, specifically; returns a Hash
|
140
|
+
# with Symbol keys
|
141
|
+
|
142
|
+
request.route_params # Gets the route parameters, specifically; returns a Hash
|
143
|
+
# with Symbol keys
|
144
|
+
|
145
|
+
request.url # Gets the request’s URL; returns an URI object
|
146
|
+
```
|
147
|
+
|
148
|
+
Note that the argument will not be passed if the method does not accept at
|
149
|
+
least one argument, so you can leave it out if you’re not gonna use it.
|
123
150
|
|
124
151
|
#### Namespaces
|
125
152
|
|
@@ -131,19 +158,19 @@ namespace '/users' do
|
|
131
158
|
attr_reader :users # GET /users
|
132
159
|
|
133
160
|
post '/new'
|
134
|
-
def add_user(
|
135
|
-
@users <<
|
161
|
+
def add_user(request) # /users/new
|
162
|
+
@users << request.body
|
136
163
|
end
|
137
164
|
|
138
165
|
namespace '/:id' do
|
139
166
|
get
|
140
|
-
def user_by_id(
|
141
|
-
@users[params[:id].to_i]
|
167
|
+
def user_by_id(request) # GET /users/:id
|
168
|
+
@users[request.params[:id].to_i]
|
142
169
|
end
|
143
170
|
|
144
171
|
put '/edit'
|
145
|
-
def edit_user(
|
146
|
-
@users[params[:id].to_i] = body
|
172
|
+
def edit_user(request) # PUT /users/:id/edit
|
173
|
+
@users[request.params[:id].to_i] = request.body
|
147
174
|
end
|
148
175
|
end
|
149
176
|
end
|
@@ -168,7 +195,7 @@ get '/no'
|
|
168
195
|
def no
|
169
196
|
'no'
|
170
197
|
end
|
171
|
-
```
|
198
|
+
```
|
172
199
|
|
173
200
|
Going to `/yes` will give you a 404, but going to `/v1/yes` will give you
|
174
201
|
`"yes"`. The same thing happens with the `/no` route.
|
@@ -178,14 +205,14 @@ In reality, `version` is just an alias for `namespace`.
|
|
178
205
|
### Responding
|
179
206
|
|
180
207
|
Whatever is returned by the route’s method is `#to_json`’d and sent back. If
|
181
|
-
you want to return a custom
|
182
|
-
|
183
|
-
argument of its constructor:
|
208
|
+
you want to return a custom response, return a `Flon::Response` object, which
|
209
|
+
takes the status code as the first argument, the body as the second argument,
|
210
|
+
and the headers as the third argument of its constructor:
|
184
211
|
|
185
212
|
```ruby
|
186
213
|
get '/admin'
|
187
214
|
def admin
|
188
|
-
Flon::Response.new(403, 'No. Go away.')
|
215
|
+
Flon::Response.new(403, 'No. Go away.', 'X-Loser' => 'Yes')
|
189
216
|
end
|
190
217
|
```
|
191
218
|
|
data/lib/flon/api.rb
CHANGED
@@ -2,25 +2,59 @@
|
|
2
2
|
|
3
3
|
require 'rack'
|
4
4
|
require 'json'
|
5
|
+
require 'uri'
|
5
6
|
|
6
7
|
module Flon
|
7
|
-
# Represents a
|
8
|
-
|
8
|
+
# Represents a request.
|
9
|
+
class Request
|
10
|
+
attr_reader :body, :headers, :method, :params, :path, :query_params,
|
11
|
+
:route_params, :url
|
12
|
+
|
13
|
+
def initialize(method, rack_request, route_params, body)
|
14
|
+
@body = body
|
15
|
+
@headers = rack_request.env
|
16
|
+
@method = method
|
17
|
+
@query_params = rack_request.params
|
18
|
+
@route_params = route_params
|
19
|
+
@params = coalesce_params(@query_params, @route_params)
|
20
|
+
@path = rack_request.path_info
|
21
|
+
@url = make_url(rack_request)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def coalesce_params(query_params, route_params)
|
27
|
+
query_params.transform_keys(&:to_sym).merge(route_params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def make_url(request)
|
31
|
+
URI.parse(request.fullpath)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Represents a response, with a status, body, and headers.
|
9
36
|
#
|
10
37
|
# @!attribute [r] status
|
11
38
|
# @return [Integer] the status to use when responding
|
12
39
|
#
|
13
40
|
# @!attribute [r] body
|
14
41
|
# @return [#to_json] the object to respond with that shall be #to_json'd
|
15
|
-
|
42
|
+
#
|
43
|
+
# @!attribute [r] headers
|
44
|
+
# @return [Hash{String => #to_s}] the headers hash; each value will be
|
45
|
+
# converted with a String with #to_s
|
46
|
+
Response = Struct.new(:status, :body, :headers)
|
16
47
|
|
17
48
|
# This class is Flon's Rack application.
|
18
|
-
class
|
19
|
-
|
49
|
+
class API
|
50
|
+
DEFAULT_RESPONSE_HEADERS = { 'Content-Type' => 'application/json' }.freeze
|
51
|
+
private_constant :DEFAULT_RESPONSE_HEADERS
|
52
|
+
|
53
|
+
# Creates a new {API} object with the given API.
|
20
54
|
#
|
21
55
|
# @param [Object] api the API to use; its class must respond to #router and
|
22
56
|
# the result must be a {Router}.
|
23
|
-
# @return [
|
57
|
+
# @return [API] the newly constructed object
|
24
58
|
def initialize(api)
|
25
59
|
raise ArgumentError, "given API's class does not respond to #routes" unless valid_api?(api)
|
26
60
|
|
@@ -44,26 +78,21 @@ module Flon
|
|
44
78
|
else dispatch(method, request, match)
|
45
79
|
end
|
46
80
|
|
47
|
-
|
81
|
+
rack_response(method, response)
|
48
82
|
end
|
49
83
|
|
50
84
|
private
|
51
85
|
|
52
|
-
def dispatch(method,
|
53
|
-
params = coalesce_params(request.params, match.params)
|
54
|
-
|
86
|
+
def dispatch(method, rack_request, match)
|
55
87
|
if receives_body?(method)
|
56
|
-
body = normalise_body(
|
88
|
+
body = normalise_body(rack_request)
|
57
89
|
return Response.new(415, '"415 Unsupported Media Type"') if body == :fail
|
58
90
|
else
|
59
91
|
body = nil
|
60
92
|
end
|
61
93
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def coalesce_params(request_params, route_params)
|
66
|
-
request_params.transform_keys(&:to_sym).merge(route_params)
|
94
|
+
request = Request.new(method, rack_request, match.params, body)
|
95
|
+
call_action(match.action, request)
|
67
96
|
end
|
68
97
|
|
69
98
|
def normalise_body(request)
|
@@ -83,8 +112,8 @@ module Flon
|
|
83
112
|
http_method != :get && http_method != :delete
|
84
113
|
end
|
85
114
|
|
86
|
-
def call_action(action,
|
87
|
-
responsify(call_respect_arity(action.bind(@api),
|
115
|
+
def call_action(action, request)
|
116
|
+
responsify(call_respect_arity(action.bind(@api), request))
|
88
117
|
end
|
89
118
|
|
90
119
|
def responsify(result)
|
@@ -100,6 +129,18 @@ module Flon
|
|
100
129
|
arity.negative? ? method.call(*args) : method.call(*args[0...arity])
|
101
130
|
end
|
102
131
|
|
132
|
+
def rack_response(method, response)
|
133
|
+
[
|
134
|
+
response.status || 200,
|
135
|
+
DEFAULT_RESPONSE_HEADERS.merge(stringify_values(response.headers || {})),
|
136
|
+
method == :head || !response.body ? [] : [response.body]
|
137
|
+
]
|
138
|
+
end
|
139
|
+
|
140
|
+
def stringify_values(hash)
|
141
|
+
hash.transform_values(&:to_s)
|
142
|
+
end
|
143
|
+
|
103
144
|
def http_method_to_symbol(http_method)
|
104
145
|
http_method.downcase.to_sym
|
105
146
|
end
|
data/lib/flon/dsl.rb
CHANGED
data/lib/flon/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unleashy
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mustermann
|