object-inspector 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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +45 -0
- data/README.md +335 -0
- data/Rakefile +13 -0
- data/examples/example.rb +131 -0
- data/features/error_display.feature +80 -0
- data/features/support/env.rb +1 -0
- data/features/syntax.feature +223 -0
- data/inspector.gemspec +24 -0
- data/lib/inspector.rb +27 -0
- data/lib/inspector/attribute_metadata.rb +19 -0
- data/lib/inspector/constraint.rb +18 -0
- data/lib/inspector/constraint/validators.rb +8 -0
- data/lib/inspector/constraint/validators/simple.rb +13 -0
- data/lib/inspector/constraint/validators/validity.rb +24 -0
- data/lib/inspector/constraint/violation.rb +34 -0
- data/lib/inspector/constraint/violation/list.rb +93 -0
- data/lib/inspector/constraints.rb +61 -0
- data/lib/inspector/constraints/email.rb +57 -0
- data/lib/inspector/constraints/empty.rb +21 -0
- data/lib/inspector/constraints/eq.rb +23 -0
- data/lib/inspector/constraints/false.rb +19 -0
- data/lib/inspector/constraints/have.rb +85 -0
- data/lib/inspector/constraints/predicate.rb +38 -0
- data/lib/inspector/constraints/true.rb +19 -0
- data/lib/inspector/constraints/valid.rb +31 -0
- data/lib/inspector/metadata.rb +75 -0
- data/lib/inspector/metadata/map.rb +24 -0
- data/lib/inspector/metadata/walker.rb +46 -0
- data/lib/inspector/property_metadata.rb +19 -0
- data/lib/inspector/type_metadata.rb +5 -0
- data/lib/inspector/validator.rb +26 -0
- data/lib/inspector/version.rb +3 -0
- data/lib/object_inspector.rb +1 -0
- data/spec/inspector/attribute_metadata_spec.rb +8 -0
- data/spec/inspector/constraint/violation/list_spec.rb +45 -0
- data/spec/inspector/constraints/false_spec.rb +18 -0
- data/spec/inspector/constraints_spec.rb +15 -0
- data/spec/inspector/metadata/map_spec.rb +38 -0
- data/spec/inspector/metadata/walker_spec.rb +7 -0
- data/spec/inspector/property_metadata_spec.rb +8 -0
- data/spec/inspector/type_metadata_spec.rb +7 -0
- data/spec/inspector/validator_spec.rb +65 -0
- data/spec/inspector_spec.rb +22 -0
- data/spec/shared_examples/metadata.rb +33 -0
- data/spec/spec_helper.rb +3 -0
- metadata +175 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
Feature: error display
|
2
|
+
|
3
|
+
It is important to be able to render validation failures with correct error messages
|
4
|
+
|
5
|
+
Scenario: object attributes
|
6
|
+
Given a file named "object.rb" with:
|
7
|
+
"""
|
8
|
+
require 'inspector'
|
9
|
+
|
10
|
+
Profile = Struct.new(:first_name, :last_name, :date_of_birth, :preferences)
|
11
|
+
User = Struct.new(:username, :email, :profile)
|
12
|
+
|
13
|
+
Inspector.valid(Profile) do
|
14
|
+
attribute(:first_name) do
|
15
|
+
should_not be_empty
|
16
|
+
should be_kind_of(String)
|
17
|
+
should have_at_least(8).characters
|
18
|
+
should have_at_most(32).characters
|
19
|
+
end
|
20
|
+
|
21
|
+
attribute(:last_name) do
|
22
|
+
should_not be_empty
|
23
|
+
should be_kind_of(String)
|
24
|
+
should have_at_least(8).characters
|
25
|
+
should have_at_most(32).characters
|
26
|
+
end
|
27
|
+
|
28
|
+
attribute(:date_of_birth) do
|
29
|
+
should_not be_empty
|
30
|
+
should be_kind_of(Date)
|
31
|
+
end
|
32
|
+
|
33
|
+
attribute(:preferences) do
|
34
|
+
property("terms_and_conditions").should be_true
|
35
|
+
property("show_email").should_not be_empty
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Inspector.valid(User) do
|
40
|
+
attribute(:username) do
|
41
|
+
should_not be_empty
|
42
|
+
should be_kind_of(String)
|
43
|
+
should have_at_least(8).characters
|
44
|
+
should have_at_most(32).characters
|
45
|
+
# attribute(:length) do
|
46
|
+
# should == 10
|
47
|
+
# end
|
48
|
+
end
|
49
|
+
|
50
|
+
attribute(:email) do
|
51
|
+
should_not be_empty
|
52
|
+
should be_kind_of(String)
|
53
|
+
should be_an_email
|
54
|
+
end
|
55
|
+
|
56
|
+
attribute(:profile) do
|
57
|
+
should be_valid(Profile)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
errors = Inspector.validate(User.new("", "bademail", Profile.new("", nil, false, {
|
62
|
+
"terms_and_conditions" => false,
|
63
|
+
"show_email" => nil
|
64
|
+
})))
|
65
|
+
|
66
|
+
errors.each do |violation|
|
67
|
+
puts violation.path
|
68
|
+
violation.constraints.each do |constraint|
|
69
|
+
puts " #{constraint}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
errors[:profile][:preferences]["show_email"]
|
74
|
+
|
75
|
+
errors.at(".profile.preferences[show_email]")
|
76
|
+
|
77
|
+
errors.at(".profile.preferences[show_email]")
|
78
|
+
"""
|
79
|
+
When I run `ruby object.rb`
|
80
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba/cucumber'
|
@@ -0,0 +1,223 @@
|
|
1
|
+
Feature: syntax
|
2
|
+
|
3
|
+
In order to be able to check an object's validity
|
4
|
+
As a developer
|
5
|
+
I want to specify what a valid object looks like
|
6
|
+
|
7
|
+
Scenario: true is true
|
8
|
+
Given a file named "true_is_true.rb" with:
|
9
|
+
"""
|
10
|
+
require 'inspector'
|
11
|
+
|
12
|
+
Inspector.valid("true value") do
|
13
|
+
should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
violations = Inspector.validate(true, :as => "true value")
|
17
|
+
if violations.empty?
|
18
|
+
puts "true is true"
|
19
|
+
exit 0
|
20
|
+
else
|
21
|
+
puts "true is not true"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
When I run `ruby true_is_true.rb`
|
26
|
+
Then it should pass with:
|
27
|
+
"""
|
28
|
+
true is true
|
29
|
+
"""
|
30
|
+
|
31
|
+
Scenario: false is not true
|
32
|
+
Given a file named "false_is_not_true.rb" with:
|
33
|
+
"""
|
34
|
+
require 'inspector'
|
35
|
+
|
36
|
+
Inspector.valid("true value") do
|
37
|
+
should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
violations = Inspector.validate(false, :as => "true value")
|
41
|
+
if violations.empty?
|
42
|
+
puts "true is true"
|
43
|
+
exit 0
|
44
|
+
else
|
45
|
+
puts "false is not true"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
"""
|
49
|
+
When I run `ruby false_is_not_true.rb`
|
50
|
+
Then it should fail with:
|
51
|
+
"""
|
52
|
+
false is not true
|
53
|
+
"""
|
54
|
+
|
55
|
+
Scenario: object attributes
|
56
|
+
Given a file named "object.rb" with:
|
57
|
+
"""
|
58
|
+
require 'inspector'
|
59
|
+
|
60
|
+
User = Struct.new(:username, :email)
|
61
|
+
|
62
|
+
Inspector.valid(User) do
|
63
|
+
attribute(:username) do
|
64
|
+
should_not be_empty
|
65
|
+
should be_kind_of(String)
|
66
|
+
should have_at_least(8).characters
|
67
|
+
should have_at_most(32).characters
|
68
|
+
end
|
69
|
+
|
70
|
+
attribute(:email) do
|
71
|
+
should_not be_empty
|
72
|
+
should be_kind_of(String)
|
73
|
+
should be_an_email
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
user = User.new("", "bademail")
|
78
|
+
violations = Inspector.validate(user)
|
79
|
+
|
80
|
+
if violations.empty?
|
81
|
+
puts "user #{user.inspect} is valid"
|
82
|
+
else
|
83
|
+
puts "invalid user #{user.inspect}:"
|
84
|
+
puts violations.to_s.split("\n").map { |line| " #{line}" }.join("\n")
|
85
|
+
end
|
86
|
+
"""
|
87
|
+
When I run `ruby object.rb`
|
88
|
+
Then the output should contain:
|
89
|
+
"""
|
90
|
+
invalid user #<struct User username="", email="bademail">:
|
91
|
+
username:
|
92
|
+
should_not.be_empty
|
93
|
+
should.have_at_least
|
94
|
+
email:
|
95
|
+
should.be_an_email
|
96
|
+
"""
|
97
|
+
|
98
|
+
Scenario: nested objects
|
99
|
+
Given a file named "post_and_author.rb" with:
|
100
|
+
"""
|
101
|
+
require 'inspector'
|
102
|
+
|
103
|
+
Post = Struct.new(:title, :body, :author)
|
104
|
+
Author = Struct.new(:email, :first_name, :last_name)
|
105
|
+
|
106
|
+
Inspector.valid(Post) do
|
107
|
+
attribute(:title) do
|
108
|
+
should_not be_empty
|
109
|
+
should be_kind_of(String)
|
110
|
+
should have_at_least(3).characters
|
111
|
+
end
|
112
|
+
|
113
|
+
attribute(:body) do
|
114
|
+
should_not be_empty
|
115
|
+
should be_kind_of(String)
|
116
|
+
should have_at_least(3).characters
|
117
|
+
end
|
118
|
+
|
119
|
+
attribute(:author).should validate(:as => Author)
|
120
|
+
end
|
121
|
+
|
122
|
+
Inspector.valid(Author) do
|
123
|
+
attribute(:email) do
|
124
|
+
should_not be_empty
|
125
|
+
should be_an_email
|
126
|
+
end
|
127
|
+
|
128
|
+
attribute(:first_name) do
|
129
|
+
should_not be_empty
|
130
|
+
should be_kind_of(String)
|
131
|
+
should have_at_least(1).character
|
132
|
+
should have_at_most(32).characters
|
133
|
+
end
|
134
|
+
|
135
|
+
attribute(:last_name) do
|
136
|
+
should_not be_empty
|
137
|
+
should be_kind_of(String)
|
138
|
+
should have_at_least(1).character
|
139
|
+
should have_at_most(32).characters
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
author = Author.new("not an email", "John", "Smith")
|
144
|
+
post = Post.new(123, nil, author)
|
145
|
+
|
146
|
+
violations = Inspector.validate(post)
|
147
|
+
|
148
|
+
if violations.empty?
|
149
|
+
puts "post #{post.inspect} is valid"
|
150
|
+
else
|
151
|
+
puts "invalid post #{post.inspect}:"
|
152
|
+
puts violations.to_s.split("\n").map { |line| " #{line}" }.join("\n")
|
153
|
+
end
|
154
|
+
"""
|
155
|
+
When I run `ruby post_and_author.rb`
|
156
|
+
Then the output should contain:
|
157
|
+
"""
|
158
|
+
invalid post #<struct Post title=123, body=nil, author=#<struct Author email="not an email", first_name="John", last_name="Smith">>:
|
159
|
+
title:
|
160
|
+
should.be_kind_of
|
161
|
+
body:
|
162
|
+
should_not.be_empty
|
163
|
+
should.be_kind_of
|
164
|
+
should.have_at_least
|
165
|
+
author:
|
166
|
+
email:
|
167
|
+
should.be_an_email
|
168
|
+
"""
|
169
|
+
|
170
|
+
Scenario: hash validation
|
171
|
+
Given a file named "request.rb" with:
|
172
|
+
"""
|
173
|
+
require 'inspector'
|
174
|
+
|
175
|
+
Inspector.valid("request parameters") do
|
176
|
+
property("title") do
|
177
|
+
should_not be_empty
|
178
|
+
should be_kind_of(String)
|
179
|
+
should have_at_least(3).characters
|
180
|
+
end
|
181
|
+
|
182
|
+
property("body") do
|
183
|
+
should_not be_empty
|
184
|
+
should be_kind_of(String)
|
185
|
+
should have_at_least(3).characters
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
violations = Inspector.validate({
|
190
|
+
"title" => 123,
|
191
|
+
"body" => nil
|
192
|
+
}, :as => "request parameters")
|
193
|
+
|
194
|
+
puts violations unless violations.empty?
|
195
|
+
"""
|
196
|
+
When I run `ruby request.rb`
|
197
|
+
Then the output should contain:
|
198
|
+
"""
|
199
|
+
[title]:
|
200
|
+
should.be_kind_of
|
201
|
+
[body]:
|
202
|
+
should_not.be_empty
|
203
|
+
should.be_kind_of
|
204
|
+
should.have_at_least
|
205
|
+
"""
|
206
|
+
|
207
|
+
Scenario: array validation
|
208
|
+
Given a file named "request.rb" with:
|
209
|
+
"""
|
210
|
+
require 'inspector'
|
211
|
+
|
212
|
+
Inspector.valid("emails") do
|
213
|
+
each_item.should be_an_email
|
214
|
+
end
|
215
|
+
|
216
|
+
puts Inspector.validate(["not an email", "username@example.com"], :as => "emails")
|
217
|
+
"""
|
218
|
+
When I run `ruby request.rb`
|
219
|
+
Then the output should contain:
|
220
|
+
"""
|
221
|
+
[0]:
|
222
|
+
should.be_an_email
|
223
|
+
"""
|
data/inspector.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path("../lib/inspector/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "object-inspector"
|
5
|
+
gem.version = Inspector::VERSION
|
6
|
+
gem.license = 'MIT'
|
7
|
+
gem.authors = ["Bulat Shakirzyanov"]
|
8
|
+
gem.email = ["mallluhuct@gmail.com"]
|
9
|
+
gem.homepage = "http://avalanche123.github.com/inspector"
|
10
|
+
gem.summary = "inspector - ruby validation library"
|
11
|
+
gem.description = "Inspector is a validation library for Ruby with zero assumptions"
|
12
|
+
|
13
|
+
gem.required_ruby_version = '>= 1.9.2'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split("\n")
|
16
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rspec', '~> 2.6'
|
21
|
+
gem.add_development_dependency 'cucumber', '~> 1.2'
|
22
|
+
gem.add_development_dependency 'aruba', '~> 0.4'
|
23
|
+
gem.add_development_dependency 'rake', '~> 0.9'
|
24
|
+
end
|
data/lib/inspector.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Inspector
|
4
|
+
autoload :Validator, 'inspector/validator'
|
5
|
+
autoload :DSL, 'inspector/dsl'
|
6
|
+
autoload :Constraints, 'inspector/constraints'
|
7
|
+
autoload :Constraint, 'inspector/constraint'
|
8
|
+
autoload :Metadata, 'inspector/metadata'
|
9
|
+
autoload :TypeMetadata, 'inspector/type_metadata'
|
10
|
+
autoload :AttributeMetadata, 'inspector/attribute_metadata'
|
11
|
+
autoload :PropertyMetadata, 'inspector/property_metadata'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend Forwardable
|
15
|
+
def_delegators :@validator, :validate, :valid
|
16
|
+
end
|
17
|
+
|
18
|
+
@validators_map = {}
|
19
|
+
@validator = Validator.new(
|
20
|
+
Metadata::Map.new,
|
21
|
+
Metadata::Walker.new(Constraint::Violation::List, @validators_map),
|
22
|
+
TypeMetadata
|
23
|
+
)
|
24
|
+
|
25
|
+
@validators_map[:simple] = Constraint::Validators::Simple.new
|
26
|
+
@validators_map[:validity] = Constraint::Validators::Validity.new(@validator)
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Inspector
|
2
|
+
class AttributeMetadata
|
3
|
+
include Metadata
|
4
|
+
|
5
|
+
attr_reader :attribute_name
|
6
|
+
|
7
|
+
def initialize(type, attribute_name)
|
8
|
+
@attribute_name = attribute_name.to_sym
|
9
|
+
super(type)
|
10
|
+
end
|
11
|
+
|
12
|
+
def attribute_value(object)
|
13
|
+
object.__send__(@attribute_name)
|
14
|
+
rescue NoMethodError
|
15
|
+
raise "metadata for #{@type.inspect} contains attribute metadata, however " +
|
16
|
+
"#{object.inspect}.#{@attribute_name.inspect} is not defined"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraint
|
3
|
+
autoload :Violation, 'inspector/constraint/violation'
|
4
|
+
autoload :Validators, 'inspector/constraint/validators'
|
5
|
+
|
6
|
+
def negate!
|
7
|
+
@negative = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def positive?
|
11
|
+
!@negative
|
12
|
+
end
|
13
|
+
|
14
|
+
def validator
|
15
|
+
:simple
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraint
|
3
|
+
module Validators
|
4
|
+
class Simple
|
5
|
+
def validate(value, constraint, violations_list)
|
6
|
+
if constraint.positive? ^ constraint.valid?(value)
|
7
|
+
violations_list << Constraint::Violation.new(constraint)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Inspector
|
2
|
+
module Constraint
|
3
|
+
module Validators
|
4
|
+
class Validity
|
5
|
+
def initialize(validator)
|
6
|
+
@validator = validator
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate(value, constraint, violations_list)
|
10
|
+
violations = @validator.validate(value, :as => constraint.validate_as(value))
|
11
|
+
|
12
|
+
if constraint.positive?
|
13
|
+
violations.violations.each { |violation| violations_list << violation }
|
14
|
+
violations.children.each { |path, list| violations_list[path] = list }
|
15
|
+
else
|
16
|
+
if violations.empty?
|
17
|
+
violations_list << Constraint::Violation.new(constraint)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|