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.
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