rschema 3.0.1.pre5 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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