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
data/lib/rschema/schemas.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
-
|
2
|
-
module Schemas
|
3
|
-
|
4
|
-
#
|
5
|
-
# A schema that matches literally any value
|
6
|
-
#
|
7
|
-
# @example The anything schema
|
8
|
-
# schema = RSchema.define { anything }
|
9
|
-
# schema.valid?(nil) #=> true
|
10
|
-
# schema.valid?(6.2) #=> true
|
11
|
-
# schema.valid?({ hello: Time.now }) #=> true
|
12
|
-
#
|
13
|
-
class Anything
|
1
|
+
# frozen_string_literal: true
|
14
2
|
|
15
|
-
|
16
|
-
|
17
|
-
|
3
|
+
module RSchema
|
4
|
+
module Schemas
|
5
|
+
#
|
6
|
+
# A schema that matches literally any value
|
7
|
+
#
|
8
|
+
# @example The anything schema
|
9
|
+
# schema = RSchema.define { anything }
|
10
|
+
# schema.valid?(nil) #=> true
|
11
|
+
# schema.valid?(6.2) #=> true
|
12
|
+
# schema.valid?({ hello: Time.now }) #=> true
|
13
|
+
#
|
14
|
+
class Anything
|
15
|
+
def self.instance
|
16
|
+
@instance ||= new
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def call(value, _options)
|
20
|
+
Result.success(value)
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
def with_wrapped_subschemas(_wrapper)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
25
27
|
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
28
|
end
|
@@ -1,36 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSchema
|
2
|
-
module Schemas
|
4
|
+
module Schemas
|
5
|
+
#
|
6
|
+
# A schema that matches only `true` and `false`
|
7
|
+
#
|
8
|
+
# @example The boolean schema
|
9
|
+
# schema = RSchema.define { boolean }
|
10
|
+
# schema.valid?(true) #=> true
|
11
|
+
# schema.valid?(false) #=> true
|
12
|
+
# schema.valid?(nil) #=> false
|
13
|
+
#
|
14
|
+
class Boolean
|
15
|
+
def self.instance
|
16
|
+
@instance ||= new
|
17
|
+
end
|
3
18
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# schema.valid?(nil) #=> false
|
12
|
-
#
|
13
|
-
class Boolean
|
14
|
-
def self.instance
|
15
|
-
@instance ||= new
|
16
|
-
end
|
19
|
+
def call(value, _options)
|
20
|
+
if value.equal?(true) || value.equal?(false)
|
21
|
+
Result.success(value)
|
22
|
+
else
|
23
|
+
Result.failure(error(value))
|
24
|
+
end
|
25
|
+
end
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
else
|
22
|
-
Result.failure(Error.new(
|
23
|
-
schema: self,
|
24
|
-
value: value,
|
25
|
-
symbolic_name: :not_a_boolean,
|
26
|
-
))
|
27
|
-
end
|
28
|
-
end
|
27
|
+
def with_wrapped_subschemas(_wrapper)
|
28
|
+
self
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
self
|
32
|
-
end
|
31
|
+
private
|
33
32
|
|
34
|
-
|
35
|
-
|
33
|
+
def error(value)
|
34
|
+
Error.new(
|
35
|
+
schema: self,
|
36
|
+
value: value,
|
37
|
+
symbolic_name: :not_a_boolean,
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
36
42
|
end
|
@@ -1,50 +1,51 @@
|
|
1
|
-
|
2
|
-
module Schemas
|
3
|
-
|
4
|
-
#
|
5
|
-
# A schema that applies a coercer to a value, before passing the coerced
|
6
|
-
# value to a subschema.
|
7
|
-
#
|
8
|
-
# This is not a type of schema that you would typically create yourself.
|
9
|
-
# It is used internally to implement RSchema's coercion functionality.
|
10
|
-
#
|
11
|
-
class Coercer
|
12
|
-
attr_reader :coercer, :subschema
|
13
|
-
|
14
|
-
def initialize(coercer, subschema)
|
15
|
-
byebug if coercer.is_a?(Array)
|
16
|
-
@coercer = coercer
|
17
|
-
@subschema = subschema
|
18
|
-
end
|
1
|
+
# frozen_string_literal: true
|
19
2
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
3
|
+
module RSchema
|
4
|
+
module Schemas
|
5
|
+
#
|
6
|
+
# A schema that applies a coercer to a value, before passing the coerced
|
7
|
+
# value to a subschema.
|
8
|
+
#
|
9
|
+
# This is not a type of schema that you would typically create yourself.
|
10
|
+
# It is used internally to implement RSchema's coercion functionality.
|
11
|
+
#
|
12
|
+
class Coercer
|
13
|
+
attr_reader :coercer, :subschema
|
14
|
+
|
15
|
+
def initialize(coercer, subschema)
|
16
|
+
@coercer = coercer
|
17
|
+
@subschema = subschema
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(value, options)
|
21
|
+
unless coercer.will_affect?(value)
|
22
|
+
# short-circuit the coercer
|
23
|
+
return @subschema.call(value, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
result = coercer.call(value)
|
27
|
+
if result.valid?
|
28
|
+
@subschema.call(result.value, options)
|
29
|
+
else
|
30
|
+
failure(value, result.error)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def with_wrapped_subschemas(wrapper)
|
35
|
+
self.class.new(coercer, wrapper.wrap(subschema))
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def failure(value, name)
|
41
|
+
Result.failure(
|
42
|
+
Error.new(
|
43
|
+
schema: self,
|
44
|
+
value: value,
|
45
|
+
symbolic_name: name || :coercion_failure,
|
46
|
+
),
|
47
|
+
)
|
48
|
+
end
|
31
49
|
end
|
32
50
|
end
|
33
|
-
|
34
|
-
def with_wrapped_subschemas(wrapper)
|
35
|
-
self.class.new(coercer, wrapper.wrap(subschema))
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def failure(value, name)
|
41
|
-
return Result.failure(Error.new(
|
42
|
-
schema: self,
|
43
|
-
value: value,
|
44
|
-
symbolic_name: name || :coercion_failure,
|
45
|
-
))
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
50
51
|
end
|
@@ -1,138 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'delegate'
|
2
4
|
|
3
5
|
module RSchema
|
4
|
-
module Schemas
|
6
|
+
module Schemas
|
7
|
+
#
|
8
|
+
# A wrapper that provides convenience methods for schema objects.
|
9
|
+
#
|
10
|
+
# Because this class inherits from `SimpleDelegator`, convenience wrappers
|
11
|
+
# behave like their underlying schemas. That is, you can call methods on the
|
12
|
+
# underlying schema object through the convenience wrapper.
|
13
|
+
#
|
14
|
+
# Schema objects only need to implement the `call` method to validate
|
15
|
+
# values. This small interface is simple for schema classes to implement,
|
16
|
+
# but not very descriptive when actually using the schema objects. So, to
|
17
|
+
# make schema objects nicer to use, this class provides a variety of
|
18
|
+
# more-descriptive methods like {#validate}, {#validate!}, {#valid?}, and
|
19
|
+
# {#invalid?}.
|
20
|
+
#
|
21
|
+
class Convenience < SimpleDelegator
|
22
|
+
def initialize(underlying_schema)
|
23
|
+
super
|
24
|
+
end
|
5
25
|
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# behave like their underlying schemas. That is, you can call methods on the
|
11
|
-
# underlying schema object through the convenience wrapper.
|
12
|
-
#
|
13
|
-
# Schema objects only need to implement the `call` method to validate values.
|
14
|
-
# This small interface is simple for schema classes to implement, but not
|
15
|
-
# very descriptive when actually using the schema objects. So, to make
|
16
|
-
# schema objects nicer to use, this class provides a variety of
|
17
|
-
# more-descriptive methods like {#validate}, {#validate!}, {#valid?}, and
|
18
|
-
# {#invalid?}.
|
19
|
-
#
|
20
|
-
class Convenience < SimpleDelegator
|
21
|
-
def initialize(underlying_schema)
|
22
|
-
super
|
23
|
-
end
|
26
|
+
# @return [schema] the underlying schema object
|
27
|
+
def underlying_schema
|
28
|
+
__getobj__
|
29
|
+
end
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
31
|
+
#
|
32
|
+
# Applies the schema to a value
|
33
|
+
#
|
34
|
+
# This is that same as the `call` method available on all schema objects,
|
35
|
+
# except that the `options` param is optional.
|
36
|
+
#
|
37
|
+
# @param value [Object] The value to validate
|
38
|
+
# @param options [RSchema::Options]
|
39
|
+
#
|
40
|
+
# @return [RSchema::Result]
|
41
|
+
#
|
42
|
+
def validate(value, options = Options.default)
|
43
|
+
call(value, options)
|
44
|
+
end
|
29
45
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
46
|
+
#
|
47
|
+
# Returns the validation error for the given value
|
48
|
+
#
|
49
|
+
# @param value [Object] The value to validate
|
50
|
+
# @param options [RSchema::Options]
|
51
|
+
#
|
52
|
+
# @return The error object if `value` is invalid, otherwise `nil`.
|
53
|
+
#
|
54
|
+
# @see Result#error
|
55
|
+
#
|
56
|
+
def error_for(value, options = Options.default)
|
57
|
+
result = underlying_schema.call(value, options)
|
58
|
+
if result.valid?
|
59
|
+
nil
|
60
|
+
else
|
61
|
+
result.error
|
62
|
+
end
|
63
|
+
end
|
44
64
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
65
|
+
#
|
66
|
+
# Applies the schema to a value, raising an exception if the value is
|
67
|
+
# invalid
|
68
|
+
#
|
69
|
+
# @param value [Object] The value to validate
|
70
|
+
# @param options [RSchema::Options]
|
71
|
+
#
|
72
|
+
# @raise [RSchema::Invalid] If the value is not valid
|
73
|
+
# @return [Object] The validated value
|
74
|
+
#
|
75
|
+
# @see Result#value
|
76
|
+
#
|
77
|
+
def validate!(value, options = Options.default)
|
78
|
+
result = underlying_schema.call(value, options)
|
79
|
+
if result.valid?
|
80
|
+
result.value
|
81
|
+
else
|
82
|
+
raise RSchema::Invalid, result.error
|
83
|
+
end
|
84
|
+
end
|
63
85
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
def validate!(value, options=Options.default)
|
76
|
-
result = underlying_schema.call(value, options)
|
77
|
-
if result.valid?
|
78
|
-
result.value
|
79
|
-
else
|
80
|
-
raise RSchema::Invalid.new(result.error)
|
81
|
-
end
|
82
|
-
end
|
86
|
+
#
|
87
|
+
# Checks whether a value is valid or not
|
88
|
+
#
|
89
|
+
# @param value [Object] The value to validate
|
90
|
+
# @return [Boolean] `true` if the value is valid, otherwise `false`
|
91
|
+
#
|
92
|
+
def valid?(value)
|
93
|
+
result = underlying_schema.call(value, Options.fail_fast)
|
94
|
+
result.valid?
|
95
|
+
end
|
83
96
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
result.valid?
|
93
|
-
end
|
97
|
+
#
|
98
|
+
# The opposite of {#valid?}
|
99
|
+
#
|
100
|
+
# @see #valid?
|
101
|
+
#
|
102
|
+
def invalid?(value)
|
103
|
+
!valid?(value)
|
104
|
+
end
|
94
105
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
106
|
+
#
|
107
|
+
# Wraps the given schema in a {Convenience}, if it isn't already wrapped.
|
108
|
+
#
|
109
|
+
# @param schema [schema] The schema to wrap
|
110
|
+
# @return {Convenience}
|
111
|
+
#
|
112
|
+
def self.wrap(schema)
|
113
|
+
if schema.is_a?(self)
|
114
|
+
schema
|
115
|
+
else
|
116
|
+
new(schema)
|
117
|
+
end
|
118
|
+
end
|
103
119
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
new(schema)
|
115
|
-
end
|
116
|
-
end
|
120
|
+
#
|
121
|
+
# Removes any {Convenience} wrappers from a schema
|
122
|
+
#
|
123
|
+
# @param schema [schema] The schema to unwrap
|
124
|
+
# @return [schema] The underlying schema, with all {Convenience} wrappers
|
125
|
+
# removed
|
126
|
+
def self.unwrap(schema)
|
127
|
+
schema = schema.underlying_schema while schema.is_a?(self)
|
128
|
+
schema
|
129
|
+
end
|
117
130
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
# @return [schema] The underlying schema, with all {Convenience} wrappers
|
123
|
-
# removed
|
124
|
-
def self.unwrap(schema)
|
125
|
-
while schema.is_a?(self)
|
126
|
-
schema = schema.underlying_schema
|
131
|
+
# @!visibility private
|
132
|
+
def with_wrapped_subschemas(wrapper)
|
133
|
+
self.class.new(wrapper.wrap(underlying_schema))
|
134
|
+
end
|
127
135
|
end
|
128
|
-
schema
|
129
|
-
end
|
130
|
-
|
131
|
-
# @!visibility private
|
132
|
-
def with_wrapped_subschemas(wrapper)
|
133
|
-
self.class.new(wrapper.wrap(underlying_schema))
|
134
136
|
end
|
135
|
-
|
136
|
-
end
|
137
|
-
end
|
138
137
|
end
|