rack-params 0.0.1.pre5 → 0.0.1.pre6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +26 -0
- data/README.md +13 -11
- data/Rakefile +1 -1
- data/lib/rack/params.rb +11 -53
- data/lib/rack/params/connector.rb +49 -0
- data/lib/rack/params/context.rb +44 -176
- data/lib/rack/params/context/array_context.rb +44 -0
- data/lib/rack/params/context/hash_context.rb +89 -0
- data/lib/rack/params/tasks.rake +1 -1
- data/lib/rack/params/validator.rb +103 -0
- data/lib/rack/params/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b8f9f809d52259627ff17cd8f7ac0950d239bd0
|
4
|
+
data.tar.gz: fe7397ffc2e416cac5984e88acdd9d9266d00220
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7927e8dd9ea5d6e0d0c047fd62a2d9e582e32f7a072c607aee42db9a0ea5dfda2db7309dbd30f72b5831665769fb9813584cb8feb0542275f2d7dd683e9662eb
|
7
|
+
data.tar.gz: 941558bf50cfd8cb34c5cdf0d08493a1df7673f9246251ccf028998c0f0973c5cef769b40c3207f83190f67b73e326d5788c6e138d96cba6875c899eca9d97d7
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to `Rack::Params` will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
This is a pretty major refactor, but since we're not really released
|
10
|
+
at all yet, I'm just including highlights in the changelog - check the
|
11
|
+
diff for explicit changes, but so much got moved around, I'm not sure
|
12
|
+
that'll be easy. Such are the perogotives of personal projects :)
|
13
|
+
|
14
|
+
* added a changelog!
|
15
|
+
* `#param` and `#every` now take a block for any type, which is passed
|
16
|
+
the value for transformation (think #map), which makes the
|
17
|
+
Hash/Array handling less special (though still special, since
|
18
|
+
they're recursed, not transformed given the block).
|
19
|
+
* extracted the `Validator` module from `Context`; easier to test,
|
20
|
+
makes more sense architecturally (context is only concerned with
|
21
|
+
being `self` for the block), and allows the use of the validation
|
22
|
+
helpers elsewhere.
|
23
|
+
* much more robust specs.
|
24
|
+
|
25
|
+
### [0.0.1.pre5]
|
26
|
+
- all basic functionality
|
data/README.md
CHANGED
@@ -3,6 +3,17 @@
|
|
3
3
|
|
4
4
|
`Rack::Request.params` validation and type coercion, on Rack.
|
5
5
|
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
**[Documentation](https://lygaret.github.io/rack-params/)**
|
9
|
+
|
10
|
+
1. Include `Rack::Params` to get the `.validator`, `#validate` and `#validate!` methods.
|
11
|
+
2. Call `.validator(name, options = {}, &code)` to register a named validator for use later.
|
12
|
+
3. Call `#validate(name = nil, params = request.params, options = {}, &code)` to build a new result, with the results of validation and coercion.
|
13
|
+
4. The blocks passed to the validation methods run in the context of `HashContext` and `ArrayContext`, which is where the coercion methods are defined.
|
14
|
+
|
15
|
+
## Example
|
16
|
+
|
6
17
|
```ruby
|
7
18
|
# NOTE (to self) - if this changes, update `readme_spec.rb`
|
8
19
|
|
@@ -16,8 +27,8 @@ class SomeExampleApp
|
|
16
27
|
param :title, String, required: true
|
17
28
|
param :created, DateTime
|
18
29
|
|
19
|
-
param :tags, Array
|
20
|
-
every
|
30
|
+
param :tags, Array do
|
31
|
+
every :symbol
|
21
32
|
end
|
22
33
|
|
23
34
|
param :content, Hash, required: true do
|
@@ -106,15 +117,6 @@ Or install it yourself as:
|
|
106
117
|
|
107
118
|
$ gem install rack-params
|
108
119
|
|
109
|
-
## Usage
|
110
|
-
|
111
|
-
**[RDoc @ master - Rack::Params](http://www.rubydoc.info/github/lygaret/rack-params/master)**
|
112
|
-
|
113
|
-
1. Include `Rack::Params` to get the `.validator`, `#validate` and `#validate!` methods.
|
114
|
-
2. Call `.validator(name, options = {}, &code)` to register a named validator for use later.
|
115
|
-
3. Call `#validate(name = nil, params = request.params, options = {}, &code)` to build a new result, with the results of validation and coercion.
|
116
|
-
4. The blocks passed to the validation methods run in the context of `HashContext` and `ArrayContext`, which is where the coercion methods are defined.
|
117
|
-
|
118
120
|
## Development
|
119
121
|
|
120
122
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
data/lib/rack/params.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'rack/params/context'
|
2
|
+
require 'rack/params/context/hash_context'
|
3
|
+
require 'rack/params/context/array_context'
|
4
|
+
|
5
|
+
require 'rack/params/connector'
|
2
6
|
require 'rack/params/errors'
|
3
7
|
require 'rack/params/result'
|
4
8
|
require 'rack/params/version'
|
@@ -6,8 +10,8 @@ require 'rack/params/version'
|
|
6
10
|
module Rack
|
7
11
|
|
8
12
|
# Rack::Params provides a lightweight DSL for type coercion and validation of request parameters.
|
9
|
-
# @!parse extend Rack::Params::ClassMethods
|
10
13
|
module Params
|
14
|
+
# @!parse extend Rack::Params::ClassMethods
|
11
15
|
|
12
16
|
# @private
|
13
17
|
def self.included(base)
|
@@ -15,15 +19,13 @@ module Rack
|
|
15
19
|
end
|
16
20
|
|
17
21
|
module ClassMethods
|
18
|
-
# holds options and a block when registering a validator.
|
19
22
|
# @private
|
20
23
|
Validator = Struct.new(:options, :code) do
|
21
|
-
def exec(values)
|
22
|
-
|
24
|
+
def exec(values, **options)
|
25
|
+
Rack::Params::Context.exec(values, options, &code)
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
|
-
# get the set of validators, keyed by name
|
27
29
|
# @private
|
28
30
|
def validators
|
29
31
|
@_rp_validators ||= {}
|
@@ -56,12 +58,12 @@ module Rack
|
|
56
58
|
name = nil
|
57
59
|
end
|
58
60
|
|
59
|
-
fail
|
61
|
+
fail "no parameters provided!" if params.nil?
|
60
62
|
if name.nil?
|
61
|
-
fail
|
62
|
-
|
63
|
+
fail "no validation block was provided!" unless block_given?
|
64
|
+
Rack::Params::Context.exec(params, **options, &block)
|
63
65
|
else
|
64
|
-
fail
|
66
|
+
fail "no validation is registered under #{name}" unless self.class.validators.key? name
|
65
67
|
self.class.validators[name].exec(params)
|
66
68
|
end
|
67
69
|
end
|
@@ -85,49 +87,5 @@ module Rack
|
|
85
87
|
fail ParameterValidationError, res.errors if res.invalid?
|
86
88
|
end
|
87
89
|
end
|
88
|
-
|
89
|
-
# mixin for frameworks that have a {#request} method in scope
|
90
|
-
# make sure you `include Rack::Params` before including
|
91
|
-
# @!attribute [r] params
|
92
|
-
# @return [Result] the validated params, including errors
|
93
|
-
module Connector
|
94
|
-
def self.included(base)
|
95
|
-
base.class_eval do
|
96
|
-
attr_reader :params
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# validates {Rack::Request#params} against a validator.
|
101
|
-
# @overload validate(name, options = {})
|
102
|
-
# @param [Symbol] name the name of a registered validator.
|
103
|
-
# @param [Hash] options
|
104
|
-
# @return [Result] a result hash containing the extracted keys, and any errors.
|
105
|
-
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
106
|
-
# @overload validate(options = {}, &block)
|
107
|
-
# validates the given parameters against the given block
|
108
|
-
# @param [Hash] options
|
109
|
-
# @yield a code block that will run in the context of a {Rack::Params::HashContext} to validate the params
|
110
|
-
# @return [Result] a result hash containing the extracted keys, and any errors.
|
111
|
-
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
112
|
-
def validate(name = nil, params = nil, options = {}, &block)
|
113
|
-
super(name, params || request.params, options, &block)
|
114
|
-
end
|
115
|
-
|
116
|
-
# validates {Rack::Request#params} against a validator, raising on errors.
|
117
|
-
# @overload validate!(name, options = {})
|
118
|
-
# @param [Symbol] name the name of a registered validator.
|
119
|
-
# @param [Hash] options
|
120
|
-
# @return [Result] a valid result hash containing the extracted keys, and no errors.
|
121
|
-
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
122
|
-
# @overload validate!(options = {}, &block)
|
123
|
-
# validates the given parameters against the given block
|
124
|
-
# @param [Hash] options
|
125
|
-
# @yield a code block that will run in the context of a {Rack::Params::HashContext} to validate the params
|
126
|
-
# @return [Result] a valid result hash containing the extracted keys, and no errors.
|
127
|
-
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
128
|
-
def validate!(name = nil, params = nil, options = {}, &block)
|
129
|
-
super(name, params || request.params, options, &block)
|
130
|
-
end
|
131
|
-
end
|
132
90
|
end
|
133
91
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rack
|
2
|
+
module Params
|
3
|
+
|
4
|
+
# mixin for frameworks that have a {#request} method in scope
|
5
|
+
# make sure you `include Rack::Params` before including
|
6
|
+
# @!attribute [r] params
|
7
|
+
# @return [Result] the validated params, including errors
|
8
|
+
module Connector
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
attr_reader :params
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# validates {Rack::Request#params} against a validator.
|
16
|
+
# @overload validate(name, options = {})
|
17
|
+
# @param [Symbol] name the name of a registered validator.
|
18
|
+
# @param [Hash] options
|
19
|
+
# @return [Result] a result hash containing the extracted keys, and any errors.
|
20
|
+
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
21
|
+
# @overload validate(options = {}, &block)
|
22
|
+
# validates the given parameters against the given block
|
23
|
+
# @param [Hash] options
|
24
|
+
# @yield a code block that will run in the context of a {Rack::Params::HashContext} to validate the params
|
25
|
+
# @return [Result] a result hash containing the extracted keys, and any errors.
|
26
|
+
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
27
|
+
def validate(name = nil, params = nil, options = {}, &block)
|
28
|
+
super(name, params || request.params, options, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# validates {Rack::Request#params} against a validator, raising on errors.
|
32
|
+
# @overload validate!(name, options = {})
|
33
|
+
# @param [Symbol] name the name of a registered validator.
|
34
|
+
# @param [Hash] options
|
35
|
+
# @return [Result] a valid result hash containing the extracted keys, and no errors.
|
36
|
+
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
37
|
+
# @overload validate!(options = {}, &block)
|
38
|
+
# validates the given parameters against the given block
|
39
|
+
# @param [Hash] options
|
40
|
+
# @yield a code block that will run in the context of a {Rack::Params::HashContext} to validate the params
|
41
|
+
# @return [Result] a valid result hash containing the extracted keys, and no errors.
|
42
|
+
# @raise [ParameterValidationError] if the parameters are invalid after validation and coercion
|
43
|
+
def validate!(name = nil, params = nil, options = {}, &block)
|
44
|
+
super(name, params || request.params, options, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/rack/params/context.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
require '
|
1
|
+
require 'rack/params/validator'
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
module Params
|
5
5
|
|
6
|
-
# the
|
6
|
+
# the validator to run.
|
7
7
|
# it contains the {#params} and {#result}, and provides methods to use in coercion.
|
8
8
|
# @abstract To subclass, provide a {Result} constant, and methods to do coercion/validation.
|
9
|
-
class Context
|
9
|
+
class Context
|
10
|
+
include Rack::Params::Validator
|
11
|
+
|
10
12
|
attr_reader :options
|
11
13
|
attr_reader :params
|
12
14
|
attr_reader :result
|
@@ -15,7 +17,11 @@ module Rack
|
|
15
17
|
# the actual {#result} will be this type extended by {Rack::Params::Result}
|
16
18
|
Result = nil
|
17
19
|
|
18
|
-
|
20
|
+
def self.exec(params, **options, &block)
|
21
|
+
HashContext.new(params, options).exec(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# create a validator with a parameter hash and options.
|
19
25
|
def initialize(params, options = {})
|
20
26
|
@options = options
|
21
27
|
@path = options[:path]
|
@@ -24,193 +30,55 @@ module Rack
|
|
24
30
|
|
25
31
|
@result = self.class::Result.new
|
26
32
|
@result.extend ::Rack::Params::Result
|
27
|
-
@result.errors = Hash.new { |h, k| h[k] = [] }
|
33
|
+
@result.errors = ::Hash.new { |h, k| h[k] = [] }
|
28
34
|
end
|
29
35
|
|
30
|
-
# execute the given block, in this
|
36
|
+
# execute the given block, in this validator
|
31
37
|
# @yield in the block, {self}'s methods are available.
|
32
|
-
# @return [Result] the result of
|
38
|
+
# @return [Result] the result of validation
|
33
39
|
def exec(&block)
|
34
40
|
instance_exec(&block)
|
35
41
|
@result
|
36
42
|
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# == valid types
|
43
|
-
# :symbol :: convert value as a symbol
|
44
|
-
# :boolean :: parse the following strings: "0, 1, false, f, true, t, no, n, yes, y"
|
45
|
-
# Symbol ::
|
46
|
-
# Int ::
|
47
|
-
# Float ::
|
48
|
-
# Date ::
|
49
|
-
# Time ::
|
50
|
-
# DateTime :: parse as the given type.
|
51
|
-
# Array :: parse value as an array. default options { sep: ' ' }
|
52
|
-
# Hash :: parse value as a Hash, default options { esep: ',', fsep: ':' }
|
53
|
-
#
|
54
|
-
# @param value the value to coerce, likely a string, hash or array
|
55
|
-
# @param type the type to coerce into
|
56
|
-
# @param options [Hash]
|
57
|
-
# @return value the coerced value
|
58
|
-
# @raise [ArgumentError] if coercion fails
|
59
|
-
def _coerce(value, type, options)
|
60
|
-
return nil if value.nil?
|
61
|
-
return value if value.is_a?(type) rescue false
|
62
|
-
|
63
|
-
return value.to_sym if type == :symbol || type == Symbol
|
64
|
-
|
65
|
-
return Integer(value, options[:base] || 0) if type == ::Integer
|
66
|
-
return Float(value) if type == ::Float
|
67
|
-
|
68
|
-
[::Date, ::Time, ::DateTime].each do |klass|
|
69
|
-
return klass.parse(value) if type == klass
|
70
|
-
end
|
71
|
-
|
72
|
-
if type == ::Array
|
73
|
-
sep = options.fetch(:sep, ',')
|
74
|
-
|
75
|
-
values = value.split(sep)
|
76
|
-
return Array(values)
|
77
|
-
end
|
78
|
-
|
79
|
-
if type == ::Hash
|
80
|
-
esep = options.fetch(:esep, ',')
|
81
|
-
fsep = options.fetch(:fsep, ':')
|
82
|
-
|
83
|
-
values = value.split(esep).map { |p| p.split(fsep) }
|
84
|
-
return ::Hash[values]
|
85
|
-
end
|
86
|
-
|
87
|
-
if type == ::TrueClass || type == ::FalseClass || type == :boolean
|
88
|
-
return false if /^(false|f|no|n|0)$/i === value
|
89
|
-
return true if /^(true|t|yes|y|1)$/i === value
|
90
|
-
raise ArgumentError # otherwise
|
91
|
-
end
|
92
|
-
|
93
|
-
# default failure
|
94
|
-
raise ArgumentError, "unknown type #{type}"
|
95
|
-
end
|
96
|
-
|
97
|
-
# todo: figure this out
|
98
|
-
# def _validate(key, value, validation, options)
|
99
|
-
# options = {} if options == true
|
100
|
-
# end
|
101
|
-
|
102
|
-
# recursively process parameters, so we can support validating nested
|
103
|
-
# parameter hashes and arrays.
|
44
|
+
# yields the value to the block, according to the type.
|
45
|
+
# @param [String] key current params path segment
|
46
|
+
# @param value to yield to the block
|
47
|
+
# @param type of value, special handling for recursible types (Hash | Array)
|
104
48
|
#
|
105
|
-
# @
|
106
|
-
#
|
107
|
-
#
|
108
|
-
|
109
|
-
path = [@path, path].reject(&:nil?).join(".")
|
110
|
-
|
111
|
-
if type == Array
|
112
|
-
ArrayContext.new(value, path: path).exec(&block)
|
113
|
-
elsif type == Hash
|
114
|
-
HashContext.new(value, path: path).exec(&block)
|
115
|
-
else
|
116
|
-
fail "can not recurse into #{type}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# the DSL for validating a hash parameter (including the top-level params hash)
|
122
|
-
class HashContext < Context
|
123
|
-
Result = Hash
|
124
|
-
|
125
|
-
# do type coercion and validation for a parameter with the given key.
|
126
|
-
# adds the coerced value to the result hash, or push an error.
|
49
|
+
# @overload _yield(key, value, type, options = {}, &block)
|
50
|
+
# returns the result of yielding the value to the block
|
51
|
+
# @param [Hash] options, same as {#_fetch}
|
52
|
+
# @return [Array] tuple of result of yielding the value to the block, and errors
|
127
53
|
#
|
128
|
-
# @
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
def param(key, type, options = {}, &block)
|
138
|
-
key = key.to_s
|
139
|
-
|
140
|
-
# default and required
|
141
|
-
value = params[key] || options[:default]
|
142
|
-
raise ArgumentError, "is required" if options[:required] && value.nil?
|
143
|
-
|
144
|
-
# type cast
|
145
|
-
value = _coerce(value, type, options)
|
146
|
-
|
147
|
-
# validate against rules
|
148
|
-
# options.each { |v, vopts| _validate(key, value, v, vopts) }
|
149
|
-
|
150
|
-
# recurse if we've got a block
|
151
|
-
if block_given?
|
152
|
-
value = _recurse(key, type, value, &block)
|
153
|
-
result.errors.merge! value.errors
|
54
|
+
# @overload _yield(key, value, type = Hash | Array, options = {}, &block)
|
55
|
+
# recursively validates the value through the block given, which is run
|
56
|
+
# inside a suitable validation context; also merges result errors.
|
57
|
+
# @param [Hash] options, same as {#initialize}
|
58
|
+
# @return [Array] tuple of validated results
|
59
|
+
def _yield(key, value, type, **options, &block)
|
60
|
+
# simple types
|
61
|
+
unless type == ::Array || type == ::Hash
|
62
|
+
return super(value, **options, &block)
|
154
63
|
end
|
155
64
|
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
# @param key the result key under which to place the collected parameters
|
168
|
-
# @param options [Hash]
|
169
|
-
# @return the collected keys as a hash
|
170
|
-
def splat(key, options = {})
|
171
|
-
key = key.to_s
|
65
|
+
# recursive types
|
66
|
+
fail "no block provided" if block.nil?
|
67
|
+
path = [@path, key].reject(&:nil?).join(".")
|
68
|
+
values =
|
69
|
+
if type == ::Array
|
70
|
+
Rack::Params::Context::ArrayContext.new(value, path: path).exec(&block)
|
71
|
+
elsif type == ::Hash
|
72
|
+
Rack::Params::Context::HashContext.new(value, path: path).exec(&block)
|
73
|
+
else
|
74
|
+
fail "cannot recurse into #{type}"
|
75
|
+
end
|
172
76
|
|
173
|
-
#
|
174
|
-
|
175
|
-
|
77
|
+
# this is special, we merge errors
|
78
|
+
result.errors.merge! values.errors
|
79
|
+
return values
|
176
80
|
end
|
177
81
|
end
|
178
82
|
|
179
|
-
# the DSL for validating an array parameter
|
180
|
-
class ArrayContext < Context
|
181
|
-
Result = Array
|
182
|
-
|
183
|
-
# validate and coerce every element in the array, using the same values.
|
184
|
-
# equivalent to {HashContext#param} over every element.
|
185
|
-
#
|
186
|
-
# @see HashContext#param
|
187
|
-
# @see #_coerce #_coerce defines valid types for coercion.
|
188
|
-
# @param type the type to use for coercion
|
189
|
-
# @param options [Hash]
|
190
|
-
# @yield
|
191
|
-
# if type is Hash or Array, passing a block will recursively
|
192
|
-
# validate the coerced value, assuming the block is a new validation
|
193
|
-
# context.
|
194
|
-
# @return the coerced value
|
195
|
-
def every(type, options = {}, &block)
|
196
|
-
params.each_with_index do |value, i|
|
197
|
-
begin
|
198
|
-
value = _coerce(value, type, options)
|
199
|
-
# options.each { |v, vopts| _validate(i.to_s, value, v, vopts) }
|
200
|
-
if block_given?
|
201
|
-
value = _recurse(i.to_s, type, value, &block)
|
202
|
-
result.errors.merge! value.errors
|
203
|
-
end
|
204
|
-
|
205
|
-
result[i] = value
|
206
|
-
rescue ArgumentError => ex
|
207
|
-
path = [@path, i].reject(&:nil?).join(".")
|
208
|
-
result.errors[path] << ex.message
|
209
|
-
nil
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
end
|
215
83
|
end
|
216
84
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rack/params/validator'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Params
|
5
|
+
class Context
|
6
|
+
|
7
|
+
# the DSL for validating an array parameter
|
8
|
+
class ArrayContext < Context
|
9
|
+
Result = ::Array
|
10
|
+
|
11
|
+
# validate and coerce every element in the array, using the same values.
|
12
|
+
# equivalent to {HashContext#param} over every element.
|
13
|
+
#
|
14
|
+
# @see HashContext#param
|
15
|
+
# @see #_coerce #_coerce defines valid types for coercion.
|
16
|
+
# @param type the type to use for coercion
|
17
|
+
# @param options [Hash]
|
18
|
+
# @yield
|
19
|
+
# if type is Hash or Array, passing a block will recursively
|
20
|
+
# validate the coerced value, assuming the block is a new validation
|
21
|
+
# context.
|
22
|
+
# @return the coerced value
|
23
|
+
def every(type, **options, &block)
|
24
|
+
options[:required] = true unless options.key?(:required)
|
25
|
+
|
26
|
+
params.each_with_index do |value, i|
|
27
|
+
begin
|
28
|
+
value = _coerce(value, type, options)
|
29
|
+
value = _yield(i.to_s, value, type, options, &block) unless block.nil?
|
30
|
+
|
31
|
+
# options.each { |v, vopts| _validate(i.to_s, value, v, vopts) }
|
32
|
+
|
33
|
+
result[i] = value
|
34
|
+
rescue ArgumentError => ex
|
35
|
+
path = [@path, i].reject(&:nil?).join(".")
|
36
|
+
result.errors[path] << ex.message
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rack/params/context'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Params
|
5
|
+
class Context
|
6
|
+
|
7
|
+
# the DSL for validating a hash parameter (including the top-level params hash)
|
8
|
+
class HashContext < Context
|
9
|
+
Result = ::Hash
|
10
|
+
|
11
|
+
# return the value from the params hash, validating it's presence from options
|
12
|
+
# @param key the name of the param to fetch
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options [Any] :default the value to return if the param is missing
|
15
|
+
# @option options [Boolean] :required will fail if key is missing from params
|
16
|
+
# @option options [Boolean] :allow_nil
|
17
|
+
# is nil considered present? for :required?,
|
18
|
+
# defaults to false, considered missing.
|
19
|
+
# @option options [Boolean] :allow_blank
|
20
|
+
# is blank considered present? for :required?,
|
21
|
+
# defaults to false, considered missing.
|
22
|
+
# @see #_blank?
|
23
|
+
def _fetch(key, options)
|
24
|
+
value = params.key?(key) ? params[key] : options[:default]
|
25
|
+
options[:required] ? _ensure(value, options) : value
|
26
|
+
end
|
27
|
+
|
28
|
+
# do type coercion and validation for a parameter with the given key.
|
29
|
+
# adds the coerced value to the result hash, or push an error.
|
30
|
+
#
|
31
|
+
# @param [String] key the key in the parameter hash
|
32
|
+
# @param [Hash] options
|
33
|
+
# @option options [Boolean] :required will fail if key is missing from params
|
34
|
+
# @option options [Any] :default the value to return if the param is missing
|
35
|
+
# @option options [Boolean] :allow_nil is nil considered present?, defaults to false, considered missing.
|
36
|
+
# @option options [Boolean] :allow_blank is blank (0-length string, or nil) considered present?, defaults to false, considered missing.
|
37
|
+
# @return the coerced value
|
38
|
+
#
|
39
|
+
# @overload param(key, type = String, options = {}, &block)
|
40
|
+
# coerces the value through the block given, marking invalid on raised errors.
|
41
|
+
# if yielding to the block, the :allow_nil and :allow_blank options apply equally to the result of the block
|
42
|
+
# (use the `allow_nil` and `allow_falsey` options to change the validity behavoir)
|
43
|
+
# @option options [Boolean] :allow_falsey is falsey considered present?, defaults to false, considered missing.
|
44
|
+
# @yield to the transformation function, failure on nil, blank or ArgumentError (unless `:allow_nil` or `:allow_blank`)
|
45
|
+
# @yieldparam [String|nil] value from the param hash to validate/coerce
|
46
|
+
# @yieldreturn the value that's been validated/coerced
|
47
|
+
# @see #_coerce #_coerce defines valid types for coercion.
|
48
|
+
#
|
49
|
+
# @overload param(key, type = Hash | Array, options = {}, &block)
|
50
|
+
# recursively validates the value through the block given, which
|
51
|
+
# is run in the context of a suitable validation context.
|
52
|
+
# @yield
|
53
|
+
# if type is {Hash} or {Array}, passing a block will recursively
|
54
|
+
# validate the coerced value, assuming the block is a new validation
|
55
|
+
# context. if type is not {Hash} or {Array}, raises `ArgumentError`
|
56
|
+
def param(key, type = String, **options, &block)
|
57
|
+
key = key.to_s
|
58
|
+
value = _fetch(key, options)
|
59
|
+
value = _coerce(value, type, options)
|
60
|
+
value = _yield(key, value, type, options, &block) unless block.nil?
|
61
|
+
|
62
|
+
# validate against rules
|
63
|
+
# options.each { |v, vopts| _validate(key, value, v, vopts) }
|
64
|
+
|
65
|
+
result[key] = value
|
66
|
+
rescue ArgumentError => ex
|
67
|
+
path = [@path, key].reject(&:nil?).join(".")
|
68
|
+
result.errors[path] << ex.message
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# collect uncoerced keys from the parameter hash into the results hash.
|
73
|
+
# collects keys not _yet_ coerced, so it should be last in the validation block.
|
74
|
+
#
|
75
|
+
# @param key the result key under which to place the collected parameters
|
76
|
+
# @param options [Hash]
|
77
|
+
# @return the collected keys as a hash
|
78
|
+
def splat(key, options = {})
|
79
|
+
key = key.to_s
|
80
|
+
|
81
|
+
# every key in params that's not already in results
|
82
|
+
value = params.reject { |k, _| result.keys.include? k }
|
83
|
+
result[key] = value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/rack/params/tasks.rake
CHANGED
@@ -4,7 +4,7 @@ require 'yard/rake/yardoc_task'
|
|
4
4
|
|
5
5
|
namespace :docs do
|
6
6
|
ROOT_DIR = `git rev-parse --show-toplevel`.strip
|
7
|
-
DOC_DIR = File.join(ROOT_DIR, '
|
7
|
+
DOC_DIR = File.join(ROOT_DIR, 'doc')
|
8
8
|
|
9
9
|
YARD::Rake::YardocTask.new(:generate) do |yard|
|
10
10
|
yard.options = ["--out", DOC_DIR]
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Params
|
5
|
+
|
6
|
+
# a bunch of helpers for coercion and validation of parameter values
|
7
|
+
module Validator
|
8
|
+
|
9
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
10
|
+
# For example, false, '', ' ', nil, [], and {} are all blank.
|
11
|
+
# #hattip rails' Object#blank?
|
12
|
+
# @param [Any] value to check
|
13
|
+
# @return true if blank, false otherwise
|
14
|
+
def _blank?(value)
|
15
|
+
value.respond_to?(:empty?) ? !!value.empty? : !value
|
16
|
+
end
|
17
|
+
|
18
|
+
# return a correctly typed value, given a string and a type.
|
19
|
+
#
|
20
|
+
# == valid types
|
21
|
+
# :boolean :: parse the following strings: "0, 1, false, f, true, t, no, n, yes, y"
|
22
|
+
# :symbol :: parse as a symbol, (#to_sym)
|
23
|
+
# String :: parse as a String, (#to_s)
|
24
|
+
# Int :: parse an an int, with an optional base (from options)
|
25
|
+
# Float :: parse as the given type
|
26
|
+
# #parse :: parse with a parse method on type (Date, Time, etc.)
|
27
|
+
# Array, Hash :: Rack::QueryParser already converts these, so return them directly
|
28
|
+
#
|
29
|
+
# @param value to coerce, likely a string, hash or array
|
30
|
+
# @param type to coerce into
|
31
|
+
# @param options to control coercion
|
32
|
+
# @option options :base [Number] the base to parse into for Integer
|
33
|
+
# @return value the coerced value
|
34
|
+
# @raise [ArgumentError] if coercion fails
|
35
|
+
# @raise [RuntimeError] if unsupported type given
|
36
|
+
def _coerce(value, type, options = {})
|
37
|
+
return nil if value.nil?
|
38
|
+
return value if value.is_a?(type) rescue false
|
39
|
+
|
40
|
+
return value.to_s if type == ::String
|
41
|
+
return value.to_sym if type == :symbol
|
42
|
+
|
43
|
+
return Integer(value, options[:base] || 0) if type == ::Integer
|
44
|
+
return Float(value) if type == ::Float
|
45
|
+
|
46
|
+
if type.respond_to?(:parse)
|
47
|
+
return type.parse(value)
|
48
|
+
end
|
49
|
+
|
50
|
+
if type == ::TrueClass || type == ::FalseClass || type == :boolean
|
51
|
+
return false if /^(false|f|no|n|0)$/i === value
|
52
|
+
return true if /^(true|t|yes|y|1)$/i === value
|
53
|
+
raise ArgumentError, "couldn't parse as boolean" # otherwise
|
54
|
+
end
|
55
|
+
|
56
|
+
# default failure
|
57
|
+
fail "unknown type #{type}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# todo: figure this out
|
61
|
+
# def _validate(key, value, validation, options)
|
62
|
+
# options = {} if options == true
|
63
|
+
# end
|
64
|
+
|
65
|
+
# ensures value, checking for nils and blank values
|
66
|
+
# @see #_blank?
|
67
|
+
# @param value to check
|
68
|
+
# @param [Hash] options
|
69
|
+
# @option options :allow_nil [Boolean] if value is nil, return it?
|
70
|
+
# @option options :allow_blank [Boolean] if value is blank, return it?
|
71
|
+
# @raise [ArgumentError] when a value is required but not present
|
72
|
+
# @return the value, conforming to the options given
|
73
|
+
def _ensure(value, options = {})
|
74
|
+
# allow nil has to precede allow blank, as nil is blank
|
75
|
+
if options[:allow_nil] && value.nil?
|
76
|
+
return value
|
77
|
+
end
|
78
|
+
|
79
|
+
if !options[:allow_blank] && _blank?(value)
|
80
|
+
fail ArgumentError, "is required."
|
81
|
+
end
|
82
|
+
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
# yields the value to the block, with required semantics
|
87
|
+
# @param value to yield to the block
|
88
|
+
# @option options :required [Boolean] ensure value returned from block exists
|
89
|
+
# @option options :allow_nil [Boolean] if :required, allow nils
|
90
|
+
# @option options :allow_blank [Boolean] if :required, allow blank
|
91
|
+
# @return value after transformation
|
92
|
+
# @see _ensure
|
93
|
+
def _yield(value, **options, &block)
|
94
|
+
fail "no block provided" if block.nil?
|
95
|
+
|
96
|
+
value = block.call(value)
|
97
|
+
return options[:required] ? _ensure(value, options) : value
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
data/lib/rack/params/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-params
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.pre6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Raphaelson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -147,6 +147,7 @@ files:
|
|
147
147
|
- ".rspec"
|
148
148
|
- ".travis.yml"
|
149
149
|
- ".yardopts"
|
150
|
+
- CHANGELOG.md
|
150
151
|
- CODE_OF_CONDUCT.md
|
151
152
|
- Gemfile
|
152
153
|
- LICENSE.txt
|
@@ -156,10 +157,14 @@ files:
|
|
156
157
|
- bin/console
|
157
158
|
- bin/setup
|
158
159
|
- lib/rack/params.rb
|
160
|
+
- lib/rack/params/connector.rb
|
159
161
|
- lib/rack/params/context.rb
|
162
|
+
- lib/rack/params/context/array_context.rb
|
163
|
+
- lib/rack/params/context/hash_context.rb
|
160
164
|
- lib/rack/params/errors.rb
|
161
165
|
- lib/rack/params/result.rb
|
162
166
|
- lib/rack/params/tasks.rake
|
167
|
+
- lib/rack/params/validator.rb
|
163
168
|
- lib/rack/params/version.rb
|
164
169
|
- rack-params.gemspec
|
165
170
|
homepage: https://github.com/lygaret/rack-params
|