syro 1.0.0 → 1.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 +4 -4
- data/CHANGELOG +6 -0
- data/lib/syro.rb +207 -90
- data/syro.gemspec +1 -1
- data/test/all.rb +44 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42c57c65ec35da48059c8844925b6baa1a3a3897
|
4
|
+
data.tar.gz: cee94b2f71a5eeea0a07fee70d9d923d5720df7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79191f17a6cb4b4bdf8c2b4a8456e59ec15f5cc1ac33911502117103ad122f3a492fb80b359e6990754b8a1cd049b50da0a2b813355fd869d8e88052922e7a4f
|
7
|
+
data.tar.gz: 97ab83421c8cb0b3772404d91494f6e08389cc96d85542e4c1f355ac0bd2a9222654e530a7180902f184f4241b7085eaeee8ba6e05420d9420fe18b6b7103c54
|
data/CHANGELOG
CHANGED
data/lib/syro.rb
CHANGED
@@ -27,12 +27,34 @@ class Syro
|
|
27
27
|
INBOX = "syro.inbox".freeze
|
28
28
|
|
29
29
|
class Response
|
30
|
-
LOCATION = "Location".freeze
|
31
|
-
DEFAULT = "text/html".freeze
|
32
|
-
|
30
|
+
LOCATION = "Location".freeze # :nodoc:
|
31
|
+
DEFAULT = "text/html".freeze # :nodoc:
|
32
|
+
|
33
|
+
# The status of the response.
|
34
|
+
#
|
35
|
+
# res.status = 200
|
36
|
+
# res.status # => 200
|
37
|
+
#
|
33
38
|
attr_accessor :status
|
34
39
|
|
40
|
+
# Returns the body of the response.
|
41
|
+
#
|
42
|
+
# res.body
|
43
|
+
# # => []
|
44
|
+
#
|
45
|
+
# res.write("there is")
|
46
|
+
# res.write("no try")
|
47
|
+
#
|
48
|
+
# res.body
|
49
|
+
# # => ["there is", "no try"]
|
50
|
+
#
|
35
51
|
attr :body
|
52
|
+
|
53
|
+
# Returns a hash with the response headers.
|
54
|
+
#
|
55
|
+
# res.headers
|
56
|
+
# # => { "Content-Type" => "text/html", "Content-Length" => "42" }
|
57
|
+
#
|
36
58
|
attr :headers
|
37
59
|
|
38
60
|
def initialize(headers = {})
|
@@ -42,14 +64,37 @@ class Syro
|
|
42
64
|
@length = 0
|
43
65
|
end
|
44
66
|
|
67
|
+
# Returns the response header corresponding to `key`.
|
68
|
+
#
|
69
|
+
# res["Content-Type"] # => "text/html"
|
70
|
+
# res["Content-Length"] # => "42"
|
71
|
+
#
|
45
72
|
def [](key)
|
46
73
|
@headers[key]
|
47
74
|
end
|
48
75
|
|
76
|
+
# Sets the given `value` with the header corresponding to `key`.
|
77
|
+
#
|
78
|
+
# res["Content-Type"] = "application/json"
|
79
|
+
# res["Content-Type"] # => "application/json"
|
80
|
+
#
|
49
81
|
def []=(key, value)
|
50
82
|
@headers[key] = value
|
51
83
|
end
|
52
84
|
|
85
|
+
# Appends `str` to `body` and updates the `Content-Length` header.
|
86
|
+
#
|
87
|
+
# res.body # => []
|
88
|
+
#
|
89
|
+
# res.write("foo")
|
90
|
+
# res.write("bar")
|
91
|
+
#
|
92
|
+
# res.body
|
93
|
+
# # => ["foo", "bar"]
|
94
|
+
#
|
95
|
+
# res["Content-Length"]
|
96
|
+
# # => 6
|
97
|
+
#
|
53
98
|
def write(str)
|
54
99
|
s = str.to_s
|
55
100
|
|
@@ -58,11 +103,42 @@ class Syro
|
|
58
103
|
@body << s
|
59
104
|
end
|
60
105
|
|
106
|
+
# Sets the `Location` header to `path` and updates the status to
|
107
|
+
# `status`. By default, `status` is `302`.
|
108
|
+
#
|
109
|
+
# res.redirect("/path")
|
110
|
+
#
|
111
|
+
# res["Location"] # => "/path"
|
112
|
+
# res.status # => 302
|
113
|
+
#
|
114
|
+
# res.redirect("http://syro.ru", 303)
|
115
|
+
#
|
116
|
+
# res["Location"] # => "http://syro.ru"
|
117
|
+
# res.status # => 303
|
118
|
+
#
|
61
119
|
def redirect(path, status = 302)
|
62
120
|
@headers[LOCATION] = path
|
63
121
|
@status = status
|
64
122
|
end
|
65
123
|
|
124
|
+
# Returns an array with three elements: the status, headers and body.
|
125
|
+
# If the status is not set, the status is set to 404 if empty body,
|
126
|
+
# otherwise the status is set to 200 and updates the `Content-Type`
|
127
|
+
# header to `text/html`.
|
128
|
+
#
|
129
|
+
# res.status = 200
|
130
|
+
# res.finish
|
131
|
+
# # => [200, {}, []]
|
132
|
+
#
|
133
|
+
# res.status = nil
|
134
|
+
# res.finish
|
135
|
+
# # => [404, {}, []]
|
136
|
+
#
|
137
|
+
# res.status = nil
|
138
|
+
# res.write("syro")
|
139
|
+
# res.finish
|
140
|
+
# # => [200, { "Content-Type" => "text/html" }, ["syro"]]
|
141
|
+
#
|
66
142
|
def finish
|
67
143
|
if @status.nil?
|
68
144
|
if @body.empty?
|
@@ -76,142 +152,183 @@ class Syro
|
|
76
152
|
[@status, @headers, @body]
|
77
153
|
end
|
78
154
|
|
155
|
+
# Sets a cookie into the response.
|
156
|
+
#
|
157
|
+
# res.set_cookie("foo", "bar")
|
158
|
+
# res["Set-Cookie"] # => "foo=bar"
|
159
|
+
#
|
160
|
+
# res.set_cookie("foo2", "bar2")
|
161
|
+
# res["Set-Cookie"] # => "foo=bar\nfoo2=bar2"
|
162
|
+
#
|
163
|
+
# res.set_cookie("bar", {
|
164
|
+
# domain: ".example.com",
|
165
|
+
# path: "/",
|
166
|
+
# # max_age: 0,
|
167
|
+
# # expires: Time.now + 10_000,
|
168
|
+
# secure: true,
|
169
|
+
# httponly: true,
|
170
|
+
# value: "bar"
|
171
|
+
# })
|
172
|
+
#
|
173
|
+
# res["Set-Cookie"].split("\n").last
|
174
|
+
# # => "bar=bar; domain=.example.com; path=/; secure; HttpOnly
|
175
|
+
#
|
176
|
+
# **NOTE:** This method doesn't sign and/or encrypt the value of the cookie.
|
177
|
+
#
|
79
178
|
def set_cookie(key, value)
|
80
179
|
Rack::Utils.set_cookie_header!(@headers, key, value)
|
81
180
|
end
|
82
181
|
|
182
|
+
# Deletes cookie.
|
183
|
+
#
|
184
|
+
# res.set_cookie("foo", "bar")
|
185
|
+
# res["Set-Cookie"]
|
186
|
+
# # => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
187
|
+
#
|
83
188
|
def delete_cookie(key, value = {})
|
84
189
|
Rack::Utils.delete_cookie_header!(@headers, key, value)
|
85
190
|
end
|
86
191
|
end
|
87
192
|
|
88
193
|
class Deck
|
89
|
-
|
90
|
-
|
91
|
-
|
194
|
+
module API
|
195
|
+
def initialize(code)
|
196
|
+
@syro_code = code
|
197
|
+
end
|
92
198
|
|
93
|
-
|
94
|
-
|
95
|
-
|
199
|
+
def env
|
200
|
+
@syro_env
|
201
|
+
end
|
96
202
|
|
97
|
-
|
98
|
-
|
99
|
-
|
203
|
+
def req
|
204
|
+
@syro_req
|
205
|
+
end
|
100
206
|
|
101
|
-
|
102
|
-
|
103
|
-
|
207
|
+
def res
|
208
|
+
@syro_res
|
209
|
+
end
|
104
210
|
|
105
|
-
|
106
|
-
|
107
|
-
|
211
|
+
def path
|
212
|
+
@syro_path
|
213
|
+
end
|
108
214
|
|
109
|
-
|
110
|
-
|
111
|
-
|
215
|
+
def inbox
|
216
|
+
@syro_inbox
|
217
|
+
end
|
112
218
|
|
113
|
-
|
114
|
-
|
115
|
-
|
219
|
+
def default_headers
|
220
|
+
return {}
|
221
|
+
end
|
116
222
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
@syro_res = Syro::Response.new(default_headers)
|
121
|
-
@syro_path = Seg.new(env.fetch(Rack::PATH_INFO))
|
122
|
-
@syro_inbox = inbox
|
223
|
+
def request_class
|
224
|
+
Rack::Request
|
225
|
+
end
|
123
226
|
|
124
|
-
|
125
|
-
|
227
|
+
def response_class
|
228
|
+
Syro::Response
|
229
|
+
end
|
230
|
+
|
231
|
+
def call(env, inbox)
|
232
|
+
@syro_env = env
|
233
|
+
@syro_req = request_class.new(env)
|
234
|
+
@syro_res = response_class.new(default_headers)
|
235
|
+
@syro_path = Seg.new(env.fetch(Rack::PATH_INFO))
|
236
|
+
@syro_inbox = inbox
|
237
|
+
|
238
|
+
catch(:halt) do
|
239
|
+
instance_eval(&@syro_code)
|
126
240
|
|
127
|
-
|
241
|
+
@syro_res.finish
|
242
|
+
end
|
128
243
|
end
|
129
|
-
end
|
130
244
|
|
131
|
-
|
132
|
-
|
245
|
+
def run(app, inbox = {})
|
246
|
+
path, script = env[Rack::PATH_INFO], env[Rack::SCRIPT_NAME]
|
133
247
|
|
134
|
-
|
135
|
-
|
136
|
-
|
248
|
+
env[Rack::PATH_INFO] = @syro_path.curr
|
249
|
+
env[Rack::SCRIPT_NAME] = @syro_path.prev
|
250
|
+
env[Syro::INBOX] = inbox
|
137
251
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
252
|
+
halt(app.call(env))
|
253
|
+
ensure
|
254
|
+
env[Rack::PATH_INFO], env[Rack::SCRIPT_NAME] = path, script
|
255
|
+
end
|
142
256
|
|
143
|
-
|
144
|
-
|
145
|
-
|
257
|
+
def halt(response)
|
258
|
+
throw(:halt, response)
|
259
|
+
end
|
146
260
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
261
|
+
def match(arg)
|
262
|
+
case arg
|
263
|
+
when String then @syro_path.consume(arg)
|
264
|
+
when Symbol then @syro_path.capture(arg, inbox)
|
265
|
+
when true then true
|
266
|
+
else false
|
267
|
+
end
|
153
268
|
end
|
154
|
-
end
|
155
269
|
|
156
|
-
|
157
|
-
|
158
|
-
|
270
|
+
def on(arg)
|
271
|
+
if match(arg)
|
272
|
+
yield(inbox[arg])
|
159
273
|
|
160
|
-
|
274
|
+
halt(res.finish)
|
275
|
+
end
|
161
276
|
end
|
162
|
-
end
|
163
277
|
|
164
|
-
|
165
|
-
|
166
|
-
|
278
|
+
def root?
|
279
|
+
@syro_path.root?
|
280
|
+
end
|
167
281
|
|
168
|
-
|
169
|
-
|
170
|
-
|
282
|
+
def root
|
283
|
+
if root?
|
284
|
+
yield
|
171
285
|
|
172
|
-
|
286
|
+
halt(res.finish)
|
287
|
+
end
|
173
288
|
end
|
174
|
-
end
|
175
289
|
|
176
|
-
|
177
|
-
|
178
|
-
|
290
|
+
def get
|
291
|
+
if root? && req.get?
|
292
|
+
yield
|
179
293
|
|
180
|
-
|
294
|
+
halt(res.finish)
|
295
|
+
end
|
181
296
|
end
|
182
|
-
end
|
183
297
|
|
184
|
-
|
185
|
-
|
186
|
-
|
298
|
+
def put
|
299
|
+
if root? && req.put?
|
300
|
+
yield
|
187
301
|
|
188
|
-
|
302
|
+
halt(res.finish)
|
303
|
+
end
|
189
304
|
end
|
190
|
-
end
|
191
305
|
|
192
|
-
|
193
|
-
|
194
|
-
|
306
|
+
def post
|
307
|
+
if root? && req.post?
|
308
|
+
yield
|
195
309
|
|
196
|
-
|
310
|
+
halt(res.finish)
|
311
|
+
end
|
197
312
|
end
|
198
|
-
end
|
199
313
|
|
200
|
-
|
201
|
-
|
202
|
-
|
314
|
+
def patch
|
315
|
+
if root? && req.patch?
|
316
|
+
yield
|
203
317
|
|
204
|
-
|
318
|
+
halt(res.finish)
|
319
|
+
end
|
205
320
|
end
|
206
|
-
end
|
207
321
|
|
208
|
-
|
209
|
-
|
210
|
-
|
322
|
+
def delete
|
323
|
+
if root? && req.delete?
|
324
|
+
yield
|
211
325
|
|
212
|
-
|
326
|
+
halt(res.finish)
|
327
|
+
end
|
213
328
|
end
|
214
329
|
end
|
330
|
+
|
331
|
+
include API
|
215
332
|
end
|
216
333
|
|
217
334
|
def initialize(deck = Deck, &code)
|
data/syro.gemspec
CHANGED
data/test/all.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
class RackApp
|
2
4
|
def call(env)
|
3
5
|
[200, {"Content-Type" => "text/html"}, ["GET /rack"]]
|
@@ -17,6 +19,28 @@ class DefaultHeaders < Syro::Deck
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
class CustomRequestAndResponse < Syro::Deck
|
23
|
+
class JSONRequest < Rack::Request
|
24
|
+
def params
|
25
|
+
JSON.parse(body.read)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class JSONResponse < Syro::Response
|
30
|
+
def write(s)
|
31
|
+
super(JSON.generate(s))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def request_class
|
36
|
+
JSONRequest
|
37
|
+
end
|
38
|
+
|
39
|
+
def response_class
|
40
|
+
JSONResponse
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
20
44
|
textual = Syro.new(TextualDeck) {
|
21
45
|
get {
|
22
46
|
text("GET /textual")
|
@@ -25,6 +49,14 @@ textual = Syro.new(TextualDeck) {
|
|
25
49
|
|
26
50
|
default_headers = Syro.new(DefaultHeaders) { }
|
27
51
|
|
52
|
+
json = Syro.new(CustomRequestAndResponse) {
|
53
|
+
root {
|
54
|
+
params = req.params
|
55
|
+
|
56
|
+
res.write(params)
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
28
60
|
admin = Syro.new {
|
29
61
|
get {
|
30
62
|
res.write("GET /admin")
|
@@ -155,6 +187,10 @@ app = Syro.new {
|
|
155
187
|
on("headers") {
|
156
188
|
run(default_headers)
|
157
189
|
}
|
190
|
+
|
191
|
+
on("json") {
|
192
|
+
run(json)
|
193
|
+
}
|
158
194
|
}
|
159
195
|
|
160
196
|
setup do
|
@@ -270,3 +306,11 @@ test "default headers" do |f|
|
|
270
306
|
|
271
307
|
assert_equal "text/html", f.last_response.headers["Content-Type"]
|
272
308
|
end
|
309
|
+
|
310
|
+
test "custom request and response class" do |f|
|
311
|
+
params = JSON.generate(foo: "foo")
|
312
|
+
|
313
|
+
f.post("/json", params)
|
314
|
+
|
315
|
+
assert_equal params, f.last_response.body
|
316
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: seg
|