object-inspector 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +45 -0
  4. data/README.md +335 -0
  5. data/Rakefile +13 -0
  6. data/examples/example.rb +131 -0
  7. data/features/error_display.feature +80 -0
  8. data/features/support/env.rb +1 -0
  9. data/features/syntax.feature +223 -0
  10. data/inspector.gemspec +24 -0
  11. data/lib/inspector.rb +27 -0
  12. data/lib/inspector/attribute_metadata.rb +19 -0
  13. data/lib/inspector/constraint.rb +18 -0
  14. data/lib/inspector/constraint/validators.rb +8 -0
  15. data/lib/inspector/constraint/validators/simple.rb +13 -0
  16. data/lib/inspector/constraint/validators/validity.rb +24 -0
  17. data/lib/inspector/constraint/violation.rb +34 -0
  18. data/lib/inspector/constraint/violation/list.rb +93 -0
  19. data/lib/inspector/constraints.rb +61 -0
  20. data/lib/inspector/constraints/email.rb +57 -0
  21. data/lib/inspector/constraints/empty.rb +21 -0
  22. data/lib/inspector/constraints/eq.rb +23 -0
  23. data/lib/inspector/constraints/false.rb +19 -0
  24. data/lib/inspector/constraints/have.rb +85 -0
  25. data/lib/inspector/constraints/predicate.rb +38 -0
  26. data/lib/inspector/constraints/true.rb +19 -0
  27. data/lib/inspector/constraints/valid.rb +31 -0
  28. data/lib/inspector/metadata.rb +75 -0
  29. data/lib/inspector/metadata/map.rb +24 -0
  30. data/lib/inspector/metadata/walker.rb +46 -0
  31. data/lib/inspector/property_metadata.rb +19 -0
  32. data/lib/inspector/type_metadata.rb +5 -0
  33. data/lib/inspector/validator.rb +26 -0
  34. data/lib/inspector/version.rb +3 -0
  35. data/lib/object_inspector.rb +1 -0
  36. data/spec/inspector/attribute_metadata_spec.rb +8 -0
  37. data/spec/inspector/constraint/violation/list_spec.rb +45 -0
  38. data/spec/inspector/constraints/false_spec.rb +18 -0
  39. data/spec/inspector/constraints_spec.rb +15 -0
  40. data/spec/inspector/metadata/map_spec.rb +38 -0
  41. data/spec/inspector/metadata/walker_spec.rb +7 -0
  42. data/spec/inspector/property_metadata_spec.rb +8 -0
  43. data/spec/inspector/type_metadata_spec.rb +7 -0
  44. data/spec/inspector/validator_spec.rb +65 -0
  45. data/spec/inspector_spec.rb +22 -0
  46. data/spec/shared_examples/metadata.rb +33 -0
  47. data/spec/spec_helper.rb +3 -0
  48. 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,8 @@
1
+ module Inspector
2
+ module Constraint
3
+ module Validators
4
+ autoload :Validity, 'inspector/constraint/validators/validity'
5
+ autoload :Simple, 'inspector/constraint/validators/simple'
6
+ end
7
+ end
8
+ 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