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.
data/lib/rschema.rb CHANGED
@@ -13,27 +13,38 @@ require 'rschema/coercion_wrapper'
13
13
  #
14
14
  module RSchema
15
15
 
16
+ #
17
+ # Creates a schema object using a DSL
18
+ #
19
+ # @param dsl (see .dsl_eval)
20
+ # @yield (see .dsl_eval)
21
+ # @return [Schemas::Convenience] The schema object returned from the block,
22
+ # wrapped in a {Schemas::Convenience}.
23
+ #
24
+ # @example (see Schemas::FixedHash)
25
+ #
26
+ def self.define(dsl = nil, &block)
27
+ schema = dsl_eval(dsl, &block)
28
+ Schemas::Convenience.wrap(schema)
29
+ end
30
+
16
31
  #
17
32
  # Runs a block using a DSL.
18
33
  #
19
34
  # @param dsl [Object] An optional DSL object to run the block with.
20
35
  # Uses {RSchema#default_dsl} if nil.
21
36
  # @yield Invokes the given block with access to the methods on `dsl`.
22
- # @return The return value of the given block (usually some kind of schema object)
37
+ # @return The return value of the given block (usually some kind of schema
38
+ # object)
23
39
  #
24
40
  # @example Creating a typical fixed hash schema
25
- # person_schema = RSchema.define do
41
+ # person_schema = RSchema.dsl_eval do
26
42
  # fixed_hash(
27
43
  # name: _String,
28
44
  # age: _Integer,
29
45
  # )
30
46
  # end
31
47
  #
32
- def self.define(dsl = nil, &block)
33
- schema = dsl_eval(dsl, &block)
34
- Schemas::Convenience.wrap(schema)
35
- end
36
-
37
48
  def self.dsl_eval(dsl = nil, &block)
