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
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
|
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.
|
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
|
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
|
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
|
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
|
77
|
-
# @yield
|
78
|
-
# @yieldreturn
|
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
|
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
|
-
#
|
6
|
-
#
|
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
|
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
|
56
|
-
#
|
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
|
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
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
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
|
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
|
145
|
-
#
|
146
|
-
#
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
267
|
-
#
|
268
|
-
#
|
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
|
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
|
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?
|
data/lib/rschema/options.rb
CHANGED
@@ -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
|