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,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
|