roda 2.2.0 → 2.3.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 +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
|