tynn 1.4.0 → 2.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
 
3
5
  class Tynn
4
- # Public: Adds helper methods for json generation.
5
- #
6
- # Examples
6
+ # Adds helper methods for JSON generation.
7
7
  #
8
8
  # require "tynn"
9
9
  # require "tynn/json"
@@ -11,16 +11,9 @@ class Tynn
11
11
  # Tynn.plugin(Tynn::JSON)
12
12
  #
13
13
  module JSON
14
- CONTENT_TYPE = "application/json".freeze
15
-
16
14
  module InstanceMethods
17
- # Public: Calls +to_json+ on +data+ and writes the generated \JSON
18
- # object into the response body. Also, It automatically sets the
19
- # +Content-Type+ header to +application/json+.
20
- #
21
- # data - Any object that responds to +to_json+.
22
- #
23
- # Examples
15
+ # Generates a JSON document from <tt>data</tt> and writes it to the response body.
16
+ # It automatically sets the <tt>Content-Type</tt> header to <tt>application/json</tt>.
24
17
  #
25
18
  # Tynn.define do
26
19
  # on("hash") do
@@ -30,16 +23,12 @@ class Tynn
30
23
  # on("array") do
31
24
  # json([1, 2, 3])
32
25
  # end
33
- #
34
- # on("to_json") do
35
- # json(Model.first)
36
- # end
37
26
  # end
38
27
  #
39
28
  def json(data)
40
- res.headers[Rack::CONTENT_TYPE] = Tynn::JSON::CONTENT_TYPE
29
+ res.headers["Content-Type"] = "application/json"
41
30
 
42
- res.write(data.to_json)
31
+ res.write(::JSON.generate(data))
43
32
  end
44
33
  end
45
34
  end
@@ -1,48 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "tilt"
2
4
 
3
5
  class Tynn
4
6
  module Render
5
7
  def self.setup(app, options = {}) # :nodoc:
6
- app.settings.update(
8
+ app.set(:render, {
7
9
  layout: options.fetch(:layout, "layout"),
8
10
  views: options.fetch(:views, File.expand_path("views", Dir.pwd)),
9
11
  engine: options.fetch(:engine, "erb"),
10
12
  engine_opts: {
11
13
  escape_html: true
12
- }.merge!(options.fetch(:options, {}))
13
- )
14
+ }.merge(options.fetch(:options, {}))
15
+ })
14
16
  end
15
17
 
16
18
  module InstanceMethods
17
- def render(template, locals = {}, layout = self.class.settings[:layout])
19
+ def render(template, locals = {}, layout = self.class.settings.dig(:render, :layout))
18
20
  res.headers[Rack::CONTENT_TYPE] ||= Syro::Response::DEFAULT
19
21
 
20
22
  res.write(view(template, locals, layout))
21
23
  end
22
24
 
23
- def view(template, locals = {}, layout = self.class.settings[:layout])
24
- return partial(layout, locals.merge(content: partial(template, locals)))
25
+ def view(template, locals = {}, layout = self.class.settings.dig(:render, :layout))
26
+ partial(layout, locals.merge(content: partial(template, locals)))
25
27
  end
26
28
 
27
29
  def partial(template, locals = {})
28
- return tilt(template_path(template), locals, self.class.settings[:engine_opts])
30
+ tilt(template_path(template), locals, self.class.settings.dig(:render, :engine_opts))
29
31
  end
30
32
 
31
33
  private
32
34
 
33
35
  def tilt(file, locals = {}, opts = {})
34
- return tilt_cache.fetch(file) { Tilt.new(file, 1, opts) }.render(self, locals)
36
+ tilt_cache.fetch(file) {
37
+ Tilt.new(file, 1, opts)
38
+ }.render(self, locals.merge(app: self))
35
39
  end
36
40
 
37
41
  def tilt_cache
38
- return Thread.current[:tilt_cache] ||= Tilt::Cache.new
42
+ Thread.current[:tilt_cache] ||= Tilt::Cache.new
39
43
  end
40
44
 
41
45
  def template_path(template)
42
- dir = self.class.settings[:views]
43
- ext = self.class.settings[:engine]
46
+ dir = self.class.settings.dig(:render, :views)
47
+ ext = self.class.settings.dig(:render, :engine)
44
48
 
45
- return File.join(dir, "#{ template }.#{ ext }")
49
+ File.join(dir, "#{ template }.#{ ext }")
46
50
  end
47
51
  end