38
49
  Docile::Execution.exec_in_proxy_context(
39
50
  dsl || default_dsl,
@@ -43,18 +54,20 @@ module RSchema
43
54
  end
44
55
 
45
56
  #
46
- # A shortcut for:
57
+ # A convenience method for creating {Schemas::FixedHash} schemas
58
+ #
59
+ # This method is a shorter way to write:
47
60
  #
48
61
  # RSchema.define do
49
62
  # fixed_hash(...)
50
63
  # end
51
64
  #
52
- # @yield Invokes the given block with access to the methods of the default DSL.
65
+ # @yield (see .dsl_eval)
53
66
  # @yieldreturn The attributes of the hash schema (the argument to {DSL#fixed_hash}).
54
- # @return [Schemas::FixedHash]
55
- #
56
- # @example A typical schema
67
+ # @return [Schemas::Convenience] A {Schemas::FixedHash} schema wrapped in a
68
+ # {Schemas::Convenience}.
57
69
  #
70
+ # @example A typical fixed hash schema
58
71
  # person_schema = RSchema.define_hash {{
59
72
  # name: _String,
60
73
  # age: _Integer,
@@ -67,19 +80,21 @@ module RSchema
67
80
  end
68
81
 
69
82
  #
70
- # A shortcut for:
83
+ # A convenience method for creating {Schemas::Predicate} schemas.
84
+ #
85
+ # This method is a shorter way to write:
71
86
  #
72
87
  # RSchema.define do
73
- # predicate { ... }
88
+ # predicate(name) { ... }
74
89
  # end
75
90
  #
76
- # @param name [String] An arbitraty name for the predicate schema.
77
- # @yield [value] Yields a single value.
78
- # @yieldreturn [Boolean] Truthy if the value is valid, otherwise falsey.
79
- # @return [Schemas::Predicate]
91
+ # @param name (see DSL#predicate)
92
+ # @yield (see DSL#predicate)
93
+ # @yieldreturn (see DSL#predicate)
94
+ # @return [Schemas::Convenience] A {Schemas::Predicate} schema wrapped in a
95
+ # {Schemas::Convenience}.
80
96
  #
81
97
  # @example A predicate schema that only allows `odd?` objects.
82
- #
83
98
  # odd_schema = RSchema.define_predicate('odd') do |x|
84
99
  # x.odd?
85
100
  # end
@@ -101,7 +116,7 @@ module RSchema
101
116
  end
102
117
 
103
118
  #
104
- # The class of the default RSchema DSL.
119
+ # The class of the default DSL object.
105
120
  #
106
121
  # By default, this only includes the methods from the {RSchema::DSL} mixin.
107
122
  #
data/lib/rschema/dsl.rb CHANGED
@@ -2,9 +2,8 @@ module RSchema
2
2
  #
3
3
  # A mixin containing all the standard RSchema DSL methods.
4
4
  #
5
- # If you are making a custom DSL, you can include this mixin to get ONLY the
6
- # standard RSchema DSL methods, without any of the extra ones that may have
7
- # been included by third-party gems.
5
+ # This mixin contains only the standard RSchema DSL methods, without any of
6
+ # the extra ones that may have been included by third-party gems/code.
8
7
  #
9
8
  # @note Do not include your custom DSL methods into this module.
10
9
  # Include them into the {DefaultDSL} class instead.
@@ -13,6 +12,10 @@ module RSchema
13
12
  # @see RSchema.default_dsl
14
13
  #
15
14
  module DSL
15
+ # A wrapper class used only by {DSL} to represent optional attributes.
16
+ #
17
+ # @see #attributes
18
+ # @see #fixed_hash
16
19
  OptionalWrapper = Struct.new(:key)
17
20
 
18
21
  #
@@ -27,18 +30,12 @@ module RSchema
27
30
  # type(Integer)
28
31
  #
29
32
  # Underscores will not work for namespaced types (types that include `::`).
30
- # In that case, it is necessary to use the `type` method:
31
- #
32
- # _MyNamespace::MyType # this will NOT work
33
- # type(MyNamespace::MyType) # this will work
33
+ # In that case, it is necessary to use the `type` method.
34
34
  #
35
35
  # @param type [Class]
36
36
  # @return [Schemas::Type]
37
37
  #
38
- # @example An `Integer` type schema
39
- # type(Integer)
40
- # # exactly the same as:
41
- # _Integer
38
+ # @example (see Schemas::Type)
42
39
  #
43
40
  def type(type)
44
41
  Schemas::Type.new(type)
@@ -52,13 +49,8 @@ module RSchema
52
49
  # in the array.
53
50
  # @return [Schemas::VariableLengthArray, Schemas::FixedLengthArray]
54
51
  #
55
- # @example A variable-length array schema
56
- # array(_Integer)
57
- # # matches [1, 2, 3, 4]
58
- #
59
- # @example A fixed-length array schema
60
- # array(_Integer, _String)
61
- # # matches [5, "hello"]
52
+ # @example (see Schemas::VariableLengthArray)
53
+ # @example (see Schemas::FixedLengthArray)
62
54
  #
63
55
  def array(*subschemas)
64
56
  subschemas = subschemas.map{ |ss| inconvenience(ss) }
@@ -75,9 +67,7 @@ module RSchema
75
67
  #
76
68
  # @return [Schemas::Boolean]
77
69
  #
78
- # @example The boolean schema
79
- # boolean
80
- # # matches only `true` and `false`
70
+ # @example (see Schemas::Boolean)
81
71
  #
82
72
  def boolean
83
73
  Schemas::Boolean.instance
@@ -89,13 +79,10 @@ module RSchema
89
79
  # @param attribute_hash (see #attributes)
90
80
  # @return [Schemas::FixedHash]
91
81
  #
92
- # @example A typical fixed hash schema
93
- # fixed_hash(
94
- # name: _String,
95
- # optional(:age) => _Integer,
96
- # )
97
- # # matches { name: "Tom" }
98
- # # matches { name: "Dane", age: 55 }
82
+ # @example (see Schemas::FixedHash)
83
+ #
84
+ # @see RSchema.define_hash
85
+ # @see #variable_hash
99
86
  #
100
87
  def fixed_hash(attribute_hash)
101
88
  Schemas::FixedHash.new(attributes(attribute_hash))
@@ -108,9 +95,7 @@ module RSchema
108
95
  # @param subschema [schema] A schema representing the elements of the set
109
96
  # @return [Schemas::Set]
110
97
  #
111
- # @example A set of integers
112
- # set(_Integer)
113
- # # matches Set[1,2,3]
98
+ # @example (see Schemas::Set)
114
99
  #
115
100
  def set(subschema)
116
101
  Schemas::Set.new(inconvenience(subschema))
@@ -141,9 +126,9 @@ module RSchema
141
126
  # The value is a schema representing all values.
142
127
  # @return [Schemas::VariableHash]
143
128
  #
144
- # @example A hash of integers to strings
145
- # variable_hash(_Integer => _String)
146
- # # matches { 5 => "hello", 7 => "world" }
129
+ # @example (see Schemas::VariableHash)
130
+ #
131
+ # @see #fixed_hash
147
132
  #
148
133
  def variable_hash(subschemas)
149
134
  unless subschemas.is_a?(Hash) && subschemas.size == 1
@@ -162,32 +147,15 @@ module RSchema
162
147
  # Primarily for use with {Schemas::FixedHash#merge}.
163
148
  #
164
149
  # @param attribute_hash [Hash<key, schema>] A hash of keys to subschemas.
165
- # The values of this parameter must be schema objects.
150
+ # The values of this hash must be schema objects.
166
151
  # The keys should be the exact keys expected in the represented `Hash`
167
152
  # (`Strings`, `Symbols`, whatever). Keys can be wrapped with {#optional}
168
- # to indicate that the key can be missing in the represented `Hash`.
153
+ # to indicate that they can be missing from the represented `Hash`.
169
154
  # @return [Array<Schemas::FixedHash::Attribute>]
170
155
  #
171
156
  # @see Schemas::FixedHash#merge
172
157
  #
173
- # @example Merging new attributes into an existing {Schemas::FixedHash} schema
174
- # person_schema = fixed_hash(
175
- # first_name: _String,
176
- # last_name: _String,
177
- # )
178
- #
179
- # person_record_schema = person_schema.merge(attributes(
180
- # id: _Integer,
181
- # optional(:updated_at) => _Time,
182
- # ))
183
- #
184
- # # person_record_schema matches:
185
- # # {
186
- # # id: 3,
187
- # # updated_at: Time.now,
188
- # # first_name: "Tom",
189
- # # last_name: "Dalling",
190
- # # }
158
+ # @example (see Schemas::FixedHash#merge)
191
159
  #
192
160
  def attributes(attribute_hash)
193
161
  attribute_hash.map do |dsl_key, value_schema|
@@ -204,10 +172,7 @@ module RSchema
204
172
  # is not `nil`.
205
173
  # @return [Schemas::Maybe]
206
174
  #
207
- # @example A nullable Integer
208
- # maybe(_Integer)
209
- # # matches 5
210
- # # matches nil
175
+ # @example (see Schemas::Maybe)
211
176
  #
212
177
  def maybe(subschema)
213
178
  Schemas::Maybe.new(inconvenience(subschema))
@@ -223,11 +188,7 @@ module RSchema
223
188
  # as the inferred subschema).
224
189
  # @return [Schemas::Enum]
225
190
  #
226
- # @example Valid Rock-Paper-Scissors turn values
227
- # enum([:rock, :paper, :scissors])
228
- # # matches :rock
229
- # # matches :paper
230
- # # matches :scissors
191
+ # @example (see Schemas::Enum)
231
192
  #
232
193
  def enum(valid_values, subschema=nil)
233
194
  subschema = inconvenience(subschema) if subschema
@@ -241,10 +202,7 @@ module RSchema
241
202
  # valid values.
242
203
  # @return [Schemas::Sum]
243
204
  #
244
- # @example A schema that matches both Integers and Strings
245
- # either(_String, _Integer)
246
- # # matches "hello"
247
- # # matches 1337
205
+ # @example (see Schemas::Sum)
248
206
  #
249
207
  def either(*subschemas)
250
208
  subschemas = subschemas.map{ |ss| inconvenience(ss) }
@@ -256,16 +214,16 @@ module RSchema
256
214
  #
257
215
  # @param name [String] An optional name for the predicate schema. This
258
216
  # serves no purpose other than to provide useful debugging information,
259
- # or perhaps some metadata for the schema.
217
+ # or perhaps some metadata.
260
218
  # @yield Values being validated are yielded to the given block. The return
261
219
  # value of the block indicates whether the value is valid or not.
262
220
  # @yieldparam value [Object] The value being validated
263
221
  # @yieldreturn [Boolean] Truthy if the value is valid, otherwise falsey.
264
222
  # @return [Schemas::Predicate]
265
223
  #
266
- # @example A predicate that checks if numbers are odd
267
- # predicate('odd'){ |x| x.odd? }
268
- # # matches 5
224
+ # @example (see Schemas::Predicate)
225
+ #
226
+ # @see RSchema.define_predicate
269
227
  #
270
228
  def predicate(name = nil, &block)
271
229
  Schemas::Predicate.new(name, &block)
@@ -278,12 +236,7 @@ module RSchema
278
236
  # in order.
279
237
  # @return [Schemas::Pipeline]
280
238
  #
281
- # @example A schema for positive floats
282
- # pipeline(
283
- # _Float,
284
- # predicate{ |f| f > 0.0 },
285
- # )
286
- # # matches 6.2
239
+ # @example (see Schemas::Pipeline)
287
240
  #
288
241
  def pipeline(*subschemas)
289
242
  subschemas = subschemas.map{ |ss| inconvenience(ss) }
@@ -295,24 +248,78 @@ module RSchema
295
248
  #
296
249
  # @return [Schemas::Anything]
297
250
  #
298
- # @example The anything schema
299
- # anything
300
- # # matches nil
301
- # # matches 6.2
302
- # # matches { hello: Time.now }
251
+ # @example (see Schemas::Anything)
303
252
  #
304
253
  def anything
305
254
  Schemas::Anything.instance
306
255
  end
307
256
 
257
+ #
258
+ # Wraps a schema in a {Schemas::Convenience}
259
+ #
260
+ # It is not normally necessary to do this wrapping manually. Methods like
261
+ # {RSchema.define}, {RSchema.define_predicate} and {RSchema.define_hash}
262
+ # already return schema objects wrapped in {Schemas::Convenience}.
263
+ #
264
+ # @param schema [schema] The schema to wrap
265
+ # @return [Schemas::Convenience]
266
+ #
267
+ # @example Manually wrapping a schema with `convenience`
268
+ # # Unlike `RSchema.define`, the `RSchema.dsl_eval` method does not
269
+ # # wrap the return value with RSchema::Schemas::Convenience, so the
270
+ # # returned schema is missing convenience methods like `valid?`
271
+ # schema = RSchema.dsl_eval { _Integer }
272
+ # schema.valid?(5) #=> NoMethodError: undefined method `valid?'
273
+ #
274
+ # # After manually wrapping the schema, the convenience methods are
275
+ # # available
276
+ # schema = RSchema.dsl_eval { convenience(_Integer) }
277
+ # schema.valid?(5) #=> true
278
+ #
308
279
  def convenience(schema)
309
280
  Schemas::Convenience.wrap(schema)
310
281
  end
311
282
 
283
+ #
284
+ # Removes any {Schemas::Convenience} wrappers from a schema.
285
+ #
286
+ # This method is only really useful when defining your own custom DSL
287
+ # methods.
288
+ #
289
+ # When creating a composite schema that contains other subschemas, it is
290
+ # unneccessary to have the subschemas wrapped in {Schemas::Convenience}.
291
+ # Using wrapped subschemas should not cause any errors, but unwrapped
292
+ # subschemas will have slightly better performance. So, when your custom
293
+ # DSL method is creating a composite schema, use {#inconvenience} to unwrap
294
+ # all the subschemas.
295
+ #
296
+ # @return [schema] The underlying schema object, once all convenience
297
+ # wrappers have been removed.
298
+ #
299
+ # @example Unwrapping subschemas in a custom DSL method
300
+ # module MyCustomDSL
301
+ # def pair(subschema)
302
+ # unwrapped = inconvenience(subschema)
303
+ # RSchema::Schemas::FixedLengthArray.new([unwrapped, unwrapped])
304
+ # end
305
+ # end
306
+ #
307
+ # RSchema::DefaultDSL.include(MyCustomDSL)
308
+ #
309
+ # schema = RSchema.define{ pair(_Integer) }
310
+ # schema.valid?([4, 6]) #=> true
311
+ #
312
312
  def inconvenience(schema)
313
313
  Schemas::Convenience.unwrap(schema)
314
314
  end
315
315
 
316
+ #
317
+ # Convenient way to create {Schemas::Type} schemas
318
+ #
319
+ # See {#type} for details.
320
+ #
321
+ # @see #type
322
+ #
316
323
  def method_missing(sym, *args, &block)
317
324
  type = sym.to_s
318
325
  if type.start_with?('_') && args.empty? && block.nil?
@@ -4,6 +4,10 @@ module RSchema
4
4
  @default ||= new
5
5
  end
6
6
 
7
+ def self.fail_fast
8
+ @fail_fast ||= new(fail_fast: true)
9
+ end
10
+
7
11
  def initialize(fail_fast: false)
8
12
  @fail_fast = fail_fast
9
13
  end
@@ -1,5 +1,15 @@
1
1
  module RSchema
2
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
+ #
3
13
  class Anything
4
14
 
5
15
  def self.instance
@@ -1,5 +1,15 @@
1
1
  module RSchema
2
2
  module Schemas
3
+
4
+ #
5
+ # A schema that matches only `true` and `false`
6
+ #
7
+ # @example The boolean schema
8
+ # schema = RSchema.define { boolean }
9
+ # schema.valid?(true) #=> true
10
+ # schema.valid?(false) #=> true
11
+ # schema.valid?(nil) #=> false
12
+ #
3
13
  class Boolean
4
14
  def self.instance
5
15
  @instance ||= new