rack-params 0.0.1.pre5 → 0.0.1.pre6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|