48
52
  end
@@ -1,11 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Tynn
2
- # Public: It provides convenience methods for pulling out information
3
- # from a request.
4
- #
5
- # Examples
4
+ # It provides convenience methods for pulling out information from a request.
6
5
  #
7
6
  # env = {
8
- # "REQUEST_METHOD" => "GET"
7
+ # "REQUEST_METHOD" => "GET",
9
8
  # "QUERY_STRING" => "email=me@tynn.xyz"
10
9
  # }
11
10
  #
@@ -13,41 +12,58 @@ class Tynn
13
12
  #
14
13
  # req.get? # => true
15
14
  # req.post? # => false
16
- #
17
15
  # req.params # => { "email" => "me@tynn.xyz" }
18
- # req[:email] # => "me@tynn.xyz"
19
16
  #
20
17
  class Request < Rack::Request
21
- # Public: Returns the value of the +key+ param.
22
- #
23
- # key - Any object that responds to +to_s+.
24
- #
25
- # Examples
26
- #
27
- # req.params
28
- # # => { "username" => "bob" }
29
- #
30
- # req[:username] # => "bob"
31
- # req["username"] # => "bob"
32
- #
33
- # Signature
34
- #
35
- # [](key)
36
- #
37
- # Inherited by Rack::Request.
38
-
39
- # Public: Returns a Hash of parameters. Includes data from the query
40
- # string and the response body.
41
- #
42
- # Examples
43
- #
44
- # req.params
45
- # # => { "user" => { "username" => "bob" } }
46
- #
47
- # Signature
48
- #
49
- # params()
50
- #
51
- # Inherited by Rack::Request.
18
+ class Headers
19
+ CGI_VARIABLES = Set.new(%w(
20
+ AUTH_TYPE
21
+ CONTENT_LENGTH
22
+ CONTENT_TYPE
23
+ GATEWAY_INTERFACE
24
+ HTTPS
25
+ PATH_INFO
26
+ PATH_TRANSLATED
27
+ QUERY_STRING
28
+ REMOTE_ADDR
29
+ REMOTE_HOST
30
+ REMOTE_IDENT
31
+ REMOTE_USER
32
+ REQUEST_METHOD
33
+ SCRIPT_NAME
34
+ SERVER_NAME
35
+ SERVER_PORT
36
+ SERVER_PROTOCOL
37
+ SERVER_SOFTWARE
38
+ )).freeze
39
+
40
+ def initialize(req)
41
+ @req = req
42
+ end
43
+
44
+ def [](key)
45
+ @req.env[transform_key(key)]
46
+ end
47
+
48
+ def fetch(key, *args, &block)
49
+ @req.env.fetch(transform_key(key), *args, &block)
50
+ end
51
+
52
+ def key?(key)
53
+ @req.env.key?(transform_key(key))
54
+ end
55
+
56
+ private
57
+
58
+ def transform_key(key)
59
+ key = key.upcase.tr("-", "_")
60
+ key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
61
+ key
62
+ end
63
+ end
64
+
65
+ def headers
66
+ @headers ||= Headers.new(self)
67
+ end
52
68
  end
53
69
  end
@@ -1,7 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Tynn
2
- # Public: It provides convenience methods to construct a Rack response.
3
- #
4
- # Examples
4
+ # It provides convenience methods to construct a Rack response.
5
5
  #
6
6
  # res = Tynn::Response.new
7
7
  #
@@ -13,185 +13,45 @@ class Tynn
13
13
  # # => [200, { "Content-Type" => "text/html", "Content-Length" => 3 }, ["foo"]]
14
14
  #
15
15
  class Response < Syro::Response
