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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5c58baab04f8cdf46c9772682dd02c47de4f6d1ab30d8104d8b19d2978060b6
4
- data.tar.gz: acc3fa9351ce74f4eed4667c8baf183e0f0d616ea0152c9ac7d22d4ae9be1c54
3
+ metadata.gz: b04216668970682a3eaa4bc062784a6d977dbd1e6e795bf30904e28aa48715b8
4
+ data.tar.gz: 94151a67d6deecf1b3cc0b799b4b0b23efb945c61e4d79d5d84bfe16144dc6c4
5
5
  SHA512:
6
- metadata.gz: feaf6f8e2f9f78315f67cefb874c5b269989229b4de33bfea5f910b291edaddf37ff6b1a2f91cf0c032087291cbaf2ab1beb469b7ab2c10153f99fd5399b9449
7
- data.tar.gz: 9d4296c13f3b8542fa81bddf160906405a2c8e9fa1fc1c2a812656ac685c5cd540d8e1f5c67ddf76109fff0757517028cb4fb8d437f5d159559eac8627fea217
6
+ metadata.gz: c201e08d594b7b2d6b9caa0fa596101d41e5a40a52bc700e0673fae1f20e8461565ba457ddb4b2b80cd9915127c3cbafbfd828205d94e1bc5db51777dda8dea0
7
+ data.tar.gz: c7da82328e46a70b91d6ee466c0efd0ae02f52ce8724663fe2ef92010354feb9783f79be68eee3745acfd483d4de3020faed11f24a6bfbf89926263464ec4c49
data/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # Ruby DTO-Schema
2
+ [![Gem Version](https://badge.fury.io/rb/dto_schema.svg)](https://badge.fury.io/rb/dto_schema)
3
+ [![Build Status](https://travis-ci.org/stepan-anokhin/dto-schema.svg?branch=master)](https://travis-ci.org/stepan-anokhin/dto-schema)
4
+ [![Coverage Status](https://coveralls.io/repos/github/stepan-anokhin/dto-schema/badge.svg?branch=master)](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 = DTO::Schema.new do
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
@@ -1,5 +1,9 @@
1
- module DTOSchema
2
- class Schema
1
+ require_relative 'dto_schema/schema'
3
2
 
3
+ module DTOSchema
4
+ def self.define(&block)
5
+ Schema.new(&block)
4
6
  end
7
+
8
+
5
9
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module DTOSchema
2
- VERSION = "0.0.0"
2
+ VERSION = "0.0.1"
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.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: