poro_validator 0.0.1

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +13 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +382 -0
  6. data/Rakefile +139 -0
  7. data/lib/poro_validator.rb +54 -0
  8. data/lib/poro_validator/configuration.rb +63 -0
  9. data/lib/poro_validator/error_store.rb +52 -0
  10. data/lib/poro_validator/errors.rb +56 -0
  11. data/lib/poro_validator/exceptions.rb +14 -0
  12. data/lib/poro_validator/validator.rb +82 -0
  13. data/lib/poro_validator/validator/base_class.rb +55 -0
  14. data/lib/poro_validator/validator/conditions.rb +71 -0
  15. data/lib/poro_validator/validator/context.rb +19 -0
  16. data/lib/poro_validator/validator/factory.rb +32 -0
  17. data/lib/poro_validator/validator/validation.rb +50 -0
  18. data/lib/poro_validator/validator/validations.rb +55 -0
  19. data/lib/poro_validator/validators/base_class.rb +53 -0
  20. data/lib/poro_validator/validators/exclusion_validator.rb +26 -0
  21. data/lib/poro_validator/validators/float_validator.rb +17 -0
  22. data/lib/poro_validator/validators/format_validator.rb +16 -0
  23. data/lib/poro_validator/validators/inclusion_validator.rb +26 -0
  24. data/lib/poro_validator/validators/integer_validator.rb +17 -0
  25. data/lib/poro_validator/validators/length_validator.rb +41 -0
  26. data/lib/poro_validator/validators/numeric_validator.rb +48 -0
  27. data/lib/poro_validator/validators/presence_validator.rb +23 -0
  28. data/lib/poro_validator/validators/range_array_validator.rb +19 -0
  29. data/lib/poro_validator/validators/with_validator.rb +21 -0
  30. data/lib/poro_validator/version.rb +3 -0
  31. data/poro_validator.gemspec +97 -0
  32. data/spec/features/composable_validations_spec.rb +26 -0
  33. data/spec/features/inheritable_spec.rb +29 -0
  34. data/spec/features/nested_validations_spec.rb +136 -0
  35. data/spec/lib/poro_validator/configuration_spec.rb +37 -0
  36. data/spec/lib/poro_validator/error_store_spec.rb +125 -0
  37. data/spec/lib/poro_validator/errors_spec.rb +79 -0
  38. data/spec/lib/poro_validator/validator/base_class_spec.rb +62 -0
  39. data/spec/lib/poro_validator/validator/conditions_spec.rb +112 -0
  40. data/spec/lib/poro_validator/validator/factory_spec.rb +23 -0
  41. data/spec/lib/poro_validator/validator/validation_spec.rb +69 -0
  42. data/spec/lib/poro_validator/validator/validations_spec.rb +34 -0
  43. data/spec/lib/poro_validator/validator_spec.rb +55 -0
  44. data/spec/lib/poro_validator/validators/base_class_spec.rb +11 -0
  45. data/spec/lib/poro_validator/validators/exclusion_validator_spec.rb +81 -0
  46. data/spec/lib/poro_validator/validators/float_validator_spec.rb +43 -0
  47. data/spec/lib/poro_validator/validators/format_validator_spec.rb +48 -0
  48. data/spec/lib/poro_validator/validators/inclusion_validator_spec.rb +81 -0
  49. data/spec/lib/poro_validator/validators/integer_validator_spec.rb +43 -0
  50. data/spec/lib/poro_validator/validators/length_validator_spec.rb +64 -0
  51. data/spec/lib/poro_validator/validators/numeric_validator_spec.rb +68 -0
  52. data/spec/lib/poro_validator/validators/presence_validator_spec.rb +52 -0
  53. data/spec/lib/poro_validator/validators/with_validator_spec.rb +90 -0
  54. data/spec/poro_validator_spec.rb +25 -0
  55. data/spec/spec_helper.rb +34 -0
  56. data/spec/support/spec_helpers/concerns.rb +46 -0
  57. data/spec/support/spec_helpers/validator_test_macros.rb +99 -0
  58. metadata +199 -0
