roda 3.5.0 → 3.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f0422791db9ddee07b535f5429fd17ae5f312c7e5c7519dfbe0288e1d285919
4
- data.tar.gz: 905f19053a3997fc7c3c4e4b942b73cd2400cb59df641cb4a86b23191cab873c
3
+ metadata.gz: f461d51ffcff3abee852c79773a13d87f2ffc5fea2c6a27346bfb2755b318e1e
4
+ data.tar.gz: c30fafc14125ef2b100bce74762193cb0f09776805732c284b7d7cd0472f07c5
5
5
  SHA512:
6
- metadata.gz: 10aff2c1df661f7cb2699cb3f49bf521587813017ca5cc5c1117c629f04805075e185170305cc012cbbdc2c4ecae59902f73efab1ff39806d3f9548acc918d8b
7
- data.tar.gz: d6b70c570f4e198b743ecb4f216f600ad09b8010d6e1df1c202873b843846d1fac3c817dfd848fc7509efc4ba8f41d5907c4273187871ab8293810dc307018e5
6
+ metadata.gz: 428d1c607d27a5f90b4c5c992e8f8f3ea33cf8dd7c49e2a0920e49e4b97de34532eb9177aae04ad1efa9a229d747a6e5a744334ae803edbaf60e1e51cf154552
7
+ data.tar.gz: dd85d32aedb8b2c09fc757bd1cbc0f92dce99849841e4ab2e78f47589de8670c87023eb9d6a144efd1c7ff06f08f705589e2510cb7c4383cba13b40020b2d489
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 3.6.0 (2018-03-26)
2
+
3
+ * Add :wrap option to json_parser plugin, for whether/how to wrap the uploaded JSON object (jeremyevans) (#142)
4
+
5
+ * Add :early_hints option to the assets plugin, for supporting sending early hints for calls to assets (jeremyevans)
6
+
7
+ * Add early_hints plugin for sending 103 Early Hint responses, currently only working on puma (jeremyevans)
8
+
1
9
  = 3.5.0 (2018-02-14)
2
10
 
3
11
  * Add request_aref plugin for configuring behavior of request [] and []= methods (jeremyevans)
@@ -0,0 +1,21 @@
1
+ = New Features
2
+
3
+ * An early_hints plugin has been added for senting 103 Early Hint
4
+ responses. This is currently only supported on puma 3.11+, and
5
+ can allow for improved performance by letting the requestor know
6
+ which related files will be needed by the request.
7
+
8
+ * An :early_hints option has been added to the assets plugin. If
9
+ given, calling the assets method will also issue an early hint
10
+ for the related assets.
11
+
12
+ * A :wrap option has been added to the json_parser plugin. If set
13
+ to :always, all uploaded json data will be stored using a hash
14
+ with a "_json" key. If set to :unless_hash, uploaded json data
15
+ will only be wrapped in such a matter if it is not already a hash.
16
+
17
+ Using the :wrap option can fix problems when using r.params when
18
+ the uploaded JSON data is an array and not a hash. However, it
19
+ does change the behavior of r.POST. It is possible to handle
20
+ uploaded JSON array data without the :wrap option by using r.GET
21
+ and r.POST directly instead of using r.params.
@@ -267,6 +267,7 @@ class Roda
267
267
  # :dependencies :: A hash of dependencies for your asset files. Keys should be paths to asset files,
268
268
  # values should be arrays of paths your asset files depends on. This is used to
269
269
  # detect changes in your asset files.
270
+ # :early_hints :: Automatically send early hints for all assets. Requires the early_hints plugin.
270
271
  # :group_subdirs :: Whether a hash used in :css and :js options requires the assets for the
271
272
  # related group are contained in a subdirectory with the same name (default: true)
272
273
  # :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
@@ -307,6 +308,7 @@ class Roda
307
308
  :concat_only => false,
308
309
  :compiled => false,
309
310
  :add_suffix => false,
311
+ :early_hints => false,
310
312
  :timestamp_paths => false,
311
313
  :group_subdirs => true,
312
314
  :compiled_css_dir => nil,
@@ -369,6 +371,10 @@ class Roda
369
371
  opts[:compiled] = ::JSON.parse(::File.read(opts[:precompiled]))
370
372
  end
371
373
 
374
+ if opts[:early_hints]
375
+ app.plugin :early_hints
376
+ end
377
+
372
378
  DEFAULTS.each do |k, v|
373
379
  opts[k] = v unless opts.has_key?(k)
374
380
  end
@@ -661,7 +667,12 @@ class Roda
661
667
  tag_end = "\" />"
662
668
  end
663
669
 
664
- assets_paths(type).map{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
670
+ paths = assets_paths(type)
671
+ if o[:early_hints]
672
+ early_hint_as = ltype == :js ? 'script' : 'style'
673
+ send_early_hints('Link'=>paths.map{|p| "<#{p}>; rel=preload; as=#{early_hint_as}"}.join("\n"))
674
+ end
675
+ paths.map{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
665
676
  end
666
677
 
667
678
  # Render the asset with the given filename. When assets are compiled,
@@ -0,0 +1,26 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The early_hints plugin allows sending 103 Early Hints responses
7
+ # using the rack.early_hints environment variable. Currently, this
8
+ # is only supported by puma 3.11+, and on other servers this is a no-op.
9
+ # Early hints allow clients to preload necessary files before receiving
10
+ # the response.
11
+ module EarlyHints
12
+ module InstanceMethods
13
+ # Send given hash of Early Hints using the rack.early_hints environment variable,
14
+ # currenly only supported by puma. hash given should generally have the single
15
+ # key 'Link', and a string or array of strings for each of the early hints.
16
+ def send_early_hints(hash)
17
+ if eh_proc = env['rack.early_hints']
18
+ eh_proc.call(hash)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ register_plugin(:early_hints, EarlyHints)
25
+ end
26
+ end
@@ -25,10 +25,24 @@ class Roda
25
25
  # :include_request :: If true, the parser will be called with the request
26
26
  # object as the second argument, so the parser needs
27
27
  # to respond to +call(str, request)+.
28
+ # :wrap :: Whether to wrap uploaded JSON data in a hash with a "_json"
29
+ # key. Without this, calls to r.params will fail if a non-Hash
30
+ # (such as an array) is uploaded in JSON format. A value of
31
+ # :always will wrap all values, and a value of :unless_hash will
32
+ # only wrap values that are not already hashes.
28
33
  def self.configure(app, opts=OPTS)
29
34
  app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER
30
35
  app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || DEFAULT_PARSER
31
36
  app.opts[:json_parser_include_request] = opts[:include_request] if opts.has_key?(:include_request)
37
+
38
+ case opts[:wrap]
39
+ when :unless_hash, :always
40
+ app.opts[:json_parser_wrap] = opts[:wrap]
41
+ when nil
42
+ # Nothing
43
+ else
44
+ raise RodaError, "unsupported option value for json_parser plugin :wrap option: #{opts[:wrap].inspect} (should be :unless_hash or :always)"
45
+ end
32
46
  end
33
47
 
34
48
  module RequestMethods
@@ -43,10 +57,16 @@ class Roda
43
57
  input.rewind
44
58
  return super if str.empty?
45
59
  begin
46
- json_params = env["roda.json_params"] = parse_json(str)
60
+ json_params = parse_json(str)
47
61
  rescue
48
62
  roda_class.opts[:json_parser_error_handler].call(self)
49
63
  end
64
+
65
+ wrap = roda_class.opts[:json_parser_wrap]
66
+ if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
67
+ json_params = {"_json"=>json_params}
68
+ end
69
+ env["roda.json_params"] = json_params
50
70
  env["rack.request.form_input"] = input
51
71
  json_params
52
72
  else
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 5
7
+ RodaMinorVersion = 6
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -196,6 +196,27 @@ if run_tests
196
196
  js.must_include('console.log')
197
197
  end
198
198
 
199
+ it 'should handle early hints if the :early_hints option is used' do
200
+ app.plugin :assets, :early_hints=>true
201
+ eh = []
202
+ html = body('/test', 'rack.early_hints'=>proc{|h| eh << h})
203
+ eh.must_equal [
204
+ {"Link"=>"</assets/css/app.scss>; rel=preload; as=style\n</assets/css/raw.css>; rel=preload; as=style"},
205
+ {"Link"=>"</assets/js/head/app.js>; rel=preload; as=script"}
206
+ ]
207
+ html.scan(/<link/).length.must_equal 2
208
+ html =~ %r{href="(/assets/css/app\.scss)"}
209
+ css = body($1)
210
+ html =~ %r{href="(/assets/css/raw\.css)"}
211
+ css2 = body($1)
212
+ html.scan(/<script/).length.must_equal 1
213
+ html =~ %r{src="(/assets/js/head/app\.js)"}
214
+ js = body($1)
215
+ css.must_match(/color: red;/)
216
+ css2.must_match(/color: blue;/)
217
+ js.must_include('console.log')
218
+ end
219
+
199
220
  it 'should handle rendering assets, linking to them, and accepting requests for them when :add_script_name app option is used' do
200
221
  app.opts[:add_script_name] = true
201
222
  app.plugin :assets
@@ -0,0 +1,19 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe "early_hints plugin" do
4
+ it "allows sending early hints to rack.early_hints" do
5
+ queue = []
6
+ app(:early_hints) do |r|
7
+ send_early_hints('Link'=>'</foo.js>; rel=preload; as=script')
8
+ queue << 'OK'
9
+ 'OK'
10
+ end
11
+
12
+ body.must_equal 'OK'
13
+ queue.must_equal ['OK']
14
+
15
+ queue = []
16
+ body('rack.early_hints'=>proc{|h| queue << h}).must_equal 'OK'
17
+ queue.must_equal [{'Link'=>'</foo.js>; rel=preload; as=script'}, 'OK']
18
+ end
19
+ end
@@ -18,6 +18,12 @@ describe "json_parser plugin" do
18
18
  it "returns 400 for invalid json" do
19
19
  req('rack.input'=>StringIO.new('{"a":{"b":1}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal [400, {}, []]
20
20
  end
21
+
22
+ it "raises by default if r.params is called and a non-hash is submitted" do
23
+ proc do
24
+ req('rack.input'=>StringIO.new('[1]'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST')
25
+ end.must_raise
26
+ end
21
27
  end
22
28
 
23
29
  describe "json_parser plugin" do
@@ -28,6 +34,45 @@ describe "json_parser plugin" do
28
34
  body('rack.input'=>StringIO.new(''), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '0'
29
35
  end
30
36
 
37
+ it "handles arrays and other non-hash values using r.POST" do
38
+ app(:json_parser) do |r|
39
+ r.POST.inspect
40
+ end
41
+ body('rack.input'=>StringIO.new('[ 1 ]'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '[1]'
42
+ end
43
+
44
+ it "supports :wrap=>:always option" do
45
+ app(:bare) do
46
+ plugin(:json_parser, :wrap=>:always)
47
+ route do |r|
48
+ r.post 'a' do r.params['_json']['a']['b'].to_s end
49
+ r.params['_json'][1].to_s
50
+ end
51
+ end
52
+ body('/a', 'rack.input'=>StringIO.new('{"a":{"b":1}}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '1'
53
+ body('rack.input'=>StringIO.new('[true, 2]'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '2'
54
+ end
55
+
56
+ it "supports :wrap=>:unless_hash option" do
57
+ app(:bare) do
58
+ plugin(:json_parser, :wrap=>:unless_hash)
59
+ route do |r|
60
+ r.post 'a' do r.params['a']['b'].to_s end
61
+ r.params['_json'][1].to_s
62
+ end
63
+ end
64
+ body('/a', 'rack.input'=>StringIO.new('{"a":{"b":1}}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '1'
65
+ body('rack.input'=>StringIO.new('[true, 2]'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '2'
66
+ end
67
+
68
+ it "raises for unsupported :wrap option" do
69
+ proc do
70
+ app(:bare) do
71
+ plugin(:json_parser, :wrap=>:foo)
72
+ end
73
+ end.must_raise Roda::RodaError
74
+ end
75
+
31
76
  it "supports :error_handler option" do
32
77
  app(:bare) do
33
78
  plugin(:json_parser, :error_handler=>proc{|r| r.halt [401, {}, ['bad']]})
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-14 00:00:00.000000000 Z
11
+ date: 2018-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -201,6 +201,7 @@ extra_rdoc_files:
201
201
  - doc/release_notes/3.3.0.txt
202
202
  - doc/release_notes/3.4.0.txt
203
203
  - doc/release_notes/3.5.0.txt
204
+ - doc/release_notes/3.6.0.txt
204
205
  files:
205
206
  - CHANGELOG
206
207
  - MIT-LICENSE
@@ -248,6 +249,7 @@ files:
248
249
  - doc/release_notes/3.3.0.txt
249
250
  - doc/release_notes/3.4.0.txt
250
251
  - doc/release_notes/3.5.0.txt
252
+ - doc/release_notes/3.6.0.txt
251
253
  - lib/roda.rb
252
254
  - lib/roda/plugins/_symbol_regexp_matchers.rb
253
255
  - lib/roda/plugins/all_verbs.rb
@@ -269,6 +271,7 @@ files:
269
271
  - lib/roda/plugins/delete_empty_headers.rb
270
272
  - lib/roda/plugins/disallow_file_uploads.rb
271
273
  - lib/roda/plugins/drop_body.rb
274
+ - lib/roda/plugins/early_hints.rb
272
275
  - lib/roda/plugins/empty_root.rb
273
276
  - lib/roda/plugins/environments.rb
274
277
  - lib/roda/plugins/error_email.rb
@@ -364,6 +367,7 @@ files:
364
367
  - spec/plugin/delete_empty_headers_spec.rb
365
368
  - spec/plugin/disallow_file_uploads_spec.rb
366
369
  - spec/plugin/drop_body_spec.rb
370
+ - spec/plugin/early_hints_spec.rb
367
371
  - spec/plugin/empty_root_spec.rb
368
372
  - spec/plugin/environments_spec.rb
369
373
  - spec/plugin/error_email_spec.rb