rschema 3.0.1.pre5 → 3.0.2
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/README.md +188 -164
- data/lib/rschema.rb +35 -20
- data/lib/rschema/dsl.rb +89 -82
- data/lib/rschema/options.rb +4 -0
- data/lib/rschema/schemas/anything.rb +10 -0
- data/lib/rschema/schemas/boolean.rb +10 -0
- data/lib/rschema/schemas/coercer.rb +8 -0
- data/lib/rschema/schemas/convenience.rb +91 -6
- data/lib/rschema/schemas/enum.rb +10 -0
- data/lib/rschema/schemas/fixed_hash.rb +57 -10
- data/lib/rschema/schemas/fixed_length_array.rb +12 -0
- data/lib/rschema/schemas/maybe.rb +12 -1
- data/lib/rschema/schemas/pipeline.rb +15 -0
- data/lib/rschema/schemas/predicate.rb +11 -0
- data/lib/rschema/schemas/set.rb +10 -0
- data/lib/rschema/schemas/sum.rb +11 -0
- data/lib/rschema/schemas/type.rb +17 -0
- data/lib/rschema/schemas/variable_hash.rb +10 -0
- data/lib/rschema/schemas/variable_length_array.rb +10 -0
- data/lib/rschema/version.rb +1 -1
- metadata +18 -4
@@ -1,5 +1,13 @@
|
|
1
1
|
module RSchema
|
2
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
|
+
#
|
3
11
|
class Coercer
|
4
12
|
attr_reader :coercer, :subschema
|
5
13
|
|
@@ -2,21 +2,78 @@ require 'delegate'
|
|
2
2
|
|
3
3
|
module RSchema
|
4
4
|
module Schemas
|
5
|
+
|
6
|
+
#
|
7
|
+
# A wrapper that provides convenience methods for schema objects.
|
8
|
+
#
|
9
|
+
# Because this class inherits from `SimpleDelegator`, convenience wrappers
|
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
|
+
#
|
5
20
|
class Convenience < SimpleDelegator
|
6
|
-
def initialize(
|
21
|
+
def initialize(underlying_schema)
|
7
22
|
super
|
8
23
|
end
|
9
24
|
|
10
|
-
|
25
|
+
# @return [schema] the underlying schema object
|
26
|
+
def underlying_schema
|
11
27
|
__getobj__
|
12
28
|
end
|
13
29
|
|
30
|
+
#
|
31
|
+
# Applies the schema to a value
|
32
|
+
#
|
33
|
+
# This is that same as the `call` method available on all schema objects,
|
34
|
+
# except that the `options` param is optional.
|
35
|
+
#
|
36
|
+
# @param value [Object] The value to validate
|
37
|
+
# @param options [RSchema::Options]
|
38
|
+
#
|
39
|
+
# @return [RSchema::Result]
|
40
|
+
#
|
14
41
|
def validate(value, options=Options.default)
|
15
42
|
call(value, options)
|
16
43
|
end
|
17
44
|
|
45
|
+
#
|
46
|
+
# Returns the validation error for the given value
|
47
|
+
#
|
48
|
+
# @param value [Object] The value to validate
|
49
|
+
# @param options [RSchema::Options]
|
50
|
+
#
|
51
|
+
# @return The error object if `value` is invalid, otherwise `nil`.
|
52
|
+
#
|
53
|
+
# @see Result#error
|
54
|
+
#
|
55
|
+
def error_for(value, options=Options.default)
|
56
|
+
result = underlying_schema.call(value, options)
|
57
|
+
if result.valid?
|
58
|
+
nil
|
59
|
+
else
|
60
|
+
result.error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Applies the schema to a value, raising an exception if the value is invalid
|
66
|
+
#
|
67
|
+
# @param value [Object] The value to validate
|
68
|
+
# @param options [RSchema::Options]
|
69
|
+
#
|
70
|
+
# @raise [RSchema::Invalid] If the value is not valid
|
71
|
+
# @return [Object] The validated value
|
72
|
+
#
|
73
|
+
# @see Result#value
|
74
|
+
#
|
18
75
|
def validate!(value, options=Options.default)
|
19
|
-
result = call(value, options)
|
76
|
+
result = underlying_schema.call(value, options)
|
20
77
|
if result.valid?
|
21
78
|
result.value
|
22
79
|
else
|
@@ -24,11 +81,32 @@ class Convenience < SimpleDelegator
|
|
24
81
|
end
|
25
82
|
end
|
26
83
|
|
84
|
+
#
|
85
|
+
# Checks whether a value is valid or not
|
86
|
+
#
|
87
|
+
# @param value [Object] The value to validate
|
88
|
+
# @return [Boolean] `true` if the value is valid, otherwise `false`
|
89
|
+
#
|
27
90
|
def valid?(value)
|
28
|
-
result = call(value, Options.
|
91
|
+
result = underlying_schema.call(value, Options.fail_fast)
|
29
92
|
result.valid?
|
30
93
|
end
|
31
94
|
|
95
|
+
#
|
96
|
+
# The opposite of {#valid?}
|
97
|
+
#
|
98
|
+
# @see #valid?
|
99
|
+
#
|
100
|
+
def invalid?(value)
|
101
|
+
not valid?(value)
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Wraps the given schema in a {Convenience}, if it isn't already wrapped.
|
106
|
+
#
|
107
|
+
# @param schema [schema] The schema to wrap
|
108
|
+
# @return {Convenience}
|
109
|
+
#
|
32
110
|
def self.wrap(schema)
|
33
111
|
if schema.is_a?(self)
|
34
112
|
schema
|
@@ -37,15 +115,22 @@ class Convenience < SimpleDelegator
|
|
37
115
|
end
|
38
116
|
end
|
39
117
|
|
118
|
+
#
|
119
|
+
# Removes any {Convenience} wrappers from a schema
|
120
|
+
#
|
121
|
+
# @param schema [schema] The schema to unwrap
|
122
|
+
# @return [schema] The underlying schema, with all {Convenience} wrappers
|
123
|
+
# removed
|
40
124
|
def self.unwrap(schema)
|
41
125
|
while schema.is_a?(self)
|
42
|
-
schema = schema.
|
126
|
+
schema = schema.underlying_schema
|
43
127
|
end
|
44
128
|
schema
|
45
129
|
end
|
46
130
|
|
131
|
+
# @!visibility private
|
47
132
|
def with_wrapped_subschemas(wrapper)
|
48
|
-
self.class.new(wrapper.wrap(
|
133
|
+
self.class.new(wrapper.wrap(underlying_schema))
|
49
134
|
end
|
50
135
|
|
51
136
|
end
|
data/lib/rschema/schemas/enum.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that matches a values in a given set.
|
6
|
+
#
|
7
|
+
# @example Rock-Paper-Scissors values
|
8
|
+
# schema = RSchema.define { enum([:rock, :paper, :scissors]) }
|
9
|
+
# schema.valid?(:rock) #=> true
|
10
|
+
# schema.valid?(:paper) #=> true
|
11
|
+
# schema.valid?(:gun) #=> false
|
12
|
+
#
|
3
13
|
class Enum
|
4
14
|
attr_reader :members, :subschema
|
5
15
|
|
@@ -1,5 +1,19 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that matches `Hash` objects with known keys
|
6
|
+
#
|
7
|
+
# @example A typical fixed hash schema
|
8
|
+
# schema = RSchema.define do
|
9
|
+
# fixed_hash(
|
10
|
+
# name: _String,
|
11
|
+
# optional(:age) => _Integer,
|
12
|
+
# )
|
13
|
+
# end
|
14
|
+
# schema.valid?({ name: "Tom" }) #=> true
|
15
|
+
# schema.valid?({ name: "Dane", age: 55 }) #=> true
|
16
|
+
#
|
3
17
|
class FixedHash
|
4
18
|
attr_reader :attributes
|
5
19
|
|
@@ -32,6 +46,28 @@ class FixedHash
|
|
32
46
|
attributes.find{ |attr| attr.key == attr_key }
|
33
47
|
end
|
34
48
|
|
49
|
+
#
|
50
|
+
# Creates a new {FixedHash} schema with the given attributes merged in
|
51
|
+
#
|
52
|
+
# @param new_attributes [Array<Attribute>] The attributes to merge
|
53
|
+
# @return [FixedHash] A new schema with the given attributes merged in
|
54
|
+
#
|
55
|
+
# @example Merging new attributes into an existing {Schemas::FixedHash} schema
|
56
|
+
# person_schema = RSchema.define_hash {{
|
57
|
+
# name: _String,
|
58
|
+
# age: _Integer,
|
59
|
+
# }}
|
60
|
+
# person_schema.valid?(name: "t", age: 5) #=> true
|
61
|
+
# person_schema.valid?(name: "t", age: 5, id: 3) #=> false
|
62
|
+
#
|
63
|
+
# person_with_id_schema = RSchema.define do
|
64
|
+
# person_schema.merge(attributes(
|
65
|
+
# id: _Integer,
|
66
|
+
# ))
|
67
|
+
# end
|
68
|
+
# person_with_id_schema.valid?(name: "t", age: 5, id: 3) #=> true
|
69
|
+
# person_with_id_schema.valid?(name: "t", age: 5) #=> false
|
70
|
+
#
|
35
71
|
def merge(new_attributes)
|
36
72
|
merged_attrs = (attributes + new_attributes)
|
37
73
|
.map { |attr| [attr.key, attr] }
|
@@ -41,6 +77,22 @@ class FixedHash
|
|
41
77
|
self.class.new(merged_attrs)
|
42
78
|
end
|
43
79
|
|
80
|
+
#
|
81
|
+
# Creates a new {FixedHash} schema with the given attributes removed
|
82
|
+
#
|
83
|
+
# @param attribute_keys [Array<Object>] The keys to remove
|
84
|
+
# @return [FixedHash] A new schema with the given attributes removed
|
85
|
+
#
|
86
|
+
# @example Removing an attribute
|
87
|
+
# cat_and_dog = RSchema.define_hash {{
|
88
|
+
# dog: _String,
|
89
|
+
# cat: _String,
|
90
|
+
# }}
|
91
|
+
#
|
92
|
+
# only_cat = RSchema.define { cat_and_dog.without(:dog) }
|
93
|
+
# only_cat.valid?({ cat: 'meow' }) #=> true
|
94
|
+
# only_cat.valid?({ cat: 'meow', dog: 'woof' }) #=> false
|
95
|
+
#
|
44
96
|
def without(attribute_keys)
|
45
97
|
filtered_attrs = attributes
|
46
98
|
.reject { |attr| attribute_keys.include?(attr.key) }
|
@@ -104,16 +156,11 @@ class FixedHash
|
|
104
156
|
subresults_by_key
|
105
157
|
end
|
106
158
|
|
107
|
-
def failure_error(
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
error[key] = attr_result.error
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
error
|
159
|
+
def failure_error(subresults)
|
160
|
+
subresults
|
161
|
+
.select{ |_, result| result.invalid? }
|
162
|
+
.map{ |key, result| [key, result.error] }
|
163
|
+
.to_h
|
117
164
|
end
|
118
165
|
|
119
166
|
def success_value(subresults)
|
@@ -1,5 +1,17 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that represents an array of fixed length
|
6
|
+
#
|
7
|
+
# Each element in the fixed-length array has its own subschema
|
8
|
+
#
|
9
|
+
# @example A fixed-length array schema
|
10
|
+
# schema = RSchema.define { array(_Integer, _String) }
|
11
|
+
# schema.valid?([5, "hello"]) #=> true
|
12
|
+
# schema.valid?([5]) #=> false
|
13
|
+
# schema.valid?([5, "hello", "world"]) #=> false
|
14
|
+
#
|
3
15
|
class FixedLengthArray
|
4
16
|
attr_reader :subschemas
|
5
17
|
|
@@ -1,5 +1,16 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema representing that a value may be `nil`
|
6
|
+
#
|
7
|
+
# If the value is not `nil`, it must conform to the subschema
|
8
|
+
#
|
9
|
+
# @example A nil-able Integer
|
10
|
+
# schema = RSchema.define{ maybe(_Integer) }
|
11
|
+
# schema.valid?(5) #=> true
|
12
|
+
# schema.valid?(nil) #=> true
|
13
|
+
#
|
3
14
|
class Maybe
|
4
15
|
attr_reader :subschema
|
5
16
|
|
@@ -8,7 +19,7 @@ class Maybe
|
|
8
19
|
end
|
9
20
|
|
10
21
|
def call(value, options)
|
11
|
-
if
|
22
|
+
if nil == value
|
12
23
|
Result.success(value)
|
13
24
|
else
|
14
25
|
@subschema.call(value, options)
|
@@ -1,5 +1,20 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that chains together an ordered list of other schemas
|
6
|
+
#
|
7
|
+
# @example A schema for positive floats
|
8
|
+
# schema = RSchema.define do
|
9
|
+
# pipeline(
|
10
|
+
# _Float,
|
11
|
+
# predicate{ |f| f > 0.0 },
|
12
|
+
# )
|
13
|
+
# end
|
14
|
+
# schema.valid?(6.2) #=> true
|
15
|
+
# schema.valid?('hi') #=> false (because it's not a Float)
|
16
|
+
# schema.valid?(-6.2) #=> false (because predicate failed)
|
17
|
+
#
|
3
18
|
class Pipeline
|
4
19
|
attr_reader :subschemas
|
5
20
|
|
@@ -1,5 +1,16 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that uses a given block to determine whether a value is valid
|
6
|
+
#
|
7
|
+
# @example A predicate that checks if numbers are odd
|
8
|
+
# schema = RSchema.define do
|
9
|
+
# predicate('odd'){ |x| x.odd? }
|
10
|
+
# end
|
11
|
+
# schema.valid?(5) #=> true
|
12
|
+
# schema.valid?(6) #=> false
|
13
|
+
#
|
3
14
|
class Predicate
|
4
15
|
attr_reader :block, :name
|
5
16
|
|
data/lib/rschema/schemas/set.rb
CHANGED
@@ -2,6 +2,16 @@ require 'set'
|
|
2
2
|
|
3
3
|
module RSchema
|
4
4
|
module Schemas
|
5
|
+
|
6
|
+
#
|
7
|
+
# A schema that matches `Set` objects (from the Ruby standard library)
|
8
|
+
#
|
9
|
+
# @example A set of integers
|
10
|
+
# require 'set'
|
11
|
+
# schema = RSchema.define { set(_Integer) }
|
12
|
+
# schema.valid?(Set[1, 2, 3]) #=> true
|
13
|
+
# schema.valid?(Set[:a, :b, :c]) #=> false
|
14
|
+
#
|
5
15
|
class Set
|
6
16
|
attr_reader :subschema
|
7
17
|
|
data/lib/rschema/schemas/sum.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that represents a "sum type"
|
6
|
+
#
|
7
|
+
# Values must conform to one of the subschemas.
|
8
|
+
#
|
9
|
+
# @example A schema that matches both Integers and Strings
|
10
|
+
# schema = RSchema.define { either(_String, _Integer) }
|
11
|
+
# schema.valid?("hello") #=> true
|
12
|
+
# schema.valid?(5) #=> true
|
13
|
+
#
|
3
14
|
class Sum
|
4
15
|
attr_reader :subschemas
|
5
16
|
|
data/lib/rschema/schemas/type.rb
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that matches values of a given type (i.e. `value.is_a?(type)`)
|
6
|
+
#
|
7
|
+
# @example An Integer schema
|
8
|
+
# schema = RSchema.define { _Integer }
|
9
|
+
# schema.valid?(5) #=> true
|
10
|
+
#
|
11
|
+
# @example A namespaced type
|
12
|
+
# schema = RSchema.define do
|
13
|
+
# # This will not work:
|
14
|
+
# # _ActiveWhatever::Thing
|
15
|
+
#
|
16
|
+
# # This will work:
|
17
|
+
# type(ActiveWhatever::Thing)
|
18
|
+
# end
|
19
|
+
#
|
3
20
|
class Type
|
4
21
|
attr_reader :type
|
5
22
|
|
@@ -1,5 +1,15 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that matches variable-sized `Hash` objects, where the keys are _not_
|
6
|
+
# known ahead of time.
|
7
|
+
#
|
8
|
+
# @example A hash of integers to strings
|
9
|
+
# schema = RSchema.define { variable_hash(_Integer => _String) }
|
10
|
+
# schema.valid?({ 5 => "hello", 7 => "world" }) #=> true
|
11
|
+
# schema.valid?({}) #=> true
|
12
|
+
#
|
3
13
|
class VariableHash
|
4
14
|
attr_reader :key_schema, :value_schema
|
5
15
|
|
@@ -1,5 +1,15 @@
|
|
1
1
|
module RSchema
|
2
2
|
module Schemas
|
3
|
+
|
4
|
+
#
|
5
|
+
# A schema that matches variable-length arrays, where all elements conform to
|
6
|
+
# a single subschema
|
7
|
+
#
|
8
|
+
# @example A variable-length array schema
|
9
|
+
# schema = RSchema.define { array(_Integer) }
|
10
|
+
# schema.valid?([1,2,3]) #=> true
|
11
|
+
# schema.valid?([]) #=> true
|
12
|
+
#
|
3
13
|
class VariableLengthArray
|
4
14
|
attr_accessor :element_schema
|
5
15
|
|
data/lib/rschema/version.rb
CHANGED