16
- # Public: Initializes a new response object.
17
- #
18
- # headers - A Hash of initial headers. Defaults to <tt>{}</tt>.
19
- #
20
- # Examples
21
- #
22
- # Tynn::Response.new.headers
23
- # # => {}
24
- #
25
- # Tynn::Response.new("Content-Type" => "text/plain").headers
26
- # # => { "Content-Type" => "text/plain" }
27
- #
28
- # Signature
29
- #
30
- # new(headers = {})
31
- #
32
- # Inherited by Syro::Response.
33
-
34
- # Public: Returns the response header corresponding to +key+.
35
- #
36
- # key - A String HTTP header field name.
37
- #
38
- # Examples
39
- #
40
- # res["Content-Type"] # => "text/html"
41
- # res["Content-Length"] # => "42"
42
- #
43
- # Signature
44
- #
45
- # [](key)
46
- #
47
- # Inherited by Syro::Response.
48
-
49
- # Public: Sets the given +value+ with the header corresponding to +key+.
50
- #
51
- # key - A String HTTP header field name.
52
- # value - A String HTTP header field value.
53
- #
54
- # Examples
55
- #
56
- # res["Content-Type"] = "application/json"
57
- # res["Content-Type"] # => "application/json"
58
- #
59
- # Signature
60
- #
61
- # []=(key, value)
62
- #
63
- # Inherited by Syro::Response.
64
-
65
- # Public: Returns the body of the response.
66
- #
67
- # Examples
68
- #
69
- # res.body
70
- # # => []
71
- #
72
- # res.write("there is")
73
- # res.write("no try")
74
- #
75
- # res.body
76
- # # => ["there is", "no try"]
77
- #
78
- # Signature
79
- #
80
- # body()
81
- #
82
- # Inherited by Syro::Response.
83
-
84
- # Public: Returns an Array with three elements: the status, headers
85
- # and body. If the status is not set, the status is set to +404+ if
86
- # empty body, otherwise the status is set to +200+ and updates the
87
- # +Content-Type+ header to +text/html+.
88
- #
89
- # Examples
90
- #
91
- # res.status = 200
92
- # res.finish
93
- # # => [200, {}, []]
16
+ # Sets a cookie into the response.
94
17
  #
95
- # res.status = nil
96
- # res.finish
97
- # # => [404, {}, []]
18
+ # res.set_cookie("foo", "bar")
19
+ # res["Set-Cookie"] # => "foo=bar"
98
20
  #
99
- # res.status = nil
100
- # res.write("yo")
101
- # res.finish
102
- # # => [200, { "Content-Type" => "text/html", "Content-Length" => 2 }, ["yo"]]
21
+ # res.set_cookie("foo2", "bar2")
22
+ # res["Set-Cookie"] # => "foo=bar\nfoo2=bar2"
103
23
  #
104
- # Signature
24
+ # res.set_cookie("bar", "bar", {
25
+ # domain: ".example.com",
26
+ # path: "/",
27
+ # # max_age: 0,
28
+ # # expires: Time.now + 10_000,
29
+ # secure: true,
30
+ # httponly: true,
31
+ # same_site: :Lax
32
+ # })
105
33
  #
106
- # finish()
34
+ # res["Set-Cookie"].split("\n").last
35
+ # # => "bar=bar; domain=.example.com; path=/; secure; HttpOnly; SameSite=Lax
107
36
  #
108
- # Inherited by Syro::Response.
109
-
110
- # Public: Returns a Hash with the response headers.
111
- #
112
- # Examples
113
- #
114
- # res.headers
115
- # # => { "Content-Type" => "text/html", "Content-Length" => "42" }
116
- #
117
- # Signature
118
- #
119
- # headers()
120
- #
121
- # Inherited by Syro::Response.
122
-
123
- # Public: Sets the +Location+ header to +url+ and updates the status
124
- # to +status+.
125
- #
126
- # url - A String URL (relative or absolute) to redirect to.
127
- # status - An Integer status code. Defaults to +302+.
128
- #
129
- # Examples
130
- #
131
- # res.redirect("/path")
132
- #
133
- # res["Location"] # => "/path"
134
- # res.status # => 302
135
- #
136
- # res.redirect("http://tynn.xyz", 303)
137
- #
138
- # res["Location"] # => "http://tynn.xyz"
139
- # res.status # => 303
140
- #
141
- # Signature
142
- #
143
- # redirect(url, status = 302)
144
- #
145
- # Inherited by Syro::Response.
146
-
147
- # Public: Returns the status of the response.
37
+ # *NOTE.* This method doesn't sign and/or encrypt the value of the cookie.
148
38
  #
149
- # Examples
150
- #
151
- # res.status # => 200
152
- #
153
- # Signature
154
- #
155
- # status()
156
- #
157
- # Inherited by Syro::Response.
39
+ def set_cookie(key, value, options = {})
40
+ Rack::Utils.set_cookie_header!(headers, key, options.merge(value: value))
41
+ end
158
42
 
