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