deeply_valid 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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Starr Horne
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = deeply_valid
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Starr Horne. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "deeply_valid"
8
+ gem.summary = %Q{Validation for complex data structures}
9
+ gem.description = %Q{This gem lets you declaratively specify validations for complex data structures, like those returned from an api.}
10
+ gem.email = "starr@chromahq.com"
11
+ gem.homepage = "http://github.com/starrhorne/deeply_valid"
12
+ gem.authors = ["Starr Horne"]
13
+ gem.add_development_dependency "shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/*_test.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/*_test.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "deeply_valid #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{deeply_valid}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Starr Horne"]
12
+ s.date = %q{2010-06-06}
13
+ s.description = %q{This gem lets you declaratively specify validations for complex data structures, like those returned from an api.}
14
+ s.email = %q{starr@chromahq.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "deeply_valid.gemspec",
27
+ "lib/deeply_valid.rb",
28
+ "lib/deeply_valid/base.rb",
29
+ "lib/deeply_valid/validation.rb",
30
+ "lib/deeply_valid/validation_helpers.rb",
31
+ "test/base_test.rb",
32
+ "test/helper.rb",
33
+ "test/validation_helpers_test.rb",
34
+ "test/validation_test.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/starrhorne/deeply_valid}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.6}
40
+ s.summary = %q{Validation for complex data structures}
41
+ s.test_files = [
42
+ "test/base_test.rb",
43
+ "test/helper.rb",
44
+ "test/validation_helpers_test.rb",
45
+ "test/validation_test.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
54
+ else
55
+ s.add_dependency(%q<shoulda>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<shoulda>, [">= 0"])
59
+ end
60
+ end
61
+
@@ -0,0 +1,59 @@
1
+ module DeeplyValid
2
+
3
+ #
4
+ # By subclassing the base class, you can easily create
5
+ # groups of validations.
6
+ #
7
+ # == Example:
8
+ #
9
+ # class Sample < DeeplyValid::Base
10
+ # define :regexp, /[a-z]+/
11
+ # define :manual, DeeplyValid::Validation.new { |d| d > 10 }
12
+ # define :literal, "x"
13
+ # define :structure, { :key => "val" }
14
+ # end
15
+ #
16
+ # Sample[:literal].valid?("x") # will return true
17
+ #
18
+ # == Example using ValidationHelpers
19
+ #
20
+ # class Sample < DeeplyValid::Base
21
+ # define :name, string(1..128)
22
+ # define :age, integer(1..70)
23
+ # define :children, hash( token => integer )
24
+ # define :colors, array(any(:red, green, :blue))
25
+ # end
26
+ #
27
+ class Base
28
+
29
+ include ValidationHelpers
30
+
31
+ class << self
32
+
33
+ #
34
+ # Add or create a validation object
35
+ #
36
+ # @param [Symbol] name A key that you'll use to retrieve the Validation
37
+ # @param rule Any validation rule accepted by `DeeplyValid::Validation.new`
38
+ #
39
+ def define(name, rule)
40
+ (@definitions ||= {})[name.to_sym] = rule.is_a?(Validation) ? rule : Validation.new(rule)
41
+ end
42
+
43
+ #
44
+ # Retrieve a validation object
45
+ #
46
+ # @param [Symbol] the key used in `define`
47
+ # @return [Validation] A validation object
48
+ #
49
+ def [](name)
50
+ (@definitions ||= {})[name.to_sym]
51
+ end
52
+
53
+ def structure(name)
54
+ Validation.new { |d| self[name.to_sym].valid?(d) }
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ module DeeplyValid
2
+
3
+ #
4
+ # The Validation lets us define validations using
5
+ # several different kinds of rules.
6
+ #
7
+ class Validation
8
+
9
+ #
10
+ # The initializer defines the conditions that will
11
+ # satasfy this validation.
12
+ #
13
+ # @param [Regexp] rule When `rule` is a `Regexp` we use `=~` to validate
14
+ # @param [Hash] rule When `rule` is a `Hash`, then `valid_structure?` does the validation
15
+ # @param [Object] rule When `rule` is any other non-nil object, use `==` to validate
16
+ # @param [Proc] &block An optional block, which will take one param and return true or false
17
+ #
18
+ def initialize(rule = nil, &block)
19
+
20
+ if !rule && !block_given?
21
+ raise "No validation rule specified"
22
+ end
23
+
24
+ @rule = rule
25
+ @block = block
26
+ end
27
+
28
+ #
29
+ # Validate data by regexp
30
+ #
31
+ # @param [String] String to be validated
32
+ #
33
+ def valid_pattern?(data)
34
+ !!(data =~ @rule)
35
+ end
36
+
37
+ #
38
+ # Recursively validate a complex data structure
39
+ # For now, only hashes are supported.
40
+ #
41
+ # == Example Rules:
42
+ #
43
+ # { :key => /regexp/ }
44
+ # { :key => Validation.new { |d| d > 20 } }
45
+ # { :key => "literal" }
46
+ # { :key => { :key2 => /regexp/ }, :key2 => "literal" }
47
+ #
48
+ # As you see, rules can be nested arbitrarily deep.
49
+ # The validaions work like you would expect.
50
+ #
51
+ # { :key => /[a-z]+/ } will validate { :key => "a" }
52
+ # { :key => /[a-z]+/ } will NOT validate { :key => 123 }
53
+ #
54
+ # @param [Hash] Hash fragment to be validated
55
+ # @param [Hash] Optional rules for validating hash fragment
56
+ # @return true if valid, false if invalid
57
+ #
58
+ def valid_structure?(data, fragment_rule = nil)
59
+ (fragment_rule || @rule).all? do |k, v|
60
+
61
+ if v.is_a?(Validation)
62
+ v.valid?(data[k])
63
+
64
+ elsif v.is_a?(Regexp)
65
+ !!(data[k] =~ v)
66
+
67
+ elsif v.is_a?(Hash)
68
+ valid_structure?(data[k], v)
69
+
70
+ else
71
+ data[k] == v
72
+ end
73
+ end
74
+ end
75
+
76
+ #
77
+ # Validate data of any rule type.
78
+ #
79
+ # @param [Object] data the data to be validated
80
+ # @return true if valid, false if invalid
81
+ #
82
+ def valid?(data)
83
+
84
+ if @rule.kind_of?(Regexp)
85
+ valid_pattern?(data)
86
+
87
+ elsif @rule.kind_of?(Hash)
88
+ valid_structure?(data)
89
+
90
+ elsif @block
91
+ @block.call(data)
92
+
93
+ else
94
+ data == @rule
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,162 @@
1
+ require 'json'
2
+
3
+ module DeeplyValid
4
+
5
+ #
6
+ # The ValidationHelpers module provides a number of 'macros'
7
+ # for creating Validations
8
+ #
9
+
10
+ module ValidationHelpers
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ #
19
+ # Validates tokens, ie. strings with letters, numbers, and underscores
20
+ #
21
+ # @param [Integer, Range] size Optional size limitation
22
+ # @return [Validation] The validation
23
+ #
24
+ def token(size = nil)
25
+ s = size ? "{#{size.is_a?(Range) ? [size.first, size.last].uniq.join(",") : size}}" : nil
26
+ /[0-9a-zA-Z_]#{ s }/
27
+ end
28
+
29
+ #
30
+ # Validates strings by size
31
+ #
32
+ # @param [Integer, Range] size Optional size limitation
33
+ # @return [Validation] The validation
34
+ #
35
+ def string(size = nil)
36
+ Validation.new { |d| d.is_a?(String) && in_range?(d.size, size) }
37
+ end
38
+
39
+ #
40
+ # Validates integer with optional limit
41
+ #
42
+ # @param [Integer, Range] limit
43
+ # @return [Validation] The validation
44
+ #
45
+ def integer(limit = nil)
46
+ Validation.new { |d| d.is_a?(Integer) && in_range?(d, limit) }
47
+ end
48
+
49
+ #
50
+ # Validate by class
51
+ #
52
+ # @param [Class] klass The class
53
+ # @return [Validation] The validation
54
+ #
55
+ def instance_of(klass)
56
+ Validation.new { |d| d.kind_of?(klass) }
57
+ end
58
+
59
+ #
60
+ # Validate that a string is valid JSON
61
+ #
62
+ # @return [Validation] The validation
63
+ #
64
+ def json
65
+ Validation.new do |d|
66
+ begin
67
+ JSON.parse(d)
68
+ rescue
69
+ false
70
+ else
71
+ true
72
+ end
73
+ end
74
+ end
75
+
76
+ #
77
+ # Check that any of the specified Validations are met
78
+ #
79
+ # @param [Validation] options One or more Validation instances
80
+ # @return [Validation] The validation
81
+ #
82
+ def any(*options)
83
+ if options.all? { |v| v.is_a?(Validation) }
84
+ Validation.new do |d|
85
+ options.any? { |v| v.valid?(d) }
86
+ end
87
+ else
88
+ Validation.new do |d|
89
+ options.include?(d)
90
+ end
91
+ end
92
+ end
93
+
94
+ #
95
+ # Check that all of the specified Validations are met
96
+ #
97
+ # @param [Validation] options One or more Validation instances
98
+ # @return [Validation] The validation
99
+ #
100
+ def all(*options)
101
+ Validation.new do |d|
102
+ options.all? { |v| v.valid?(d) }
103
+ end
104
+ end
105
+
106
+ #
107
+ # Validate all keys / values in a hash
108
+ #
109
+ # == Example
110
+ #
111
+ # To make sure that all keys are 32 char long tokens,
112
+ # and all values have a size from 1 to 128 chars,
113
+ # do this:
114
+ #
115
+ # hash( token(32) => string(1..128) )
116
+ #
117
+ # @param [Hash] example The desired hash format
118
+ # @return [Validation] The validation
119
+ #
120
+ def hash(example = nil)
121
+ return instance_of(Hash) unless example
122
+
123
+ k_rule, v_rule = example.to_a.first
124
+
125
+ k_validation = k_rule.is_a?(Validation) ? k_rule : Validation.new(k_rule)
126
+ v_validation = v_rule.is_a?(Validation) ? v_rule : Validation.new(v_rule)
127
+
128
+ Validation.new do |d|
129
+ d.is_a?(Hash) && d.all? { |k, v| k_validation.valid?(k) && v_validation.valid?(v) }
130
+ end
131
+ end
132
+
133
+ #
134
+ # Validate all values in an array
135
+ #
136
+ # @param [Validation] rule The validation rule
137
+ # @return [Validation] The validation
138
+ #
139
+ def array(rule)
140
+ return instance_of(Array) unless rule
141
+
142
+ validation = rule.is_a?(Validation) ? rule : Validation.new(rule)
143
+
144
+ Validation.new do |d|
145
+ d.is_a?(Array) && d.all? { |v| validation.valid?(v) }
146
+ end
147
+ end
148
+
149
+ protected
150
+
151
+ #
152
+ # Determine if a val is included in a range. Handles
153
+ # range==int and range==nil correctly
154
+ #
155
+ def in_range?(val, range)
156
+ return true unless range
157
+ range = [range] unless range.respond_to?(:include?)
158
+ range.include?(val)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,3 @@
1
+ require "deeply_valid/validation"
2
+ require "deeply_valid/validation_helpers"
3
+ require "deeply_valid/base"
data/test/base_test.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+
5
+ include DeeplyValid
6
+
7
+ context "a subclass of Base" do
8
+ setup do
9
+ class Sample < DeeplyValid::Base
10
+ define :self_reference, structure(:regexp)
11
+ define :regexp, /[a-z]+/
12
+ define :manual, DeeplyValid::Validation.new { |d| d > 10 }
13
+ define :literal, "x"
14
+ define :hash, { :key => "val" }
15
+ end
16
+ end
17
+
18
+ [[:regexp, "abc"], [:manual, 11], [:literal, "x"], [:hash, {:key => "val"}]].each do |r, d|
19
+ should "make #{ r } accessible via []" do
20
+ assert Sample[r]
21
+ end
22
+
23
+ should "wrap #{ r } in Validation class" do
24
+ assert Sample[r].is_a?(Validation)
25
+ end
26
+
27
+ should "validate #{ r }" do
28
+ assert Sample[r].valid?(d)
29
+ end
30
+
31
+ end
32
+
33
+ should "Handle self reference" do
34
+ assert Sample[:self_reference]
35
+ assert Sample[:self_reference].valid?("abc")
36
+ assert !Sample[:self_reference].valid?("123")
37
+ end
38
+
39
+ end
40
+
41
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'deeply_valid'
8
+
9
+ class Test::Unit::TestCase
10
+ class << self
11
+
12
+ def should_validate_all(data)
13
+ data.each do |d|
14
+ should "validate #{ d.inspect }" do
15
+ assert @validation.valid?(d)
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+ def should_not_validate_any(data)
22
+ data.each do |d|
23
+ should "not validate #{ d.inspect }" do
24
+ assert !@validation.valid?(d)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,146 @@
1
+ require 'helper'
2
+
3
+ class ValidationHelpersTest < Test::Unit::TestCase
4
+
5
+ include DeeplyValid
6
+
7
+ context "a class with validation helpers" do
8
+
9
+ setup do
10
+ class Sample
11
+ include DeeplyValid::ValidationHelpers
12
+ end
13
+ end
14
+
15
+ context "token helper" do
16
+
17
+ should "return correct regexp when no size specified" do
18
+ assert Sample.token == /[0-9a-zA-Z_]/
19
+ end
20
+
21
+ should "return correct regexp when exact size specified" do
22
+ assert Sample.token(32) == /[0-9a-zA-Z_]{32}/
23
+ end
24
+
25
+ should "return correct regexp when range size specified" do
26
+ assert Sample.token(1..23) == /[0-9a-zA-Z_]{1,23}/
27
+ end
28
+
29
+ end
30
+
31
+ context "string helper" do
32
+
33
+ should "validate correctly, without size" do
34
+ assert Sample.string.valid?("asdf")
35
+ end
36
+
37
+ should "validate correctly, with exact size" do
38
+ assert Sample.string(4).valid?("asdf")
39
+ assert !Sample.string(4).valid?("a")
40
+ end
41
+
42
+ should "validate correctly, with range size" do
43
+ assert Sample.string(1..5).valid?("asdf")
44
+ assert !Sample.string(3..5).valid?("a")
45
+ end
46
+
47
+ end
48
+
49
+ context "integer helper" do
50
+
51
+ should "validate correctly, without limit" do
52
+ assert Sample.integer.valid?(123)
53
+ end
54
+
55
+ should "validate correctly, with exact limit" do
56
+ assert Sample.integer(4).valid?(4)
57
+ assert !Sample.integer(4).valid?(5)
58
+ end
59
+
60
+ should "validate correctly, with range limit" do
61
+ assert Sample.integer(1..5).valid?(3)
62
+ assert !Sample.integer(3..5).valid?(20)
63
+ end
64
+
65
+ end
66
+
67
+ context "instance_of helper" do
68
+
69
+ should "validate correctly" do
70
+ assert Sample.instance_of(String).valid?("asdf")
71
+ assert Sample.instance_of(Array).valid?([1,2,3])
72
+
73
+ assert !Sample.instance_of(String).valid?([1,2,3])
74
+ assert !Sample.instance_of(Array).valid?("asdf")
75
+ end
76
+
77
+ end
78
+
79
+
80
+ context "json helper" do
81
+
82
+ should "validate correctly" do
83
+ assert Sample.json.valid?('{"x": "y"}')
84
+ assert Sample.json.valid?('{"key": ["y", 1, 2, true, false]}')
85
+
86
+ assert !Sample.json.valid?('{"x" "y"}')
87
+ assert !Sample.json.valid?('{"key": {"y", 1, 2, true, false]}')
88
+ end
89
+
90
+ end
91
+
92
+
93
+ context "any helper" do
94
+
95
+ should "validate literals correctly" do
96
+ assert Sample.any("a", "b", "c").valid?("c")
97
+ assert Sample.any(1, 2, 3).valid?(2)
98
+
99
+ assert !Sample.any("a", "b", "c").valid?("x")
100
+ assert !Sample.any(1, 2, 3).valid?(20)
101
+ end
102
+
103
+ should "validate correctly using nested validations" do
104
+ assert Sample.any(Validation.new("a"), Validation.new("b")).valid?("a")
105
+ assert Sample.any(Validation.new(/[a-z]+/), Validation.new(/[0-9]+/)).valid?("12")
106
+
107
+ assert !Sample.any(Validation.new("a"), Validation.new("b")).valid?("abc")
108
+ assert !Sample.any(Validation.new(/^[a-z]+/), Validation.new(/^[0-9]+/)).valid?("Z12")
109
+ end
110
+
111
+ end
112
+
113
+ context "all helper" do
114
+
115
+ should "validate correctly" do
116
+ assert Sample.all(Validation.new(/[a-z]+/), Validation.new(/[0-9]+/)).valid?("ab12")
117
+ assert !Sample.all(Validation.new(/^[a-z]+/), Validation.new(/^[0-9]+/)).valid?("ab12")
118
+ end
119
+
120
+ end
121
+
122
+ context "hash helper" do
123
+ should "validate correctly" do
124
+ assert Sample.hash({ (/^[a-z]+$/) => /^[0-9]+$/ }).valid?({ "abc" => "123" })
125
+ assert Sample.hash({ (/^[a-z]+$/) => Validation.new { |d| d.is_a?(Array) } }).valid?({ "abc" => [] })
126
+ assert Sample.hash({ (/^[a-z]+$/) => Validation.new { |d| d.is_a?(Array) } }).valid?({ "abc" => [], "cc" => [], "asd" => [] })
127
+
128
+ assert !Sample.hash({ (/^[a-z]+$/) => /^[0-9]+$/ }).valid?({ "abc" => 123 })
129
+ assert !Sample.hash({ (/^[a-z]+$/) => Validation.new { |d| d.is_a?(Array) } }).valid?({ "abc" => {} })
130
+ assert !Sample.hash({ (/^[a-z]+$/) => Validation.new { |d| d.is_a?(Array) } }).valid?({ "abc" => [], "cde" => 123 })
131
+ end
132
+ end
133
+
134
+ context "array helper" do
135
+ should "validate correctly" do
136
+ assert Sample.array(/^[a-z]+$/).valid?([ "abc", "def", "xyz" ])
137
+ assert Sample.array(Validation.new { |d| d > 10 }).valid?([ 11, 12, 23 ])
138
+
139
+ assert !Sample.array(/^[a-z]+$/).valid?([ "1abc", "def", "xyz" ])
140
+ assert !Sample.array(Validation.new { |d| d > 10 }).valid?([ 11, 0, 12, 23 ])
141
+ end
142
+ end
143
+
144
+ end
145
+
146
+ end
@@ -0,0 +1,104 @@
1
+ require 'helper'
2
+
3
+ class ValidationTest < Test::Unit::TestCase
4
+
5
+ include DeeplyValid
6
+
7
+ context "A regexp validation for alphanumeric strings" do
8
+
9
+ setup do
10
+ @validation = Validation.new(/^[a-zA-Z0-9]*$/)
11
+ end
12
+
13
+ should_validate_all %w[ abc a91 192 AbC A912D ljasd122 asdDLf9sS ]
14
+
15
+ should_not_validate_any %w[ !asdlj s82_s saf.dfd 1u0od#s ]
16
+
17
+ end
18
+
19
+ context "A block validation for numbers > 5 and < 20" do
20
+
21
+ setup do
22
+ @validation = Validation.new { |d| d > 5 && d < 20 }
23
+ end
24
+
25
+ should_validate_all (6..19)
26
+
27
+ should_not_validate_any ((0..5).to_a + (20..25).to_a)
28
+
29
+ end
30
+
31
+ context "A structure validation for {:key => 'alnum'} using nested validation" do
32
+ setup do
33
+ rule = { :key => Validation.new(/^[a-zA-Z0-9]*$/) }
34
+ @validation = Validation.new(rule)
35
+ end
36
+
37
+ should_validate_all([
38
+ {:key => "123"},
39
+ {:key => "1shj23"},
40
+ {:key => "123KJHjhksj1"},
41
+ {:key => "12askjfh12aBn3"},
42
+ ])
43
+
44
+ should_not_validate_any([
45
+ {:key => "12##3"},
46
+ {:xkey => "1sh j23"},
47
+ {:key => "123K_a s@#JHjhksj1"},
48
+ {:ke => "12askjfh1*)}2aBn3"},
49
+ ])
50
+
51
+ end
52
+
53
+ context "A structure validation for {:key => 'alnum'} using regexp" do
54
+ setup do
55
+ rule = { :key => /^[a-zA-Z0-9]*$/ }
56
+ @validation = Validation.new(rule)
57
+ end
58
+
59
+ should_validate_all([
60
+ {:key => "123"},
61
+ {:key => "1shj23"},
62
+ {:key => "123KJHjhksj1"},
63
+ {:key => "12askjfh12aBn3"},
64
+ ])
65
+
66
+ should_not_validate_any([
67
+ {:sdkey => "12##3"},
68
+ {"key" => "1sh j23"},
69
+ {:key => "123K_a s@#JHjhksj1"},
70
+ {:key => "12askjfh1*)}2aBn3"},
71
+ ])
72
+
73
+ end
74
+
75
+ context "A nested structure validation for {:token => alnum, :person => { :name => string, :age => int }}" do
76
+ setup do
77
+
78
+ rule = {
79
+ :token => /^[a-zA-Z0-9]*$/,
80
+ :person => {
81
+ :name => Validation.new { |d| d.is_a?(String) },
82
+ :age => Validation.new { |d| d.is_a?(Integer) && d > 0 }
83
+ }
84
+ }
85
+
86
+ @validation = Validation.new(rule)
87
+ end
88
+
89
+ should_validate_all([
90
+ {:token => "token234", :person => { :name => "Bob Jones", :age => 22 }},
91
+ {:token => "tjgkh", :person => { :name => "Bob", :age => 1 }, :extra => true},
92
+ {:token => "token234", :person => { :name => "", :age => 2002 }},
93
+ ])
94
+
95
+ should_not_validate_any([
96
+ {:person => { :name => "Bob Jones", :age => 22 }},
97
+ {:token => "t__jgkh", :person => { :name => "Bob", :age => 0 }},
98
+ {:token => "token234", :person => { :namex => "", :age => 2002 }},
99
+ {"token" => "token234", :person => { :name => "", :age => 2002 }},
100
+ ])
101
+
102
+ end
103
+
104
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deeply_valid
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Starr Horne
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-06 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: shoulda
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ description: This gem lets you declaratively specify validations for complex data structures, like those returned from an api.
33
+ email: starr@chromahq.com
34
+ executables: []
35
+
36
+ extensions: []
37
+
38
+ extra_rdoc_files:
39
+ - LICENSE
40
+ - README.rdoc
41
+ files:
42
+ - .document
43
+ - .gitignore
44
+ - LICENSE
45
+ - README.rdoc
46
+ - Rakefile
47
+ - VERSION
48
+ - deeply_valid.gemspec
49
+ - lib/deeply_valid.rb
50
+ - lib/deeply_valid/base.rb
51
+ - lib/deeply_valid/validation.rb
52
+ - lib/deeply_valid/validation_helpers.rb
53
+ - test/base_test.rb
54
+ - test/helper.rb
55
+ - test/validation_helpers_test.rb
56
+ - test/validation_test.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/starrhorne/deeply_valid
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --charset=UTF-8
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.6
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Validation for complex data structures
87
+ test_files:
88
+ - test/base_test.rb
89
+ - test/helper.rb
90
+ - test/validation_helpers_test.rb
91
+ - test/validation_test.rb