ultra-lite-hub 0.0.1
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 +7 -0
- data/roda-3.105.0/MIT-LICENSE +21 -0
- data/roda-3.105.0/lib/roda/cache.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
- data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
- data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
- data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
- data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
- data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
- data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
- data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
- data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
- data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
- data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
- data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
- data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
- data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
- data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
- data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
- data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
- data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
- data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
- data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
- data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
- data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
- data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
- data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
- data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
- data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
- data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
- data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
- data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
- data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
- data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
- data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
- data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
- data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
- data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
- data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
- data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
- data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
- data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
- data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
- data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
- data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
- data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
- data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
- data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
- data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
- data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
- data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
- data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
- data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
- data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
- data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
- data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
- data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
- data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
- data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
- data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
- data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins.rb +68 -0
- data/roda-3.105.0/lib/roda/request.rb +722 -0
- data/roda-3.105.0/lib/roda/response.rb +221 -0
- data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
- data/roda-3.105.0/lib/roda/version.rb +18 -0
- data/roda-3.105.0/lib/roda.rb +586 -0
- data/ultra-lite-hub.gemspec +12 -0
- metadata +201 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
class Roda
|
|
7
|
+
module RodaPlugins
|
|
8
|
+
# The typecast_params plugin allows for type conversion of submitted parameters.
|
|
9
|
+
# Submitted parameters should be considered untrusted input, and in standard use
|
|
10
|
+
# with browsers, parameters are submitted as strings (or a hash/array containing
|
|
11
|
+
# strings). In most cases it makes sense to explicitly convert the parameter to the
|
|
12
|
+
# desired type. While this can be done via manual conversion:
|
|
13
|
+
#
|
|
14
|
+
# val = request.params['key'].to_i
|
|
15
|
+
# val = nil unless val > 0
|
|
16
|
+
#
|
|
17
|
+
# the typecast_params plugin adds a friendlier interface:
|
|
18
|
+
#
|
|
19
|
+
# val = typecast_params.pos_int('key')
|
|
20
|
+
#
|
|
21
|
+
# As +typecast_params+ is a fairly long method name, and may be a method you call
|
|
22
|
+
# frequently, you may want to consider aliasing it to something more terse in your
|
|
23
|
+
# application, such as +tp+.
|
|
24
|
+
#
|
|
25
|
+
# typecast_params offers support for default values:
|
|
26
|
+
#
|
|
27
|
+
# val = typecast_params.pos_int('key', 1)
|
|
28
|
+
#
|
|
29
|
+
# The default value is only used if no value has been submitted for the parameter,
|
|
30
|
+
# or if the conversion of the value results in +nil+. Handling defaults for parameter
|
|
31
|
+
# conversion manually is more difficult, since the parameter may not be present at all,
|
|
32
|
+
# or it may be present but an empty string because the user did not enter a value on
|
|
33
|
+
# the related form. Use of typecast_params for the conversion handles both cases.
|
|
34
|
+
#
|
|
35
|
+
# In many cases, parameters should be required, and if they aren't submitted, that
|
|
36
|
+
# should be considered an error. typecast_params handles this with ! methods:
|
|
37
|
+
#
|
|
38
|
+
# val = typecast_params.pos_int!('key')
|
|
39
|
+
#
|
|
40
|
+
# These ! methods raise an error instead of returning +nil+, and do not allow defaults.
|
|
41
|
+
#
|
|
42
|
+
# The errors raised by this plugin use a specific exception class,
|
|
43
|
+
# +Roda::RodaPlugins::TypecastParams::Error+. This allows you to handle
|
|
44
|
+
# this specific exception class globally and return an appropriate 4xx
|
|
45
|
+
# response to the client. You can use the Error#param_name and Error#reason
|
|
46
|
+
# methods to get more information about the error.
|
|
47
|
+
#
|
|
48
|
+
# To make it easy to handle cases where many parameters need the same conversion
|
|
49
|
+
# done, you can pass an array of keys to a conversion method, and it will return an array
|
|
50
|
+
# of converted values:
|
|
51
|
+
#
|
|
52
|
+
# val1, val2 = typecast_params.pos_int(['key1', 'key2'])
|
|
53
|
+
#
|
|
54
|
+
# This is equivalent to:
|
|
55
|
+
#
|
|
56
|
+
# val1 = typecast_params.pos_int('key1')
|
|
57
|
+
# val2 = typecast_params.pos_int('key2')
|
|
58
|
+
#
|
|
59
|
+
# The ! methods also support arrays, ensuring that all parameters have a value:
|
|
60
|
+
#
|
|
61
|
+
# val1, val2 = typecast_params.pos_int!(['key1', 'key2'])
|
|
62
|
+
#
|
|
63
|
+
# For handling of array parameters, where all entries in the array use the
|
|
64
|
+
# same conversion, there is an +array+ method which takes the type as the first argument
|
|
65
|
+
# and the keys to convert as the second argument:
|
|
66
|
+
#
|
|
67
|
+
# vals = typecast_params.array(:pos_int, 'keys')
|
|
68
|
+
#
|
|
69
|
+
# If you want to ensure that all entries in the array are converted successfully and that
|
|
70
|
+
# there is a value for the array itself, you can use +array!+:
|
|
71
|
+
#
|
|
72
|
+
# vals = typecast_params.array!(:pos_int, 'keys')
|
|
73
|
+
#
|
|
74
|
+
# This will raise an exception if any of the values in the array for parameter +keys+ cannot
|
|
75
|
+
# be converted to integer.
|
|
76
|
+
#
|
|
77
|
+
# Both +array+ and +array!+ support default values which are used if no value is present
|
|
78
|
+
# for the parameter:
|
|
79
|
+
#
|
|
80
|
+
# vals1 = typecast_params.array(:pos_int, 'keys1', [])
|
|
81
|
+
# vals2 = typecast_params.array!(:pos_int, 'keys2', [])
|
|
82
|
+
#
|
|
83
|
+
# You can also pass an array of keys to +array+ or +array!+, if you would like to perform
|
|
84
|
+
# the same conversion on multiple arrays:
|
|
85
|
+
#
|
|
86
|
+
# foo_ids, bar_ids = typecast_params.array!(:pos_int, ['foo_ids', 'bar_ids'])
|
|
87
|
+
#
|
|
88
|
+
# The previous examples have shown use of the +pos_int+ method, which uses +to_i+ to convert the
|
|
89
|
+
# value to an integer, but returns +nil+ if the resulting integer is not positive. Unless you need
|
|
90
|
+
# to handle negative numbers, it is recommended to use +pos_int+ instead of +int+ as +int+ will
|
|
91
|
+
# convert invalid values to 0 (since that is how <tt>String#to_i</tt> works).
|
|
92
|
+
#
|
|
93
|
+
# There are many built in methods for type conversion:
|
|
94
|
+
#
|
|
95
|
+
# any :: Returns the value as is without conversion
|
|
96
|
+
# str :: Raises if value is not already a string
|
|
97
|
+
# nonempty_str :: Raises if value is not already a string, and converts
|
|
98
|
+
# the empty string or string containing only whitespace to +nil+
|
|
99
|
+
# bool :: Converts entry to boolean if in one of the recognized formats:
|
|
100
|
+
# nil :: nil, ''
|
|
101
|
+
# true :: true, 1, '1', 't', 'true', 'yes', 'y', 'on' # case insensitive
|
|
102
|
+
# false :: false, 0, '0', 'f', 'false', 'no', 'n', 'off' # case insensitive
|
|
103
|
+
# If not in one of those formats, raises an error.
|
|
104
|
+
# int :: Converts value to integer using +to_i+ (note that invalid input strings will be
|
|
105
|
+
# returned as 0)
|
|
106
|
+
# pos_int :: Converts value using +to_i+, but non-positive values are converted to +nil+
|
|
107
|
+
# Integer :: Converts value to integer using <tt>Kernel::Integer</tt>, with base 10 for
|
|
108
|
+
# string inputs, and a check that the output value is equal to the input
|
|
109
|
+
# value for numeric inputs.
|
|
110
|
+
# float :: Converts value to float using +to_f+ (note that invalid input strings will be
|
|
111
|
+
# returned as 0.0)
|
|
112
|
+
# Float :: Converts value to float using <tt>Kernel::Float(value)</tt>
|
|
113
|
+
# Hash :: Raises if value is not already a hash
|
|
114
|
+
# date :: Converts value to Date using <tt>Date.parse(value)</tt>
|
|
115
|
+
# time :: Converts value to Time using <tt>Time.parse(value)</tt>
|
|
116
|
+
# datetime :: Converts value to DateTime using <tt>DateTime.parse(value)</tt>
|
|
117
|
+
# file :: Raises if value is not already a hash with a :tempfile key whose value
|
|
118
|
+
# responds to +read+ (this is the format rack uses for uploaded files).
|
|
119
|
+
#
|
|
120
|
+
# All of these methods also support ! methods (e.g. +pos_int!+), and all of them can be
|
|
121
|
+
# used in the +array+ and +array!+ methods to support arrays of values.
|
|
122
|
+
#
|
|
123
|
+
# Since parameter hashes can be nested, the <tt>[]</tt> method can be used to access nested
|
|
124
|
+
# hashes:
|
|
125
|
+
#
|
|
126
|
+
# # params: {'key'=>{'sub_key'=>'1'}}
|
|
127
|
+
# typecast_params['key'].pos_int!('sub_key') # => 1
|
|
128
|
+
#
|
|
129
|
+
# This works to an arbitrary depth:
|
|
130
|
+
#
|
|
131
|
+
# # params: {'key'=>{'sub_key'=>{'sub_sub_key'=>'1'}}}
|
|
132
|
+
# typecast_params['key']['sub_key'].pos_int!('sub_sub_key') # => 1
|
|
133
|
+
#
|
|
134
|
+
# And also works with arrays at any depth, if those arrays contain hashes:
|
|
135
|
+
#
|
|
136
|
+
# # params: {'key'=>[{'sub_key'=>{'sub_sub_key'=>'1'}}]}
|
|
137
|
+
# typecast_params['key'][0]['sub_key'].pos_int!('sub_sub_key') # => 1
|
|
138
|
+
#
|
|
139
|
+
# # params: {'key'=>[{'sub_key'=>['1']}]}
|
|
140
|
+
# typecast_params['key'][0].array!(:pos_int, 'sub_key') # => [1]
|
|
141
|
+
#
|
|
142
|
+
# To allow easier access to nested data, there is a +dig+ method:
|
|
143
|
+
#
|
|
144
|
+
# typecast_params.dig(:pos_int, 'key', 'sub_key')
|
|
145
|
+
# typecast_params.dig(:pos_int, 'key', 0, 'sub_key', 'sub_sub_key')
|
|
146
|
+
#
|
|
147
|
+
# +dig+ will return +nil+ if any access while looking up the nested value returns +nil+.
|
|
148
|
+
# There is also a +dig!+ method, which will raise an Error if +dig+ would return +nil+:
|
|
149
|
+
#
|
|
150
|
+
# typecast_params.dig!(:pos_int, 'key', 'sub_key')
|
|
151
|
+
# typecast_params.dig!(:pos_int, 'key', 0, 'sub_key', 'sub_sub_key')
|
|
152
|
+
#
|
|
153
|
+
# Note that none of these conversion methods modify +request.params+. They purely do the
|
|
154
|
+
# conversion and return the converted value. However, in some cases it is useful to do all
|
|
155
|
+
# the conversion up front, and then pass a hash of converted parameters to an internal
|
|
156
|
+
# method that expects to receive values in specific types. The +convert!+ method does
|
|
157
|
+
# this, and there is also a +convert_each!+ method
|
|
158
|
+
# designed for converting multiple values using the same block:
|
|
159
|
+
#
|
|
160
|
+
# converted_params = typecast_params.convert! do |tp|
|
|
161
|
+
# tp.int('page')
|
|
162
|
+
# tp.pos_int!('artist_id')
|
|
163
|
+
# tp.array!(:pos_int, 'album_ids')
|
|
164
|
+
# tp.convert!('sales') do |stp|
|
|
165
|
+
# stp.pos_int!(['num_sold', 'num_shipped'])
|
|
166
|
+
# end
|
|
167
|
+
# tp.convert!('members') do |mtp|
|
|
168
|
+
# mtp.convert_each! do |stp|
|
|
169
|
+
# stp.str!(['first_name', 'last_name'])
|
|
170
|
+
# end
|
|
171
|
+
# end
|
|
172
|
+
# end
|
|
173
|
+
#
|
|
174
|
+
# # converted_params:
|
|
175
|
+
# # {
|
|
176
|
+
# # 'page' => 1,
|
|
177
|
+
# # 'artist_id' => 2,
|
|
178
|
+
# # 'album_ids' => [3, 4],
|
|
179
|
+
# # 'sales' => {
|
|
180
|
+
# # 'num_sold' => 5,
|
|
181
|
+
# # 'num_shipped' => 6
|
|
182
|
+
# # },
|
|
183
|
+
# # 'members' => [
|
|
184
|
+
# # {'first_name' => 'Foo', 'last_name' => 'Bar'},
|
|
185
|
+
# # {'first_name' => 'Baz', 'last_name' => 'Quux'}
|
|
186
|
+
# # ]
|
|
187
|
+
# # }
|
|
188
|
+
#
|
|
189
|
+
# +convert!+ and +convert_each!+ only return values you explicitly specify for conversion
|
|
190
|
+
# inside the passed block.
|
|
191
|
+
#
|
|
192
|
+
# You can specify the +:symbolize+ option to +convert!+ or +convert_each!+, which will
|
|
193
|
+
# symbolize the resulting hash keys:
|
|
194
|
+
#
|
|
195
|
+
# converted_params = typecast_params.convert!(symbolize: true) do |tp|
|
|
196
|
+
# tp.int('page')
|
|
197
|
+
# tp.pos_int!('artist_id')
|
|
198
|
+
# tp.array!(:pos_int, 'album_ids')
|
|
199
|
+
# tp.convert!('sales') do |stp|
|
|
200
|
+
# stp.pos_int!(['num_sold', 'num_shipped'])
|
|
201
|
+
# end
|
|
202
|
+
# tp.convert!('members') do |mtp|
|
|
203
|
+
# mtp.convert_each! do |stp|
|
|
204
|
+
# stp.str!(['first_name', 'last_name'])
|
|
205
|
+
# end
|
|
206
|
+
# end
|
|
207
|
+
# end
|
|
208
|
+
#
|
|
209
|
+
# # converted_params:
|
|
210
|
+
# # {
|
|
211
|
+
# # :page => 1,
|
|
212
|
+
# # :artist_id => 2,
|
|
213
|
+
# # :album_ids => [3, 4],
|
|
214
|
+
# # :sales => {
|
|
215
|
+
# # :num_sold => 5,
|
|
216
|
+
# # :num_shipped => 6
|
|
217
|
+
# # },
|
|
218
|
+
# # :members => [
|
|
219
|
+
# # {:first_name => 'Foo', :last_name => 'Bar'},
|
|
220
|
+
# # {:first_name => 'Baz', :last_name => 'Quux'}
|
|
221
|
+
# # ]
|
|
222
|
+
# # }
|
|
223
|
+
#
|
|
224
|
+
# Using the +:symbolize+ option makes it simpler to transition from untrusted external
|
|
225
|
+
# data (string keys), to semitrusted data that can be used internally (trusted in the sense that
|
|
226
|
+
# the expected types are used, not that you trust the values).
|
|
227
|
+
#
|
|
228
|
+
# Note that if there are multiple conversion errors raised inside a +convert!+ or +convert_each!+
|
|
229
|
+
# block, they are recorded and a single TypecastParams::Error instance is raised after
|
|
230
|
+
# processing the block. TypecastParams::Error#param_names can be called on the exception to
|
|
231
|
+
# get an array of all parameter names with conversion issues, and TypecastParams::Error#all_errors
|
|
232
|
+
# can be used to get an array of all Error instances.
|
|
233
|
+
#
|
|
234
|
+
# Because of how +convert!+ and +convert_each!+ work, you should avoid calling
|
|
235
|
+
# TypecastParams::Params#[] inside the block you pass to these methods, because if the #[]
|
|
236
|
+
# call fails, it will skip the reminder of the block.
|
|
237
|
+
#
|
|
238
|
+
# Be aware that when you use +convert!+ and +convert_each!+, the conversion methods called
|
|
239
|
+
# inside the block may return nil if there is a error raised, and nested calls to
|
|
240
|
+
# +convert!+ and +convert_each!+ may not return values.
|
|
241
|
+
#
|
|
242
|
+
# When loading the typecast_params plugin, a subclass of +TypecastParams::Params+ is created
|
|
243
|
+
# specific to the Roda application. You can add support for custom types by passing a block
|
|
244
|
+
# when loading the typecast_params plugin. This block is executed in the context of the
|
|
245
|
+
# subclass, and calling +handle_type+ in the block can be used to add conversion methods.
|
|
246
|
+
# +handle_type+ accepts a type name, an options hash, and the block used to convert the type.
|
|
247
|
+
# Supported options are:
|
|
248
|
+
# +:invalid_value_message+ :: The message to use for type conversions that result in a nil value
|
|
249
|
+
# (a space and the parameter name is appended to this).
|
|
250
|
+
# +:max_input_bytesize+ :: The maximum bytesize of string input.
|
|
251
|
+
#
|
|
252
|
+
# You can override the invalid value message of an existing type using the
|
|
253
|
+
# +invalid_value_message+ method. You can also override the max input bytesize of an existing
|
|
254
|
+
# type using the +max_input_bytesize+ method.
|
|
255
|
+
#
|
|
256
|
+
# plugin :typecast_params do
|
|
257
|
+
# handle_type(:album, max_input_bytesize: 100,
|
|
258
|
+
# invalid_value_message: "invalid album id in parameter") do |value|
|
|
259
|
+
# if id = convert_pos_int(val)
|
|
260
|
+
# Album[id]
|
|
261
|
+
# end
|
|
262
|
+
# end
|
|
263
|
+
# max_input_bytesize(:date, 256)
|
|
264
|
+
# invalid_value_message(:pos_int, "value must be greater than 0 for parameter")
|
|
265
|
+
# end
|
|
266
|
+
#
|
|
267
|
+
# By default, the typecast_params conversion procs are passed the parameter value directly
|
|
268
|
+
# from +request.params+ without modification. In some cases, it may be beneficial to
|
|
269
|
+
# strip leading and trailing whitespace from parameter string values before processing, which
|
|
270
|
+
# you can do by passing the <tt>strip: :all</tt> option when loading the plugin.
|
|
271
|
+
#
|
|
272
|
+
# By default, the typecasting methods for some types check whether the bytesize of input
|
|
273
|
+
# strings is over the maximum expected values, and raise an error in such cases. The input
|
|
274
|
+
# bytesize is checked prior to any type conversion. If you would like to skip this check
|
|
275
|
+
# and allow any bytesize when doing type conversion for param string values, you can do so by
|
|
276
|
+
# passing the # <tt>:skip_bytesize_checking</tt> option when loading the plugin. By default,
|
|
277
|
+
# there is an 100 byte limit on integer input, an 1000 byte input on float input, and a 128
|
|
278
|
+
# byte limit on date/time input.
|
|
279
|
+
#
|
|
280
|
+
# By default, the typecasting methods check whether input strings have null bytes, and raise
|
|
281
|
+
# an error in such cases. This check for null bytes occurs prior to any type conversion.
|
|
282
|
+
# If you would like to skip this check and allow null bytes in param string values,
|
|
283
|
+
# you can do so by passing the <tt>:allow_null_bytes</tt> option when loading the plugin.
|
|
284
|
+
#
|
|
285
|
+
# You can use the :date_parse_input_handler option to specify custom handling of date
|
|
286
|
+
# parsing input. Modern versions of Ruby and the date gem internally raise if the input to
|
|
287
|
+
# date parsing methods is too large to prevent denial of service. If you are using an
|
|
288
|
+
# older version of Ruby, you can use this option to enforce the same check:
|
|
289
|
+
#
|
|
290
|
+
# plugin :typecast_params, date_parse_input_handler: proc {|string|
|
|
291
|
+
# raise ArgumentError, "too big" if string.bytesize > 128
|
|
292
|
+
# string
|
|
293
|
+
# }
|
|
294
|
+
#
|
|
295
|
+
# You can also use this option to modify the input, such as truncating it to the first
|
|
296
|
+
# 128 bytes:
|
|
297
|
+
#
|
|
298
|
+
# plugin :typecast_params, date_parse_input_handler: proc {|string|
|
|
299
|
+
# string.b[0, 128]
|
|
300
|
+
# }
|
|
301
|
+
#
|
|
302
|
+
# The +date_parse_input_handler+ is only called if the value is under the max input
|
|
303
|
+
# bytesize, so you may need to call +max_input_bytesize+ for the +:date+, +:time+, and
|
|
304
|
+
# +:datetime+ methods to override the max input bytesize if you want to use this option
|
|
305
|
+
# for input strings over 128 bytes.
|
|
306
|
+
#
|
|
307
|
+
# By design, typecast_params only deals with string keys, it is not possible to use
|
|
308
|
+
# symbol keys as arguments to the conversion methods and have them converted.
|
|
309
|
+
module TypecastParams
|
|
310
|
+
# Sentinal value for whether to raise exception during #process
|
|
311
|
+
CHECK_NIL = Object.new.freeze
|
|
312
|
+
|
|
313
|
+
# Exception class for errors that are caused by misuse of the API by the programmer.
|
|
314
|
+
# These are different from +Error+ which are raised because the submitted parameters
|
|
315
|
+
# do not match what is expected. Should probably be treated as a 5xx error.
|
|
316
|
+
class ProgrammerError < RodaError; end
|
|
317
|
+
|
|
318
|
+
# Exception class for errors that are due to the submitted parameters not matching
|
|
319
|
+
# what is expected. Should probably be treated as a 4xx error.
|
|
320
|
+
class Error < RodaError
|
|
321
|
+
# Set the keys in the given exception. If the exception is not already an
|
|
322
|
+
# instance of the class, create a new instance to wrap it.
|
|
323
|
+
def self.create(keys, reason, e)
|
|
324
|
+
if e.is_a?(self)
|
|
325
|
+
e.keys ||= keys
|
|
326
|
+
e.reason ||= reason
|
|
327
|
+
e
|
|
328
|
+
else
|
|
329
|
+
backtrace = e.backtrace
|
|
330
|
+
e = new("#{e.class}: #{e.message}")
|
|
331
|
+
e.keys = keys
|
|
332
|
+
e.reason = reason
|
|
333
|
+
e.set_backtrace(backtrace) if backtrace
|
|
334
|
+
e
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# The keys used to access the parameter that caused the error. This is an array
|
|
339
|
+
# that can be splatted to +dig+ to get the value of the parameter causing the error.
|
|
340
|
+
attr_accessor :keys
|
|
341
|
+
|
|
342
|
+
# An array of all other errors that were raised with this error. If the error
|
|
343
|
+
# was not raised inside Params#convert! or Params#convert_each!, this will just be
|
|
344
|
+
# an array containing the current receiver.
|
|
345
|
+
#
|
|
346
|
+
# This allows you to use Params#convert! to process a form input, and if any
|
|
347
|
+
# conversion errors occur inside the block, it can provide an array of all parameter
|
|
348
|
+
# names and reasons for parameters with problems.
|
|
349
|
+
attr_writer :all_errors
|
|
350
|
+
|
|
351
|
+
def all_errors
|
|
352
|
+
@all_errors ||= [self]
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# The reason behind this error. If this error was caused by a conversion method,
|
|
356
|
+
# this will be the conversion method symbol. If this error was caused
|
|
357
|
+
# because a value was missing, then it will be +:missing+. If this error was
|
|
358
|
+
# caused because a value was not the correct type, then it will be +:invalid_type+.
|
|
359
|
+
attr_accessor :reason
|
|
360
|
+
|
|
361
|
+
# The likely parameter name where the contents were not expected. This is
|
|
362
|
+
# designed for cases where the parameter was submitted with the typical
|
|
363
|
+
# application/x-www-form-urlencoded or multipart/form-data content types,
|
|
364
|
+
# and assumes the typical rack parsing of these content types into
|
|
365
|
+
# parameters. # If the parameters were submitted via JSON, #keys should be
|
|
366
|
+
# used directly.
|
|
367
|
+
#
|
|
368
|
+
# Example:
|
|
369
|
+
#
|
|
370
|
+
# # keys: ['page']
|
|
371
|
+
# param_name => 'page'
|
|
372
|
+
#
|
|
373
|
+
# # keys: ['artist', 'name']
|
|
374
|
+
# param_name => 'artist[name]'
|
|
375
|
+
#
|
|
376
|
+
# # keys: ['album', 'artist', 'name']
|
|
377
|
+
# param_name => 'album[artist][name]'
|
|
378
|
+
def param_name
|
|
379
|
+
if keys.length > 1
|
|
380
|
+
first, *rest = keys
|
|
381
|
+
v = first.dup
|
|
382
|
+
rest.each do |param|
|
|
383
|
+
v << "["
|
|
384
|
+
v << param unless param.is_a?(Integer)
|
|
385
|
+
v << "]"
|
|
386
|
+
end
|
|
387
|
+
v
|
|
388
|
+
else
|
|
389
|
+
keys.first
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# An array of all parameter names for parameters where the context were not
|
|
394
|
+
# expected. If Params#convert! was not used, this will be an array containing
|
|
395
|
+
# #param_name. If Params#convert! was used and multiple exceptions were
|
|
396
|
+
# captured inside the convert! block, this will contain the parameter names
|
|
397
|
+
# related to all captured exceptions.
|
|
398
|
+
def param_names
|
|
399
|
+
all_errors.map(&:param_name)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
module AllowNullByte
|
|
404
|
+
private
|
|
405
|
+
|
|
406
|
+
# Allow ASCII NUL bytes ("\0") in parameter string values.
|
|
407
|
+
def check_null_byte(v)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
module StringStripper
|
|
412
|
+
private
|
|
413
|
+
|
|
414
|
+
# Strip any resulting input string.
|
|
415
|
+
def param_value(key)
|
|
416
|
+
v = super
|
|
417
|
+
|
|
418
|
+
if v.is_a?(String)
|
|
419
|
+
v = v.strip
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
v
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
module DateParseInputHandler
|
|
427
|
+
# Pass input string to date parsing through handle_date_parse_input.
|
|
428
|
+
def _string_parse!(klass, v)
|
|
429
|
+
v = handle_date_parse_input(v)
|
|
430
|
+
super
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
module SkipBytesizeChecking
|
|
435
|
+
private
|
|
436
|
+
|
|
437
|
+
# Do not check max input bytesize
|
|
438
|
+
def check_allowed_bytesize(v, max)
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Class handling conversion of submitted parameters to desired types.
|
|
443
|
+
class Params
|
|
444
|
+
# Handle conversions for the given type using the given block.
|
|
445
|
+
# For a type named +foo+, this will create the following methods:
|
|
446
|
+
#
|
|
447
|
+
# * foo(key, default=nil)
|
|
448
|
+
# * foo!(key)
|
|
449
|
+
# * convert_foo(value) # private
|
|
450
|
+
# * _convert_array_foo(value) # private
|
|
451
|
+
# * _invalid_value_message_for_foo # private
|
|
452
|
+
# * _max_input_bytesize_for_foo # private
|
|
453
|
+
#
|
|
454
|
+
# This method is used to define all type conversions, even the built
|
|
455
|
+
# in ones. It can be called in subclasses to setup subclass-specific
|
|
456
|
+
# types.
|
|
457
|
+
def self.handle_type(type, opts=OPTS, &block)
|
|
458
|
+
convert_meth = :"convert_#{type}"
|
|
459
|
+
define_method(convert_meth, &block)
|
|
460
|
+
|
|
461
|
+
convert_array_meth = :"_convert_array_#{type}"
|
|
462
|
+
define_method(convert_array_meth) do |v|
|
|
463
|
+
raise Error, "expected array but received #{v.inspect}" unless v.is_a?(Array)
|
|
464
|
+
v.map! do |val|
|
|
465
|
+
check_allowed_bytesize(val, _max_input_bytesize_for(type))
|
|
466
|
+
check_null_byte(val)
|
|
467
|
+
send(convert_meth, val)
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
private convert_meth, convert_array_meth
|
|
472
|
+
|
|
473
|
+
invalid_value_message(type, opts[:invalid_value_message])
|
|
474
|
+
max_input_bytesize(type, opts[:max_input_bytesize])
|
|
475
|
+
|
|
476
|
+
define_method(type) do |key, default=nil|
|
|
477
|
+
process_arg(convert_meth, key, default, type) if require_hash!
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
define_method(:"#{type}!") do |key|
|
|
481
|
+
send(type, key, CHECK_NIL)
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Set the invalid message for the given type.
|
|
486
|
+
def self.invalid_value_message(type, message)
|
|
487
|
+
invalid_value_message_meth = :"_invalid_value_message_for_#{type}"
|
|
488
|
+
define_method(invalid_value_message_meth){message}
|
|
489
|
+
private invalid_value_message_meth
|
|
490
|
+
alias_method invalid_value_message_meth, invalid_value_message_meth
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Set the maximum input bytesize for the given type.
|
|
494
|
+
def self.max_input_bytesize(type, bytesize)
|
|
495
|
+
max_input_bytesize_meth = :"_max_input_bytesize_for_#{type}"
|
|
496
|
+
define_method(max_input_bytesize_meth){bytesize}
|
|
497
|
+
private max_input_bytesize_meth
|
|
498
|
+
alias_method max_input_bytesize_meth, max_input_bytesize_meth
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# Create a new instance with the given object and nesting level.
|
|
502
|
+
# +obj+ should be an array or hash, and +nesting+ should be an
|
|
503
|
+
# array. Designed for internal use, should not be called by
|
|
504
|
+
# external code.
|
|
505
|
+
def self.nest(obj, nesting)
|
|
506
|
+
v = allocate
|
|
507
|
+
v.instance_variable_set(:@nesting, nesting)
|
|
508
|
+
v.send(:initialize, obj)
|
|
509
|
+
v
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
handle_type(:any) do |v|
|
|
513
|
+
v
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
handle_type(:str) do |v|
|
|
517
|
+
raise Error, "expected string but received #{v.inspect}" unless v.is_a?(::String)
|
|
518
|
+
v
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
handle_type(:nonempty_str, :invalid_value_message=>"empty string provided for parameter") do |v|
|
|
522
|
+
if (v = convert_str(v)) && !v.strip.empty?
|
|
523
|
+
v
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
handle_type(:bool, :invalid_value_message=>"empty string provided for parameter") do |v|
|
|
528
|
+
case v
|
|
529
|
+
when ''
|
|
530
|
+
nil
|
|
531
|
+
when false, 0, /\A(?:0|f(?:alse)?|no?|off)\z/i
|
|
532
|
+
false
|
|
533
|
+
when true, 1, /\A(?:1|t(?:rue)?|y(?:es)?|on)\z/i
|
|
534
|
+
true
|
|
535
|
+
else
|
|
536
|
+
raise Error, "expected bool but received #{v.inspect}"
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
handle_type(:int, :max_input_bytesize=>100, :invalid_value_message=>"empty string provided for parameter") do |v|
|
|
541
|
+
string_or_numeric!(v) && v.to_i
|
|
542
|
+
end
|
|
543
|
+
alias base_convert_int convert_int
|
|
544
|
+
|
|
545
|
+
handle_type(:pos_int, :max_input_bytesize=>100, :invalid_value_message=>"empty string, non-integer, or non-positive integer provided for parameter") do |v|
|
|
546
|
+
if (v = base_convert_int(v)) && v > 0
|
|
547
|
+
v
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
handle_type(:Integer, :max_input_bytesize=>100, :invalid_value_message=>"empty string provided for parameter") do |v|
|
|
552
|
+
if string_or_numeric!(v)
|
|
553
|
+
case v
|
|
554
|
+
when String
|
|
555
|
+
::Kernel::Integer(v, 10)
|
|
556
|
+
when Integer
|
|
557
|
+
v
|
|
558
|
+
else
|
|
559
|
+
i = ::Kernel::Integer(v)
|
|
560
|
+
raise Error, "numeric value passed to Integer contains non-Integer part: #{v.inspect}" unless i == v
|
|
561
|
+
i
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
alias base_convert_Integer convert_Integer
|
|
566
|
+
|
|
567
|
+
handle_type(:float, :max_input_bytesize=>1000, :invalid_value_message=>"empty string provided for parameter") do |v|
|
|
568
|
+
string_or_numeric!(v) && v.to_f
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
handle_type(:Float, :max_input_bytesize=>1000, :invalid_value_message=>"empty string provided for parameter") do |v|
|
|
572
|
+
string_or_numeric!(v) && ::Kernel::Float(v)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
handle_type(:Hash) do |v|
|
|
576
|
+
raise Error, "expected hash but received #{v.inspect}" unless v.is_a?(::Hash)
|
|
577
|
+
v
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
handle_type(:date, :max_input_bytesize=>128) do |v|
|
|
581
|
+
parse!(::Date, v)
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
handle_type(:time, :max_input_bytesize=>128) do |v|
|
|
585
|
+
parse!(::Time, v)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
handle_type(:datetime, :max_input_bytesize=>128) do |v|
|
|
589
|
+
parse!(::DateTime, v)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
handle_type(:file) do |v|
|
|
593
|
+
raise Error, "expected hash with :tempfile entry" unless v.is_a?(::Hash) && v.has_key?(:tempfile) && v[:tempfile].respond_to?(:read)
|
|
594
|
+
v
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Set the object used for converting. Conversion methods will convert members of
|
|
598
|
+
# the passed object.
|
|
599
|
+
def initialize(obj)
|
|
600
|
+
case @obj = obj
|
|
601
|
+
when Hash, Array
|
|
602
|
+
# nothing
|
|
603
|
+
else
|
|
604
|
+
if @nesting
|
|
605
|
+
handle_error(nil, (@obj.nil? ? :missing : :invalid_type), "value of #{param_name(nil)} parameter not an array or hash: #{obj.inspect}", true)
|
|
606
|
+
else
|
|
607
|
+
handle_error(nil, :invalid_type, "parameters given not an array or hash: #{obj.inspect}", true)
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# If key is a String Return whether the key is present in the object,
|
|
613
|
+
def present?(key)
|
|
614
|
+
case key
|
|
615
|
+
when String
|
|
616
|
+
!any(key).nil?
|
|
617
|
+
when Array
|
|
618
|
+
key.all? do |k|
|
|
619
|
+
raise ProgrammerError, "non-String element in array argument passed to present?: #{k.inspect}" unless k.is_a?(String)
|
|
620
|
+
!any(k).nil?
|
|
621
|
+
end
|
|
622
|
+
else
|
|
623
|
+
raise ProgrammerError, "unexpected argument passed to present?: #{key.inspect}"
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Return a new Params instance for the given +key+. The value of +key+ should be an array
|
|
628
|
+
# if +key+ is an integer, or hash otherwise.
|
|
629
|
+
def [](key)
|
|
630
|
+
@subs ||= {}
|
|
631
|
+
if sub = @subs[key]
|
|
632
|
+
return sub
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
if @obj.is_a?(Array)
|
|
636
|
+
unless key.is_a?(Integer)
|
|
637
|
+
handle_error(key, :invalid_type, "invalid use of non-integer key for accessing array: #{key.inspect}", true)
|
|
638
|
+
end
|
|
639
|
+
else
|
|
640
|
+
if key.is_a?(Integer)
|
|
641
|
+
handle_error(key, :invalid_type, "invalid use of integer key for accessing hash: #{key}", true)
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
v = @obj[key]
|
|
646
|
+
v = yield if v.nil? && defined?(yield)
|
|
647
|
+
|
|
648
|
+
begin
|
|
649
|
+
sub = self.class.nest(v, Array(@nesting) + [key])
|
|
650
|
+
rescue => e
|
|
651
|
+
handle_error(key, :invalid_type, e, true)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
@subs[key] = sub
|
|
655
|
+
sub.sub_capture(@capture, @symbolize, @skip_missing)
|
|
656
|
+
sub
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# Return the nested value for key. If there is no nested_value for +key+,
|
|
660
|
+
# calls the block to return the value, or returns nil if there is no block given.
|
|
661
|
+
def fetch(key)
|
|
662
|
+
send(:[], key){return(yield if defined?(yield))}
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Captures conversions inside the given block, and returns a hash of all conversions,
|
|
666
|
+
# including conversions of subkeys. +keys+ should be an array of subkeys to access,
|
|
667
|
+
# or nil to convert the current object. If +keys+ is given as a hash, it is used as
|
|
668
|
+
# the options hash. Options:
|
|
669
|
+
#
|
|
670
|
+
# :raise :: If set to false, do not raise errors for missing keys
|
|
671
|
+
# :skip_missing :: If set to true, does not store values if the key is not
|
|
672
|
+
# present in the params.
|
|
673
|
+
# :symbolize :: Convert any string keys in the resulting hash and for any
|
|
674
|
+
# conversions below
|
|
675
|
+
def convert!(keys=nil, opts=OPTS)
|
|
676
|
+
if keys.is_a?(Hash)
|
|
677
|
+
opts = keys
|
|
678
|
+
keys = nil
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
_capture!(:nested_params, opts) do
|
|
682
|
+
if sub = subkey(Array(keys).dup, opts.fetch(:raise, true))
|
|
683
|
+
yield sub
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# Runs conversions similar to convert! for each key specified by the :keys option. If :keys option is not given
|
|
689
|
+
# and the object is an array, runs conversions for all entries in the array. If the :keys
|
|
690
|
+
# option is not given and the object is a Hash with string keys '0', '1', ..., 'N' (with
|
|
691
|
+
# no skipped keys), runs conversions for all entries in the hash. If :keys option is a Proc
|
|
692
|
+
# or a Method, calls the proc/method with the current object, which should return an
|
|
693
|
+
# array of keys to use.
|
|
694
|
+
# Supports options given to #convert!, and this additional option:
|
|
695
|
+
#
|
|
696
|
+
# :keys :: The keys to extract from the object. If a proc or method,
|
|
697
|
+
# calls the value with the current object, which should return the array of keys
|
|
698
|
+
# to use.
|
|
699
|
+
def convert_each!(opts=OPTS, &block)
|
|
700
|
+
np = !@capture
|
|
701
|
+
|
|
702
|
+
_capture!(nil, opts) do
|
|
703
|
+
case keys = opts[:keys]
|
|
704
|
+
when nil
|
|
705
|
+
keys = (0...@obj.length)
|
|
706
|
+
|
|
707
|
+
valid = if @obj.is_a?(Array)
|
|
708
|
+
true
|
|
709
|
+
else
|
|
710
|
+
keys = keys.map(&:to_s)
|
|
711
|
+
keys.all?{|k| @obj.has_key?(k)}
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
unless valid
|
|
715
|
+
handle_error(nil, :invalid_type, "convert_each! called on object not an array or hash with keys '0'..'N'")
|
|
716
|
+
next
|
|
717
|
+
end
|
|
718
|
+
when Array
|
|
719
|
+
# nothing to do
|
|
720
|
+
when Proc, Method
|
|
721
|
+
keys = keys.call(@obj)
|
|
722
|
+
else
|
|
723
|
+
raise ProgrammerError, "unsupported convert_each! :keys option: #{keys.inspect}"
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
keys.map do |i|
|
|
727
|
+
begin
|
|
728
|
+
if v = subkey([i], opts.fetch(:raise, true))
|
|
729
|
+
yield v
|
|
730
|
+
v.nested_params if np
|
|
731
|
+
end
|
|
732
|
+
rescue => e
|
|
733
|
+
handle_error(i, :invalid_type, e)
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# Convert values nested under the current obj. Traverses the current object using +nest+, then converts
|
|
740
|
+
# +key+ on that object using +type+:
|
|
741
|
+
#
|
|
742
|
+
# tp.dig(:pos_int, 'foo') # tp.pos_int('foo')
|
|
743
|
+
# tp.dig(:pos_int, 'foo', 'bar') # tp['foo'].pos_int('bar')
|
|
744
|
+
# tp.dig(:pos_int, 'foo', 'bar', 'baz') # tp['foo']['bar'].pos_int('baz')
|
|
745
|
+
#
|
|
746
|
+
# Returns nil if any of the values are not present or not the expected type. If the nest path results
|
|
747
|
+
# in an object that is not an array or hash, then raises an Error.
|
|
748
|
+
#
|
|
749
|
+
# You can use +dig+ to get access to nested arrays by using <tt>:array</tt> or <tt>:array!</tt> as
|
|
750
|
+
# the first argument and providing the type in the second argument:
|
|
751
|
+
#
|
|
752
|
+
# tp.dig(:array, :pos_int, 'foo', 'bar', 'baz') # tp['foo']['bar'].array(:pos_int, 'baz')
|
|
753
|
+
def dig(type, *nest, key)
|
|
754
|
+
_dig(false, type, nest, key)
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Similar to +dig+, but raises an Error instead of returning +nil+ if no value is found.
|
|
758
|
+
def dig!(type, *nest, key)
|
|
759
|
+
_dig(true, type, nest, key)
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
# Convert the value of +key+ to an array of values of the given +type+. If +default+ is
|
|
763
|
+
# given, any +nil+ values in the array are replaced with +default+. If +key+ is an array
|
|
764
|
+
# then this returns an array of arrays, one for each respective value of +key+. If there is
|
|
765
|
+
# no value for +key+, nil is returned instead of an array.
|
|
766
|
+
def array(type, key, default=nil)
|
|
767
|
+
meth = :"_convert_array_#{type}"
|
|
768
|
+
raise ProgrammerError, "no typecast_params type registered for #{type.inspect}" unless respond_to?(meth, true)
|
|
769
|
+
process_arg(meth, key, default, type) if require_hash!
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# Call +array+ with the +type+, +key+, and +default+, but if the return value is nil or any value in
|
|
773
|
+
# the returned array is +nil+, raise an Error.
|
|
774
|
+
def array!(type, key, default=nil)
|
|
775
|
+
v = array(type, key, default)
|
|
776
|
+
|
|
777
|
+
if key.is_a?(Array)
|
|
778
|
+
key.zip(v).each do |k, arr|
|
|
779
|
+
check_array!(k, arr)
|
|
780
|
+
end
|
|
781
|
+
else
|
|
782
|
+
check_array!(key, v)
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
v
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
protected
|
|
789
|
+
|
|
790
|
+
# Recursively descendent into all known subkeys and get the converted params from each.
|
|
791
|
+
def nested_params
|
|
792
|
+
return @nested_params if @nested_params
|
|
793
|
+
|
|
794
|
+
params = @params
|
|
795
|
+
|
|
796
|
+
if @subs
|
|
797
|
+
@subs.each do |key, v|
|
|
798
|
+
if key.is_a?(String) && symbolize?
|
|
799
|
+
key = key.to_sym
|
|
800
|
+
end
|
|
801
|
+
params[key] = v.nested_params
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
params
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# Recursive method to get subkeys.
|
|
809
|
+
def subkey(keys, do_raise)
|
|
810
|
+
unless key = keys.shift
|
|
811
|
+
return self
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
reason = :invalid_type
|
|
815
|
+
|
|
816
|
+
case key
|
|
817
|
+
when String
|
|
818
|
+
unless @obj.is_a?(Hash)
|
|
819
|
+
raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise
|
|
820
|
+
return
|
|
821
|
+
end
|
|
822
|
+
present = !@obj[key].nil?
|
|
823
|
+
when Integer
|
|
824
|
+
unless @obj.is_a?(Array)
|
|
825
|
+
raise Error, "parameter #{param_name(nil)} is not an array" if do_raise
|
|
826
|
+
return
|
|
827
|
+
end
|
|
828
|
+
present = key < @obj.length
|
|
829
|
+
else
|
|
830
|
+
raise ProgrammerError, "invalid argument used to traverse parameters: #{key.inspect}"
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
unless present
|
|
834
|
+
reason = :missing
|
|
835
|
+
raise Error, "parameter #{param_name(key)} is not present" if do_raise
|
|
836
|
+
return
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
self[key].subkey(keys, do_raise)
|
|
840
|
+
rescue => e
|
|
841
|
+
handle_error(key, reason, e)
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
# Inherit given capturing and symbolize setting from parent object.
|
|
845
|
+
def sub_capture(capture, symbolize, skip_missing)
|
|
846
|
+
if @capture = capture
|
|
847
|
+
@symbolize = symbolize
|
|
848
|
+
@skip_missing = skip_missing
|
|
849
|
+
@params = @obj.class.new
|
|
850
|
+
end
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
private
|
|
854
|
+
|
|
855
|
+
# Whether to symbolize keys when capturing. Note that the method
|
|
856
|
+
# is renamed to +symbolize?+.
|
|
857
|
+
attr_reader :symbolize
|
|
858
|
+
alias symbolize? symbolize
|
|
859
|
+
undef symbolize
|
|
860
|
+
|
|
861
|
+
# Internals of convert! and convert_each!.
|
|
862
|
+
def _capture!(ret, opts)
|
|
863
|
+
previous_symbolize = @symbolize
|
|
864
|
+
previous_skip_missing = @skip_missing
|
|
865
|
+
|
|
866
|
+
unless cap = @capture
|
|
867
|
+
@params = @obj.class.new
|
|
868
|
+
@subs.clear if @subs
|
|
869
|
+
capturing_started = true
|
|
870
|
+
cap = @capture = []
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
if opts.has_key?(:symbolize)
|
|
874
|
+
@symbolize = !!opts[:symbolize]
|
|
875
|
+
end
|
|
876
|
+
if opts.has_key?(:skip_missing)
|
|
877
|
+
@skip_missing = !!opts[:skip_missing]
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
begin
|
|
881
|
+
v = yield
|
|
882
|
+
rescue Error => e
|
|
883
|
+
cap << e unless cap.last == e
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
if capturing_started
|
|
887
|
+
unless cap.empty?
|
|
888
|
+
e = cap[0]
|
|
889
|
+
e.all_errors = cap
|
|
890
|
+
raise e
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
if ret == :nested_params
|
|
894
|
+
nested_params
|
|
895
|
+
else
|
|
896
|
+
v
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
ensure
|
|
900
|
+
@nested_params = nil
|
|
901
|
+
|
|
902
|
+
if capturing_started
|
|
903
|
+
# Unset capturing if capturing was already started.
|
|
904
|
+
@capture = nil
|
|
905
|
+
else
|
|
906
|
+
# If capturing was not already started, update cached nested params
|
|
907
|
+
# before resetting symbolize setting.
|
|
908
|
+
@nested_params = nested_params
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
@symbolize = previous_symbolize
|
|
912
|
+
@skip_missing = previous_skip_missing
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
# Raise an error if the array given does contains nil values.
|
|
916
|
+
def check_array!(key, arr)
|
|
917
|
+
if arr
|
|
918
|
+
if arr.any?{|val| val.nil?}
|
|
919
|
+
handle_error(key, :invalid_type, "invalid value in array parameter #{param_name(key)}")
|
|
920
|
+
end
|
|
921
|
+
else
|
|
922
|
+
handle_error(key, :missing, "missing parameter for #{param_name(key)}")
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
# Internals of dig/dig!
|
|
927
|
+
def _dig(force, type, nest, key)
|
|
928
|
+
if type == :array || type == :array!
|
|
929
|
+
conv_type = nest.shift
|
|
930
|
+
unless conv_type.is_a?(Symbol)
|
|
931
|
+
raise ProgrammerError, "incorrect subtype given when using #{type} as argument for dig/dig!: #{conv_type.inspect}"
|
|
932
|
+
end
|
|
933
|
+
meth = type
|
|
934
|
+
type = conv_type
|
|
935
|
+
args = [meth, type]
|
|
936
|
+
else
|
|
937
|
+
meth = type
|
|
938
|
+
args = [type]
|
|
939
|
+
end
|
|
940
|
+
|
|
941
|
+
unless respond_to?("_convert_array_#{type}", true)
|
|
942
|
+
raise ProgrammerError, "no typecast_params type registered for #{meth.inspect}"
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
if v = subkey(nest, force)
|
|
946
|
+
v.send(*args, key, (CHECK_NIL if force))
|
|
947
|
+
end
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
# Format a reasonable parameter name value, for use in exception messages.
|
|
951
|
+
def param_name(key)
|
|
952
|
+
first, *rest = keys(key)
|
|
953
|
+
if first
|
|
954
|
+
v = first.dup
|
|
955
|
+
rest.each do |param|
|
|
956
|
+
v << "[#{param}]"
|
|
957
|
+
end
|
|
958
|
+
v
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
# If +key+ is not +nil+, add it to the given nesting. Otherwise, just return the given nesting.
|
|
963
|
+
# Designed for use in setting the +keys+ values in raised exceptions.
|
|
964
|
+
def keys(key)
|
|
965
|
+
Array(@nesting) + Array(key)
|
|
966
|
+
end
|
|
967
|
+
|
|
968
|
+
# Handle any conversion errors. By default, reraises Error instances with the keys set,
|
|
969
|
+
# converts ::ArgumentError instances to Error instances, and reraises other exceptions.
|
|
970
|
+
def handle_error(key, reason, e, do_raise=false)
|
|
971
|
+
case e
|
|
972
|
+
when String
|
|
973
|
+
handle_error(key, reason, Error.new(e), do_raise)
|
|
974
|
+
when Error, ArgumentError
|
|
975
|
+
if @capture && (le = @capture.last) && le == e
|
|
976
|
+
raise e if do_raise
|
|
977
|
+
return
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
e = Error.create(keys(key), reason, e)
|
|
981
|
+
|
|
982
|
+
if @capture
|
|
983
|
+
@capture << e
|
|
984
|
+
raise e if do_raise
|
|
985
|
+
nil
|
|
986
|
+
else
|
|
987
|
+
raise e
|
|
988
|
+
end
|
|
989
|
+
else
|
|
990
|
+
raise e
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
# Issue an error unless the current object is a hash. Used to ensure we don't try to access
|
|
995
|
+
# entries if the current object is an array.
|
|
996
|
+
def require_hash!
|
|
997
|
+
@obj.is_a?(Hash) || handle_error(nil, :invalid_type, "expected hash object in #{param_name(nil)} but received array object")
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
# If +key+ is not an array, convert the value at the given +key+ using the +meth+ method and +default+
|
|
1001
|
+
# value. If +key+ is an array, return an array with the conversion done for each respective member of +key+.
|
|
1002
|
+
def process_arg(meth, key, default, type)
|
|
1003
|
+
case key
|
|
1004
|
+
when String
|
|
1005
|
+
v = process(meth, key, default, type)
|
|
1006
|
+
|
|
1007
|
+
if @capture
|
|
1008
|
+
key = key.to_sym if symbolize?
|
|
1009
|
+
if !@skip_missing || @obj.has_key?(key)
|
|
1010
|
+
@params[key] = v
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
v
|
|
1015
|
+
when Array
|
|
1016
|
+
key.map do |k|
|
|
1017
|
+
raise ProgrammerError, "non-String element in array argument passed to typecast_params: #{k.inspect}" unless k.is_a?(String)
|
|
1018
|
+
process_arg(meth, k, default, type)
|
|
1019
|
+
end
|
|
1020
|
+
else
|
|
1021
|
+
raise ProgrammerError, "Unsupported argument for typecast_params conversion method: #{key.inspect}"
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
# The invalid message to use if the given type conversion fails, which may be nil to use the default.
|
|
1026
|
+
def _invalid_value_message_for(type)
|
|
1027
|
+
send(:"_invalid_value_message_for_#{type}")
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
# The maximum input bytesize for the given type, which may be nil.
|
|
1031
|
+
def _max_input_bytesize_for(type)
|
|
1032
|
+
send(:"_max_input_bytesize_for_#{type}")
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
# Raise an Error if the value is a string with bytesize over max (if max is given)
|
|
1036
|
+
def check_allowed_bytesize(v, max)
|
|
1037
|
+
if max && v.is_a?(String) && v.bytesize > max
|
|
1038
|
+
handle_error(nil, :too_long, "string parameter is too long for type", true)
|
|
1039
|
+
end
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
# Raise an Error if the value is a string containing a null byte.
|
|
1043
|
+
def check_null_byte(v)
|
|
1044
|
+
if v.is_a?(String) && v.index("\0")
|
|
1045
|
+
handle_error(nil, :null_byte, "string parameter contains null byte", true)
|
|
1046
|
+
end
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
# Get the value of +key+ for the object, and convert it to the expected type using +meth+.
|
|
1050
|
+
# If the value either before or after conversion is nil, return the +default+ value.
|
|
1051
|
+
def process(meth, key, default, type)
|
|
1052
|
+
orig_v = v = param_value(key)
|
|
1053
|
+
|
|
1054
|
+
if v.nil?
|
|
1055
|
+
if default == CHECK_NIL
|
|
1056
|
+
handle_error(key, :missing, "missing parameter for #{param_name(key)}")
|
|
1057
|
+
end
|
|
1058
|
+
else
|
|
1059
|
+
check_allowed_bytesize(v, _max_input_bytesize_for(type))
|
|
1060
|
+
check_null_byte(v)
|
|
1061
|
+
v = send(meth, v)
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
if v.nil?
|
|
1065
|
+
if !orig_v.nil? && default == CHECK_NIL
|
|
1066
|
+
invalid_value_message = _invalid_value_message_for(type)
|
|
1067
|
+
invalid_value_message ||= "invalid parameter value for"
|
|
1068
|
+
handle_error(key, :invalid_value, "#{invalid_value_message} #{param_name(key)}")
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
default
|
|
1072
|
+
else
|
|
1073
|
+
v
|
|
1074
|
+
end
|
|
1075
|
+
rescue => e
|
|
1076
|
+
handle_error(key, meth.to_s.sub(/\A_?convert_/, '').to_sym, e)
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
# Get the value for the given key in the object.
|
|
1080
|
+
def param_value(key)
|
|
1081
|
+
@obj[key]
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
# Helper for conversion methods where '' should be considered nil,
|
|
1085
|
+
# and only String or Numeric values should be converted.
|
|
1086
|
+
def string_or_numeric!(v)
|
|
1087
|
+
case v
|
|
1088
|
+
when ''
|
|
1089
|
+
nil
|
|
1090
|
+
when String, Numeric
|
|
1091
|
+
true
|
|
1092
|
+
else
|
|
1093
|
+
raise Error, "unexpected value received: #{v.inspect}"
|
|
1094
|
+
end
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
# Helper for conversion methods where '' should be considered nil,
|
|
1098
|
+
# and only String values should be converted by calling +parse+ on
|
|
1099
|
+
# the given +klass+.
|
|
1100
|
+
def parse!(klass, v)
|
|
1101
|
+
case v
|
|
1102
|
+
when ''
|
|
1103
|
+
nil
|
|
1104
|
+
when String
|
|
1105
|
+
_string_parse!(klass, v)
|
|
1106
|
+
else
|
|
1107
|
+
raise Error, "unexpected value received: #{v.inspect}"
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
# Handle parsing for string values passed to parse!.
|
|
1112
|
+
def _string_parse!(klass, v)
|
|
1113
|
+
klass.parse(v)
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
# Set application-specific Params subclass unless one has been set,
|
|
1118
|
+
# and if a block is passed, eval it in the context of the subclass.
|
|
1119
|
+
# Respect the <tt>strip: :all</tt> to strip all parameter strings
|
|
1120
|
+
# before processing them.
|
|
1121
|
+
def self.configure(app, opts=OPTS, &block)
|
|
1122
|
+
app.const_set(:TypecastParams, Class.new(RodaPlugins::TypecastParams::Params)) unless app.const_defined?(:TypecastParams)
|
|
1123
|
+
app::TypecastParams.class_eval(&block) if block
|
|
1124
|
+
if opts[:strip] == :all
|
|
1125
|
+
app::TypecastParams.send(:include, StringStripper)
|
|
1126
|
+
end
|
|
1127
|
+
if opts[:allow_null_bytes]
|
|
1128
|
+
app::TypecastParams.send(:include, AllowNullByte)
|
|
1129
|
+
end
|
|
1130
|
+
if opts[:skip_bytesize_checking]
|
|
1131
|
+
app::TypecastParams.send(:include, SkipBytesizeChecking)
|
|
1132
|
+
end
|
|
1133
|
+
if opts[:date_parse_input_handler]
|
|
1134
|
+
app::TypecastParams.class_eval do
|
|
1135
|
+
include DateParseInputHandler
|
|
1136
|
+
define_method(:handle_date_parse_input, &opts[:date_parse_input_handler])
|
|
1137
|
+
private :handle_date_parse_input
|
|
1138
|
+
alias handle_date_parse_input handle_date_parse_input
|
|
1139
|
+
end
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
module ClassMethods
|
|
1144
|
+
# Freeze the Params subclass when freezing the class.
|
|
1145
|
+
def freeze
|
|
1146
|
+
self::TypecastParams.freeze
|
|
1147
|
+
super
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
# Assign the application subclass a subclass of the current Params subclass.
|
|
1151
|
+
def inherited(subclass)
|
|
1152
|
+
super
|
|
1153
|
+
subclass.const_set(:TypecastParams, Class.new(self::TypecastParams))
|
|
1154
|
+
end
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
module InstanceMethods
|
|
1158
|
+
# Return and cache the instance of the TypecastParams class wrapping access
|
|
1159
|
+
# to the request's params (merging query string params and body params).
|
|
1160
|
+
# Type conversion methods will be called on the result of this method.
|
|
1161
|
+
def typecast_params
|
|
1162
|
+
@_typecast_params ||= self.class::TypecastParams.new(@_request.params)
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
# Return and cache the instance of the TypecastParams class wrapping
|
|
1166
|
+
# access to parameters in the request's query string.
|
|
1167
|
+
# Type conversion methods will be called on the result of this method.
|
|
1168
|
+
def typecast_query_params
|
|
1169
|
+
@_typecast_query_params ||= self.class::TypecastParams.new(@_request.GET)
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# Return and cache the instance of the TypecastParams class wrapping
|
|
1173
|
+
# access to parameters in the request's body.
|
|
1174
|
+
# Type conversion methods will be called on the result of this method.
|
|
1175
|
+
def typecast_body_params
|
|
1176
|
+
@_typecast_body_params ||= self.class::TypecastParams.new(@_request.POST)
|
|
1177
|
+
end
|
|
1178
|
+
end
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
register_plugin(:typecast_params, TypecastParams)
|
|
1182
|
+
end
|
|
1183
|
+
end
|