dto_schema 0.0.1 → 0.1.0
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 +319 -11
- data/dto_schema.gemspec +1 -0
- data/lib/dto_schema/checks.rb +28 -2
- data/lib/dto_schema/schema.rb +60 -30
- data/lib/dto_schema/validators.rb +90 -66
- data/lib/dto_schema/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a767f94c364d3093e4c335a8a50e66cf93c774d840c6300eed506c4e8bbc2566
|
4
|
+
data.tar.gz: 5bbbfd55ba7887b3130f7bbbfa42768be731b3bad9f8030f9b1b16a9c54713c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5427d6ea29d2096a6fbf06cec0cfa82aeea1dc3951d51d42055e6bdbda6436eee2f0d6300adf4431e774f565e6838ec0babca4a701af8c979090b2be678cb14b
|
7
|
+
data.tar.gz: 95b5de458d50891e2360c6c21c3a1b3bb2f4501dc080b3762abb1b423244c25eba7f461bc5a7e4f46f1e8f7734f57960ebf26cc6d996e7f98a67b7252481ce05
|
data/README.md
CHANGED
@@ -11,15 +11,16 @@ DTO-Schema is a small Ruby library to validate simple data.
|
|
11
11
|
gem install dto_schema
|
12
12
|
```
|
13
13
|
|
14
|
-
## What is validated?
|
14
|
+
## What is being validated?
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
* `
|
19
|
-
* `
|
20
|
-
*
|
21
|
-
*
|
22
|
-
* `
|
16
|
+
DTO-Schema validates a _"simple data"_.
|
17
|
+
A notion of _"simple data"_ could be defined as follows:
|
18
|
+
* `nil` is a _simple data_
|
19
|
+
* `String` is a _simple data_
|
20
|
+
* `Numeric` is a _simple data_
|
21
|
+
* Boolean is a _simple data_
|
22
|
+
* `Array` of _simple data_ is a _simple data_ itself
|
23
|
+
* `Hash` with symbolic keys and _simple-data_ values is a _simple data_ itself
|
23
24
|
|
24
25
|
## Example
|
25
26
|
|
@@ -35,7 +36,6 @@ schema = DTOSchema::define do
|
|
35
36
|
|
36
37
|
object :post do
|
37
38
|
required :title, String, check: [:not_empty]
|
38
|
-
required :body, String, check: [:not_empty]
|
39
39
|
optional :tags, list[:tag]
|
40
40
|
end
|
41
41
|
|
@@ -49,12 +49,320 @@ And then use it to validate data:
|
|
49
49
|
```ruby
|
50
50
|
require 'json'
|
51
51
|
|
52
|
-
data = JSON.parse('{"
|
52
|
+
data = JSON.parse('{"tags":[42, {"name":"", "value":"foo"}]}', symbolize_names: true)
|
53
53
|
p schema.post.validate data
|
54
54
|
```
|
55
55
|
|
56
56
|
And result will be:
|
57
57
|
```
|
58
|
-
{:title=>["
|
58
|
+
{:title=>["Cannot be null"], :tags=>{0=>["Must be object"], 1=>{:name=>["Cannot be empty"]}}}
|
59
59
|
```
|
60
60
|
|
61
|
+
## Usage
|
62
|
+
|
63
|
+
* [Creating a schema](#creating-a-schema)
|
64
|
+
* [Defining a DTO type](#defining-a-dto-type)
|
65
|
+
* [Validating data](#validating-data)
|
66
|
+
* [Inline checks](#inline-checks)
|
67
|
+
* [Reusing checks](#reusing-checks)
|
68
|
+
* [Parametrized checks](#parametrized-checks)
|
69
|
+
* [Referencing one DTO from another](#referencing-one-dto-from-another)
|
70
|
+
* [List as attribute type](#list-as-attribute-type)
|
71
|
+
* [List as DTO](#list-as-dto)
|
72
|
+
* [Invariants](#invariants)
|
73
|
+
* [Using Bool and Any](#using-bool-and-any)
|
74
|
+
|
75
|
+
### Creating a schema
|
76
|
+
|
77
|
+
DTO-Schema is defined with `DTOSchema::define` method
|
78
|
+
```ruby
|
79
|
+
require 'dto_schema'
|
80
|
+
|
81
|
+
schema = DTOSchema::define do
|
82
|
+
# schema definition goes here ...
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### Defining a DTO type
|
87
|
+
|
88
|
+
Use the `object <name> do <definition> end` method to define a new DTO type:
|
89
|
+
```ruby
|
90
|
+
require 'dto_schema'
|
91
|
+
|
92
|
+
schema = DTOSchema::define do
|
93
|
+
object :post do
|
94
|
+
required :title, String
|
95
|
+
optional :body, String
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
Each DTO definition provides `required` and `optional` method to declare DTO attributes.
|
100
|
+
|
101
|
+
The schema above defines `post` DTO with optional attribute `body` and required attribute `title`.
|
102
|
+
|
103
|
+
### Validating data
|
104
|
+
|
105
|
+
For each defined DTO schema provides a number of dynamically-generated methods to access it.
|
106
|
+
For example if we define a `post` DTO as shown above, the schema will have the
|
107
|
+
following methods:
|
108
|
+
* `post` - to get the *post*-validator
|
109
|
+
* `post? (data)` - to check if the data has a valid structure
|
110
|
+
(equivalent of `schema.post.valid_structure? data`, more on this later)
|
111
|
+
* `valid_post? (data)` - to check if data is a valid `post` DTO (a short-hand of `schema.post.valid? data`)
|
112
|
+
* `validate_post (data)` - get the data validation errors (a short-hand of `schema.post.validate data`)
|
113
|
+
|
114
|
+
For example:
|
115
|
+
```ruby
|
116
|
+
data = {
|
117
|
+
body: 42
|
118
|
+
}
|
119
|
+
p schema.validate_post data
|
120
|
+
```
|
121
|
+
```
|
122
|
+
{:body=>["Must be a String"], :title=>["Cannot be null"]}
|
123
|
+
```
|
124
|
+
|
125
|
+
### Inline checks
|
126
|
+
To apply a custom checks to a DTO attributes, `required` and `optional` methods accept
|
127
|
+
an optional block which is called during validation with an attribute value as an argument.
|
128
|
+
|
129
|
+
A block must return either an Array of error-messages, a single error message or `nil`.
|
130
|
+
|
131
|
+
Example:
|
132
|
+
```ruby
|
133
|
+
schema = DTOSchema::define do
|
134
|
+
object :post do
|
135
|
+
required :title, String do |value|
|
136
|
+
next "Cannot be empty" if value.empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
optional :body, String do |value|
|
140
|
+
next "Cannot be empty" if value.empty?
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
```ruby
|
146
|
+
data = {
|
147
|
+
title: true,
|
148
|
+
body: ""
|
149
|
+
}
|
150
|
+
p schema.validate_post data
|
151
|
+
```
|
152
|
+
```
|
153
|
+
{:title=>["Must be a String"], :body=>["Cannot be empty"]}
|
154
|
+
```
|
155
|
+
|
156
|
+
### Reusing checks
|
157
|
+
|
158
|
+
You can define and reuse checks using a `check` method.
|
159
|
+
Each attribute may have any number of checks.
|
160
|
+
```ruby
|
161
|
+
schema = DTOSchema::define do
|
162
|
+
object :post do
|
163
|
+
required :title, String, check: :not_empty
|
164
|
+
optional :body, String, check: [:not_empty]
|
165
|
+
end
|
166
|
+
|
167
|
+
check :not_empty do |value|
|
168
|
+
next "Cannot be empty" if value.empty?
|
169
|
+
end
|
170
|
+
end
|
171
|
+
```
|
172
|
+
This is an equivalent of the previous example.
|
173
|
+
|
174
|
+
### Parametrized checks
|
175
|
+
|
176
|
+
Sometimes it is useful to define a parametrized checks. It could be done like this:
|
177
|
+
```ruby
|
178
|
+
schema = DTOSchema::define do
|
179
|
+
object :post do
|
180
|
+
required :title, String, check: check.length(min: 3)
|
181
|
+
optional :body, String, check: check.length(max: 2)
|
182
|
+
end
|
183
|
+
|
184
|
+
check :length do |value, min: 0, max: nil|
|
185
|
+
next "Must contain at least #{min} chars" if value.size < min
|
186
|
+
next "Must contain at max #{max} chars" if !max.nil? && value.size > max
|
187
|
+
end
|
188
|
+
end
|
189
|
+
```
|
190
|
+
```ruby
|
191
|
+
data = {
|
192
|
+
title: "hi",
|
193
|
+
body: "foo"
|
194
|
+
}
|
195
|
+
p schema.validate_post data
|
196
|
+
```
|
197
|
+
```
|
198
|
+
{:title=>["Must contain at least 3 chars"], :body=>["Must contain at max 2 chars"]}
|
199
|
+
```
|
200
|
+
|
201
|
+
### Referencing one DTO from another
|
202
|
+
|
203
|
+
You may define any number of DTOs and embed one into another.
|
204
|
+
To do that you simply use dto name as an attribute type.
|
205
|
+
```ruby
|
206
|
+
schema = DTOSchema::define do
|
207
|
+
object :book_details do
|
208
|
+
required :pages, Numeric
|
209
|
+
required :author, String
|
210
|
+
required :language, String
|
211
|
+
end
|
212
|
+
|
213
|
+
object :book do
|
214
|
+
required :title, String
|
215
|
+
required :text, String
|
216
|
+
required :details, :book_details
|
217
|
+
end
|
218
|
+
end
|
219
|
+
```
|
220
|
+
```ruby
|
221
|
+
data = {
|
222
|
+
title: "Moby-Dick",
|
223
|
+
text: "Call me Ishmael...",
|
224
|
+
details: {
|
225
|
+
author: "Herman Melville",
|
226
|
+
pages: 768,
|
227
|
+
}
|
228
|
+
}
|
229
|
+
p schema.validate_book data
|
230
|
+
```
|
231
|
+
```
|
232
|
+
{:details=>{:language=>["Cannot be null"]}}
|
233
|
+
```
|
234
|
+
|
235
|
+
### List as attribute type
|
236
|
+
|
237
|
+
DTO-Schema provides a `list` method to define list-attributes:
|
238
|
+
```ruby
|
239
|
+
schema = DTOSchema::define do
|
240
|
+
object :book do
|
241
|
+
required :title, String
|
242
|
+
required :pages, list[String]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
```
|
246
|
+
```ruby
|
247
|
+
data = {
|
248
|
+
title: "Moby-Dick",
|
249
|
+
pages: ["Call me Ishmael...", 42]
|
250
|
+
}
|
251
|
+
p schema.validate_book data
|
252
|
+
```
|
253
|
+
```
|
254
|
+
{:pages=>{1=>["Must be a String"]}}
|
255
|
+
```
|
256
|
+
|
257
|
+
You may reference any DTO as a list item type:
|
258
|
+
```ruby
|
259
|
+
schema = DTOSchema::define do
|
260
|
+
object :tree do
|
261
|
+
required :value, Numeric
|
262
|
+
optional :child, list[:tree]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
266
|
+
```ruby
|
267
|
+
data = {
|
268
|
+
value: 42,
|
269
|
+
child: [
|
270
|
+
{
|
271
|
+
value: 12
|
272
|
+
},
|
273
|
+
{
|
274
|
+
value: "wrong!"
|
275
|
+
}
|
276
|
+
]
|
277
|
+
}
|
278
|
+
p schema.validate_tree data
|
279
|
+
```
|
280
|
+
```
|
281
|
+
{:child=>{1=>{:value=>["Must be a Numeric"]}}}
|
282
|
+
```
|
283
|
+
List item type may be any valid type including list itself: `list[list[Numeric]]`.
|
284
|
+
|
285
|
+
### List as DTO
|
286
|
+
|
287
|
+
DTO-Schema allows to declare list as independent DTO type
|
288
|
+
```ruby
|
289
|
+
schema = DTOSchema::define do
|
290
|
+
object :polygon do
|
291
|
+
required :name, String
|
292
|
+
optional :vertices, list[:point]
|
293
|
+
end
|
294
|
+
|
295
|
+
list :point, Float do |items|
|
296
|
+
next "Must contain exactly 2 items" unless items.size == 2
|
297
|
+
end
|
298
|
+
end
|
299
|
+
```
|
300
|
+
```ruby
|
301
|
+
data = {
|
302
|
+
name: "My Polygon",
|
303
|
+
vertices: [
|
304
|
+
[1.0, 2.0],
|
305
|
+
[5.0]
|
306
|
+
],
|
307
|
+
}
|
308
|
+
p schema.validate_polygon data
|
309
|
+
p schema.point? [1.0, 2.0]
|
310
|
+
```
|
311
|
+
```
|
312
|
+
{:vertices=>{1=>["Must contain exactly 2 items"]}}
|
313
|
+
true
|
314
|
+
```
|
315
|
+
|
316
|
+
### Invariants
|
317
|
+
Sometimes it is useful to check some invariants across DTO attributes.
|
318
|
+
To declare such validation DTO-Schema provides an `invariant (fields, &block)` method.
|
319
|
+
```ruby
|
320
|
+
schema = DTOSchema::define do
|
321
|
+
object :new_account do
|
322
|
+
required :password, String
|
323
|
+
required :confirm_password, String
|
324
|
+
|
325
|
+
invariant :confirm_password do |data|
|
326
|
+
next "Passwords must be equal" if data[:password] != data[:confirm_password]
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
```
|
331
|
+
```ruby
|
332
|
+
data = {
|
333
|
+
password: "foo",
|
334
|
+
confirm_password: "bar",
|
335
|
+
}
|
336
|
+
p schema.validate_new_account data
|
337
|
+
```
|
338
|
+
```
|
339
|
+
{:confirm_password=>["Passwords must be equal"]}
|
340
|
+
```
|
341
|
+
|
342
|
+
### Using Bool and Any
|
343
|
+
|
344
|
+
Ruby doesn't have a single class for boolean values (instead it has `TrueClass` and `FalseClass`).
|
345
|
+
DTO-Schema provides a `bool` method to expect boolean attribute type.
|
346
|
+
|
347
|
+
Sometimes we want to verify that some attribute is presented whatever value it has. For this purpose
|
348
|
+
DTO-Schema provides an `any` method that could be used as attribute type.
|
349
|
+
```ruby
|
350
|
+
schema = DTOSchema::define do
|
351
|
+
object :envelope do
|
352
|
+
required :custom_data, any
|
353
|
+
required :flags, list[bool]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
```
|
357
|
+
```ruby
|
358
|
+
data = {
|
359
|
+
custom_data: {
|
360
|
+
anything: "whatever"
|
361
|
+
},
|
362
|
+
flags: [true, false, 42]
|
363
|
+
}
|
364
|
+
p schema.validate_envelope data
|
365
|
+
```
|
366
|
+
```
|
367
|
+
{:flags=>{2=>["Must be boolean"]}}
|
368
|
+
```
|
data/dto_schema.gemspec
CHANGED
data/lib/dto_schema/checks.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
module DTOSchema
|
2
2
|
module Checks
|
3
3
|
|
4
|
+
# A custom DTO-attribute validation
|
4
5
|
class Check
|
5
6
|
def initialize (check)
|
6
7
|
@check = check
|
7
8
|
end
|
8
9
|
|
10
|
+
# Validate attribute value
|
9
11
|
def validate (data, args = nil)
|
10
12
|
result = @check.call(data) if args.nil?
|
11
13
|
result = @check.call(data, args) unless args.nil?
|
@@ -14,52 +16,76 @@ module DTOSchema
|
|
14
16
|
[]
|
15
17
|
end
|
16
18
|
|
19
|
+
# Ensure check doesn't have dangling references
|
17
20
|
def resolve
|
18
|
-
self
|
21
|
+
self # simple Check never has references
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
25
|
+
# A dynamic reference to a custom Check
|
22
26
|
class CheckReference
|
23
27
|
def initialize (schema, ref, args = nil)
|
24
28
|
@schema, @ref = schema, ref
|
25
29
|
@args = args
|
26
30
|
end
|
27
31
|
|
32
|
+
# Validate attribute value
|
28
33
|
def validate (data, args = nil)
|
29
34
|
resolve.validate data, args
|
30
35
|
end
|
31
36
|
|
37
|
+
# Get the referent check
|
32
38
|
def resolve
|
33
39
|
@schema.resolve_check @ref
|
34
40
|
end
|
35
41
|
end
|
36
42
|
|
43
|
+
# A validation check with bound key-word arguments.
|
44
|
+
#
|
37
45
|
class BoundCheck
|
38
46
|
def initialize (check, args)
|
39
47
|
@check = check
|
40
48
|
@args = args
|
41
49
|
end
|
42
50
|
|
51
|
+
# Validate attribute value
|
43
52
|
def validate (data, args = {})
|
44
53
|
args = @args.merge(args)
|
45
|
-
@check.validate data, args
|
54
|
+
@check.validate data, args # pass predefined kw-args
|
46
55
|
end
|
47
56
|
|
57
|
+
# Ensure the underlying check doesn't have dangling references
|
48
58
|
def resolve
|
49
59
|
@check.resolve
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
63
|
+
# It provides check-binding API:
|
64
|
+
# check.length(min: 2, max: 3)
|
53
65
|
class CheckBinder
|
54
66
|
def initialize (schema)
|
55
67
|
@schema = schema
|
56
68
|
end
|
57
69
|
|
70
|
+
# Bind a check by its name passed as dynamic method name.
|
71
|
+
# It is this method that implements check-binding API in schema definition.
|
58
72
|
def method_missing (name, args = {})
|
59
73
|
check = CheckReference.new @schema, name
|
60
74
|
BoundCheck.new check, args
|
61
75
|
end
|
62
76
|
end
|
63
77
|
|
78
|
+
def self.create_check (spec, schema)
|
79
|
+
return spec if spec.is_a? Checks::BoundCheck
|
80
|
+
return Checks::CheckReference.new schema, spec if spec.is_a? Symbol
|
81
|
+
raise ArgumentError, "Unexpected check type: #{spec.class}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.parse_checks(checks, schema)
|
85
|
+
checks ||= []
|
86
|
+
checks = [checks] if checks.is_a?(Symbol) || checks.is_a?(BoundCheck)
|
87
|
+
checks.collect { |spec| Checks::create_check(spec, schema) }
|
88
|
+
end
|
89
|
+
|
64
90
|
end
|
65
91
|
end
|
data/lib/dto_schema/schema.rb
CHANGED
@@ -13,6 +13,7 @@ module DTOSchema
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
|
16
17
|
class Schema
|
17
18
|
def initialize(&block)
|
18
19
|
@validators = {}
|
@@ -22,15 +23,44 @@ module DTOSchema
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def define (&block)
|
25
|
-
|
26
|
+
raise ArgumentError, "Block is expected" if block.nil?
|
27
|
+
builder = Builder.new self
|
28
|
+
builder.instance_eval &block
|
26
29
|
resolve
|
27
30
|
end
|
28
31
|
|
29
|
-
def
|
30
|
-
validator = Validators::ObjectValidator.new self
|
31
|
-
validator.instance_eval(&definition)
|
32
|
+
def define_validator(name, validator)
|
32
33
|
@validators[name] = validator
|
34
|
+
generate_methods(name, validator)
|
35
|
+
end
|
36
|
+
|
37
|
+
def define_check(name, check)
|
38
|
+
@checks[name] = check
|
39
|
+
end
|
40
|
+
|
41
|
+
def bind_check
|
42
|
+
@check_binder
|
43
|
+
end
|
44
|
+
|
45
|
+
def resolve
|
46
|
+
@validators.each_value { |validator| validator.resolve }
|
47
|
+
@checks.each_value { |check| check.resolve }
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_validator (name)
|
52
|
+
raise NameError, "Undefined validator `#{name}'" unless @validators.include? name
|
53
|
+
@validators[name]
|
54
|
+
end
|
55
|
+
|
56
|
+
def resolve_check (name)
|
57
|
+
raise NameError, "Undefined check `#{name}'" unless @checks.include? name
|
58
|
+
@checks[name]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
33
62
|
|
63
|
+
def generate_methods (name, validator)
|
34
64
|
self.define_singleton_method(name) do
|
35
65
|
validator
|
36
66
|
end
|
@@ -46,37 +76,37 @@ module DTOSchema
|
|
46
76
|
self.define_singleton_method("valid_#{name}?".to_sym) do |data|
|
47
77
|
validator.valid? data
|
48
78
|
end
|
49
|
-
|
50
|
-
validator
|
51
|
-
end
|
52
|
-
|
53
|
-
def list
|
54
|
-
Validators::ListValidator.new self, Validators::AnyValidator.new
|
55
79
|
end
|
56
80
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
result = Checks::Check.new(body)
|
62
|
-
@checks[name] = result
|
63
|
-
result
|
64
|
-
end
|
81
|
+
class Builder
|
82
|
+
def initialize(schema)
|
83
|
+
@schema = schema
|
84
|
+
end
|
65
85
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
86
|
+
def object(name, &definition)
|
87
|
+
validator = Validators::ObjectValidator.new @schema
|
88
|
+
builder = Validators::ObjectValidator::Builder.new @schema, validator
|
89
|
+
builder.instance_eval(&definition)
|
90
|
+
@schema.define_validator(name, validator)
|
91
|
+
validator
|
92
|
+
end
|
71
93
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
94
|
+
def list(name, item_type, check: nil, &predicate)
|
95
|
+
check = Checks::parse_checks check, @schema
|
96
|
+
check << Checks::Check.new(predicate) unless predicate.nil?
|
97
|
+
item_validator = Validators::Parse::parse_validator item_type, @schema
|
98
|
+
validator = Validators::ListValidator.new @schema, item_validator, check
|
99
|
+
@schema.define_validator(name, validator)
|
100
|
+
validator
|
101
|
+
end
|
76
102
|
|
77
|
-
|
78
|
-
|
79
|
-
|
103
|
+
def check(name = nil, &body)
|
104
|
+
raise ArgumentError, "Check definition name is not provided" if name.nil?
|
105
|
+
raise ArgumentError, "Check definition is not provided for `#{name}`" if body.nil?
|
106
|
+
result = Checks::Check.new(body)
|
107
|
+
@schema.define_check(name, result)
|
108
|
+
result
|
109
|
+
end
|
80
110
|
end
|
81
111
|
end
|
82
112
|
end
|
@@ -7,30 +7,6 @@ module DTOSchema
|
|
7
7
|
def resolve
|
8
8
|
self
|
9
9
|
end
|
10
|
-
|
11
|
-
protected
|
12
|
-
|
13
|
-
def resolve_validator (spec)
|
14
|
-
return PrimitiveValidator.new(spec) if primitive? spec
|
15
|
-
return spec if validator? spec
|
16
|
-
resolve_reference spec if reference? spec
|
17
|
-
end
|
18
|
-
|
19
|
-
def primitive? (spec)
|
20
|
-
spec.is_a?(Class) && (spec <= Numeric || spec <= String)
|
21
|
-
end
|
22
|
-
|
23
|
-
def validator? (spec)
|
24
|
-
spec.is_a? BaseValidator
|
25
|
-
end
|
26
|
-
|
27
|
-
def reference? (spec)
|
28
|
-
spec.is_a? Symbol
|
29
|
-
end
|
30
|
-
|
31
|
-
def resolve_reference (ref)
|
32
|
-
ValidatorReference.new @schema, ref
|
33
|
-
end
|
34
10
|
end
|
35
11
|
|
36
12
|
class BoolValidator < BaseValidator
|
@@ -44,6 +20,8 @@ module DTOSchema
|
|
44
20
|
end
|
45
21
|
|
46
22
|
alias :valid_structure? :valid?
|
23
|
+
|
24
|
+
INSTANCE = BoolValidator.new
|
47
25
|
end
|
48
26
|
|
49
27
|
class AnyValidator < BaseValidator
|
@@ -51,11 +29,13 @@ module DTOSchema
|
|
51
29
|
true
|
52
30
|
end
|
53
31
|
|
54
|
-
def validate
|
32
|
+
def validate (data)
|
55
33
|
[]
|
56
34
|
end
|
57
35
|
|
58
36
|
alias :valid_structure? :valid?
|
37
|
+
|
38
|
+
INSTANCE = AnyValidator.new
|
59
39
|
end
|
60
40
|
|
61
41
|
class PrimitiveValidator < BaseValidator
|
@@ -98,8 +78,9 @@ module DTOSchema
|
|
98
78
|
end
|
99
79
|
|
100
80
|
class ListValidator < BaseValidator
|
101
|
-
def initialize(schema, item_validator)
|
81
|
+
def initialize(schema, item_validator, checks = nil)
|
102
82
|
@schema, @item_validator = schema, item_validator
|
83
|
+
@checks = checks || []
|
103
84
|
end
|
104
85
|
|
105
86
|
def valid? (data)
|
@@ -113,35 +94,46 @@ module DTOSchema
|
|
113
94
|
errors = @item_validator.validate value
|
114
95
|
result[i] = errors unless errors.empty?
|
115
96
|
end
|
116
|
-
result
|
97
|
+
return result unless result.empty?
|
98
|
+
@checks.each do |check|
|
99
|
+
errors = check.validate data
|
100
|
+
return errors unless errors.empty?
|
101
|
+
end
|
102
|
+
{}
|
117
103
|
end
|
118
104
|
|
119
105
|
def valid_structure? (data)
|
120
106
|
data.is_a?(Array) && data.all? { |item| @item_validator.valid_structure? item }
|
121
107
|
end
|
122
108
|
|
123
|
-
def [] (spec)
|
124
|
-
validator = resolve_validator spec
|
125
|
-
ListValidator.new @schema, validator
|
126
|
-
end
|
127
|
-
|
128
109
|
def resolve
|
129
110
|
@item_validator.resolve
|
130
111
|
self
|
131
112
|
end
|
113
|
+
|
114
|
+
class Builder
|
115
|
+
def initialize(schema)
|
116
|
+
@schema = schema
|
117
|
+
end
|
118
|
+
|
119
|
+
def [] (spec)
|
120
|
+
validator = Parse::parse_validator spec, @schema
|
121
|
+
ListValidator.new @schema, validator
|
122
|
+
end
|
123
|
+
end
|
132
124
|
end
|
133
125
|
|
134
126
|
class FieldValidator < BaseValidator
|
135
127
|
def initialize(schema, name, required, type, check)
|
136
|
-
raise ArgumentError, "'#{name}' cannot have checks as it is not a primitive" unless check.empty? || primitive?(type)
|
128
|
+
raise ArgumentError, "'#{name}' cannot have checks as it is not a primitive" unless check.empty? || Parse::primitive?(type)
|
137
129
|
@schema, @name, @required, @type, @check = schema, name, required, type, check
|
138
|
-
@type_validator =
|
130
|
+
@type_validator = Parse::parse_validator type, @schema
|
139
131
|
end
|
140
132
|
|
141
133
|
def validate (data)
|
142
134
|
return ["Cannot be null"] if @required && data.nil?
|
143
135
|
return [] if !@required && data.nil?
|
144
|
-
return @type_validator.validate data unless primitive? @type
|
136
|
+
return @type_validator.validate data unless Parse::primitive? @type
|
145
137
|
type_check = @type_validator.validate data
|
146
138
|
return type_check unless type_check.empty?
|
147
139
|
@check.collect { |check| check.validate data }.flatten(1)
|
@@ -186,22 +178,6 @@ module DTOSchema
|
|
186
178
|
@invariants = []
|
187
179
|
end
|
188
180
|
|
189
|
-
def field (name, required: false, type: AnyValidator.new, check: nil, &validations)
|
190
|
-
check ||= []
|
191
|
-
check = [check] if check.is_a?(Symbol) || check.is_a?(Checks::BoundCheck)
|
192
|
-
check = check.collect { |check_spec| resolve_check check_spec }
|
193
|
-
check.append(Check.new validations) unless validations.nil?
|
194
|
-
@fields[name] = FieldValidator.new @schema, name, required, type, check
|
195
|
-
end
|
196
|
-
|
197
|
-
def required(name, type, check: nil, &validations)
|
198
|
-
field(name, required: true, type: type, check: check, &validations)
|
199
|
-
end
|
200
|
-
|
201
|
-
def optional(name, type, check: nil, &validations)
|
202
|
-
field(name, required: false, type: type, check: check, &validations)
|
203
|
-
end
|
204
|
-
|
205
181
|
def validate (data)
|
206
182
|
return ["Cannot be null"] if data.nil?
|
207
183
|
return ["Must be object"] unless data.is_a? Hash
|
@@ -227,18 +203,12 @@ module DTOSchema
|
|
227
203
|
@fields.all? { |name, validator| validator.valid_structure? data[name] }
|
228
204
|
end
|
229
205
|
|
230
|
-
def
|
231
|
-
@
|
232
|
-
end
|
233
|
-
|
234
|
-
def check
|
235
|
-
@schema.check
|
206
|
+
def define_invariant(invariant)
|
207
|
+
@invariants << invariant
|
236
208
|
end
|
237
209
|
|
238
|
-
def
|
239
|
-
fields
|
240
|
-
fields = [fields] if fields.is_a? Symbol
|
241
|
-
@invariants << Invariant.new(fields, block)
|
210
|
+
def define_field(name, field)
|
211
|
+
@fields[name] = field
|
242
212
|
end
|
243
213
|
|
244
214
|
def resolve
|
@@ -246,15 +216,69 @@ module DTOSchema
|
|
246
216
|
self
|
247
217
|
end
|
248
218
|
|
249
|
-
|
219
|
+
class Builder
|
220
|
+
def initialize(schema, validator)
|
221
|
+
@schema = schema
|
222
|
+
@validator = validator
|
223
|
+
end
|
224
|
+
|
225
|
+
def field (name, required: false, type: AnyValidator::INSTANCE, check: nil, &validations)
|
226
|
+
check = Checks::parse_checks check, @schema
|
227
|
+
check << Checks::Check.new(validations) unless validations.nil?
|
228
|
+
field = FieldValidator.new(@schema, name, required, type, check)
|
229
|
+
@validator.define_field(name, field)
|
230
|
+
end
|
250
231
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
232
|
+
def required(name, type, check: nil, &validations)
|
233
|
+
field(name, required: true, type: type, check: check, &validations)
|
234
|
+
end
|
235
|
+
|
236
|
+
def optional(name, type, check: nil, &validations)
|
237
|
+
field(name, required: false, type: type, check: check, &validations)
|
238
|
+
end
|
239
|
+
|
240
|
+
def list
|
241
|
+
ListValidator::Builder.new @schema
|
242
|
+
end
|
243
|
+
|
244
|
+
def bool
|
245
|
+
BoolValidator::INSTANCE
|
246
|
+
end
|
247
|
+
|
248
|
+
def any
|
249
|
+
AnyValidator::INSTANCE
|
250
|
+
end
|
251
|
+
|
252
|
+
def check
|
253
|
+
@schema.bind_check
|
254
|
+
end
|
255
|
+
|
256
|
+
def invariant (fields = nil, &block)
|
257
|
+
fields = [] if fields.nil?
|
258
|
+
fields = [fields] if fields.is_a? Symbol
|
259
|
+
@validator.define_invariant Invariant.new(fields, block)
|
260
|
+
end
|
255
261
|
end
|
256
262
|
end
|
257
263
|
|
264
|
+
module Parse
|
265
|
+
def self.parse_validator (spec, schema)
|
266
|
+
return PrimitiveValidator.new(spec) if primitive? spec
|
267
|
+
return spec if validator? spec
|
268
|
+
ValidatorReference.new(schema, spec) if reference? spec
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.primitive? (spec)
|
272
|
+
spec.is_a?(Class) && (spec <= Numeric || spec <= String)
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.validator? (spec)
|
276
|
+
spec.is_a? BaseValidator
|
277
|
+
end
|
258
278
|
|
279
|
+
def self.reference? (spec)
|
280
|
+
spec.is_a? Symbol
|
281
|
+
end
|
282
|
+
end
|
259
283
|
end
|
260
284
|
end
|
data/lib/dto_schema/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dto_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stepan Anokhin
|
@@ -36,7 +36,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
39
|
+
version: 2.2.10
|
40
40
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
41
|
requirements:
|
42
42
|
- - ">="
|