rschema 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34db15e35dacc01ece4a64f6bc6f1d699d56a82f
|
4
|
+
data.tar.gz: 0104cab32868f52759495fe90c1139ac7f6ec2b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea3e3ea16cc3c1416b03a6df47f6b288f69cca1ed8dd53dfffb3ed8ffd0344eb9eefa7f5695f0d85ec529225b94b298dc9c6a23845558086881dc89f907d60a9
|
7
|
+
data.tar.gz: 649d06c7033f88cd0594f1c89943f991fd351703334780bfe1d4be349bbb4edba29156ecd24c8534d5496ef51b64a6942afb797a24423460e498ca7ec135d709
|
data/lib/rschema.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'docile'
|
3
4
|
require 'rschema/options'
|
4
5
|
require 'rschema/error'
|
5
6
|
require 'rschema/result'
|
@@ -12,7 +13,6 @@ require 'rschema/coercion_wrapper'
|
|
12
13
|
# Schema-based validation and coercion
|
13
14
|
#
|
14
15
|
module RSchema
|
15
|
-
|
16
16
|
#
|
17
17
|
# Creates a schema object using a DSL
|
18
18
|
#
|
@@ -46,6 +46,10 @@ module RSchema
|
|
46
46
|
# end
|
47
47
|
#
|
48
48
|
def self.dsl_eval(dsl = nil, &block)
|
49
|
+
if block.nil?
|
50
|
+
raise ArgumentError, 'Must provide a block for the RSchema DSL'
|
51
|
+
end
|
52
|
+
|
49
53
|
Docile::Execution.exec_in_proxy_context(
|
50
54
|
dsl || default_dsl,
|
51
55
|
Docile::FallbackContextProxy,
|
@@ -63,7 +67,8 @@ module RSchema
|
|
63
67
|
# end
|
64
68
|
#
|
65
69
|
# @yield (see .dsl_eval)
|
66
|
-
# @yieldreturn The attributes of the hash schema
|
70
|
+
# @yieldreturn The attributes of the hash schema
|
71
|
+
# (the argument to {DSL#fixed_hash}).
|
67
72
|
# @return [Schemas::Convenience] A {Schemas::FixedHash} schema wrapped in a
|
68
73
|
# {Schemas::Convenience}.
|
69
74
|
#
|
@@ -75,7 +80,7 @@ module RSchema
|
|
75
80
|
#
|
76
81
|
def self.define_hash(&block)
|
77
82
|
Schemas::Convenience.wrap(
|
78
|
-
default_dsl.fixed_hash(dsl_eval(&block))
|
83
|
+
default_dsl.fixed_hash(dsl_eval(&block)),
|
79
84
|
)
|
80
85
|
end
|
81
86
|
|
@@ -103,7 +108,7 @@ module RSchema
|
|
103
108
|
#
|
104
109
|
def self.define_predicate(name = nil, &block)
|
105
110
|
Schemas::Convenience.wrap(
|
106
|
-
default_dsl.predicate(name, &block)
|
111
|
+
default_dsl.predicate(name, &block),
|
107
112
|
)
|
108
113
|
end
|
109
114
|
|
@@ -127,6 +132,9 @@ module RSchema
|
|
127
132
|
include RSchema::DSL
|
128
133
|
end
|
129
134
|
|
135
|
+
#
|
136
|
+
# Indicates that validation has failed
|
137
|
+
#
|
130
138
|
class Invalid < StandardError
|
131
139
|
attr_reader :validation_error
|
132
140
|
|
data/lib/rschema/coercers.rb
CHANGED
data/lib/rschema/coercers/any.rb
CHANGED
@@ -1,44 +1,50 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
3
|
-
|
4
|
-
class Any
|
5
|
-
attr_reader :subcoercers
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Applies subcoercers, in order, until one succeeds
|
7
|
+
#
|
8
|
+
class Any
|
9
|
+
attr_reader :subcoercers
|
10
|
+
|
11
|
+
def self.[](*subbuilders)
|
12
|
+
Builder.new(subbuilders)
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
def initialize(subcoercers)
|
16
|
+
@subcoercers = subcoercers
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
def call(value)
|
20
|
+
subcoercers.each do |coercer|
|
21
|
+
result = coercer.call(value)
|
22
|
+
return result if result.valid?
|
23
|
+
end
|
24
|
+
Result.failure
|
19
25
|
end
|
20
|
-
Result.failure
|
21
|
-
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
def will_affect?(value)
|
28
|
+
subcoercers.any? { |sc| sc.will_affect?(value) }
|
29
|
+
end
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
#
|
32
|
+
# Builder for Coercers::Any
|
33
|
+
#
|
34
|
+
class Builder
|
35
|
+
attr_reader :subbuilders
|
29
36
|
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
def initialize(subbuilders)
|
38
|
+
@subbuilders = subbuilders
|
39
|
+
end
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
def build(schema)
|
42
|
+
subcoercers = subbuilders.map do |builder|
|
43
|
+
builder.build(schema)
|
44
|
+
end
|
45
|
+
Any.new(subcoercers)
|
37
46
|
end
|
38
|
-
Any.new(subcoercers)
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
42
|
-
|
43
|
-
end
|
44
50
|
end
|
@@ -1,34 +1,44 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Coerces certain strings, and nil, to true or false
|
7
|
+
#
|
8
|
+
module Boolean
|
9
|
+
extend self
|
6
10
|
|
7
|
-
|
8
|
-
|
11
|
+
TRUTHY_STRINGS = %w[on 1 true yes].freeze
|
12
|
+
FALSEY_STRINGS = %w[off 0 false no].freeze
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
def build(_schema)
|
15
|
+
self
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
case
|
20
|
-
when TRUTHY_STRINGS.include?(value.downcase) then Result.success(true)
|
21
|
-
when FALSEY_STRINGS.include?(value.downcase) then Result.success(false)
|
18
|
+
def call(value)
|
19
|
+
case value
|
20
|
+
when true, false then Result.success(value)
|
21
|
+
when nil then Result.success(false)
|
22
|
+
when String then coerce_string(value)
|
22
23
|
else Result.failure
|
23
24
|
end
|
24
|
-
else Result.failure
|
25
25
|
end
|
26
|
-
end
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
def will_affect?(value)
|
28
|
+
value != true && value != false
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def coerce_string(str)
|
34
|
+
if TRUTHY_STRINGS.include?(str.downcase)
|
35
|
+
Result.success(true)
|
36
|
+
elsif FALSEY_STRINGS.include?(str.downcase)
|
37
|
+
Result.success(false)
|
38
|
+
else
|
39
|
+
Result.failure
|
40
|
+
end
|
41
|
+
end
|
30
42
|
end
|
31
43
|
end
|
32
|
-
|
33
|
-
end
|
34
44
|
end
|
@@ -1,45 +1,51 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
3
|
-
|
4
|
-
class Chain
|
5
|
-
attr_reader :subcoercers
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Applies a list of coercers, in order
|
7
|
+
#
|
8
|
+
class Chain
|
9
|
+
attr_reader :subcoercers
|
10
|
+
|
11
|
+
def self.[](*subbuilders)
|
12
|
+
Builder.new(subbuilders)
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
def initialize(subcoercers)
|
16
|
+
@subcoercers = subcoercers
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
def call(value)
|
20
|
+
result = Result.success(value)
|
21
|
+
subcoercers.each do |coercer|
|
22
|
+
result = coercer.call(result.value)
|
23
|
+
break if result.invalid?
|
24
|
+
end
|
25
|
+
result
|
20
26
|
end
|
21
|
-
result
|
22
|
-
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
def will_affect?(value)
|
29
|
+
subcoercers.any? { |sc| sc.will_affect?(value) }
|
30
|
+
end
|
27
31
|
|
28
|
-
|
29
|
-
|
32
|
+
#
|
33
|
+
# Builder for Coercers::Chain
|
34
|
+
#
|
35
|
+
class Builder
|
36
|
+
attr_reader :subbuilders
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
def initialize(subbuilders)
|
39
|
+
@subbuilders = subbuilders
|
40
|
+
end
|
34
41
|
|
35
|
-
|
36
|
-
|
37
|
-
|
42
|
+
def build(schema)
|
43
|
+
subcoercers = subbuilders.map do |builder|
|
44
|
+
builder.build(schema)
|
45
|
+
end
|
46
|
+
Chain.new(subcoercers)
|
38
47
|
end
|
39
|
-
Chain.new(subcoercers)
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
43
|
-
|
44
|
-
end
|
45
51
|
end
|
@@ -1,29 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
2
|
-
module Coercers
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Coerces strings into `Date` objects using `Date.parse`
|
7
|
+
module Date
|
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 ::Date 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 ::Date
|
14
|
-
Result.success(value)
|
15
|
-
when ::String
|
16
|
-
date = ::Date.parse(value) rescue nil
|
17
|
-
date ? Result.success(date) : Result.failure
|
18
|
-
else
|
19
|
-
Result.failure
|
22
|
+
def will_affect?(value)
|
23
|
+
!value.is_a?(::Date)
|
20
24
|
end
|
21
|
-
end
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
private
|
27
|
+
|
28
|
+
def coerce_string(str)
|
29
|
+
date = begin
|
30
|
+
::Date.parse(str)
|
31
|
+
rescue
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
date ? Result.success(date) : Result.failure
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
27
|
-
|
28
|
-
end
|
29
38
|
end
|
@@ -1,75 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
module RSchema
|
4
|
-
module Coercers
|
5
|
-
module FixedHash
|
6
|
+
module Coercers
|
7
|
+
module FixedHash
|
8
|
+
# The HTTP standard says that when a form is submitted, all unchecked
|
9
|
+
# check boxes will _not_ be sent to the server. That is, they will not
|
10
|
+
# be present at all in the params hash.
|
11
|
+
#
|
12
|
+
# This class coerces these missing values into an empty array, where an
|
13
|
+
# array is expected.
|
14
|
+
class DefaultArraysToEmpty
|
15
|
+
attr_reader :hash_attributes
|
6
16
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
# This class coerces these missing values into an empty array, where an array
|
12
|
-
# is expected.
|
13
|
-
class DefaultArraysToEmpty
|
14
|
-
attr_reader :hash_attributes
|
17
|
+
def self.build(schema)
|
18
|
+
new(schema)
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
21
|
+
def initialize(fixed_hash_schema)
|
22
|
+
# TODO: make fixed hash attributes frozen, and eliminate dup
|
23
|
+
@hash_attributes = fixed_hash_schema.attributes.map(&:dup)
|
24
|
+
end
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
26
|
+
def call(value)
|
27
|
+
Result.success(default_arrays_to_empty(value))
|
28
|
+
end
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
|
30
|
+
def will_affect?(value)
|
31
|
+
keys_to_default(value).any?
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
keys_to_default(value).any?
|
31
|
-
end
|
34
|
+
private
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
missing_keys = keys_to_default(hash)
|
36
|
+
def default_arrays_to_empty(hash)
|
37
|
+
missing_keys = keys_to_default(hash)
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
if missing_keys.any?
|
40
|
+
defaults = missing_keys.map { |k| [k, []] }.to_h
|
41
|
+
hash.merge(defaults)
|
42
|
+
else
|
43
|
+
hash # no coercion necessary
|
44
|
+
end
|
42
45
|
end
|
43
|
-
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
def keys_to_default(value)
|
48
|
+
if value.is_a?(Hash)
|
49
|
+
keys_for_array_defaulting - value.keys
|
50
|
+
else
|
51
|
+
[]
|
52
|
+
end
|
50
53
|
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def keys_for_array_defaulting
|
54
|
-
@keys_for_array_defaulting ||= Set.new(
|
55
|
-
hash_attributes
|
56
|
-
.reject(&:optional)
|
57
|
-
.select { |attr| is_array_schema?(attr.value_schema) }
|
58
|
-
.map(&:key)
|
59
|
-
)
|
60
|
-
end
|
61
54
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
55
|
+
def keys_for_array_defaulting
|
56
|
+
@keys_for_array_defaulting ||= Set.new(
|
57
|
+
hash_attributes
|
58
|
+
.reject(&:optional)
|
59
|
+
.select { |attr| array_schema?(attr.value_schema) }
|
60
|
+
.map(&:key),
|
61
|
+
)
|
67
62
|
end
|
68
63
|
|
69
|
-
|
64
|
+
def array_schema?(schema)
|
65
|
+
# dig through all the coercers
|
66
|
+
non_coercer = schema
|
67
|
+
while non_coercer.is_a?(Schemas::Coercer)
|
68
|
+
non_coercer = non_coercer.subschema
|
69
|
+
end
|
70
|
+
|
71
|
+
non_coercer.is_a?(Schemas::VariableLengthArray)
|
72
|
+
end
|
70
73
|
end
|
74
|
+
end
|
71
75
|
end
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
75
76
|
end
|