dto_schema 0.0.0 → 0.0.1
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 +10 -1
- data/lib/dto_schema.rb +6 -2
- data/lib/dto_schema/checks.rb +65 -0
- data/lib/dto_schema/schema.rb +82 -0
- data/lib/dto_schema/validators.rb +260 -0
- data/lib/dto_schema/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b04216668970682a3eaa4bc062784a6d977dbd1e6e795bf30904e28aa48715b8
|
4
|
+
data.tar.gz: 94151a67d6deecf1b3cc0b799b4b0b23efb945c61e4d79d5d84bfe16144dc6c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c201e08d594b7b2d6b9caa0fa596101d41e5a40a52bc700e0673fae1f20e8461565ba457ddb4b2b80cd9915127c3cbafbfd828205d94e1bc5db51777dda8dea0
|
7
|
+
data.tar.gz: c7da82328e46a70b91d6ee466c0efd0ae02f52ce8724663fe2ef92010354feb9783f79be68eee3745acfd483d4de3020faed11f24a6bfbf89926263464ec4c49
|
data/README.md
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
# Ruby DTO-Schema
|
2
|
+
[](https://badge.fury.io/rb/dto_schema)
|
3
|
+
[](https://travis-ci.org/stepan-anokhin/dto-schema)
|
4
|
+
[](https://coveralls.io/github/stepan-anokhin/dto-schema?branch=master)
|
2
5
|
|
3
6
|
DTO-Schema is a small Ruby library to validate simple data.
|
4
7
|
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```shell script
|
11
|
+
gem install dto_schema
|
12
|
+
```
|
13
|
+
|
5
14
|
## What is validated?
|
6
15
|
|
7
16
|
A notion of `Simple Data` could be defined as follows:
|
@@ -18,7 +27,7 @@ Define a schema:
|
|
18
27
|
```ruby
|
19
28
|
require 'dto_schema'
|
20
29
|
|
21
|
-
schema =
|
30
|
+
schema = DTOSchema::define do
|
22
31
|
object :tag do
|
23
32
|
required :name, String, check: [:not_empty]
|
24
33
|
required :value, String, check: [:not_empty]
|
data/lib/dto_schema.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
module DTOSchema
|
2
|
+
module Checks
|
3
|
+
|
4
|
+
class Check
|
5
|
+
def initialize (check)
|
6
|
+
@check = check
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate (data, args = nil)
|
10
|
+
result = @check.call(data) if args.nil?
|
11
|
+
result = @check.call(data, args) unless args.nil?
|
12
|
+
return result if result.is_a? Array
|
13
|
+
return [result] if result.is_a? String
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
def resolve
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class CheckReference
|
23
|
+
def initialize (schema, ref, args = nil)
|
24
|
+
@schema, @ref = schema, ref
|
25
|
+
@args = args
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate (data, args = nil)
|
29
|
+
resolve.validate data, args
|
30
|
+
end
|
31
|
+
|
32
|
+
def resolve
|
33
|
+
@schema.resolve_check @ref
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class BoundCheck
|
38
|
+
def initialize (check, args)
|
39
|
+
@check = check
|
40
|
+
@args = args
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate (data, args = {})
|
44
|
+
args = @args.merge(args)
|
45
|
+
@check.validate data, args
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolve
|
49
|
+
@check.resolve
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class CheckBinder
|
54
|
+
def initialize (schema)
|
55
|
+
@schema = schema
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing (name, args = {})
|
59
|
+
check = CheckReference.new @schema, name
|
60
|
+
BoundCheck.new check, args
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative 'checks'
|
2
|
+
require_relative 'validators'
|
3
|
+
|
4
|
+
module DTOSchema
|
5
|
+
class CheckBinder
|
6
|
+
def initialize (schema)
|
7
|
+
@schema = schema
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing (name, args = {})
|
11
|
+
check = Checks::CheckReference.new @schema, name
|
12
|
+
Checks::BoundCheck.new check, args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Schema
|
17
|
+
def initialize(&block)
|
18
|
+
@validators = {}
|
19
|
+
@checks = {}
|
20
|
+
@check_binder = CheckBinder.new self
|
21
|
+
define(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def define (&block)
|
25
|
+
self.instance_eval &block unless block.nil?
|
26
|
+
resolve
|
27
|
+
end
|
28
|
+
|
29
|
+
def object(name, &definition)
|
30
|
+
validator = Validators::ObjectValidator.new self
|
31
|
+
validator.instance_eval(&definition)
|
32
|
+
@validators[name] = validator
|
33
|
+
|
34
|
+
self.define_singleton_method(name) do
|
35
|
+
validator
|
36
|
+
end
|
37
|
+
|
38
|
+
self.define_singleton_method("#{name}?".to_sym) do |data|
|
39
|
+
validator.valid_structure? data
|
40
|
+
end
|
41
|
+
|
42
|
+
self.define_singleton_method("validate_#{name}".to_sym) do |data|
|
43
|
+
validator.validate data
|
44
|
+
end
|
45
|
+
|
46
|
+
self.define_singleton_method("valid_#{name}?".to_sym) do |data|
|
47
|
+
validator.valid? data
|
48
|
+
end
|
49
|
+
|
50
|
+
validator
|
51
|
+
end
|
52
|
+
|
53
|
+
def list
|
54
|
+
Validators::ListValidator.new self, Validators::AnyValidator.new
|
55
|
+
end
|
56
|
+
|
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
|
65
|
+
|
66
|
+
def resolve
|
67
|
+
@validators.each_value { |validator| validator.resolve }
|
68
|
+
@checks.each_value { |check| check.resolve }
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def resolve_validator (name)
|
73
|
+
raise NameError, "Undefined validator `#{name}'" unless @validators.include? name
|
74
|
+
@validators[name]
|
75
|
+
end
|
76
|
+
|
77
|
+
def resolve_check (name)
|
78
|
+
raise NameError, "Undefined check `#{name}'" unless @checks.include? name
|
79
|
+
@checks[name]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require_relative 'checks'
|
2
|
+
|
3
|
+
module DTOSchema
|
4
|
+
module Validators
|
5
|
+
|
6
|
+
class BaseValidator
|
7
|
+
def resolve
|
8
|
+
self
|
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
|
+
end
|
35
|
+
|
36
|
+
class BoolValidator < BaseValidator
|
37
|
+
def valid? (data)
|
38
|
+
data.is_a?(TrueClass) || data.is_a?(FalseClass)
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate (data)
|
42
|
+
return ["Must be boolean"] unless valid? data
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
|
46
|
+
alias :valid_structure? :valid?
|
47
|
+
end
|
48
|
+
|
49
|
+
class AnyValidator < BaseValidator
|
50
|
+
def valid? (data)
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
|
58
|
+
alias :valid_structure? :valid?
|
59
|
+
end
|
60
|
+
|
61
|
+
class PrimitiveValidator < BaseValidator
|
62
|
+
def initialize (type)
|
63
|
+
@type = type
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid? (data)
|
67
|
+
data.is_a? @type
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate (data)
|
71
|
+
return ["Must be a #{@type}"] unless valid? data
|
72
|
+
[]
|
73
|
+
end
|
74
|
+
|
75
|
+
alias :valid_structure? :valid?
|
76
|
+
end
|
77
|
+
|
78
|
+
class ValidatorReference < BaseValidator
|
79
|
+
def initialize (schema, ref)
|
80
|
+
@schema, @ref = schema, ref
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid? (data)
|
84
|
+
resolve.valid? data
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate (data)
|
88
|
+
resolve.validate data
|
89
|
+
end
|
90
|
+
|
91
|
+
def valid_structure? (data)
|
92
|
+
resolve.valid_structure? data
|
93
|
+
end
|
94
|
+
|
95
|
+
def resolve
|
96
|
+
@schema.resolve_validator @ref
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class ListValidator < BaseValidator
|
101
|
+
def initialize(schema, item_validator)
|
102
|
+
@schema, @item_validator = schema, item_validator
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid? (data)
|
106
|
+
data.is_a?(Array) && data.all? { |item| @item_validator.valid? item }
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate (data)
|
110
|
+
return ["Must be an array"] unless data.is_a? Array
|
111
|
+
result = {}
|
112
|
+
data.each_with_index do |value, i|
|
113
|
+
errors = @item_validator.validate value
|
114
|
+
result[i] = errors unless errors.empty?
|
115
|
+
end
|
116
|
+
result
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_structure? (data)
|
120
|
+
data.is_a?(Array) && data.all? { |item| @item_validator.valid_structure? item }
|
121
|
+
end
|
122
|
+
|
123
|
+
def [] (spec)
|
124
|
+
validator = resolve_validator spec
|
125
|
+
ListValidator.new @schema, validator
|
126
|
+
end
|
127
|
+
|
128
|
+
def resolve
|
129
|
+
@item_validator.resolve
|
130
|
+
self
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class FieldValidator < BaseValidator
|
135
|
+
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)
|
137
|
+
@schema, @name, @required, @type, @check = schema, name, required, type, check
|
138
|
+
@type_validator = resolve_validator type
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate (data)
|
142
|
+
return ["Cannot be null"] if @required && data.nil?
|
143
|
+
return [] if !@required && data.nil?
|
144
|
+
return @type_validator.validate data unless primitive? @type
|
145
|
+
type_check = @type_validator.validate data
|
146
|
+
return type_check unless type_check.empty?
|
147
|
+
@check.collect { |check| check.validate data }.flatten(1)
|
148
|
+
end
|
149
|
+
|
150
|
+
def valid_structure? (data)
|
151
|
+
return !@required if data.nil?
|
152
|
+
@type_validator.valid_structure? data
|
153
|
+
end
|
154
|
+
|
155
|
+
def valid? (data)
|
156
|
+
validate(data).empty?
|
157
|
+
end
|
158
|
+
|
159
|
+
def resolve
|
160
|
+
@type_validator.resolve
|
161
|
+
@check.each { |check| check.resolve }
|
162
|
+
self
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Invariant
|
167
|
+
def initialize (fields, block)
|
168
|
+
@fields = fields || []
|
169
|
+
@check = Checks::Check.new block
|
170
|
+
end
|
171
|
+
|
172
|
+
def validate (data)
|
173
|
+
errors = @check.validate data
|
174
|
+
return {} if errors.empty?
|
175
|
+
return errors if @fields.empty?
|
176
|
+
result = {}
|
177
|
+
@fields.each { |field| result[field] = errors }
|
178
|
+
result
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class ObjectValidator < BaseValidator
|
183
|
+
def initialize (schema)
|
184
|
+
@schema = schema
|
185
|
+
@fields = {}
|
186
|
+
@invariants = []
|
187
|
+
end
|
188
|
+
|
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
|
+
def validate (data)
|
206
|
+
return ["Cannot be null"] if data.nil?
|
207
|
+
return ["Must be object"] unless data.is_a? Hash
|
208
|
+
result = {}
|
209
|
+
@fields.each_pair do |name, validator|
|
210
|
+
errors = validator.validate data[name]
|
211
|
+
result[name] = errors unless errors.empty?
|
212
|
+
end
|
213
|
+
return result unless result.empty?
|
214
|
+
@invariants.each do |invariant|
|
215
|
+
errors = invariant.validate data
|
216
|
+
return errors unless errors.empty?
|
217
|
+
end
|
218
|
+
{}
|
219
|
+
end
|
220
|
+
|
221
|
+
def valid? (data)
|
222
|
+
validate(data).empty?
|
223
|
+
end
|
224
|
+
|
225
|
+
def valid_structure? (data)
|
226
|
+
return false unless data.is_a? Hash
|
227
|
+
@fields.all? { |name, validator| validator.valid_structure? data[name] }
|
228
|
+
end
|
229
|
+
|
230
|
+
def list
|
231
|
+
@schema.list
|
232
|
+
end
|
233
|
+
|
234
|
+
def check
|
235
|
+
@schema.check
|
236
|
+
end
|
237
|
+
|
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)
|
242
|
+
end
|
243
|
+
|
244
|
+
def resolve
|
245
|
+
@fields.each_value { |field| field.resolve }
|
246
|
+
self
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
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}"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
end
|
260
|
+
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.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stepan Anokhin
|
@@ -20,6 +20,9 @@ files:
|
|
20
20
|
- README.md
|
21
21
|
- dto_schema.gemspec
|
22
22
|
- lib/dto_schema.rb
|
23
|
+
- lib/dto_schema/checks.rb
|
24
|
+
- lib/dto_schema/schema.rb
|
25
|
+
- lib/dto_schema/validators.rb
|
23
26
|
- lib/dto_schema/version.rb
|
24
27
|
homepage: https://github.com/stepan-anokhin/dto-schema
|
25
28
|
licenses:
|