rschema 3.1.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rschema.rb +13 -5
- data/lib/rschema/coercers.rb +2 -0
- data/lib/rschema/coercers/any.rb +37 -31
- data/lib/rschema/coercers/boolean.rb +33 -23
- data/lib/rschema/coercers/chain.rb +38 -32
- data/lib/rschema/coercers/date.rb +29 -20
- data/lib/rschema/coercers/fixed_hash/default_arrays_to_empty.rb +57 -56
- data/lib/rschema/coercers/fixed_hash/default_booleans_to_false.rb +56 -55
- data/lib/rschema/coercers/fixed_hash/remove_extraneous_attributes.rb +43 -39
- data/lib/rschema/coercers/fixed_hash/symbolize_keys.rb +55 -51
- data/lib/rschema/coercers/float.rb +22 -15
- data/lib/rschema/coercers/integer.rb +21 -15
- data/lib/rschema/coercers/nil_empty_strings.rb +20 -17
- data/lib/rschema/coercers/symbol.rb +20 -17
- data/lib/rschema/coercers/time.rb +29 -20
- data/lib/rschema/coercion_wrapper.rb +25 -26
- data/lib/rschema/coercion_wrapper/rack_params.rb +18 -19
- data/lib/rschema/dsl.rb +20 -13
- data/lib/rschema/error.rb +9 -4
- data/lib/rschema/options.rb +5 -0
- data/lib/rschema/rails.rb +60 -0
- data/lib/rschema/result.rb +9 -11
- data/lib/rschema/schemas.rb +2 -0
- data/lib/rschema/schemas/anything.rb +23 -24
- data/lib/rschema/schemas/boolean.rb +36 -30
- data/lib/rschema/schemas/coercer.rb +47 -46
- data/lib/rschema/schemas/convenience.rb +122 -123
- data/lib/rschema/schemas/enum.rb +41 -34
- data/lib/rschema/schemas/fixed_hash.rb +165 -162
- data/lib/rschema/schemas/fixed_length_array.rb +66 -58
- data/lib/rschema/schemas/maybe.rb +28 -28
- data/lib/rschema/schemas/pipeline.rb +35 -35
- data/lib/rschema/schemas/predicate.rb +40 -34
- data/lib/rschema/schemas/set.rb +57 -48
- data/lib/rschema/schemas/sum.rb +31 -34
- data/lib/rschema/schemas/type.rb +44 -38
- data/lib/rschema/schemas/variable_hash.rb +63 -61
- data/lib/rschema/schemas/variable_length_array.rb +57 -51
- data/lib/rschema/version.rb +3 -1
- metadata +54 -25
@@ -1,25 +1,28 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Coerces `String`s to `Symbol`s
|
7
|
+
#
|
8
|
+
module Symbol
|
9
|
+
extend self
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
def build(_schema)
|
12
|
+
self
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def call(value)
|
16
|
+
case value
|
17
|
+
when ::Symbol then Result.success(value)
|
18
|
+
when ::String then Result.success(value.to_sym)
|
19
|
+
else Result.failure
|
20
|
+
end
|
16
21
|
end
|
17
|
-
end
|
18
22
|
|
19
|
-
|
20
|
-
|
23
|
+
def will_affect?(value)
|
24
|
+
!value.is_a?(Symbol)
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
|
-
|
24
|
-
end
|
25
28
|
end
|
@@ -1,29 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
2
|
-
module Coercers
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Coerces `String`s to `Time`s using `Time.parse`
|
7
|
+
module Time
|
8
|
+
extend self
|
3
9
|
|
4
|
-
|
5
|
-
|
10
|
+
def build(_schema)
|
11
|
+
self
|
12
|
+
end
|
6
13
|
|
7
|
-
|
8
|
-
|
9
|
-
|
14
|
+
def call(value)
|
15
|
+
case value
|
16
|
+
when ::Time then Result.success(value)
|
17
|
+
when ::String then coerce_string(value)
|
18
|
+
else Result.failure
|
19
|
+
end
|
20
|
+
end
|
10
21
|
|
11
|
-
|
12
|
-
|
13
|
-
when ::Time
|
14
|
-
Result.success(value)
|
15
|
-
when ::String
|
16
|
-
time = ::Time.parse(value) rescue nil
|
17
|
-
time ? Result.success(time) : Result.failure
|
18
|
-
else
|
19
|
-
Result.failure
|
22
|
+
def will_affect?(value)
|
23
|
+
!value.is_a?(Time)
|
20
24
|
end
|
21
|
-
end
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
private
|
27
|
+
|
28
|
+
def coerce_string(str)
|
29
|
+
time = begin
|
30
|
+
::Time.parse(str)
|
31
|
+
rescue
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
time ? Result.success(time) : Result.failure
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
27
|
-
|
28
|
-
end
|
29
38
|
end
|
@@ -1,4 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
4
|
+
#
|
5
|
+
# Builds coercing schemas, by wrapping coercers around an existing schema.
|
6
|
+
#
|
2
7
|
class CoercionWrapper
|
3
8
|
def initialize(&initializer)
|
4
9
|
@builder_by_schema = {}
|
@@ -21,36 +26,30 @@ module RSchema
|
|
21
26
|
|
22
27
|
private
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
builder_for_type(schema.type)
|
28
|
-
else
|
29
|
-
nil
|
30
|
-
end
|
31
|
-
end
|
29
|
+
def builder_for_schema(schema)
|
30
|
+
@builder_by_schema.fetch(schema.class) do
|
31
|
+
builder_for_type(schema.type) if schema.is_a?(Schemas::Type)
|
32
32
|
end
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
nil
|
35
|
+
def builder_for_type(type)
|
36
|
+
# polymorphic lookup
|
37
|
+
type.ancestors.each do |ancestor|
|
38
|
+
builder = @builder_by_type[ancestor]
|
39
|
+
return builder if builder
|
42
40
|
end
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
if builder
|
47
|
-
coercer = builder.build(schema)
|
48
|
-
Schemas::Coercer.new(coercer, schema)
|
49
|
-
else
|
50
|
-
schema
|
51
|
-
end
|
52
|
-
end
|
42
|
+
nil
|
43
|
+
end
|
53
44
|
|
45
|
+
def wrap_with_coercer(schema)
|
46
|
+
builder = builder_for_schema(schema)
|
47
|
+
if builder
|
48
|
+
coercer = builder.build(schema)
|
49
|
+
Schemas::Coercer.new(coercer, schema)
|
50
|
+
else
|
51
|
+
schema
|
52
|
+
end
|
53
|
+
end
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
@@ -1,25 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'date'
|
2
4
|
|
3
5
|
module RSchema
|
4
|
-
class CoercionWrapper
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
coerce_type Date, with: Coercers::Date
|
6
|
+
class CoercionWrapper
|
7
|
+
RACK_PARAMS = CoercionWrapper.new do
|
8
|
+
coerce_type Symbol, with: Coercers::Symbol
|
9
|
+
coerce_type Integer, with: Coercers::Integer
|
10
|
+
coerce_type Float, with: Coercers::Float
|
11
|
+
coerce_type Time, with: Coercers::Time
|
12
|
+
coerce_type Date, with: Coercers::Date
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
coerce Schemas::Maybe, with: Coercers::NilEmptyStrings
|
15
|
+
coerce Schemas::Boolean, with: Coercers::Boolean
|
16
|
+
coerce Schemas::FixedHash, with: Coercers::Chain[
|
17
|
+
Coercers::FixedHash::SymbolizeKeys,
|
18
|
+
Coercers::FixedHash::RemoveExtraneousAttributes,
|
19
|
+
Coercers::FixedHash::DefaultBooleansToFalse,
|
20
|
+
Coercers::FixedHash::DefaultArraysToEmpty,
|
21
|
+
]
|
22
|
+
end
|
21
23
|
end
|
22
|
-
|
23
24
|
end
|
24
|
-
end
|
25
|
-
|
data/lib/rschema/dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
2
4
|
#
|
3
5
|
# A mixin containing all the standard RSchema DSL methods.
|
@@ -45,15 +47,15 @@ module RSchema
|
|
45
47
|
# Creates a {Schemas::VariableLengthArray} if given one argument, otherwise
|
46
48
|
# creates a {Schemas::FixedLengthArray}
|
47
49
|
#
|
48
|
-
# @param subschemas [Array<schema>] one or more schema objects representing
|
49
|
-
# in the array.
|
50
|
+
# @param subschemas [Array<schema>] one or more schema objects representing
|
51
|
+
# elements in the array.
|
50
52
|
# @return [Schemas::VariableLengthArray, Schemas::FixedLengthArray]
|
51
53
|
#
|
52
54
|
# @example (see Schemas::VariableLengthArray)
|
53
55
|
# @example (see Schemas::FixedLengthArray)
|
54
56
|
#
|
55
57
|
def array(*subschemas)
|
56
|
-
subschemas = subschemas.map{ |ss| inconvenience(ss) }
|
58
|
+
subschemas = subschemas.map { |ss| inconvenience(ss) }
|
57
59
|
|
58
60
|
if subschemas.count == 1
|
59
61
|
Schemas::VariableLengthArray.new(subschemas.first)
|
@@ -87,7 +89,7 @@ module RSchema
|
|
87
89
|
def fixed_hash(attribute_hash)
|
88
90
|
Schemas::FixedHash.new(attributes(attribute_hash))
|
89
91
|
end
|
90
|
-
|
92
|
+
alias hash fixed_hash
|
91
93
|
|
92
94
|
#
|
93
95
|
# Creates a {Schemas::Set} schema
|
@@ -143,8 +145,9 @@ module RSchema
|
|
143
145
|
end
|
144
146
|
|
145
147
|
#
|
146
|
-
# Turns an "attribute hash" into an array of
|
147
|
-
# Primarily for use with
|
148
|
+
# Turns an "attribute hash" into an array of
|
149
|
+
# {Schemas::FixedHash::Attribute}. Primarily for use with
|
150
|
+
# {Schemas::FixedHash#merge}.
|
148
151
|
#
|
149
152
|
# @param attribute_hash [Hash<key, schema>] A hash of keys to subschemas.
|
150
153
|
# The values of this hash must be schema objects.
|
@@ -161,7 +164,9 @@ module RSchema
|
|
161
164
|
attribute_hash.map do |dsl_key, value_schema|
|
162
165
|
optional = dsl_key.is_a?(OptionalWrapper)
|
163
166
|
key = optional ? dsl_key.key : dsl_key
|
164
|
-
Schemas::FixedHash::Attribute.new(
|
167
|
+
Schemas::FixedHash::Attribute.new(
|
168
|
+
key, inconvenience(value_schema), optional,
|
169
|
+
)
|
165
170
|
end
|
166
171
|
end
|
167
172
|
|
@@ -190,9 +195,11 @@ module RSchema
|
|
190
195
|
#
|
191
196
|
# @example (see Schemas::Enum)
|
192
197
|
#
|
193
|
-
def enum(valid_values, subschema=nil)
|
198
|
+
def enum(valid_values, subschema = nil)
|
194
199
|
subschema = inconvenience(subschema) if subschema
|
195
|
-
Schemas::Enum.new(
|
200
|
+
Schemas::Enum.new(
|
201
|
+
valid_values, subschema || type(valid_values.first.class),
|
202
|
+
)
|
196
203
|
end
|
197
204
|
|
198
205
|
#
|
@@ -205,7 +212,7 @@ module RSchema
|
|
205
212
|
# @example (see Schemas::Sum)
|
206
213
|
#
|
207
214
|
def either(*subschemas)
|
208
|
-
subschemas = subschemas.map{ |ss| inconvenience(ss) }
|
215
|
+
subschemas = subschemas.map { |ss| inconvenience(ss) }
|
209
216
|
Schemas::Sum.new(subschemas)
|
210
217
|
end
|
211
218
|
|
@@ -239,7 +246,7 @@ module RSchema
|
|
239
246
|
# @example (see Schemas::Pipeline)
|
240
247
|
#
|
241
248
|
def pipeline(*subschemas)
|
242
|
-
subschemas = subschemas.map{ |ss| inconvenience(ss) }
|
249
|
+
subschemas = subschemas.map { |ss| inconvenience(ss) }
|
243
250
|
Schemas::Pipeline.new(subschemas)
|
244
251
|
end
|
245
252
|
|
@@ -331,9 +338,9 @@ module RSchema
|
|
331
338
|
end
|
332
339
|
|
333
340
|
# @!visibility private
|
334
|
-
def
|
341
|
+
def respond_to_missing?(sym, include_all = false)
|
335
342
|
# check if method starts with an underscore followed by a capital
|
336
|
-
super ||
|
343
|
+
super || sym.to_s.match(/\A_[A-Z]/)
|
337
344
|
end
|
338
345
|
end
|
339
346
|
end
|
data/lib/rschema/error.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
4
|
+
#
|
5
|
+
# Contains info about how a schema failed validation
|
6
|
+
#
|
2
7
|
class Error
|
3
8
|
attr_reader :schema, :value, :symbolic_name, :vars
|
4
9
|
|
5
10
|
def initialize(schema:, value:, symbolic_name:, vars: {})
|
6
|
-
raise ArgumentError.new(
|
11
|
+
raise ArgumentError.new('vars must be a hash') unless vars.is_a?(Hash)
|
7
12
|
|
8
13
|
@schema = schema
|
9
14
|
@value = value
|
@@ -19,10 +24,10 @@ module RSchema
|
|
19
24
|
|
20
25
|
def inspect
|
21
26
|
attrs = vars.merge(value: value)
|
22
|
-
|
23
|
-
|
27
|
+
.map { |k, v| "#{k}=#{v.inspect}" }
|
28
|
+
.join(' ')
|
24
29
|
|
25
|
-
"<#{self.class} #{
|
30
|
+
"<#{self.class} #{self} #{attrs}>"
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
data/lib/rschema/options.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rschema'
|
4
|
+
require 'rschema/coercion_wrapper/rack_params'
|
5
|
+
|
6
|
+
module RSchema
|
7
|
+
module Rails
|
8
|
+
#
|
9
|
+
# A mixin for ActionController that provides methods for validating params.
|
10
|
+
#
|
11
|
+
module Controller
|
12
|
+
def self.included(klass)
|
13
|
+
klass.include(InstanceMethods)
|
14
|
+
klass.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Instance methods added to ActionController classes
|
19
|
+
#
|
20
|
+
module InstanceMethods
|
21
|
+
def param_schema(&schema_block)
|
22
|
+
self.class.param_schema(&schema_block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_params(schema = nil, &schema_block)
|
26
|
+
schema ||= param_schema(&schema_block)
|
27
|
+
schema.validate(request.parameters.to_hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_params!(*args, &block)
|
31
|
+
result = validate_params(*args, &block)
|
32
|
+
raise InvalidParams.new(result.error) if result.invalid?
|
33
|
+
result.value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Class methods added to ActionController classes
|
39
|
+
#
|
40
|
+
module ClassMethods
|
41
|
+
def param_schema(&schema_block)
|
42
|
+
schema = RSchema.define_hash(&schema_block)
|
43
|
+
RSchema::CoercionWrapper::RACK_PARAMS.wrap(schema)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Raised when {validate_params!} fails
|
50
|
+
#
|
51
|
+
class InvalidParams < StandardError
|
52
|
+
attr_reader :error
|
53
|
+
|
54
|
+
def initialize(error)
|
55
|
+
@error = error
|
56
|
+
super('Parameters do not conform to schema')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/rschema/result.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
4
|
+
#
|
5
|
+
# The return value when calling a schema
|
6
|
+
#
|
2
7
|
class Result
|
3
8
|
def self.success(value = nil)
|
4
9
|
if value.nil?
|
@@ -28,22 +33,15 @@ module RSchema
|
|
28
33
|
end
|
29
34
|
|
30
35
|
def invalid?
|
31
|
-
|
36
|
+
!valid?
|
32
37
|
end
|
33
38
|
|
34
39
|
def value
|
35
|
-
if
|
36
|
-
|
37
|
-
else
|
38
|
-
raise InvalidError
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def error
|
43
|
-
@error
|
40
|
+
raise RSchema::Invalid.new(error) if invalid?
|
41
|
+
@value
|
44
42
|
end
|
45
43
|
|
46
|
-
|
44
|
+
attr_reader :error
|
47
45
|
|
48
46
|
# @!visibility private
|
49
47
|
NIL_SUCCESS = new(true, nil, nil)
|