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
@@ -1,74 +1,75 @@
|
|
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 `false`.
|
13
|
+
class DefaultBooleansToFalse
|
14
|
+
attr_reader :hash_attributes
|
6
15
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
# This class coerces these missing values into `false`.
|
12
|
-
class DefaultBooleansToFalse
|
13
|
-
attr_reader :hash_attributes
|
16
|
+
def self.build(schema)
|
17
|
+
new(schema)
|
18
|
+
end
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
def initialize(fixed_hash_schema)
|
21
|
+
# TODO: make fixed hash attributes frozen, and eliminate dup
|
22
|
+
@hash_attributes = fixed_hash_schema.attributes.map(&:dup)
|
23
|
+
end
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
25
|
+
def call(value)
|
26
|
+
Result.success(default_bools_to_false(value))
|
27
|
+
end
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
def will_affect?(value)
|
30
|
+
keys_to_default(value).any?
|
31
|
+
end
|
27
32
|
|
28
|
-
|
29
|
-
keys_to_default(value).any?
|
30
|
-
end
|
33
|
+
private
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
missing_keys = keys_to_default(hash)
|
35
|
+
def default_bools_to_false(hash)
|
36
|
+
missing_keys = keys_to_default(hash)
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
if missing_keys.any?
|
39
|
+
defaults = missing_keys.map { |k| [k, false] }.to_h
|
40
|
+
hash.merge(defaults)
|
41
|
+
else
|
42
|
+
hash # no coercion necessary
|
43
|
+
end
|
41
44
|
end
|
42
|
-
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
def keys_to_default(value)
|
47
|
+
if value.is_a?(Hash)
|
48
|
+
keys_for_bool_defaulting - value.keys
|
49
|
+
else
|
50
|
+
[]
|
51
|
+
end
|
49
52
|
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def keys_for_bool_defaulting
|
53
|
-
@keys_for_bool_defaulting ||= Set.new(
|
54
|
-
hash_attributes
|
55
|
-
.reject(&:optional)
|
56
|
-
.select { |attr| is_bool_schema?(attr.value_schema) }
|
57
|
-
.map(&:key)
|
58
|
-
)
|
59
|
-
end
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
54
|
+
def keys_for_bool_defaulting
|
55
|
+
@keys_for_bool_defaulting ||= Set.new(
|
56
|
+
hash_attributes
|
57
|
+
.reject(&:optional)
|
58
|
+
.select { |attr| bool_schema?(attr.value_schema) }
|
59
|
+
.map(&:key),
|
60
|
+
)
|
66
61
|
end
|
67
62
|
|
68
|
-
|
63
|
+
def bool_schema?(schema)
|
64
|
+
# dig through all the coercers
|
65
|
+
non_coercer = schema
|
66
|
+
while non_coercer.is_a?(Schemas::Coercer)
|
67
|
+
non_coercer = non_coercer.subschema
|
68
|
+
end
|
69
|
+
|
70
|
+
non_coercer.is_a?(Schemas::Boolean)
|
71
|
+
end
|
69
72
|
end
|
73
|
+
end
|
70
74
|
end
|
71
|
-
|
72
|
-
end
|
73
|
-
end
|
74
75
|
end
|
@@ -1,54 +1,58 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
3
|
-
module FixedHash
|
4
|
-
|
5
|
-
class RemoveExtraneousAttributes
|
6
|
-
attr_reader :hash_attributes
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
module FixedHash
|
6
|
+
#
|
7
|
+
# Removes elements from `Hash` values that are not defined in the given
|
8
|
+
# `FixedHash` schema.
|
9
|
+
#
|
10
|
+
class RemoveExtraneousAttributes
|
11
|
+
attr_reader :hash_attributes
|
12
|
+
|
13
|
+
def self.build(schema)
|
14
|
+
new(schema)
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
def initialize(fixed_hash_schema)
|
18
|
+
# TODO: make fixed hash attributes frozen, and eliminate dup
|
19
|
+
@hash_attributes = fixed_hash_schema.attributes.map(&:dup)
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
def call(value)
|
23
|
+
Result.success(remove_extraneous_elements(value))
|
24
|
+
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
def will_affect?(value)
|
27
|
+
keys_to_remove(value).any?
|
28
|
+
end
|
24
29
|
|
25
|
-
|
30
|
+
private
|
26
31
|
|
27
|
-
|
28
|
-
|
32
|
+
def remove_extraneous_elements(hash)
|
33
|
+
extra_keys = keys_to_remove(hash)
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
if extra_keys.any?
|
36
|
+
hash.dup.tap do |stripped_hash|
|
37
|
+
extra_keys.each { |k| stripped_hash.delete(k) }
|
38
|
+
end
|
39
|
+
else
|
40
|
+
hash
|
33
41
|
end
|
34
|
-
else
|
35
|
-
hash
|
36
42
|
end
|
37
|
-
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
+
def keys_to_remove(value)
|
45
|
+
if value.is_a?(Hash)
|
46
|
+
value.keys - valid_keys
|
47
|
+
else
|
48
|
+
[]
|
49
|
+
end
|
44
50
|
end
|
45
|
-
end
|
46
51
|
|
47
|
-
|
48
|
-
|
52
|
+
def valid_keys
|
53
|
+
@valid_keys ||= hash_attributes.map(&:key)
|
54
|
+
end
|
49
55
|
end
|
56
|
+
end
|
50
57
|
end
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
54
58
|
end
|
@@ -1,70 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
module RSchema
|
4
|
-
module Coercers
|
5
|
-
module FixedHash
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
module Coercers
|
7
|
+
module FixedHash
|
8
|
+
#
|
9
|
+
# Coerces `String` keys into `Symbol`s according to the attributes in
|
10
|
+
# a given `FixedHash` schema.
|
11
|
+
#
|
12
|
+
class SymbolizeKeys
|
13
|
+
attr_reader :hash_attributes
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
15
|
+
def self.build(schema)
|
16
|
+
new(schema)
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
def initialize(fixed_hash_schema)
|
20
|
+
# TODO: make fixed hash attributes frozen, and eliminate dup
|
21
|
+
@hash_attributes = fixed_hash_schema.attributes.map(&:dup)
|
22
|
+
end
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
24
|
+
def call(value)
|
25
|
+
Result.success(symbolize_keys(value))
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
28
|
+
def will_affect?(_value)
|
29
|
+
keys_to_symbolize(hash).any?
|
30
|
+
end
|
26
31
|
|
27
|
-
|
32
|
+
private
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
def symbolize_keys(hash)
|
35
|
+
keys = keys_to_symbolize(hash)
|
36
|
+
if keys.any?
|
37
|
+
hash.dup.tap do |new_hash|
|
38
|
+
keys.each { |k| new_hash[k.to_sym] = new_hash.delete(k) }
|
39
|
+
end
|
40
|
+
else
|
41
|
+
hash
|
34
42
|
end
|
35
|
-
else
|
36
|
-
hash
|
37
43
|
end
|
38
|
-
end
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
def keys_to_symbolize(value)
|
46
|
+
if value.is_a?(Hash)
|
47
|
+
non_string_keys = Set.new(value.keys) - string_keys
|
48
|
+
non_string_keys.intersection(symbol_keys_as_strings)
|
49
|
+
else
|
50
|
+
[]
|
51
|
+
end
|
46
52
|
end
|
47
|
-
end
|
48
53
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def symbol_keys_as_strings
|
55
|
+
@symbol_keys_as_strings ||= Set.new(
|
56
|
+
all_keys
|
57
|
+
.select { |k| k.is_a?(::Symbol) }
|
58
|
+
.map(&:to_s),
|
59
|
+
)
|
60
|
+
end
|
56
61
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
def string_keys
|
63
|
+
@string_keys ||= Set.new(
|
64
|
+
all_keys.select { |k| k.is_a?(::String) },
|
65
|
+
)
|
66
|
+
end
|
62
67
|
|
63
|
-
|
64
|
-
|
68
|
+
def all_keys
|
69
|
+
@all_keys ||= hash_attributes.map(&:key)
|
70
|
+
end
|
65
71
|
end
|
72
|
+
end
|
66
73
|
end
|
67
|
-
|
68
|
-
end
|
69
|
-
end
|
70
74
|
end
|
@@ -1,22 +1,29 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Coerces values into `Float` objects using `Kernel#Float`
|
7
|
+
#
|
8
|
+
module Float
|
9
|
+
extend self
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
def build(_schema)
|
12
|
+
self
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
def call(value)
|
16
|
+
flt = begin
|
17
|
+
Float(value)
|
18
|
+
rescue
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
flt ? Result.success(flt) : Result.failure
|
22
|
+
end
|
15
23
|
|
16
|
-
|
17
|
-
|
24
|
+
def will_affect?(value)
|
25
|
+
!value.is_a?(Float)
|
26
|
+
end
|
18
27
|
end
|
19
28
|
end
|
20
|
-
|
21
|
-
end
|
22
29
|
end
|
@@ -1,22 +1,28 @@
|
|
1
|
-
|
2
|
-
module Coercers
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
module RSchema
|
4
|
+
module Coercers
|
5
|
+
#
|
6
|
+
# Coerces values to `Integer`s using `Kernel#Integer`
|
7
|
+
module Integer
|
8
|
+
extend self
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
+
def build(_schema)
|
11
|
+
self
|
12
|
+
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
def call(value)
|
15
|
+
int = begin
|
16
|
+
Integer(value)
|
17
|
+
rescue
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
int ? Result.success(int) : Result.failure
|
21
|
+
end
|
15
22
|
|
16
|
-
|
17
|
-
|
23
|
+
def will_affect?(value)
|
24
|
+
!value.is_a?(Integer)
|
25
|
+
end
|
18
26
|
end
|
19
27
|
end
|
20
|
-
|
21
|
-
end
|
22
28
|
end
|
@@ -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 empty strings to `nil`
|
7
|
+
#
|
8
|
+
module NilEmptyStrings
|
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
|
+
if value == ''
|
17
|
+
Result.success(nil)
|
18
|
+
else
|
19
|
+
Result.success(value)
|
20
|
+
end
|
16
21
|
end
|
17
|
-
end
|
18
22
|
|
19
|
-
|
20
|
-
|
23
|
+
def will_affect?(value)
|
24
|
+
value == ''
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
|
-
|
24
|
-
end
|
25
28
|
end
|