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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b04216668970682a3eaa4bc062784a6d977dbd1e6e795bf30904e28aa48715b8
4
- data.tar.gz: 94151a67d6deecf1b3cc0b799b4b0b23efb945c61e4d79d5d84bfe16144dc6c4
3
+ metadata.gz: a767f94c364d3093e4c335a8a50e66cf93c774d840c6300eed506c4e8bbc2566
4
+ data.tar.gz: 5bbbfd55ba7887b3130f7bbbfa42768be731b3bad9f8030f9b1b16a9c54713c0
5
5
  SHA512:
6
- metadata.gz: c201e08d594b7b2d6b9caa0fa596101d41e5a40a52bc700e0673fae1f20e8461565ba457ddb4b2b80cd9915127c3cbafbfd828205d94e1bc5db51777dda8dea0
7
- data.tar.gz: c7da82328e46a70b91d6ee466c0efd0ae02f52ce8724663fe2ef92010354feb9783f79be68eee3745acfd483d4de3020faed11f24a6bfbf89926263464ec4c49
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
- A notion of `Simple Data` could be defined as follows:
17
- * `nil` is a simple data
18
- * `String` is a simple data
19
- * `Numeric` is a simple data
20
- * Boolean is a simple data
21
- * `Array` of simple data is a simple data
22
- * `Hash` with symbolic keys and simple-data values is a simple data
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('{"title": 42, "tags":[42, {"name":"", "value":"foo"}]}', symbolize_names: true)
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=>["Must be a String"], :body=>["Cannot be null"], :tags=>{0=>["Must be object"], 1=>{:name=>["Cannot be empty"]}}}
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
@@ -13,4 +13,5 @@ Gem::Specification.new do |s|
13
13
  s.files = %w(dto_schema.gemspec) + Dir["*.md", "lib/**/*.rb"]
14
14
  s.homepage = 'https://github.com/stepan-anokhin/dto-schema'
15
15
  s.license = 'MIT'
16
+ s.required_ruby_version = ">= 2.2.10"
16
17
  end
@@ -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
@@ -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
- self.instance_eval &block unless block.nil?
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 object(name, &definition)
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
- def check(name = nil, &body)
58
- return @check_binder if name.nil? && body.nil?
59
- raise ArgumentError, "Check definition name is not provided" if name.nil?
60
- raise ArgumentError, "Check definition is not provided for `#{name}`" if body.nil?
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
- def resolve
67
- @validators.each_value { |validator| validator.resolve }
68
- @checks.each_value { |check| check.resolve }
69
- self
70
- end
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
- def resolve_validator (name)
73
- raise NameError, "Undefined validator `#{name}'" unless @validators.include? name
74
- @validators[name]
75
- end
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
- def resolve_check (name)
78
- raise NameError, "Undefined check `#{name}'" unless @checks.include? name
79
- @checks[name]
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 = resolve_validator type
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 list
231
- @schema.list
232
- end
233
-
234
- def check
235
- @schema.check
206
+ def define_invariant(invariant)
207
+ @invariants << invariant
236
208
  end
237
209
 
238
- def invariant (fields = nil, &block)
239
- fields = [] if fields.nil?
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
- private
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
- def resolve_check (check)
252
- return check if check.is_a? Checks::BoundCheck
253
- return Checks::CheckReference.new @schema, check if check.is_a? Symbol
254
- raise ArgumentError, "Unexpected check type: #{check.class}"
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
@@ -1,3 +1,3 @@
1
1
  module DTOSchema
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
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.1
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: '0'
39
+ version: 2.2.10
40
40
  required_rubygems_version: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - ">="