roda 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/Rakefile +22 -46
- data/doc/release_notes/2.3.0.txt +109 -0
- data/lib/roda/plugins/assets.rb +2 -1
- data/lib/roda/plugins/caching.rb +1 -1
- data/lib/roda/plugins/chunked.rb +1 -1
- data/lib/roda/plugins/error_email.rb +1 -1
- data/lib/roda/plugins/head.rb +6 -0
- data/lib/roda/plugins/heartbeat.rb +40 -0
- data/lib/roda/plugins/json.rb +23 -3
- data/lib/roda/plugins/json_parser.rb +72 -0
- data/lib/roda/plugins/mailer.rb +22 -5
- data/lib/roda/plugins/named_templates.rb +2 -2
- data/lib/roda/plugins/path_rewriter.rb +82 -0
- data/lib/roda/plugins/precompile_templates.rb +87 -0
- data/lib/roda/plugins/render.rb +111 -43
- data/lib/roda/plugins/render_each.rb +1 -1
- data/lib/roda/plugins/shared_vars.rb +1 -1
- data/lib/roda/plugins/view_options.rb +28 -3
- data/lib/roda/version.rb +1 -1
- data/spec/composition_spec.rb +3 -3
- data/spec/env_spec.rb +1 -1
- data/spec/freeze_spec.rb +6 -6
- data/spec/integration_spec.rb +16 -15
- data/spec/matchers_spec.rb +110 -110
- data/spec/opts_spec.rb +8 -8
- data/spec/plugin/_erubis_escaping_spec.rb +34 -3
- data/spec/plugin/all_verbs_spec.rb +8 -8
- data/spec/plugin/assets_spec.rb +164 -150
- data/spec/plugin/backtracking_array_spec.rb +18 -18
- data/spec/plugin/caching_spec.rb +70 -70
- data/spec/plugin/chunked_spec.rb +38 -38
- data/spec/plugin/class_level_routing_spec.rb +78 -78
- data/spec/plugin/content_for_spec.rb +2 -2
- data/spec/plugin/cookies_spec.rb +4 -4
- data/spec/plugin/csrf_spec.rb +8 -8
- data/spec/plugin/default_headers_spec.rb +6 -6
- data/spec/plugin/delay_build_spec.rb +7 -6
- data/spec/plugin/delegate_spec.rb +2 -2
- data/spec/plugin/delete_empty_headers_spec.rb +2 -2
- data/spec/plugin/drop_body_spec.rb +6 -6
- data/spec/plugin/empty_root_spec.rb +3 -3
- data/spec/plugin/environments_spec.rb +7 -7
- data/spec/plugin/error_email_spec.rb +23 -23
- data/spec/plugin/error_handler_spec.rb +14 -14
- data/spec/plugin/flash_spec.rb +30 -29
- data/spec/plugin/h_spec.rb +1 -1
- data/spec/plugin/halt_spec.rb +16 -16
- data/spec/plugin/hash_matcher_spec.rb +5 -5
- data/spec/plugin/head_spec.rb +10 -10
- data/spec/plugin/header_matchers_spec.rb +13 -13
- data/spec/plugin/heartbeat_spec.rb +74 -0
- data/spec/plugin/hooks_spec.rb +20 -20
- data/spec/plugin/indifferent_params_spec.rb +1 -1
- data/spec/plugin/json_parser_spec.rb +72 -0
- data/spec/plugin/json_spec.rb +22 -9
- data/spec/plugin/mailer_spec.rb +72 -58
- data/spec/plugin/match_affix_spec.rb +2 -2
- data/spec/plugin/middleware_spec.rb +7 -7
- data/spec/plugin/module_include_spec.rb +4 -4
- data/spec/plugin/multi_route_spec.rb +66 -66
- data/spec/plugin/multi_run_spec.rb +21 -21
- data/spec/plugin/named_templates_spec.rb +6 -6
- data/spec/plugin/not_allowed_spec.rb +17 -17
- data/spec/plugin/not_found_spec.rb +14 -14
- data/spec/plugin/padrino_render_spec.rb +2 -2
- data/spec/plugin/param_matchers_spec.rb +6 -6
- data/spec/plugin/partials_spec.rb +3 -3
- data/spec/plugin/pass_spec.rb +7 -7
- data/spec/plugin/path_matchers_spec.rb +6 -6
- data/spec/plugin/path_rewriter_spec.rb +37 -0
- data/spec/plugin/path_spec.rb +41 -40
- data/spec/plugin/per_thread_caching_spec.rb +6 -6
- data/spec/plugin/precompile_templates_spec.rb +74 -0
- data/spec/plugin/render_each_spec.rb +4 -4
- data/spec/plugin/render_spec.rb +179 -76
- data/spec/plugin/shared_vars_spec.rb +4 -4
- data/spec/plugin/sinatra_helpers_spec.rb +121 -121
- data/spec/plugin/slash_path_empty_spec.rb +10 -10
- data/spec/plugin/static_spec.rb +4 -4
- data/spec/plugin/streaming_spec.rb +11 -11
- data/spec/plugin/symbol_matchers_spec.rb +24 -24
- data/spec/plugin/symbol_views_spec.rb +3 -3
- data/spec/plugin/view_options_spec.rb +10 -10
- data/spec/plugin_spec.rb +2 -2
- data/spec/redirect_spec.rb +10 -10
- data/spec/request_spec.rb +8 -8
- data/spec/response_spec.rb +23 -23
- data/spec/session_spec.rb +4 -4
- data/spec/spec_helper.rb +5 -19
- data/spec/version_spec.rb +4 -4
- data/spec/views/iv.erb +1 -0
- metadata +16 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c94569f43d36e67b81fa0f67d739a24f9b5a594
|
4
|
+
data.tar.gz: 8420ceb5dafb0f76e840fdeff028e46f4b0842f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab68ffe4810164018008821e8a8ae7a66469a58884ff5a25599e0cac03330e393d9b62cf5d8712ceea8e9a870afd431f52a072294c710285a7e69d1a243326b0
|
7
|
+
data.tar.gz: ab79ec088d85b8daa3f705fb92bcd9e64be19ae9a5bf131f9414fca72c53a89f00a9c82465ed8db68440bf120a783c1ad4e521e295f15868a4a372463ecf8a06
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
= 2.3.0 (2015-05-13)
|
2
|
+
|
3
|
+
* Make assets plugin work better with json plugin when r.assets is the last method called in a route block (jeremyevans) (#27)
|
4
|
+
|
5
|
+
* Support no_mail! method in the mailer plugin, for skipping an email (jeremyevans)
|
6
|
+
|
7
|
+
* Add precompile_templates plugin, for saving memory when using a forking webserver (jeremyevans)
|
8
|
+
|
9
|
+
* Document how to allow per-branch HTML escaping of <%= %> in the view_options plugin (jeremyevans)
|
10
|
+
|
11
|
+
* Add :include_request option to json and json_parser plugins to include request in :serializer/:parser call (janko-m) (#26)
|
12
|
+
|
13
|
+
* Optimize template cache lookup in render plugin when :cache_key is given (jeremyevans)
|
14
|
+
|
15
|
+
* Add :engine_opts option to render plugin, for specifying per-template engine options (jeremyevans)
|
16
|
+
|
17
|
+
* The render plugin and render/view :ext option is now replaced by the :engine option (jeremyevans)
|
18
|
+
|
19
|
+
* Add path_rewriter plugin, for rewriting paths before routing (jeremyevans)
|
20
|
+
|
21
|
+
* Add :cache_key option to render/view to explicitly set the template cache key (jeremyevans)
|
22
|
+
|
23
|
+
* Don't cache templates if :template_block is given to render/view, unless :cache=>true is used (jeremyevans)
|
24
|
+
|
25
|
+
* Add :cache option to render/view to force caching or not caching the template (jeremyevans)
|
26
|
+
|
27
|
+
* Avoid rehashing hashes at runtime in plugins (jeremyevans)
|
28
|
+
|
29
|
+
* Add heartbeat plugin for heartbeat support (jeremyevans)
|
30
|
+
|
31
|
+
* Support :serializer option in json plugin (janko-m) (#21)
|
32
|
+
|
33
|
+
* Add json_parser plugin, for parsing request bodies in JSON format (jeremyevans)
|
34
|
+
|
1
35
|
= 2.2.0 (2015-04-13)
|
2
36
|
|
3
37
|
* Add :escaper render plugin option to support custom escaping of <%= %> tags when :escape is used (jeremyevans)
|
data/Rakefile
CHANGED
@@ -66,53 +66,29 @@ end
|
|
66
66
|
|
67
67
|
### Specs
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
desc "#{d} with -w, some warnings filtered"
|
88
|
-
task "#{name}_w" do
|
89
|
-
ENV['RUBYOPT'] ? (ENV['RUBYOPT'] += " -w") : (ENV['RUBYOPT'] = '-w')
|
90
|
-
rake = ENV['RAKE'] || "#{FileUtils::RUBY} -S rake"
|
91
|
-
sh "#{rake} #{name} 2>&1 | egrep -v \"(spec/.*: warning: (possibly )?useless use of == in void context|: warning: instance variable @.* not initialized|: warning: method redefined; discarding old|: warning: previous definition of)|rspec\""
|
92
|
-
end
|
93
|
-
|
94
|
-
desc d
|
95
|
-
spec_class.new(name) do |t|
|
96
|
-
t.send spec_files_meth, files
|
97
|
-
t.spec_opts = ENV["#{NAME.upcase}_SPEC_OPTS"].split if ENV["#{NAME.upcase}_SPEC_OPTS"]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
spec_with_cov = lambda do |name, files, d|
|
102
|
-
spec.call(name, files, d)
|
103
|
-
desc "#{d} with coverage"
|
104
|
-
task "#{name}_cov" do
|
105
|
-
ENV['COVERAGE'] = '1'
|
106
|
-
Rake::Task[name].invoke
|
107
|
-
end
|
108
|
-
end
|
69
|
+
spec = proc do |env|
|
70
|
+
env.each{|k,v| ENV[k] = v}
|
71
|
+
sh "#{FileUtils::RUBY} -rubygems -I lib -e 'ARGV.each{|f| require f}' ./spec/*_spec.rb ./spec/plugin/*_spec.rb"
|
72
|
+
env.each{|k,v| ENV.delete(k)}
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "Run specs"
|
76
|
+
task "spec" do
|
77
|
+
spec.call({})
|
78
|
+
end
|
79
|
+
|
80
|
+
task :default=>:spec
|
81
|
+
|
82
|
+
desc "Run specs with coverage"
|
83
|
+
task "spec_cov" do
|
84
|
+
spec.call('COVERAGE'=>'1')
|
85
|
+
end
|
109
86
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
87
|
+
desc "Run specs with -w, some warnings filtered"
|
88
|
+
task "spec_w" do
|
89
|
+
ENV['RUBYOPT'] ? (ENV['RUBYOPT'] += " -w") : (ENV['RUBYOPT'] = '-w')
|
90
|
+
rake = ENV['RAKE'] || "#{FileUtils::RUBY} -S rake"
|
91
|
+
sh %{#{rake} 2>&1 | egrep -v \": warning: instance variable @.* not initialized|: warning: method redefined; discarding old|: warning: previous definition of|: warning: statement not reached"}
|
116
92
|
end
|
117
93
|
|
118
94
|
### Other
|
@@ -0,0 +1,109 @@
|
|
1
|
+
= New Plugins
|
2
|
+
|
3
|
+
* A json_parser plugin has been added, for parsing request bodies in
|
4
|
+
JSON format. This is faster than using a middleware to perform
|
5
|
+
the same task. This plugin supports a :parser option to use a
|
6
|
+
custom JSON parser, an :include_request option to include the
|
7
|
+
request when calling the parser, and a :error_handler option for
|
8
|
+
a proc to call with the request if there is an error when parsing.
|
9
|
+
Example:
|
10
|
+
|
11
|
+
plugin :json_parser,
|
12
|
+
:parser=>JSON.method(:parse),
|
13
|
+
:include_request=>false,
|
14
|
+
:error_handler=>proc{|r| r.halt [400, {}, []]}
|
15
|
+
|
16
|
+
* A path_rewriter plugin has been added, allowing for the rewriting
|
17
|
+
of paths before routing. This allows you to rewrite just the
|
18
|
+
routing path (the default), or PATH_INFO as well as the routing
|
19
|
+
path (if the :path_info option is used). This is useful if you
|
20
|
+
want to internally treat one path exactly the same as another
|
21
|
+
path.
|
22
|
+
|
23
|
+
By default, path rewriting is done on prefixes, so any path that
|
24
|
+
starts with the prefix will be rewritten. You can pass a
|
25
|
+
Regexp when rewriting the path for more complete control.
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
|
29
|
+
plugin :path_rewriter
|
30
|
+
rewrite_path '/a', '/b'
|
31
|
+
# GET /a treated as GET /b
|
32
|
+
# GET /a/c treated as GET /b/c
|
33
|
+
|
34
|
+
rewrite_path /\A\/c\z/, '/d'
|
35
|
+
# GET /c treated as GET /d
|
36
|
+
# GET /c/e no change
|
37
|
+
|
38
|
+
* A precompiled_templates plugin has been added, for precompiling
|
39
|
+
templates before starting the application. This can save a
|
40
|
+
substantial amount of memory if you are using large templates or
|
41
|
+
a large number of small templates in conjunction when using
|
42
|
+
application preloading with a forking webserver. Example:
|
43
|
+
|
44
|
+
plugin :precompile_templates
|
45
|
+
precompile_templates "views/\*\*/*.erb"
|
46
|
+
precompile_templates "views/users/_*.erb", :locals=>[:user]
|
47
|
+
|
48
|
+
* A heartbeat plugin has been added, for easily handling
|
49
|
+
heartbeat/status requests. If a heartbeat/status request comes in,
|
50
|
+
it will get a 200 response with a body of "OK". This is designed
|
51
|
+
for automated systems that check if the application is functioning.
|
52
|
+
The default heartbeat path is /heartbeat, but you can choose a
|
53
|
+
different one using the :path option.
|
54
|
+
|
55
|
+
plugin :heartbeat, :path=>'/heartbeat'
|
56
|
+
|
57
|
+
= Other New Features
|
58
|
+
|
59
|
+
* The json plugin now supports a :serializer option to use a custom
|
60
|
+
serializer. Additionally, it now supports a :include_request
|
61
|
+
option to include the request when calling the serializer.
|
62
|
+
|
63
|
+
* In the render plugin, the render/view methods now support a
|
64
|
+
:cache=>false option to not cache the template. This can be useful
|
65
|
+
for large but rarely used templates, or where a new template object
|
66
|
+
is created for every render/view call.
|
67
|
+
|
68
|
+
* In the render plugin, the render/view methods now support a
|
69
|
+
:cache_key option to force a specific cache key. Manually setting
|
70
|
+
cache keys can result in improved performance, as automatically
|
71
|
+
determining the cache key can be a relatively expensive operation.
|
72
|
+
|
73
|
+
* The render plugin now supports a :engine_opts option, to specify
|
74
|
+
per-template engine options. :engine options should be a hash
|
75
|
+
keyed by render engine strings, with values being hashes of
|
76
|
+
template options.
|
77
|
+
|
78
|
+
* In the mailer plugin, a no_mail! method is now supported when
|
79
|
+
mailing, which will skip the current mail. This makes it easier
|
80
|
+
to delay the decision about actually sending the email till it is
|
81
|
+
time to send the email, which makes it easier to avoid race
|
82
|
+
conditions if you are using a job queue for mailing.
|
83
|
+
|
84
|
+
= Other Improvements
|
85
|
+
|
86
|
+
* Roda avoids rehashing hashes at runtime in some places, for a minor
|
87
|
+
speedup.
|
88
|
+
|
89
|
+
* If the :template_block is given to render/view, default to not
|
90
|
+
caching the template, since it is likely the template block is
|
91
|
+
specific to the request. Allow for the :cache=>true option to be
|
92
|
+
used to force the caching of the template.
|
93
|
+
|
94
|
+
* Roda now returns a 404 response for unmatched GET requests when
|
95
|
+
using the assets and json plugins where r.assets is the last
|
96
|
+
method called in a route block.
|
97
|
+
|
98
|
+
= Backwards Compatibility
|
99
|
+
|
100
|
+
* In the render plugin, the :ext option to the plugin and to the
|
101
|
+
render/view methods is now replaced by the :engine option.
|
102
|
+
Previously, :engine was used by default if :ext was not given. In
|
103
|
+
general, there is no need for two separate options, the engine is
|
104
|
+
used as the extension by Tilt.
|
105
|
+
|
106
|
+
In general, this is a backwards compatible change, except when
|
107
|
+
both :ext and :engine were specified differently as plugin options,
|
108
|
+
and an inline template is used with render or view without either
|
109
|
+
the :ext or :engine options being specified.
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -214,7 +214,7 @@ class Roda
|
|
214
214
|
# :js_opts :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
|
215
215
|
# :js_route :: Route under :prefix for javascript assets (default: :js_dir)
|
216
216
|
# :path :: Path to your asset source directory (default: 'assets'). Relative
|
217
|
-
#
|
217
|
+
# paths will be considered relative to the application's :root option.
|
218
218
|
# :prefix :: Prefix for assets path in your URL/routes (default: 'assets')
|
219
219
|
# :precompiled :: Path to the compiled asset metadata file. If the file exists, will use compiled
|
220
220
|
# mode using the metadata in the file. If the file does not exist, will use
|
@@ -611,6 +611,7 @@ class Roda
|
|
611
611
|
scope.render_asset(file, type)
|
612
612
|
end
|
613
613
|
end
|
614
|
+
nil
|
614
615
|
end
|
615
616
|
end
|
616
617
|
end
|
data/lib/roda/plugins/caching.rb
CHANGED
@@ -197,7 +197,7 @@ class Roda
|
|
197
197
|
# cached for. Also sets the Expires header, useful if you have
|
198
198
|
# HTTP 1.0 clients (Cache-Control is an HTTP 1.1 header).
|
199
199
|
def expires(max_age, opts=OPTS)
|
200
|
-
cache_control(opts.merge(:max_age=>max_age))
|
200
|
+
cache_control(Hash[opts].merge!(:max_age=>max_age))
|
201
201
|
self[EXPIRES] = (Time.now + max_age).httpdate
|
202
202
|
end
|
203
203
|
|
data/lib/roda/plugins/chunked.rb
CHANGED
@@ -92,7 +92,7 @@ END
|
|
92
92
|
def error_email(e)
|
93
93
|
email_opts = self.class.opts[:error_email].dup
|
94
94
|
headers = email_opts[:default_headers].call(email_opts, e)
|
95
|
-
headers = headers.merge(email_opts[:headers])
|
95
|
+
headers = Hash[headers].merge!(email_opts[:headers])
|
96
96
|
headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
|
97
97
|
body = email_opts[:body].call(self, e)
|
98
98
|
email_opts[:message] = "#{headers}\r\n\r\n#{body}"
|
data/lib/roda/plugins/head.rb
CHANGED
@@ -22,6 +22,12 @@ class Roda
|
|
22
22
|
#
|
23
23
|
# HEAD requests for +/+, +/a+, and +/b+ will all return 200 status
|
24
24
|
# with an empty body.
|
25
|
+
#
|
26
|
+
# NOTE: if you have a public facing website it is recommended that
|
27
|
+
# you enable this plugin. Search engines and other bots may send a
|
28
|
+
# HEAD request prior to crawling a page with a GET request. Without
|
29
|
+
# this plugin those HEAD requests will return a 404 status, which
|
30
|
+
# may prevent search engine's from crawling your website.
|
25
31
|
module Head
|
26
32
|
EMPTY_ARRAY = [].freeze
|
27
33
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The heartbeat handles heartbeat/status requests. If a request for
|
4
|
+
# the heartbeat path comes in, a 200 response with a
|
5
|
+
# text/plain Content-Type and a body of "OK" will be returned.
|
6
|
+
# The default heartbeat path is "/heartbeat", so to use that:
|
7
|
+
#
|
8
|
+
# plugin :heartbeat
|
9
|
+
#
|
10
|
+
# You can also specify a custom heartbeat path:
|
11
|
+
#
|
12
|
+
# plugin :heartbeat, :path=>'/status'
|
13
|
+
module Heartbeat
|
14
|
+
OPTS = {}.freeze
|
15
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
16
|
+
HEARTBEAT_RESPONSE = [200, {'Content-Type'=>'text/plain'}.freeze, ['OK'.freeze].freeze].freeze
|
17
|
+
|
18
|
+
# Set the heartbeat path to the given path.
|
19
|
+
def self.configure(app, opts=OPTS)
|
20
|
+
app.opts[:heartbeat_path] = (opts[:path] || app.opts[:heartbeat_path] || "/heartbeat").dup.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
# If the request is for a heartbeat path, return the heartbeat response.
|
25
|
+
def call
|
26
|
+
if env[PATH_INFO] == opts[:heartbeat_path]
|
27
|
+
response = HEARTBEAT_RESPONSE.dup
|
28
|
+
response[1] = Hash[response[1]]
|
29
|
+
response
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
register_plugin(:heartbeat, Heartbeat)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
data/lib/roda/plugins/json.rb
CHANGED
@@ -32,16 +32,34 @@ class Roda
|
|
32
32
|
# using the :classes option when loading the plugin:
|
33
33
|
#
|
34
34
|
# plugin :json, :classes=>[Array, Hash, Sequel::Model]
|
35
|
+
#
|
36
|
+
# By default objects are serialized with +to_json+, but you
|
37
|
+
# can pass in a custom serializer, which can be any object
|
38
|
+
# that responds to +call(object)+.
|
39
|
+
#
|
40
|
+
# plugin :json, :serializer=>proc{|o| o.to_json(root: true)}
|
41
|
+
#
|
42
|
+
# If you need the request information during serialization, such
|
43
|
+
# as HTTP headers or query parameters, you can pass in the
|
44
|
+
# +:include_request+ option, which will pass in the request
|
45
|
+
# object as the second argument when calling the serializer.
|
46
|
+
#
|
47
|
+
# plugin :json, include_request=>true, serializer=>proc{|o, request| ...}
|
35
48
|
module Json
|
36
49
|
OPTS = {}.freeze
|
50
|
+
DEFAULT_SERIALIZER = lambda{|o| o.to_json}
|
37
51
|
|
38
|
-
# Set the classes to automatically convert to JSON
|
52
|
+
# Set the classes to automatically convert to JSON, and the serializer to use.
|
39
53
|
def self.configure(app, opts=OPTS)
|
40
54
|
classes = opts[:classes] || [Array, Hash]
|
41
55
|
app.opts[:json_result_classes] ||= []
|
42
56
|
app.opts[:json_result_classes] += classes
|
43
57
|
app.opts[:json_result_classes].uniq!
|
44
58
|
app.opts[:json_result_classes].freeze
|
59
|
+
|
60
|
+
app.opts[:json_result_serializer] = opts[:serializer] || app.opts[:json_result_serializer] || DEFAULT_SERIALIZER
|
61
|
+
|
62
|
+
app.opts[:json_result_include_request] = opts[:include_request] || app.opts[:json_result_include_request]
|
45
63
|
end
|
46
64
|
|
47
65
|
module ClassMethods
|
@@ -71,9 +89,11 @@ class Roda
|
|
71
89
|
end
|
72
90
|
|
73
91
|
# Convert the given object to JSON. Uses to_json by default,
|
74
|
-
# but can
|
92
|
+
# but can use a custom serializer passed to the plugin.
|
75
93
|
def convert_to_json(obj)
|
76
|
-
obj
|
94
|
+
args = [obj]
|
95
|
+
args << self if roda_class.opts[:json_result_include_request]
|
96
|
+
roda_class.opts[:json_result_serializer].call(*args)
|
77
97
|
end
|
78
98
|
end
|
79
99
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Roda
|
4
|
+
module RodaPlugins
|
5
|
+
# The json_parser plugin parses request bodies in json format
|
6
|
+
# if the request's content type specifies json. This is mostly
|
7
|
+
# designed for use with JSON API sites.
|
8
|
+
#
|
9
|
+
# This only parses the request body as JSON if the Content-Type
|
10
|
+
# header for the request includes "json".
|
11
|
+
module JsonParser
|
12
|
+
OPTS = {}.freeze
|
13
|
+
JSON_PARAMS_KEY = "roda.json_params".freeze
|
14
|
+
INPUT_KEY = "rack.input".freeze
|
15
|
+
FORM_HASH_KEY = "rack.request.form_hash".freeze
|
16
|
+
FORM_INPUT_KEY = "rack.request.form_input".freeze
|
17
|
+
DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]}
|
18
|
+
DEFAULT_PARSER = JSON.method(:parse)
|
19
|
+
|
20
|
+
# Handle options for the json_parser plugin:
|
21
|
+
# :error_handler :: A proc to call if an exception is raised when
|
22
|
+
# parsing a JSON request body. The proc is called
|
23
|
+
# with the request object, and should probably call
|
24
|
+
# halt on the request or raise an exception.
|
25
|
+
# :parser :: The parser to use for parsing incoming json. Should be
|
26
|
+
# an object that responds to +call(str)+ and returns the
|
27
|
+
# parsed data. The default is to call JSON.parse.
|
28
|
+
# :include_request :: If true, the parser will be called with the request
|
29
|
+
# object as the second argument, so the parser needs
|
30
|
+
# to respond to +call(str, request)+.
|
31
|
+
def self.configure(app, opts=OPTS)
|
32
|
+
app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER
|
33
|
+
app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || DEFAULT_PARSER
|
34
|
+
app.opts[:json_parser_include_request] = opts[:include_request] || app.opts[:json_parser_include_request]
|
35
|
+
end
|
36
|
+
|
37
|
+
module RequestMethods
|
38
|
+
# If the Content-Type header in the request includes "json",
|
39
|
+
# parse the request body as JSON. Ignore an empty request body.
|
40
|
+
def POST
|
41
|
+
env = @env
|
42
|
+
if post_params = (env[JSON_PARAMS_KEY] || env[FORM_HASH_KEY])
|
43
|
+
post_params
|
44
|
+
elsif (input = env[INPUT_KEY]) && content_type =~ /json/
|
45
|
+
str = input.read
|
46
|
+
input.rewind
|
47
|
+
return super if str.empty?
|
48
|
+
begin
|
49
|
+
json_params = env[JSON_PARAMS_KEY] = parse_json(str)
|
50
|
+
rescue
|
51
|
+
roda_class.opts[:json_parser_error_handler].call(self)
|
52
|
+
end
|
53
|
+
env[FORM_INPUT_KEY] = input
|
54
|
+
json_params
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def parse_json(str)
|
63
|
+
args = [str]
|
64
|
+
args << self if roda_class.opts[:json_parser_include_request]
|
65
|
+
roda_class.opts[:json_parser_parser].call(*args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
register_plugin(:json_parser, JsonParser)
|
71
|
+
end
|
72
|
+
end
|