deeply_valid 0.1.5 → 0.1.7
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.
- 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
|