tynn 0.0.4 → 1.0.0.rc1
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/lib/tynn/all_methods.rb +17 -0
- data/lib/tynn/environment.rb +12 -8
- data/lib/tynn/matchers.rb +37 -19
- data/lib/tynn/request.rb +4 -0
- data/lib/tynn/response.rb +151 -0
- data/lib/tynn/secure_headers.rb +28 -4
- data/lib/tynn/session.rb +74 -3
- data/lib/tynn/ssl.rb +2 -2
- data/lib/tynn/static.rb +26 -0
- data/lib/tynn/test.rb +37 -2
- data/lib/tynn/version.rb +7 -2
- data/lib/tynn.rb +88 -8
- data/test/all_methods_test.rb +16 -0
- data/test/core_test.rb +0 -86
- data/test/matchers_test.rb +11 -8
- data/test/middleware_test.rb +79 -0
- data/test/protection_test.rb +37 -0
- data/test/session_test.rb +1 -6
- metadata +45 -52
- data/.gems +0 -10
- data/docs/bin/build +0 -38
- data/docs/guides/security.md +0 -1
- data/docs/index.md +0 -89
- data/docs/layout.html +0 -61
- data/docs/public/.gitignore +0 -1
- data/docs/public/css/styles.css +0 -111
- data/docs/public/guides/.gitignore +0 -2
- data/docs/syro/syro.rb +0 -112
- data/examples/composition.ru +0 -40
- data/examples/hello.ru +0 -9
- data/examples/protection.ru +0 -18
- data/examples/render.ru +0 -13
- data/examples/views/home.erb +0 -1
- data/examples/views/layout.erb +0 -5
- data/lib/tynn/csrf.rb +0 -48
- data/lib/tynn/options.rb +0 -9
- data/makefile +0 -15
- data/test/csrf_test.rb +0 -98
- data/test/options_test.rb +0 -16
- data/test/views/custom_layout.erb +0 -1
- data/test/views/custom_layout.mote +0 -1
- data/test/views/layout.erb +0 -1
- data/test/views/layout.mote +0 -1
- data/test/views/partial.erb +0 -1
- data/test/views/partial.mote +0 -1
- data/test/views/view.erb +0 -1
- data/test/views/view.mote +0 -1
- data/tynn.gemspec +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4778b186382c25ab99b1687323ec3562c045a0ee
|
4
|
+
data.tar.gz: 3b8c8d0c9a80cf5cce5cfb235e27be3b34093197
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef2895b73ebb6b9180e747a28499de003fb430a6e6eaee6027f7d7ae5c2569c9161347aed31aacb25552ce010dff48a01ebdbdf39af026f61702316c38ac3036
|
7
|
+
data.tar.gz: 47e3ccf946f417ba6c444d4197d511eef8df1c987e83468214a5949d3ffa6c46a89e891da13d032e077bdd6b1781ec64313a60a3a399e5eb259ced0513fe6869
|
data/lib/tynn/environment.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
# Adds helper methods to get and check the current environment.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/environment"
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Tynn.helpers(Tynn::Environment)
|
7
8
|
#
|
8
|
-
#
|
9
|
+
# Tynn.environment # => :development
|
9
10
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# Tynn.development? # => true
|
12
|
+
# Tynn.production? # => false
|
13
|
+
# Tynn.test? # => false
|
14
|
+
# ```
|
13
15
|
#
|
14
16
|
# By default, the environment is based on `ENV["RACK_ENV"]`.
|
15
17
|
#
|
16
|
-
#
|
18
|
+
# ```
|
19
|
+
# Tynn.helpers(Tynn::Environment, env: ENV["RACK_ENV"])
|
20
|
+
# ```
|
17
21
|
#
|
18
22
|
module Tynn::Environment
|
19
23
|
def self.setup(app, env: ENV["RACK_ENV"]) # :nodoc:
|
data/lib/tynn/matchers.rb
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
# Adds extra matchers to Tynn.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/matchers"
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Tynn.helpers(Tynn::Matchers)
|
8
|
+
# ```
|
7
9
|
#
|
8
10
|
module Tynn::Matchers
|
9
11
|
# A catch-all matcher.
|
10
12
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
13
|
+
# ```
|
14
|
+
# Tynn.define do
|
15
|
+
# authenticated? do
|
16
|
+
# # ...
|
17
|
+
# end
|
15
18
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
19
|
+
# default do # on true
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# ```
|
20
24
|
#
|
21
25
|
# :call-seq: default(&block)
|
22
26
|
#
|
@@ -26,15 +30,29 @@ module Tynn::Matchers
|
|
26
30
|
halt(res.finish)
|
27
31
|
end
|
28
32
|
|
29
|
-
# Match if the given `
|
33
|
+
# Match if the given `key` is present in `req.params`.
|
30
34
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# end
|
35
|
+
# ```
|
36
|
+
# Tynn.define do
|
37
|
+
# param(:user) do |params|
|
38
|
+
# user = User.create(params)
|
36
39
|
#
|
37
|
-
|
38
|
-
|
40
|
+
# # ...
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# default do
|
44
|
+
# res.write("missing param")
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# ```
|
48
|
+
#
|
49
|
+
# :call-seq: param(key, &block)
|
50
|
+
#
|
51
|
+
def param(key)
|
52
|
+
if (v = req[key]) && !v.empty?
|
53
|
+
yield(v)
|
54
|
+
|
55
|
+
halt(res.finish)
|
56
|
+
end
|
39
57
|
end
|
40
58
|
end
|
data/lib/tynn/request.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
class Tynn
|
2
|
+
class Response < Syro::Response
|
3
|
+
##
|
4
|
+
# :method: []
|
5
|
+
#
|
6
|
+
# Returns the response header corresponding to `key`.
|
7
|
+
#
|
8
|
+
# res["Content-Type"] # => "text/html"
|
9
|
+
# res["Content-Length"] # => "42"
|
10
|
+
|
11
|
+
##
|
12
|
+
# :method: []=
|
13
|
+
# :call-seq: []=(value)
|
14
|
+
#
|
15
|
+
# Sets the given `value` with the header corresponding to `key`.
|
16
|
+
#
|
17
|
+
# res["Content-Type"] = "application/json"
|
18
|
+
# res["Content-Type"] # => "application/json"
|
19
|
+
|
20
|
+
##
|
21
|
+
# :method: body
|
22
|
+
#
|
23
|
+
# Returns the body of the response.
|
24
|
+
#
|
25
|
+
# res.body
|
26
|
+
# # => []
|
27
|
+
#
|
28
|
+
# res.write("there is")
|
29
|
+
# res.write("no try")
|
30
|
+
#
|
31
|
+
# res.body
|
32
|
+
# # => ["there is", "no try"]
|
33
|
+
|
34
|
+
##
|
35
|
+
# :method: finish
|
36
|
+
#
|
37
|
+
# Returns an array with three elements: the status, headers and body.
|
38
|
+
# If the status is not set, the status is set to 404 if empty body,
|
39
|
+
# otherwise the status is set to 200 and updates the `Content-Type`
|
40
|
+
# header to `text/html`.
|
41
|
+
#
|
42
|
+
# res.status = 200
|
43
|
+
# res.finish
|
44
|
+
# # => [200, {}, []]
|
45
|
+
#
|
46
|
+
# res.status = nil
|
47
|
+
# res.finish
|
48
|
+
# # => [404, {}, []]
|
49
|
+
#
|
50
|
+
# res.status = nil
|
51
|
+
# res.write("yo!")
|
52
|
+
# res.finish
|
53
|
+
# # => [200, { "Content-Type" => "text/html" }, ["yo!"]]
|
54
|
+
|
55
|
+
##
|
56
|
+
# :method: headers
|
57
|
+
#
|
58
|
+
# Returns a hash with the response headers.
|
59
|
+
#
|
60
|
+
# res.headers
|
61
|
+
# # => { "Content-Type" => "text/html", "Content-Length" => "42" }
|
62
|
+
|
63
|
+
##
|
64
|
+
# :method: redirect
|
65
|
+
# :call-seq: redirect(path, 302)
|
66
|
+
#
|
67
|
+
# Sets the `Location` header to `path` and updates the status to
|
68
|
+
# `status`. By default, `status` is `302`.
|
69
|
+
#
|
70
|
+
# res.redirect("/path")
|
71
|
+
#
|
72
|
+
# res["Location"] # => "/path"
|
73
|
+
# res.status # => 302
|
74
|
+
#
|
75
|
+
# res.redirect("http://tynn.ru", 303)
|
76
|
+
#
|
77
|
+
# res["Location"] # => "http://tynn.ru"
|
78
|
+
# res.status # => 303
|
79
|
+
|
80
|
+
##
|
81
|
+
# :method: status
|
82
|
+
#
|
83
|
+
# Returns the status of the response.
|
84
|
+
#
|
85
|
+
# res.status # => 200
|
86
|
+
#
|
87
|
+
|
88
|
+
##
|
89
|
+
# :method: status=
|
90
|
+
# :call-seq: status=(status)
|
91
|
+
#
|
92
|
+
# Sets the status of the response.
|
93
|
+
#
|
94
|
+
# res.status = 200
|
95
|
+
#
|
96
|
+
|
97
|
+
##
|
98
|
+
# :method: write
|
99
|
+
# :call-seq: write(str)
|
100
|
+
#
|
101
|
+
# Appends `str` to `body` and updates the `Content-Length` header.
|
102
|
+
#
|
103
|
+
# res.body # => []
|
104
|
+
#
|
105
|
+
# res.write("foo")
|
106
|
+
# res.write("bar")
|
107
|
+
#
|
108
|
+
# res.body
|
109
|
+
# # => ["foo", "bar"]
|
110
|
+
#
|
111
|
+
# res["Content-Length"]
|
112
|
+
# # => 6
|
113
|
+
|
114
|
+
##
|
115
|
+
# :method: set_cookie
|
116
|
+
# :call-seq: set_cookie(key, value)
|
117
|
+
#
|
118
|
+
# Sets a cookie into the response.
|
119
|
+
#
|
120
|
+
# res.set_cookie("foo", "bar")
|
121
|
+
# res["Set-Cookie"] # => "foo=bar"
|
122
|
+
#
|
123
|
+
# res.set_cookie("foo2", "bar2")
|
124
|
+
# res["Set-Cookie"] # => "foo=bar\nfoo2=bar2"
|
125
|
+
#
|
126
|
+
# res.set_cookie("bar", {
|
127
|
+
# domain: ".example.com",
|
128
|
+
# path: "/",
|
129
|
+
# # max_age: 0,
|
130
|
+
# # expires: Time.now + 10_000,
|
131
|
+
# secure: true,
|
132
|
+
# httponly: true,
|
133
|
+
# value: "bar"
|
134
|
+
# })
|
135
|
+
#
|
136
|
+
# res["Set-Cookie"].split("\n").last
|
137
|
+
# # => "bar=bar; domain=.example.com; path=/; secure; HttpOnly
|
138
|
+
#
|
139
|
+
# **NOTE:** This method doesn't sign and/or encrypt the value of the cookie.
|
140
|
+
|
141
|
+
##
|
142
|
+
# :method: delete_cookie
|
143
|
+
# :call-seq: delete_cookie(key, value = {})
|
144
|
+
#
|
145
|
+
# Deletes cookie.
|
146
|
+
#
|
147
|
+
# res.set_cookie("foo", "bar")
|
148
|
+
# res["Set-Cookie"]
|
149
|
+
# # => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
150
|
+
end
|
151
|
+
end
|
data/lib/tynn/secure_headers.rb
CHANGED
@@ -1,9 +1,33 @@
|
|
1
1
|
# Adds security related HTTP headers.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/secure_headers"
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Tynn.helpers(Tynn::SecureHeaders)
|
8
|
+
# ```
|
9
|
+
#
|
10
|
+
# This helper applies the following headers:
|
11
|
+
#
|
12
|
+
# * **X-Content-Type-Options:** Prevents IE and Chrome from
|
13
|
+
# [content type sniffing][mime-sniffing].
|
14
|
+
#
|
15
|
+
# * **X-Frame-Options (XFO):** Provides [Clickjacking][clickjacking]
|
16
|
+
# protection. Check the [X-Frame-Options draft][x-frame-options] for
|
17
|
+
# more information.
|
18
|
+
#
|
19
|
+
# * **X-Permitted-Cross-Domain-Policies:** Restricts Adobe Flash Player's
|
20
|
+
# access to data. Check this [article][pcdp] for more information.
|
21
|
+
#
|
22
|
+
# * **X-XSS-Protection:** Enables the [XSS][xss] protection filter built
|
23
|
+
# into IE, Chrome and Safari. This filter is usually enabled by default,
|
24
|
+
# the use of this header is to re-enable it if it was disabled by the user.
|
25
|
+
#
|
26
|
+
# [clickjacking]: https://www.owasp.org/index.php/Clickjacking
|
27
|
+
# [mime-sniffing]: https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx
|
28
|
+
# [pcdp]: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
|
29
|
+
# [x-frame-options]: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02
|
30
|
+
# [xss]: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
7
31
|
#
|
8
32
|
module Tynn::SecureHeaders
|
9
33
|
HEADERS = {
|
@@ -13,7 +37,7 @@ module Tynn::SecureHeaders
|
|
13
37
|
"X-XSS-Protection" => "1; mode=block"
|
14
38
|
} # :nodoc:
|
15
39
|
|
16
|
-
def default_headers
|
40
|
+
def default_headers # :nodoc:
|
17
41
|
return super.merge(HEADERS)
|
18
42
|
end
|
19
43
|
end
|
data/lib/tynn/session.rb
CHANGED
@@ -1,13 +1,84 @@
|
|
1
|
+
# Adds simple cookie based session management. If a secret token is
|
2
|
+
# given, it signs the cookie data to ensure that it cannot be altered
|
3
|
+
# by unauthorized means.
|
4
|
+
#
|
5
|
+
# ```
|
6
|
+
# require "tynn"
|
7
|
+
# require "tynn/session"
|
8
|
+
#
|
9
|
+
# Tynn.helpers(Tynn::Session, secret: "__change_me__")
|
10
|
+
#
|
11
|
+
# Tynn.define do
|
12
|
+
# root do
|
13
|
+
# res.write(sprintf("hei %s", session[:username]))
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# on(:username) do |username|
|
17
|
+
# session[:username] = username
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# The following command generates a cryptographically secure secret ready
|
23
|
+
# to use:
|
24
|
+
#
|
25
|
+
# ```
|
26
|
+
# $ ruby -r securerandom -e "puts SecureRandom.hex(64)"
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
# It's important to keep the token secret. Knowing the token allows an
|
30
|
+
# attacker to tamper the data. So, it's recommended to load the token
|
31
|
+
# from the environment.
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
# Tynn.helpers(Tynn::Session, secret: ENV["SESSION_SECRET"])
|
35
|
+
# ```
|
36
|
+
#
|
37
|
+
# Under the hood, Tynn::Session uses the [Rack::Session::Cookie][rack-session]
|
38
|
+
# middleware. Thus, supports all the options available for this middleware.
|
39
|
+
#
|
40
|
+
# * `:key` - the name of the cookie. Defaults to `"rack.session"`.
|
41
|
+
# * `:expire_after` - sets the lifespan of the cookie. If `nil`,
|
42
|
+
# the cookie will be deleted after the user close the browser.
|
43
|
+
# Defaults to `nil`.
|
44
|
+
# * `:httponly` - if `true`, sets the [HttpOnly][cookie-httponly] attribute.
|
45
|
+
# This mitigates the risk of client side scripting accessing the cookie.
|
46
|
+
# Defaults to `true`.
|
47
|
+
# * `:secure` - if `true`, sets the [Secure][cookie-secure] attribute.
|
48
|
+
# This tells the browser to only transmit the cookie over HTTPS. Defaults
|
49
|
+
# to `false`.
|
50
|
+
#
|
51
|
+
# ```
|
52
|
+
# Tynn.helpers(
|
53
|
+
# Tynn::Session,
|
54
|
+
# key: "app",
|
55
|
+
# secret: ENV["SESSION_SECRET"],
|
56
|
+
# expire_after: 36_000, # seconds
|
57
|
+
# httponly: true,
|
58
|
+
# secure: true
|
59
|
+
# )
|
60
|
+
# ```
|
61
|
+
#
|
62
|
+
# [cookie-httponly]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#HttpOnly_Attribute
|
63
|
+
# [cookie-secure]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Secure_Attribute
|
64
|
+
# [rack-session]: http://www.rubydoc.info/gems/rack/Rack/Session/Cookie
|
65
|
+
#
|
1
66
|
module Tynn::Session
|
2
67
|
RACK_SESSION = "rack.session".freeze # :nodoc:
|
3
68
|
|
4
69
|
def self.setup(app, options = {}) # :nodoc:
|
5
|
-
options = options.dup
|
6
|
-
options[:http_only] ||= true
|
7
|
-
|
8
70
|
app.use(Rack::Session::Cookie, options)
|
9
71
|
end
|
10
72
|
|
73
|
+
# Returns the session hash.
|
74
|
+
#
|
75
|
+
# ```
|
76
|
+
# session # => {}
|
77
|
+
#
|
78
|
+
# session[:foo] = "foo"
|
79
|
+
# session[:foo] # => "foo"
|
80
|
+
# ```
|
81
|
+
#
|
11
82
|
def session
|
12
83
|
return env[RACK_SESSION]
|
13
84
|
end
|
data/lib/tynn/ssl.rb
CHANGED
data/lib/tynn/static.rb
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
# Adds support for static files (javascript files, images, stylesheets, etc).
|
2
|
+
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/static"
|
6
|
+
#
|
7
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"])
|
8
|
+
# ```
|
9
|
+
#
|
10
|
+
# By default, serve all requests beginning with the given paths from the folder
|
11
|
+
# `public` in the current directory (e.g. `public/js/*`, `public/css/*`). You
|
12
|
+
# can change the default by passing the `:root` option.
|
13
|
+
#
|
14
|
+
# ```
|
15
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], root: "assets")
|
16
|
+
# ```
|
17
|
+
#
|
18
|
+
# Under the hood, it uses the [Rack::Static][rack-static] middleware.
|
19
|
+
# Thus, supports all the options available for this middleware.
|
20
|
+
#
|
21
|
+
# ```
|
22
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], index: "index.html")
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# [rack-static]: http://www.rubydoc.info/gems/rack/Rack/Static
|
26
|
+
#
|
1
27
|
module Tynn::Static
|
2
28
|
def self.setup(app, urls, options = {}) # :nodoc:
|
3
29
|
options = options.dup
|
data/lib/tynn/test.rb
CHANGED
@@ -1,10 +1,45 @@
|
|
1
1
|
require "rack/test"
|
2
2
|
|
3
|
+
# A simple helper class that uses [rack-test][rack-test] to simulate requests
|
4
|
+
# to your application.
|
5
|
+
#
|
6
|
+
# ```
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/test"
|
9
|
+
#
|
10
|
+
# Tynn.define do
|
11
|
+
# root do
|
12
|
+
# res.write("hei")
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# app = Tynn::Test.new
|
17
|
+
# app.get("/")
|
18
|
+
#
|
19
|
+
# 200 == app.res.status # => true
|
20
|
+
# "hei" == app.res.body # => true
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# **NOTE:** Tynn doesn't ship with [rack-test][rack-test]. In order to
|
24
|
+
# use this plugin, you need to install it first.
|
25
|
+
#
|
26
|
+
# [rack-test]: http://rubygems.org/gems/rack-test
|
27
|
+
#
|
3
28
|
class Tynn::Test
|
4
29
|
include Rack::Test::Methods
|
5
30
|
|
6
|
-
|
7
|
-
|
31
|
+
# Instantiates a new Tynn::Test object with the given `application` to test.
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
# class API < Tynn
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# app = Tynn::Test.new(API)
|
38
|
+
# app.get("/json")
|
39
|
+
# ```
|
40
|
+
#
|
41
|
+
def initialize(application = Tynn)
|
42
|
+
@app = application
|
8
43
|
end
|
9
44
|
|
10
45
|
def app # :nodoc:
|
data/lib/tynn/version.rb
CHANGED
data/lib/tynn.rb
CHANGED
@@ -1,15 +1,48 @@
|
|
1
1
|
require "seteable"
|
2
2
|
require "syro"
|
3
|
+
require_relative "tynn/request"
|
4
|
+
require_relative "tynn/response"
|
5
|
+
require_relative "tynn/version"
|
3
6
|
|
4
|
-
class Tynn
|
7
|
+
class Tynn
|
5
8
|
include Seteable
|
9
|
+
include Syro::Deck::API
|
6
10
|
|
11
|
+
# Sets the application handler.
|
12
|
+
#
|
13
|
+
# ```
|
14
|
+
# class Users < Tynn
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Users.define do
|
18
|
+
# on(:id) do |id|
|
19
|
+
# get do
|
20
|
+
# res.write("GET /users")
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# post do
|
24
|
+
# res.write("POST /users")
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# ```
|
29
|
+
#
|
7
30
|
def self.define(&block)
|
8
31
|
@syro = Syro.new(self, &block)
|
9
32
|
end
|
10
33
|
|
11
|
-
|
12
|
-
|
34
|
+
# Adds given Rack `middleware` to the stack.
|
35
|
+
#
|
36
|
+
# ```
|
37
|
+
# require "rack/common_logger"
|
38
|
+
# require "rack/show_exceptions"
|
39
|
+
#
|
40
|
+
# Tynn.use(Rack::CommonLogger)
|
41
|
+
# Tynn.use(Rack::ShowExceptions)
|
42
|
+
# ```
|
43
|
+
#
|
44
|
+
def self.use(middleware, *args, &block)
|
45
|
+
__middleware << proc { |app| middleware.new(app, *args, &block) }
|
13
46
|
end
|
14
47
|
|
15
48
|
def self.call(env) # :nodoc:
|
@@ -19,14 +52,14 @@ class Tynn < Syro::Deck
|
|
19
52
|
def self.to_app # :nodoc:
|
20
53
|
fail("Missing application handler. Try #{ self }.define") unless @syro
|
21
54
|
|
22
|
-
if
|
55
|
+
if __middleware.empty?
|
23
56
|
return @syro
|
24
57
|
else
|
25
|
-
return
|
58
|
+
return __middleware.reverse.inject(@syro) { |a, m| m.call(a) }
|
26
59
|
end
|
27
60
|
end
|
28
61
|
|
29
|
-
def self.
|
62
|
+
def self.__middleware # :nodoc:
|
30
63
|
return @middleware ||= []
|
31
64
|
end
|
32
65
|
|
@@ -35,6 +68,47 @@ class Tynn < Syro::Deck
|
|
35
68
|
@middleware = []
|
36
69
|
end
|
37
70
|
|
71
|
+
# Extends Tynn functionality with the given `helper` module.
|
72
|
+
#
|
73
|
+
# ```
|
74
|
+
# module AppName
|
75
|
+
# def self.setup(app, name)
|
76
|
+
# app.settings[:app_name] = name
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def app_name
|
80
|
+
# return self.class.app_name
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# module ClassMethods
|
84
|
+
# def app_name=(new_name)
|
85
|
+
# settings[:app_name] = new_name
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# def app_name
|
89
|
+
# return settings[:app_name]
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Tynn.helpers(AppName, "MyApplication")
|
95
|
+
#
|
96
|
+
# Tynn.app_name # => "MyApplication"
|
97
|
+
#
|
98
|
+
# Tynn.app_name = "MyGreatestApp"
|
99
|
+
# Tynn.app_name # => "MyGreatestApp"
|
100
|
+
#
|
101
|
+
# Tynn.define do
|
102
|
+
# root do
|
103
|
+
# res.write(app_name)
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
# ```
|
107
|
+
#
|
108
|
+
# Check the [helpers][examples] that come with tynn for more examples.
|
109
|
+
#
|
110
|
+
# [examples]: https://github.com/harmoni/tynn/tree/master/lib/tynn
|
111
|
+
#
|
38
112
|
def self.helpers(helper, *args, &block)
|
39
113
|
self.include(helper)
|
40
114
|
|
@@ -46,6 +120,12 @@ class Tynn < Syro::Deck
|
|
46
120
|
helper.setup(self, *args, &block)
|
47
121
|
end
|
48
122
|
end
|
49
|
-
end
|
50
123
|
|
51
|
-
|
124
|
+
def request_class # :nodoc:
|
125
|
+
return Tynn::Request
|
126
|
+
end
|
127
|
+
|
128
|
+
def response_class # :nodoc:
|
129
|
+
return Tynn::Response
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative "../lib/tynn/all_methods"
|
2
|
+
|
3
|
+
Tynn.helpers(Tynn::AllMethods)
|
4
|
+
|
5
|
+
test "methods" do
|
6
|
+
[:head, :options].each do |method|
|
7
|
+
Tynn.define do
|
8
|
+
send(method) { res.write "" }
|
9
|
+
end
|
10
|
+
|
11
|
+
app = Tynn::Test.new
|
12
|
+
app.send(method, "/")
|
13
|
+
|
14
|
+
assert_equal 200, app.res.status
|
15
|
+
end
|
16
|
+
end
|