tynn 2.0.0.beta3 → 2.0.0.beta4
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/LICENSE +1 -1
- data/README.md +83 -660
- data/lib/tynn.rb +186 -50
- data/lib/tynn/environment.rb +1 -84
- data/lib/tynn/json.rb +24 -25
- data/lib/tynn/render.rb +72 -33
- data/lib/tynn/request.rb +2 -82
- data/lib/tynn/response.rb +4 -118
- data/lib/tynn/secure_headers.rb +2 -24
- data/lib/tynn/session.rb +4 -87
- data/lib/tynn/ssl.rb +12 -71
- data/lib/tynn/static.rb +1 -19
- data/lib/tynn/test.rb +2 -125
- data/lib/tynn/utils.rb +51 -8
- data/lib/tynn/version.rb +1 -1
- data/lib/tynn/x/versioning.rb +29 -0
- data/test/json_test.rb +197 -7
- data/test/render_test.rb +56 -8
- data/test/request_test.rb +2 -0
- data/test/response_test.rb +7 -3
- data/test/routing_test.rb +56 -4
- data/test/settings_test.rb +31 -0
- data/test/versioning_test.rb +201 -0
- metadata +10 -35
- data/lib/tynn/base.rb +0 -434
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tynn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francesco Rodriguez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -30,28 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: '1.1'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: erubis
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '2.7'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '2.7'
|
40
|
+
version: '1.1'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: minitest
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,22 +66,9 @@ dependencies:
|
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
68
|
version: '11.0'
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '2.0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '2.0'
|
97
|
-
description: A thin library for web development in Ruby
|
98
|
-
email: hello@frodsan.com
|
69
|
+
description: Tynn is a simple and flexible library for building JSON web services
|
70
|
+
and web applications in Ruby
|
71
|
+
email: frodsan@protonmail.com
|
99
72
|
executables: []
|
100
73
|
extensions: []
|
101
74
|
extra_rdoc_files: []
|
@@ -103,7 +76,6 @@ files:
|
|
103
76
|
- LICENSE
|
104
77
|
- README.md
|
105
78
|
- lib/tynn.rb
|
106
|
-
- lib/tynn/base.rb
|
107
79
|
- lib/tynn/environment.rb
|
108
80
|
- lib/tynn/json.rb
|
109
81
|
- lib/tynn/render.rb
|
@@ -116,6 +88,7 @@ files:
|
|
116
88
|
- lib/tynn/test.rb
|
117
89
|
- lib/tynn/utils.rb
|
118
90
|
- lib/tynn/version.rb
|
91
|
+
- lib/tynn/x/versioning.rb
|
119
92
|
- test/default_headers_test.rb
|
120
93
|
- test/environment_test.rb
|
121
94
|
- test/helper.rb
|
@@ -132,6 +105,7 @@ files:
|
|
132
105
|
- test/settings_test.rb
|
133
106
|
- test/ssl_test.rb
|
134
107
|
- test/static_test.rb
|
108
|
+
- test/versioning_test.rb
|
135
109
|
homepage: https://github.com/frodsan/tynn
|
136
110
|
licenses:
|
137
111
|
- MIT
|
@@ -173,3 +147,4 @@ test_files:
|
|
173
147
|
- test/settings_test.rb
|
174
148
|
- test/ssl_test.rb
|
175
149
|
- test/static_test.rb
|
150
|
+
- test/versioning_test.rb
|
data/lib/tynn/base.rb
DELETED
@@ -1,434 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright (c) 2016 Francesco Rodriguez
|
4
|
-
# Copyright (c) 2015-2016 Michel Martens (Portions of https://github.com/soveran/syro)
|
5
|
-
#
|
6
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
-
# of this software and associated documentation files (the "Software"), to deal
|
8
|
-
# in the Software without restriction, including without limitation the rights
|
9
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
-
# copies of the Software, and to permit persons to whom the Software is
|
11
|
-
# furnished to do so, subject to the following conditions:
|
12
|
-
#
|
13
|
-
# The above copyright notice and this permission notice shall be included in
|
14
|
-
# all copies or substantial portions of the Software.
|
15
|
-
#
|
16
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
-
# THE SOFTWARE.
|
23
|
-
|
24
|
-
require "rack"
|
25
|
-
require "seg"
|
26
|
-
require_relative "request"
|
27
|
-
require_relative "response"
|
28
|
-
require_relative "utils"
|
29
|
-
|
30
|
-
class Tynn
|
31
|
-
module Base
|
32
|
-
module ClassMethods
|
33
|
-
# Sets the application handler.
|
34
|
-
#
|
35
|
-
# class Users < Tynn
|
36
|
-
# end
|
37
|
-
#
|
38
|
-
# Users.define do
|
39
|
-
# on :id do |id|
|
40
|
-
# on get do
|
41
|
-
# res.write("GET /users/#{ id }")
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# on post do
|
45
|
-
# res.write("POST /users/#{ id }")
|
46
|
-
# end
|
47
|
-
# end
|
48
|
-
# end
|
49
|
-
#
|
50
|
-
def define(&block)
|
51
|
-
@__app = build_app(proc { |env| new(block).call(env) })
|
52
|
-
end
|
53
|
-
|
54
|
-
def build_app(app) # :nodoc:
|
55
|
-
middleware.freeze.reverse.inject(app) { |a, e| e.call(a) }
|
56
|
-
end
|
57
|
-
|
58
|
-
# Adds given Rack <tt>middleware</tt> to the stack.
|
59
|
-
#
|
60
|
-
# [middleware] A Rack middleware.
|
61
|
-
# [*args] A list of arguments passed to the middleware initialization.
|
62
|
-
# [&block] A block passed to the middleware initialization.
|
63
|
-
#
|
64
|
-
# require "rack/common_logger"
|
65
|
-
# require "rack/show_exceptions"
|
66
|
-
#
|
67
|
-
# Tynn.use(Rack::CommonLogger)
|
68
|
-
# Tynn.use(Rack::ShowExceptions)
|
69
|
-
#
|
70
|
-
# If applications handler is already set, it raises an error.
|
71
|
-
#
|
72
|
-
# Tynn.define {}
|
73
|
-
#
|
74
|
-
# Tynn.use(Rack::CommonLogger)
|
75
|
-
# # => Application middleware is frozen.
|
76
|
-
#
|
77
|
-
def use(middleware, *args, &block)
|
78
|
-
if self.middleware.frozen?
|
79
|
-
Tynn::Utils.raise_error("Application middleware is frozen", tag: :frozen_middleware)
|
80
|
-
else
|
81
|
-
self.middleware.push(proc { |app| middleware.new(app, *args, &block) })
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def middleware # :nodoc:
|
86
|
-
@__middleware ||= []
|
87
|
-
end
|
88
|
-
|
89
|
-
def call(env) # :nodoc:
|
90
|
-
app.call(env)
|
91
|
-
end
|
92
|
-
|
93
|
-
def app # :nodoc:
|
94
|
-
(defined?(@__app) && @__app) or
|
95
|
-
Tynn::Utils.raise_error("Application handler is missing", tag: :missing_handler)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Copies settings into the subclass. If a setting is not found,
|
99
|
-
# checks parent's settings.
|
100
|
-
def inherited(subclass) # :nodoc:
|
101
|
-
subclass.settings.replace(Tynn::Utils.deepclone_hash(settings))
|
102
|
-
subclass.settings.default_proc = proc { |h, k| h[k] = settings[k] }
|
103
|
-
end
|
104
|
-
|
105
|
-
# Returns a Hash with the application settings.
|
106
|
-
#
|
107
|
-
# Tynn.set(:environment, :development)
|
108
|
-
#
|
109
|
-
# Tynn.settings
|
110
|
-
# # => { :environment => :development }
|
111
|
-
#
|
112
|
-
def settings
|
113
|
-
@settings ||= {}
|
114
|
-
end
|
115
|
-
|
116
|
-
# Sets an <tt>option</tt> to the given </tt>value</tt>. If a setting
|
117
|
-
# with the <tt>option</tt> key exists and is a hash value, it merges
|
118
|
-
# the stored hash with <tt>value</tt>.
|
119
|
-
#
|
120
|
-
# Tynn.set(:environment, :staging)
|
121
|
-
#
|
122
|
-
# Tynn.settings[:environment]
|
123
|
-
# # => :staging
|
124
|
-
#
|
125
|
-
# Tynn.default_headers
|
126
|
-
# # => { "Content-Type" => "text/html" }
|
127
|
-
#
|
128
|
-
# Tynn.set(:default_headers, "X-Frame-Options" => "DENY")
|
129
|
-
#
|
130
|
-
# Tynn.default_headers
|
131
|
-
# # => { "Content-Type" => "text/html", "X-Frame-Options" => "DENY" }
|
132
|
-
#
|
133
|
-
def set(option, value)
|
134
|
-
v = settings[option]
|
135
|
-
|
136
|
-
if Hash === v
|
137
|
-
set!(option, v.merge(value))
|
138
|
-
else
|
139
|
-
set!(option, value)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# Sets an <tt>option</tt> to the given </tt>value</tt>.
|
144
|
-
#
|
145
|
-
# Tynn.set!(:environment, :staging)
|
146
|
-
#
|
147
|
-
# Tynn.settings[:environment]
|
148
|
-
# # => :staging
|
149
|
-
#
|
150
|
-
# Tynn.default_headers
|
151
|
-
# # => { "Content-Type" => "text/html" }
|
152
|
-
#
|
153
|
-
# Tynn.set!(:default_headers, "X-Frame-Options" => "DENY")
|
154
|
-
#
|
155
|
-
# Tynn.default_headers
|
156
|
-
# # => { "X-Frame-Options" => "DENY" }
|
157
|
-
#
|
158
|
-
def set!(option, value)
|
159
|
-
settings[option] = value
|
160
|
-
end
|
161
|
-
|
162
|
-
# Returns a Hash with the default headers.
|
163
|
-
#
|
164
|
-
# Tynn.set(:default_headers, {
|
165
|
-
# "Content-Type" => "application/json"
|
166
|
-
# })
|
167
|
-
#
|
168
|
-
# Tynn.default_headers["Content-Type"]
|
169
|
-
# # => "application/json"
|
170
|
-
#
|
171
|
-
def default_headers
|
172
|
-
settings.fetch(:default_headers, {})
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
# Monkey-patches capture to return the path segment instead of storing it
|
177
|
-
# in a hash.
|
178
|
-
class Seg < ::Seg # :nodoc:
|
179
|
-
def capture
|
180
|
-
return nil if root?
|
181
|
-
|
182
|
-
len = (@path.index(SLASH, @pos) || @size) - @pos
|
183
|
-
|
184
|
-
segment = @path[@pos, len]
|
185
|
-
|
186
|
-
move(len)
|
187
|
-
|
188
|
-
segment
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
module InstanceMethods
|
193
|
-
def initialize(code) # :nodoc:
|
194
|
-
@__code = code
|
195
|
-
end
|
196
|
-
|
197
|
-
def call(env) # :nodoc:
|
198
|
-
@__env = env
|
199
|
-
@__req = Tynn::Request.new(env)
|
200
|
-
@__res = Tynn::Response.new(Hash[self.class.default_headers])
|
201
|
-
@__seg = Tynn::Base::Seg.new(env["PATH_INFO"])
|
202
|
-
|
203
|
-
catch(:halt) do
|
204
|
-
instance_eval(&@__code)
|
205
|
-
|
206
|
-
@__res.finish
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
# Returns the incoming request object. This object is an instance of
|
211
|
-
# Tynn::Request.
|
212
|
-
#
|
213
|
-
# req.post?
|
214
|
-
# # => true
|
215
|
-
#
|
216
|
-
# req.params
|
217
|
-
# # => { "username" => "bob", "password" => "secret" }
|
218
|
-
#
|
219
|
-
# req.headers["Content-Type"]
|
220
|
-
# # => "application/x-www-form-urlencoded"
|
221
|
-
#
|
222
|
-
def req
|
223
|
-
@__req
|
224
|
-
end
|
225
|
-
|
226
|
-
# Returns the current response object. This object is an instance of
|
227
|
-
# Tynn::Response.
|
228
|
-
#
|
229
|
-
# res.status = 200
|
230
|
-
# res.headers["Content-Type"] = "text/html"
|
231
|
-
# res.write("<h1>Welcome back!</h1>")
|
232
|
-
#
|
233
|
-
def res
|
234
|
-
@__res
|
235
|
-
end
|
236
|
-
|
237
|
-
# Executes a given block if <tt>arg</tt> matches one of these conditions.
|
238
|
-
#
|
239
|
-
# If <tt>arg</tt> is a string, it matches a path segment.
|
240
|
-
#
|
241
|
-
# Tynn.define do
|
242
|
-
# # Matches if /foo
|
243
|
-
# on "foo" do
|
244
|
-
# # Matches if /foo/bar/baz
|
245
|
-
# on "bar/baz" do
|
246
|
-
# res.write("/foo/bar/baz")
|
247
|
-
# end
|
248
|
-
# end
|
249
|
-
# end
|
250
|
-
#
|
251
|
-
# If <tt>arg</tt> is a symbol, it matches and capture a path segment.
|
252
|
-
#
|
253
|
-
# Tynn.define do
|
254
|
-
# # Matches if /users
|
255
|
-
# on "users" do
|
256
|
-
# # Captures id if matches /users/id
|
257
|
-
# on :id do |id|
|
258
|
-
# res.write("/users/#{ id }")
|
259
|
-
# end
|
260
|
-
# end
|
261
|
-
# end
|
262
|
-
#
|
263
|
-
# Finally, if <tt>arg</tt> is <tt>true</tt>, it executes the given block
|
264
|
-
# inconditionally.
|
265
|
-
#
|
266
|
-
# Tynn.define do
|
267
|
-
# on current_user.nil? do
|
268
|
-
# res.status = 401
|
269
|
-
# res.write("Unauthorized")
|
270
|
-
# end
|
271
|
-
# end
|
272
|
-
#
|
273
|
-
def on(arg)
|
274
|
-
(v = match(arg)) && yield(v)
|
275
|
-
end
|
276
|
-
|
277
|
-
def match(arg)
|
278
|
-
case arg
|
279
|
-
when String then @__seg.consume(arg)
|
280
|
-
when Symbol then @__seg.capture
|
281
|
-
when true then true
|
282
|
-
else false
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
private :match
|
287
|
-
|
288
|
-
# Immediately stops the request and returns <tt>response</tt>
|
289
|
-
# as per Rack's specification.
|
290
|
-
#
|
291
|
-
# halt([200, { "Content-Type" => "text/html" }, ["hello"]])
|
292
|
-
# halt(res.finish)
|
293
|
-
#
|
294
|
-
def halt(response)
|
295
|
-
throw(:halt, response)
|
296
|
-
end
|
297
|
-
|
298
|
-
# Runs a Tynn <tt>app</tt> and pass an optional hash of values to it.
|
299
|
-
#
|
300
|
-
# Tynn.define do
|
301
|
-
# on "admin" do
|
302
|
-
# run(Admin, subdomain: subdomain)
|
303
|
-
# end
|
304
|
-
# end
|
305
|
-
#
|
306
|
-
def run(app, inbox = nil)
|
307
|
-
path, script = @__env["PATH_INFO"], @__env["SCRIPT_NAME"]
|
308
|
-
|
309
|
-
@__env["PATH_INFO"] = @__seg.curr
|
310
|
-
@__env["SCRIPT_NAME"] = @__seg.prev
|
311
|
-
|
312
|
-
@__env.delete("tynn.inbox")
|
313
|
-
@__env["tynn.inbox"] = inbox if inbox
|
314
|
-
|
315
|
-
halt(app.call(@__env))
|
316
|
-
ensure
|
317
|
-
@__env["PATH_INFO"], @__env["SCRIPT_NAME"] = path, script
|
318
|
-
end
|
319
|
-
|
320
|
-
# Returns a hash with variables passed to the application.
|
321
|
-
#
|
322
|
-
# Tynn.define do
|
323
|
-
# on "api/v1" do
|
324
|
-
# run(API, version: 1)
|
325
|
-
# end
|
326
|
-
#
|
327
|
-
# on "api/v2" do
|
328
|
-
# run(API, version: 2)
|
329
|
-
# end
|
330
|
-
# end
|
331
|
-
#
|
332
|
-
# API.define do
|
333
|
-
# version = inbox[:version]
|
334
|
-
# end
|
335
|
-
#
|
336
|
-
def inbox
|
337
|
-
@__env.fetch("tynn.inbox", {})
|
338
|
-
end
|
339
|
-
|
340
|
-
# Returns <tt>true</tt> if the request method is <tt>GET</tt> and
|
341
|
-
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
342
|
-
#
|
343
|
-
# Tynn.define do
|
344
|
-
# on "users" do
|
345
|
-
# on get do
|
346
|
-
# res.write("GET /users")
|
347
|
-
# end
|
348
|
-
# end
|
349
|
-
# end
|
350
|
-
#
|
351
|
-
def get
|
352
|
-
root? && req.get?
|
353
|
-
end
|
354
|
-
|
355
|
-
# Returns <tt>true</tt> if the request method is <tt>POST</tt> and
|
356
|
-
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
357
|
-
#
|
358
|
-
# Tynn.define do
|
359
|
-
# on "users" do
|
360
|
-
# post do
|
361
|
-
# res.write("POST /users")
|
362
|
-
# end
|
363
|
-
# end
|
364
|
-
# end
|
365
|
-
#
|
366
|
-
def post
|
367
|
-
root? && req.post?
|
368
|
-
end
|
369
|
-
|
370
|
-
# Returns <tt>true</tt> if the request method is <tt>PATCH</tt> and
|
371
|
-
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
372
|
-
#
|
373
|
-
# Tynn.define do
|
374
|
-
# on "users" do
|
375
|
-
# on :id do |id|
|
376
|
-
# on patch do
|
377
|
-
# res.write("PATCH /users/#{ id }")
|
378
|
-
# end
|
379
|
-
# end
|
380
|
-
# end
|
381
|
-
# end
|
382
|
-
#
|
383
|
-
def patch
|
384
|
-
root? && req.patch?
|
385
|
-
end
|
386
|
-
|
387
|
-
# Returns <tt>true</tt> if the request method is <tt>PUT</tt> and
|
388
|
-
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
389
|
-
#
|
390
|
-
# Tynn.define do
|
391
|
-
# on "users" do
|
392
|
-
# on :id do |id|
|
393
|
-
# on put do
|
394
|
-
# res.write("PUT /users/#{ id }")
|
395
|
-
# end
|
396
|
-
# end
|
397
|
-
# end
|
398
|
-
# end
|
399
|
-
#
|
400
|
-
def put
|
401
|
-
root? && req.put?
|
402
|
-
end
|
403
|
-
|
404
|
-
# Returns <tt>true</tt> if the request method is <tt>DELETE</tt> and
|
405
|
-
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
406
|
-
#
|
407
|
-
# Tynn.define do
|
408
|
-
# on "users" do
|
409
|
-
# on :id do |id|
|
410
|
-
# on delete do
|
411
|
-
# res.write("DELETE /users/#{ id }")
|
412
|
-
# end
|
413
|
-
# end
|
414
|
-
# end
|
415
|
-
# end
|
416
|
-
#
|
417
|
-
def delete
|
418
|
-
root? && req.delete?
|
419
|
-
end
|
420
|
-
|
421
|
-
# Returns <tt>true</tt> if the path yet to be consumed is empty.
|
422
|
-
#
|
423
|
-
# Tynn.define do
|
424
|
-
# on root? do
|
425
|
-
# res.write("/")
|
426
|
-
# end
|
427
|
-
# end
|
428
|
-
#
|
429
|
-
def root?
|
430
|
-
@__seg.root?
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
434
|
-
end
|