@@ -0,0 +1,19 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class RangeArrayValidator < BaseClass
4
+ def validate_option(option)
5
+ !option.nil? && (option.is_a?(::Range) || option.is_a?(::Array))
6
+ end
7
+
8
+ def covered?(range, value)
9
+ return unless range.respond_to?(:cover?)
10
+ range.cover?(value)
11
+ end
12
+
13
+ def included?(array, value)
14
+ return unless array.respond_to?(:include?)
15
+ array.include?(value)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class WithValidator < BaseClass
4
+ def validate(attribute, value, options)
5
+ validator_class = options.fetch(:with)
6
+ case validator_class
7
+ when Class
8
+ klass = validator_class.new
9
+ klass.valid?(context.entity.public_send(attribute))
10
+ klass.errors.public_send(:store).data.each do |k,v|
11
+ errors.add([attribute, k.to_sym], v.pop)
12
+ end
13
+ else
14
+ raise ::PoroValidator::InvalidValidator.new(
15
+ "Requires a class object for this validator."
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module PoroValidator
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,97 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'poro_validator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "poro_validator"
8
+ spec.version = PoroValidator::VERSION
9
+ spec.authors = ["Kareem Gan"]
10
+ spec.email = ["kareemgan@gmail.com"]
11
+
12
+ spec.summary = %q{A PORO (Plain Old Ruby Object) validator.}
13
+ spec.description = %q{Validations for ruby objects or POROs.}
14
+ spec.homepage = "https://github.com/magicalbanana/poro_validator"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency("bundler")
31
+ spec.add_development_dependency("rake")
32
+ spec.add_development_dependency("rspec")
33
+ spec.add_development_dependency('simplecov')
34
+ spec.add_development_dependency('coveralls')
35
+ spec.add_development_dependency('recursive-open-struct')
36
+ spec.add_development_dependency('nyan-cat-formatter')
37
+ # = MANIFEST =
38
+ spec.files = %w[
39
+ CODE_OF_CONDUCT.md
40
+ Gemfile
41
+ LICENSE.txt
42
+ README.md
43
+ Rakefile
44
+ lib/poro_validator.rb
45
+ lib/poro_validator/configuration.rb
46
+ lib/poro_validator/error_store.rb
47
+ lib/poro_validator/errors.rb
48
+ lib/poro_validator/exceptions.rb
49
+ lib/poro_validator/validator.rb
50
+ lib/poro_validator/validator/base_class.rb
51
+ lib/poro_validator/validator/conditions.rb
52
+ lib/poro_validator/validator/context.rb
53
+ lib/poro_validator/validator/factory.rb
54
+ lib/poro_validator/validator/validation.rb
55
+ lib/poro_validator/validator/validations.rb
56
+ lib/poro_validator/validators/base_class.rb
57
+ lib/poro_validator/validators/exclusion_validator.rb
58
+ lib/poro_validator/validators/float_validator.rb
59
+ lib/poro_validator/validators/format_validator.rb
60
+ lib/poro_validator/validators/inclusion_validator.rb
61
+ lib/poro_validator/validators/integer_validator.rb
62
+ lib/poro_validator/validators/length_validator.rb
63
+ lib/poro_validator/validators/numeric_validator.rb
64
+ lib/poro_validator/validators/presence_validator.rb
65
+ lib/poro_validator/validators/range_array_validator.rb
66
+ lib/poro_validator/validators/with_validator.rb
67
+ lib/poro_validator/version.rb
68
+ poro_validator.gemspec
69
+ spec/features/composable_validations_spec.rb
70
+ spec/features/inheritable_spec.rb
71
+ spec/features/nested_validations_spec.rb
72
+ spec/lib/poro_validator/configuration_spec.rb
73
+ spec/lib/poro_validator/error_store_spec.rb
74
+ spec/lib/poro_validator/errors_spec.rb
75
+ spec/lib/poro_validator/validator/base_class_spec.rb
76
+ spec/lib/poro_validator/validator/conditions_spec.rb
77
+ spec/lib/poro_validator/validator/factory_spec.rb
78
+ spec/lib/poro_validator/validator/validation_spec.rb
79
+ spec/lib/poro_validator/validator/validations_spec.rb
80
+ spec/lib/poro_validator/validator_spec.rb
81
+ spec/lib/poro_validator/validators/base_class_spec.rb
82
+ spec/lib/poro_validator/validators/exclusion_validator_spec.rb
83
+ spec/lib/poro_validator/validators/float_validator_spec.rb
84
+ spec/lib/poro_validator/validators/format_validator_spec.rb
85
+ spec/lib/poro_validator/validators/inclusion_validator_spec.rb
86
+ spec/lib/poro_validator/validators/integer_validator_spec.rb
87
+ spec/lib/poro_validator/validators/length_validator_spec.rb
88
+ spec/lib/poro_validator/validators/numeric_validator_spec.rb
89
+ spec/lib/poro_validator/validators/presence_validator_spec.rb
90
+ spec/lib/poro_validator/validators/with_validator_spec.rb
91
+ spec/poro_validator_spec.rb
92
+ spec/spec_helper.rb
93
+ spec/support/spec_helpers/concerns.rb
94
+ spec/support/spec_helpers/validator_test_macros.rb
95
+ ]
96
+ # = MANIFEST =
97
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Compose validations using existing validations", type: :feature do
4
+ context "an existing validator already exists" do
5
+ AddressEntityValidator = Class.new do
6
+ include PoroValidator.validator
7
+
8
+ validates :line1, presence: true
9
+ validates :line2, presence: true
10
+ validates :city, presence: true
11
+ validates :state, presence: true
12
+ validates :zip_code, presence: true
13
+ end
14
+
15
+ it "uses the existing validator as for the validation" do
16
+ CustomerDetailEntityValidation = Class.new do
17
+ include PoroValidator.validator
18
+
19
+ validates :address, with: AddressEntityValidator
20
+ end
21
+
22
+ expect(CustomerDetailEntityValidation.validations.validators).
23
+ to eq({with: AddressEntityValidator})
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Allows a validator class to be subclassed", type: :feature do
4
+ context "if an existing validator is subclassed" do
5
+ it "ensures that the validation rules are carried over" do
6
+ class PersonValidator
7
+ include PoroValidator.validator
8
+
9
+ validates :name, presence: true
10
+ end
11
+
12
+ class CustomerValidator < PersonValidator
13
+ validates :customer_id, presence: true
14
+ end
15
+
16
+ entity = Class.new do
17
+ attr_accessor :customer_id
18
+ attr_accessor :name
19
+ end.new
20
+
21
+ validator = CustomerValidator.new
22
+ validator.valid?(entity)
23
+
24
+ expect(validator.errors.count).to eq(2)
25
+ expect(validator.errors.full_messages).
26
+ to eq(["name is not present", "customer_id is not present"])
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+ require 'recursive-open-struct'
3
+
4
+ RSpec.describe "Allows nested validations to be setup", type: :feature do
5
+ context "allows nested structure validations" do
6
+ subject(:validator) do
7
+ Class.new do
8
+ include PoroValidator.validator
9
+
10
+ validates :customer_id, presence: true
11
+
12
+ validates :customer do
13
+ validates :first_name, presence: true
14
+ validates :last_name, presence: true
15
+ end
16
+
17
+ validates :address do
18
+ validates :line1, presence: true
19
+ validates :line2, presence: true
20
+ validates :city, presence: true
21
+ validates :country do
22
+ validates :iso_code, presence: true
23
+ validates :short_name, presence: true
24
+ validates :coordinates do
25
+ validates :longtitude, presence: true
26
+ validates :latitude, presence:true
27
+ validates :planet do
28
+ validates :name, presence: true
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ it "creates validatons for the nested structure" do
37
+ expected_validations = [
38
+ {
39
+ validator: PoroValidator::Validators::PresenceValidator,
40
+ attribute: :customer_id
41
+ },
42
+ {
43
+ validator: PoroValidator::Validators::PresenceValidator,
44
+ attribute: [:customer, :first_name]
45
+ },
46
+ {
47
+ validator: PoroValidator::Validators::PresenceValidator,
48
+ attribute: [:customer, :last_name]
49
+ },
50
+ {
51
+ validator: PoroValidator::Validators::PresenceValidator,
52
+ attribute: [:address, :line1]
53
+ },
54
+ {
55
+ validator: PoroValidator::Validators::PresenceValidator,
56
+ attribute: [:address, :line2]
57
+ },
58
+ {
59
+ validator: PoroValidator::Validators::PresenceValidator,
60
+ attribute: [:address, :city]
61
+ },
62
+ {
63
+ validator: PoroValidator::Validators::PresenceValidator,
64
+ attribute: [:address, [:country, :iso_code]]
65
+ },
66
+ {
67
+ validator: PoroValidator::Validators::PresenceValidator,
68
+ attribute: [:address, [:country, :short_name]]
69
+ },
70
+ {
71
+ validator: PoroValidator::Validators::PresenceValidator,
72
+ attribute: [:address, [:country, [:coordinates, :longtitude]]]
73
+ },
74
+ {
75
+ validator: PoroValidator::Validators::PresenceValidator,
76
+ attribute: [:address, [:country, [:coordinates, :latitude]]]
77
+ },
78
+ {
79
+ validator: PoroValidator::Validators::PresenceValidator,
80
+ attribute: [:address, [:country, [:coordinates, [:planet, :name]]]]
81
+ }
82
+ ]
83
+
84
+ actual_validations = \
85
+ validator.validations.validations.inject([]) do |result, value|
86
+ result << { validator: value[:validator].class,
87
+ attribute: value[:validator].attribute
88
+ }
89
+ end
90
+
91
+ expect(actual_validations).to eq(expected_validations)
92
+ end
93
+
94
+ context "if validation fails" do
95
+ let(:validator) { subject.new }
96
+
97
+ let(:invalid_entity) do
98
+ RecursiveOpenStruct.new(
99
+ customer: {
100
+ first_name: "Man Bear",
101
+ last_name: nil,
102
+ },
103
+ address: {
104
+ line1: "175 West Jackson Boulevard",
105
+ line2: "5th Floor",
106
+ city: "Chicago",
107
+ country: {
108
+ iso_code: "33CHIC",
109
+ short_name: "CHICAGOUSA",
110
+ coordinates: {
111
+ longtitude: nil,
112
+ latitude: nil,
113
+ planet: {
114
+ name: nil
115
+ }
116
+ }
117
+ }
118
+ }
119
+ )
120
+ end
121
+
122
+ describe "#valid?" do
123
+ it "returns false" do
124
+ expect(validator.valid?(invalid_entity)).to be_falsey
125
+ end
126
+ end
127
+
128
+ describe "#errors" do
129
+ it "returns a non empty error hash" do
130
+ validator.valid?(invalid_entity)
131
+ expect(validator.errors.count).to be > 0
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe PoroValidator::Configuration do
4
+ describe described_class::Message do
5
+ let(:message) { described_class.new }
6
+
7
+ describe "#get" do
8
+ context "if the validator has a preset mesasge" do
9
+ it "gets the message for that validator" do
10
+ expect(message.get(:presence)).to eq("is not present")
11
+ end
12
+ end
13
+
14
+ context "if the validator does not have a preset message" do
15
+ it "gets the default message" do
16
+ expect(message.get(:man_bear_pig)).to eq("is not valid")
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#set" do
22
+ context "if a non proc class is passed in for the message" do
23
+ it "raises an exception of PoroValidator::ValidatorException" do
24
+ expect { message.set(:presence, "no message") }.
25
+ to raise_error(PoroValidator::ConfigError)
26
+ end
27
+ end
28
+
29
+ context "if a proc class is passed in for the message" do
30
+ it "sets the message for that validator" do
31
+ message.set(:presence, lambda { "custom message" })
32
+ expect(message.get(:presence)).to eq("custom message")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe PoroValidator::ErrorStore do
4
+ describe "#initialize" do
5
+ context "if no data is loaded" do
6
+ it "returns an empty hash" do
7
+ store = described_class.new
8
+ expect(store.data).to eq({})
9
+ end
10
+ end
11
+
12
+ context "if data is loaded" do
13
+ it "returns the passed data" do
14
+ data = { "key" => "foo" }
15
+ store = described_class.new(data)
16
+ expect(store.data).to eq(data)
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#get" do
22
+ let(:store) { described_class.new }
23
+
24
+ context "if the key is valid" do
25
+ it "returns a value if set" do
26
+ store.set("name", "foo")
27
+ expect(store.get("name")).to eq("foo")
28
+ end
29
+
30
+ it "returns nil if the value is not set" do
31
+ expect(store.get("name")).to be_nil
32
+ end
33
+ end
34
+
35
+ context "if the key is not valid" do
36
+ it "raises TypeError exception" do
37
+ expect { store.get(1) }.to raise_error(
38
+ ::PoroValidator::InvalidType
39
+ )
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#set" do
45
+ let(:store) { described_class.new }
46
+ let(:key) { "foo" }
47
+ let(:val) { "fam" }
48
+
49
+ context "if the key is valid" do
50
+ it "does not raise a TypeError exception" do
51
+ expect { store.set(key, val) }.to_not raise_error
52
+ end
53
+
54
+ context "and the key has a value given" do
55
+ it "sets a key with the given value" do
56
+ store.set(key, val)
57
+ expect(store.get(key)).to eq(val)
58
+ end
59
+ end
60
+
61
+ context "and the key has no valie given" do
62
+ it "sets the key's value to nil" do
63
+ store.set(key)
64
+ expect(store.get(key)).to be_nil
65
+ end
66
+ end
67
+
68
+ context "and a block is passed is as a value" do
69
+ it "sets the value from the given block" do
70
+ store.set(key, val) { "block_val" }
71
+ expect(store.get(key)).to_not eq(val)
72
+ expect(store.get(key)).to eq("block_val")
73
+ end
74
+ end
75
+ end
76
+
77
+ context "if the key is not valid" do
78
+ it "raises a TypeError exception" do
79
+ expect { store.set(1, val) }.to raise_error(
80
+ ::PoroValidator::InvalidType
81
+ )
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#set?" do
87
+ let(:store) { described_class.new }
88
+ let(:key) { "foo" }
89
+ let(:val) { "fam" }
90
+
91
+ before(:each) do
92
+ store.set(key, val)
93
+ end
94
+
95
+ context "if a key has already been set" do
96
+ it "returns true" do
97
+ expect(store.set?(key)).to be_truthy
98
+ end
99
+ end
100
+
101
+ context "if the key has not been set" do
102
+ it "returns false" do
103
+ expect(store.set?("banana")).to be_falsey
104
+ end
105
+ end
106
+
107
+ context "if the key is not valid" do
108
+ it "raises a TypeError exception" do
109
+ expect { store.set?(1) }.to raise_error(
110
+ ::PoroValidator::InvalidType
111
+ )
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#reset" do
117
+ let(:store) { described_class.new }
118
+
119
+ it "clears the data stored" do
120
+ store.set(:foo, "foo")
121
+ expect(store.get(:foo)).to eq("foo")
122
+ expect(store.reset).to eq({})
123
+ end
124
+ end
125
+ end