tynn 1.4.0 → 2.0.0.alpha

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.
@@ -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