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.
@@ -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(raw_schema)
21
+ def initialize(underlying_schema)
7
22
  super
8
23
  end
9
24
 
10
- def raw_schema
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.new(fail_fast: true))
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.raw_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(raw_schema))
133
+ self.class.new(wrapper.wrap(underlying_schema))
49
134
  end
50
135
 
51
136
  end
@@ -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(results)
108
- error = {}
109
-
110
- results.each do |key, attr_result|
111
- if attr_result.invalid?
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 value == nil
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
 
@@ -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
 
@@ -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
 
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module RSchema
2
- VERSION = '3.0.1.pre5'
2
+ VERSION = '3.0.2'
3
3
  end