159
- # Public: Sets the status of the response.
160
- #
161
- # status - An Integer HTTP status code.
162
- #
163
- # Examples
164
- #
165
- # res.status = 200
166
- #
167
- # Signature
168
- #
169
- # status=(status)
170
- #
171
- # Inherited by Syro::Response.
172
-
173
- # Public: Appends +str+ to the response body and updates the
174
- # +Content-Length+ header.
175
- #
176
- # str - Any object that responds to +to_s+.
177
- #
178
- # Examples
179
- #
180
- # res.body # => []
181
- #
182
- # res.write("foo")
183
- # res.write("bar")
184
- #
185
- # res.body
186
- # # => ["foo", "bar"]
187
- #
188
- # res["Content-Length"]
189
- # # => 6
43
+ # Deletes cookie by <tt>key</tt>.
190
44
  #
191
- # Signature
45
+ # res.set_cookie("foo", "bar")
46
+ # res["Set-Cookie"]
47
+ # # => "foo=bar"
192
48
  #
193
- # write(str)
49
+ # res.delete_cookie("foo")
50
+ # res["Set-Cookie"]
51
+ # # => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
194
52
  #
195
- # Inherited by Syro::Response.
53
+ def delete_cookie(key, options = {})
54
+ Rack::Utils.delete_cookie_header!(headers, options)
55
+ end
196
56
  end
197
57
  end
@@ -1,44 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Tynn
2
- # Public: Adds security related HTTP headers.
4
+ # Adds the following security related HTTP headers:
3
5
  #
4
- # Examples
6
+ # [X-Content-Type-Options]
7
+ # Prevents IE and Chrome from {content type sniffing}[https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx].
8
+ # Defaults to <tt>"nosniff"</tt>.
5
9
  #
6
- # require "tynn"
7
- # require "tynn/secure_headers"
8
- #
9
- # Tynn.plugin(Tynn::SecureHeaders)
10
- #
11
- # This plugin applies the following headers:
12
- #
13
- # *X-Content-Type-Options:* <tt>"nosniff"</tt>
10
+ # [X-Frame-Options]
11
+ # Provides {Clickjacking}[https://www.owasp.org/index.php/Clickjacking]
12
+ # protection. Defaults to <tt>"SAMEORIGIN"</tt>.
14
13
  #
15
- # Prevents IE and Chrome from
16
- # {content type sniffing}[https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx]
14
+ # [X-Permitted-Cross-Domain-Policies]
15
+ # Restricts Adobe Flash Player's access to data. Defaults to <tt>"none"</tt>.
17
16
  #
18
- # *X-Frame-Options:* <tt>"SAMEORIGIN"</tt>
17
+ # [X-XSS-Protection]
18
+ # Enables the XSS protection filter built into IE, Chrome and Safari.
19
+ # This filter is usually enabled by default, the use of this header is to
20
+ # re-enable it if it was turned off by the user. Defaults to <tt>"1; mode=block"</tt>.
19
21
  #
20
- # Provides {Clickjacking}[https://www.owasp.org/index.php/Clickjacking]
21
- # protection.
22
+ # <tt></tt>
22
23
  #
23
- # *X-Permitted-Cross-Domain-Policies:* <tt>"none"</tt>
24
- #
25
- # Restricts Adobe Flash Player's access to data.
26
- #
27
- # *X-XSS-Protection:* <tt>"1; mode=block"</tt>
24
+ # require "tynn"
25
+ # require "tynn/secure_headers"
28
26
  #
29
- # Enables the XSS protection filter built into IE, Chrome and Safari.
30
- # This filter is usually enabled by default, the use of this header
31
- # is to re-enable it if it was turned off by the user.
27
+ # Tynn.plugin(Tynn::SecureHeaders)
32
28
  #
33
29
  module SecureHeaders
34
- # Internal: Sets the default HTTP secure headers.
35
- def self.setup(app)
36
- app.settings[:default_headers].update(
37
- "X-Content-Type-Options" => "nosniff",
38
- "X-Frame-Options" => "SAMEORIGIN",
39
- "X-Permitted-Cross-Domain-Policies" => "none",
40
- "X-XSS-Protection" => "1; mode=block"
41
- )
30
+ HEADERS = {
31
+ "X-Content-Type-Options" => "nosniff",
32
+ "X-Frame-Options" => "SAMEORIGIN",
33
+ "X-Permitted-Cross-Domain-Policies" => "none",
34
+ "X-XSS-Protection" => "1; mode=block"
35
+ }.freeze # :nodoc:
36
+
37
+ def self.setup(app) # :nodoc:
38
+ app.set!(:default_headers, HEADERS.merge(app.default_headers))
42
39
  end
43
40
  end
44
41
  end