flon 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: 9d77f8f763e06566ba8fd67310e4bf2eb7cf25cf1a2927685973c8ab8310a673
4
- data.tar.gz: f693eedb16f5dbff5505c00408ff728b4dbf8911294dea690d36139b811027db
3
+ metadata.gz: dadeb3b4469a7ee38098b5e24548f525a32fa08d2577a8f67a64175f34d965e3
4
+ data.tar.gz: fe9c3d073b64a8bb443f1ce9b87d33889cb3c2750d255df3979b05563b3bfb42
5
5
  SHA512:
6
- metadata.gz: 1ff850c5ec62797dfba5deda611c90aa941110e824bc39bb3cb577fa04594e2f1b7e5210874a9e910b9d5b136d9c7ecb40b23077b800d1b3f39a3b82549a011f
7
- data.tar.gz: 179296c86b0de6ebca2629a1977a78159d1e4f261f03d3b397a834a9f41c89c5cb7cd129825aee22285d1d6ccf7421efa8f1909f834a3aac79a7ff8384d6a3b4
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
- $ bundle
16
+ ```sh
17
+ bundle
18
+ ```
17
19
 
18
20
  Or install it yourself as:
19
21
 
20
- $ gem install flon
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 MyApi
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(_params, body)
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(params)
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(params, body)
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::Api` using this:
67
+ You can then create a `Flon::API` using this:
64
68
 
65
69
  ```ruby
66
- api = Flon::Api.new(MyApi.new)
70
+ api = Flon::API.new(MyAPI.new)
67
71
  ```
68
72
 
69
- Note how you initialise the `MyApi` normally—you can use this for dependency
73
+ Note how you initialise everything normally—you can use this for dependency
70
74
  injection without a headache.
71
75
 
72
- `Flon::Api` is a bog-standard Rack app, so you can just use it in `config.ru`,
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 two arguments, in that order:
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
- 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`.
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(_params, user) # /users/new
135
- @users << user
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(params) # GET /users/: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(params, body) # PUT /users/:id/edit
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 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:
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 response, with an integer status and a body that will be
8
- # #to_json'd.
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
- Response = Struct.new(:status, :body)
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 Api
19
- # Creates a new {Api} object with the given API.
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 [Api] the newly constructed object
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
- [response.status, { 'Content-Type' => 'application/json' }, [response.body]]
81
+ rack_response(method, response)
48
82
  end
49
83
 
50
84
  private
51
85
 
52
- def dispatch(method, request, match)
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(request)
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
- call_action(match.action, params, body)
63
- end
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, params, body)
87
- responsify(call_respect_arity(action.bind(@api), params, body))
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
@@ -6,7 +6,7 @@ module Flon
6
6
  # This module holds Flon's DSL. You should extend it in your class.
7
7
  #
8
8
  # @example
9
- # class BreadApi
9
+ # class BreadAPI
10
10
  # extend Flon::DSL
11
11
  #
12
12
  # get '/bread'
data/lib/flon/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Flon
4
4
  # Flon's version.
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
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.2.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-07 00:00:00.000000000 Z
11
+ date: 2019-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mustermann