deeply_valid 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/deeply_valid.gemspec +2 -2
- data/lib/deeply_valid/base.rb +12 -2
- data/lib/deeply_valid/validation.rb +23 -4
- data/lib/deeply_valid/validation_helpers.rb +30 -8
- data/test/base_test.rb +7 -0
- data/test/helper.rb +1 -0
- data/test/system_test.rb +44 -5
- data/test/validation_helpers_test.rb +2 -11
- data/test/validation_test.rb +38 -0
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.7
|
data/deeply_valid.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{deeply_valid}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.7"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Starr Horne"]
|
12
|
-
s.date = %q{2010-06-
|
12
|
+
s.date = %q{2010-06-15}
|
13
13
|
s.description = %q{This gem lets you declaratively specify validations for complex data structures, like those returned from an api.}
|
14
14
|
s.email = %q{starr@chromahq.com}
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/deeply_valid/base.rb
CHANGED
@@ -51,8 +51,18 @@ module DeeplyValid
|
|
51
51
|
(@definitions ||= {})[name.to_sym]
|
52
52
|
end
|
53
53
|
|
54
|
-
def structure(name)
|
55
|
-
|
54
|
+
def structure(name = nil, &block)
|
55
|
+
if name
|
56
|
+
Validation.new { |d| self[name.to_sym].valid?(d) }
|
57
|
+
elsif block_given?
|
58
|
+
block
|
59
|
+
else
|
60
|
+
raise "structure requires name or block"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def value(&block)
|
65
|
+
block
|
56
66
|
end
|
57
67
|
|
58
68
|
end
|
@@ -6,6 +6,8 @@ module DeeplyValid
|
|
6
6
|
#
|
7
7
|
class Validation
|
8
8
|
|
9
|
+
attr_accessor :options
|
10
|
+
|
9
11
|
#
|
10
12
|
# The initializer defines the conditions that will
|
11
13
|
# satasfy this validation.
|
@@ -15,12 +17,13 @@ module DeeplyValid
|
|
15
17
|
# @param [Object] rule When `rule` is any other non-nil object, use `==` to validate
|
16
18
|
# @param [Proc] &block An optional block, which will take one param and return true or false
|
17
19
|
#
|
18
|
-
def initialize(rule = nil, &block)
|
20
|
+
def initialize(rule = nil, options = {}, &block)
|
19
21
|
|
20
22
|
if rule.nil? && !block_given?
|
21
23
|
raise "No validation rule specified"
|
22
24
|
end
|
23
25
|
|
26
|
+
@options = options
|
24
27
|
@rule = rule
|
25
28
|
@block = block
|
26
29
|
end
|
@@ -58,8 +61,10 @@ module DeeplyValid
|
|
58
61
|
def valid_structure?(data, fragment_rule = nil)
|
59
62
|
(fragment_rule || @rule).all? do |k, v|
|
60
63
|
|
64
|
+
v = v.call(@data) if v.is_a?(Proc)
|
65
|
+
|
61
66
|
if v.is_a?(Validation)
|
62
|
-
v.valid?(data[k])
|
67
|
+
(v.options[:optional] && !data.has_key?(k)) || v.valid?(data[k])
|
63
68
|
|
64
69
|
elsif v.is_a?(Regexp)
|
65
70
|
!!(data[k] =~ v)
|
@@ -67,6 +72,9 @@ module DeeplyValid
|
|
67
72
|
elsif v.is_a?(Hash)
|
68
73
|
valid_structure?(data[k], v)
|
69
74
|
|
75
|
+
elsif v.nil?
|
76
|
+
!data.has_key?(k)
|
77
|
+
|
70
78
|
else
|
71
79
|
data[k] == v
|
72
80
|
end
|
@@ -81,10 +89,17 @@ module DeeplyValid
|
|
81
89
|
#
|
82
90
|
def valid?(data)
|
83
91
|
|
84
|
-
|
92
|
+
@data = data
|
93
|
+
|
94
|
+
rule = @rule.is_a?(Proc) ? @rule.call(@data) : @rule
|
95
|
+
|
96
|
+
result = if rule.kind_of?(Regexp)
|
85
97
|
valid_pattern?(data)
|
86
98
|
|
87
|
-
elsif
|
99
|
+
elsif rule.kind_of?(Validation)
|
100
|
+
rule.valid?(data)
|
101
|
+
|
102
|
+
elsif rule.kind_of?(Hash)
|
88
103
|
valid_structure?(data)
|
89
104
|
|
90
105
|
elsif @block
|
@@ -94,6 +109,10 @@ module DeeplyValid
|
|
94
109
|
data == @rule
|
95
110
|
end
|
96
111
|
|
112
|
+
@data = nil
|
113
|
+
|
114
|
+
result
|
115
|
+
|
97
116
|
end
|
98
117
|
|
99
118
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'date'
|
2
3
|
|
3
4
|
module DeeplyValid
|
4
5
|
|
@@ -66,23 +67,23 @@ module DeeplyValid
|
|
66
67
|
end
|
67
68
|
|
68
69
|
#
|
69
|
-
# Validates
|
70
|
+
# Validates time with optional limit
|
70
71
|
#
|
71
72
|
# @param [Integer, Range] limit
|
72
73
|
# @return [Validation] The validation
|
73
74
|
#
|
74
|
-
def
|
75
|
-
Validation.new { |d| d.is_a?(
|
75
|
+
def time(limit = nil)
|
76
|
+
Validation.new { |d| d.is_a?(Time) && in_range?(d, limit) }
|
76
77
|
end
|
77
78
|
|
78
79
|
#
|
79
|
-
# Validates
|
80
|
+
# Validates nil. Normally a nil means "don't validate". Use this
|
81
|
+
# if you're expecting an actual nil
|
80
82
|
#
|
81
|
-
# @param [Integer, Range] limit
|
82
83
|
# @return [Validation] The validation
|
83
84
|
#
|
84
|
-
def
|
85
|
-
Validation.new { |d| d.
|
85
|
+
def nil_value
|
86
|
+
Validation.new { |d| d.nil? }
|
86
87
|
end
|
87
88
|
|
88
89
|
#
|
@@ -228,8 +229,29 @@ module DeeplyValid
|
|
228
229
|
range = [range] unless range.respond_to?(:include?)
|
229
230
|
range.include?(val)
|
230
231
|
end
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# Set a validation as optional. This only has any effect
|
236
|
+
# if the validation is part of a structure.
|
237
|
+
#
|
238
|
+
# The following validation:
|
239
|
+
#
|
240
|
+
# {:key => optional(integer)}
|
241
|
+
#
|
242
|
+
# Will validate both:
|
243
|
+
#
|
244
|
+
# {:key => 1}
|
245
|
+
# {}
|
246
|
+
#
|
247
|
+
# @param [Validation] validation
|
248
|
+
# @return [Validation]
|
249
|
+
#
|
250
|
+
def optional(validation)
|
251
|
+
validation.options[:optional] = true
|
252
|
+
validation
|
253
|
+
end
|
231
254
|
|
232
|
-
end
|
233
255
|
end
|
234
256
|
end
|
235
257
|
end
|
data/test/base_test.rb
CHANGED
@@ -12,6 +12,7 @@ class BaseTest < Test::Unit::TestCase
|
|
12
12
|
define :manual, DeeplyValid::Validation.new { |d| d > 10 }
|
13
13
|
define :literal, "x"
|
14
14
|
define :hash, { :key => "val" }
|
15
|
+
define :nested_self_reference, structure { |data| structure(:regexp) }
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -35,6 +36,12 @@ class BaseTest < Test::Unit::TestCase
|
|
35
36
|
assert Sample[:self_reference].valid?("abc")
|
36
37
|
assert !Sample[:self_reference].valid?("123")
|
37
38
|
end
|
39
|
+
|
40
|
+
should "Handle nested self-reference" do
|
41
|
+
assert Sample[:nested_self_reference]
|
42
|
+
assert Sample[:nested_self_reference].valid?("abc")
|
43
|
+
assert !Sample[:nested_self_reference].valid?("123")
|
44
|
+
end
|
38
45
|
|
39
46
|
end
|
40
47
|
|
data/test/helper.rb
CHANGED
data/test/system_test.rb
CHANGED
@@ -16,7 +16,10 @@ class SystemTest < Test::Unit::TestCase
|
|
16
16
|
:name => any('sales', 'accounting', 'engineering'),
|
17
17
|
:building => integer
|
18
18
|
},
|
19
|
-
:performace_reviews => array( structure(:review) )
|
19
|
+
:performace_reviews => array( structure(:review) ),
|
20
|
+
:pension => value { |data| integer if data[:age] > 50 },
|
21
|
+
:hobby => optional(string)
|
22
|
+
|
20
23
|
}
|
21
24
|
|
22
25
|
# This structure is referenced in the "person" structure
|
@@ -26,7 +29,7 @@ class SystemTest < Test::Unit::TestCase
|
|
26
29
|
}
|
27
30
|
end
|
28
31
|
|
29
|
-
@
|
32
|
+
@valid_person1 = {
|
30
33
|
:id => "x"*32,
|
31
34
|
:age => 22,
|
32
35
|
:name => "Bob Jones",
|
@@ -40,10 +43,44 @@ class SystemTest < Test::Unit::TestCase
|
|
40
43
|
]
|
41
44
|
}
|
42
45
|
|
43
|
-
|
46
|
+
# this person is over 50, so there's a value for pension
|
47
|
+
@valid_person2 = {
|
48
|
+
:id => "x"*32,
|
49
|
+
:age => 61,
|
50
|
+
:name => "Bob Jones",
|
51
|
+
:pension => 666,
|
52
|
+
:hobby => "boating",
|
53
|
+
:department => {
|
54
|
+
:name => "sales",
|
55
|
+
:building => 33
|
56
|
+
},
|
57
|
+
:performace_reviews => [
|
58
|
+
{ :author => "joe", :body => "a review" },
|
59
|
+
{ :author => "bill", :body => "another review" }
|
60
|
+
]
|
61
|
+
}
|
62
|
+
|
63
|
+
# this person is under 50, and has a value for pension,
|
64
|
+
# so the record is invalid.
|
65
|
+
@invalid_person1 = {
|
44
66
|
:id => "x"*32,
|
45
67
|
:age => 22,
|
46
68
|
:name => "Bob Jones",
|
69
|
+
:pension => 666,
|
70
|
+
:department => {
|
71
|
+
:name => "sales",
|
72
|
+
:building => 33
|
73
|
+
},
|
74
|
+
:performace_reviews => [
|
75
|
+
{ :author => "joe", :body => "a review" },
|
76
|
+
{ :author => "bill", :body => "another review" }
|
77
|
+
]
|
78
|
+
}
|
79
|
+
|
80
|
+
@invalid_person2 = {
|
81
|
+
:id => "x"*32,
|
82
|
+
:age => 32,
|
83
|
+
:name => "Bob Jones",
|
47
84
|
:department => {
|
48
85
|
:name => "sales",
|
49
86
|
:building => 33
|
@@ -57,8 +94,10 @@ class SystemTest < Test::Unit::TestCase
|
|
57
94
|
end
|
58
95
|
|
59
96
|
should "validate correctly" do
|
60
|
-
assert MyValidations[:person].valid?(@
|
61
|
-
assert
|
97
|
+
assert MyValidations[:person].valid?(@valid_person1)
|
98
|
+
assert MyValidations[:person].valid?(@valid_person2)
|
99
|
+
assert !MyValidations[:person].valid?(@invalid_person1)
|
100
|
+
assert !MyValidations[:person].valid?(@invalid_person2)
|
62
101
|
end
|
63
102
|
|
64
103
|
end
|
@@ -92,17 +92,8 @@ class ValidationHelpersTest < Test::Unit::TestCase
|
|
92
92
|
context "date helper" do
|
93
93
|
|
94
94
|
should "validate correctly" do
|
95
|
-
assert Sample.date.valid?(Date.
|
96
|
-
assert !Sample.date(:before => Date.
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
context "datetime helper" do
|
102
|
-
|
103
|
-
should "validate correctly" do
|
104
|
-
assert Sample.datetime.valid?(DateTime.now)
|
105
|
-
assert !Sample.datetime(:before => DateTime.now).valid?(DateTime.now)
|
95
|
+
assert Sample.date.valid?(Date.civil(2010, 3, 10))
|
96
|
+
assert !Sample.date(:before => Date.civil(2010, 3, 10)).valid?(Date.civil(2010, 4, 10))
|
106
97
|
end
|
107
98
|
|
108
99
|
end
|
data/test/validation_test.rb
CHANGED
@@ -101,4 +101,42 @@ class ValidationTest < Test::Unit::TestCase
|
|
101
101
|
|
102
102
|
end
|
103
103
|
|
104
|
+
context "A structure validation for {:key => nil}" do
|
105
|
+
setup do
|
106
|
+
rule = { :key => nil, :bogus => 1 }
|
107
|
+
@validation = Validation.new(rule)
|
108
|
+
end
|
109
|
+
|
110
|
+
should_validate_all([
|
111
|
+
{:bogus => 1}
|
112
|
+
])
|
113
|
+
|
114
|
+
should_not_validate_any([
|
115
|
+
{:key => false, :bogus => 1},
|
116
|
+
{:key => true, :bogus => 1},
|
117
|
+
{:key => nil, :bogus => 1},
|
118
|
+
{:key => "sesame", :bogus => 1},
|
119
|
+
])
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
context "A structure with an optional validation" do
|
124
|
+
setup do
|
125
|
+
rule = { :key => Validation.new(1, :optional => true) }
|
126
|
+
@validation = Validation.new(rule)
|
127
|
+
end
|
128
|
+
|
129
|
+
should_validate_all([
|
130
|
+
{:key => 1},
|
131
|
+
{}
|
132
|
+
])
|
133
|
+
|
134
|
+
should_not_validate_any([
|
135
|
+
{:key => 2},
|
136
|
+
{:key => nil},
|
137
|
+
{:key => false}
|
138
|
+
])
|
139
|
+
|
140
|
+
end
|
141
|
+
|
104
142
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 7
|
9
|
+
version: 0.1.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Starr Horne
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-06-
|
17
|
+
date: 2010-06-15 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|