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 Validator
3
+ class Context
4
+ attr_reader :entity, :context, :errors
5
+
6
+ # Initializes a new validator context
7
+ #
8
+ # @param entity [Object] The entity which to be validated
9
+ # @param context [self] The validating/validator class
10
+ # @param errors [Object] Error cache
11
+
12
+ def initialize(entity, context, errors)
13
+ @entity = entity
14
+ @context = context
15
+ @errors = errors
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ module PoroValidator
2
+ module Validator
3
+ class Factory
4
+ class Validators
5
+ class << self
6
+ def set_validator(attr_name, validator, options={})
7
+ klass = class_name(validator)
8
+ begin
9
+ PoroValidator.const_get(klass).new(attr_name, options)
10
+ rescue NameError => e
11
+ raise(::PoroValidator::ValidatorNotFound.new(
12
+ "Validator not found: ::PoroValidator::#{klass} exception: #{e}"
13
+ ))
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def class_name(validator)
20
+ "Validators::#{camel_case(validator.to_s)}Validator"
21
+ end
22
+
23
+ def camel_case(str)
24
+ str.split('_').map do |char|
25
+ char.capitalize
26
+ end.join('')
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ module PoroValidator
2
+ module Validator
3
+ class Validation
4
+ # Builds a validation from the params passed which is initiated first
5
+ # then passed into the Validators.set_validator method
6
+ #
7
+ # @params[attr_name] - name of the attribute to be validated
8
+ # @params[validator] - validator class to be used for validating
9
+ # attribute
10
+ # @params[options] - arguments against validator and additional options
11
+ # e.g, message:, on:, etc.
12
+ def self.build(attr_name, validator, options)
13
+ b = new(attr_name, validator, options)
14
+ b = Factory::Validators.set_validator(b.attr_name,
15
+ b.validator,
16
+ b.options)
17
+ b
18
+ end
19
+
20
+ def initialize(attr_name, validator, options = nil)
21
+ @attr_name = attr_name
22
+ @validator = validator
23
+ opts =
24
+ case options
25
+ when TrueClass
26
+ {}
27
+ when Hash
28
+ options
29
+ when Range, Array
30
+ { in: options }
31
+ else
32
+ { with: options }
33
+ end
34
+ @options = opts
35
+ end
36
+
37
+ def options
38
+ @options
39
+ end
40
+
41
+ def attr_name
42
+ @attr_name
43
+ end
44
+
45
+ def validator
46
+ @validator
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ module PoroValidator
2
+ module Validator
3
+ # @private
4
+ class Validations < BaseClass
5
+ def self.build_validations(validations = [])
6
+ inst = new
7
+ unless validations.empty?
8
+ validations.each do |validation|
9
+ inst << validation
10
+ end
11
+ end
12
+ inst
13
+ end
14
+
15
+ def initialize(&block)
16
+ super()
17
+ instance_eval(&block) if block_given?
18
+ end
19
+
20
+ def build(attr, **options)
21
+ @validators = options.reject do |k,v|
22
+ [:if, :unless].include?(k)
23
+ end
24
+
25
+ @conditions = [] << options.select do |k,v|
26
+ [:if, :unless].include?(k)
27
+ end
28
+
29
+ @validators.each do |validator,options|
30
+ nested_conditions = {}
31
+ if options.is_a?(::Hash)
32
+ # Check if there are any nested conditions for a validator
33
+ # options and select if it exists.
34
+ #
35
+ # e.g
36
+ # validates :foo, format: { with: "hello", unless: :foo_rule }
37
+ nested_conditions = options.select do |k,v|
38
+ [:if, :unless].include?(k)
39
+ end
40
+
41
+ # Remove the nested conditions from the validator options.
42
+ unless nested_conditions.empty?
43
+ options.reject! { |k,v| [:if, :unless].include?(k) }
44
+ @conditions << nested_conditions
45
+ end
46
+ end
47
+
48
+ self << { validator: Validation.build(attr, validator, options),
49
+ conditions: @conditions.reject(&:empty?)
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,53 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class BaseClass
4
+ attr_writer :attribute
5
+
6
+ def initialize(attribute, options = {})
7
+ @attribute = attribute
8
+ @options = options
9
+ end
10
+
11
+ def options
12
+ @options
13
+ end
14
+
15
+ def attribute
16
+ @attribute
17
+ end
18
+
19
+ def errors
20
+ @errors ||= context.errors
21
+ end
22
+
23
+ def context
24
+ @context
25
+ end
26
+
27
+ def nested?
28
+ attribute.is_a?(::Array)
29
+ end
30
+
31
+ def validate(attribute, value, options)
32
+ raise ::PoroValidator::OverloadriddenRequired.new(
33
+ "This method needs to be overloaded/overriden."
34
+ )
35
+ end
36
+
37
+ def value
38
+ if nested?
39
+ @value = attribute.flatten.inject(context.entity, :public_send)
40
+ else
41
+ @value = context.entity.public_send(attribute)
42
+ end
43
+ end
44
+
45
+ # @private
46
+ def __validate__(validator_context)
47
+ @context = validator_context
48
+ @errors = context.errors
49
+ validate(attribute, value, options)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,26 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class ExclusionValidator < RangeArrayValidator
4
+ def validate(attribute, value, options)
5
+ in_option = options[:in]
6
+
7
+ unless validate_option(in_option)
8
+ raise ::PoroValidator::InvalidValidator,
9
+ [
10
+ "There was no range or array specified. Pass in a range or array.",
11
+ "e.g",
12
+ "validates :foo, exclusion: 1..10",
13
+ "or",
14
+ "validates :boo, exclusion: { in: 1..10 }"
15
+ ].join("\n")
16
+ end
17
+
18
+ message = options.fetch(:message, :exclusion)
19
+
20
+ if covered?(in_option, value) || included?(in_option, value)
21
+ errors.add(attribute, message, in_option)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class FloatValidator < BaseClass
4
+ def validate(attribute, value, options)
5
+ message = options.fetch(:message, :float)
6
+
7
+ begin
8
+ Kernel.Float(value.to_s)
9
+ nil
10
+ rescue
11
+ errors.add(attribute, message)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,16 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class FormatValidator < BaseClass
4
+ def validate(attribute, value, options)
5
+ return if value.nil?
6
+
7
+ pattern = options.fetch(:with)
8
+ message = options.fetch(:message, :format)
9
+
10
+ unless value.to_s =~ pattern
11
+ errors.add(attribute, message, pattern)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class InclusionValidator < RangeArrayValidator
4
+ def validate(attribute, value, options)
5
+ in_option = options[:in]
6
+
7
+ unless validate_option(in_option)
8
+ raise ::PoroValidator::InvalidValidator,
9
+ [
10
+ "There was no range or array specified. Pass in a range or array.",
11
+ "e.g",
12
+ "validates :foo, inclusion: 1..10",
13
+ "or",
14
+ "validates :boo, inclusion: { in: 1..10 }"
15
+ ].join("\n")
16
+ end
17
+
18
+ message = options.fetch(:message, :inclusion)
19
+
20
+ unless covered?(in_option, value) || included?(in_option, value)
21
+ errors.add(attribute, message, in_option)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class IntegerValidator < BaseClass
4
+ def validate(attribute, value, options)
5
+ message = options.fetch(:message, :integer)
6
+
7
+ begin
8
+ Kernel.Integer(value.to_s)
9
+ nil
10
+ rescue
11
+ errors.add(attribute, message)
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class LengthValidator < BaseClass
4
+ def validate(attribute, value, options)
5
+ message = options.fetch(:message, :length)
6
+
7
+ options.keys.each do |key|
8
+ matcher = matchers(key)
9
+ next if matcher.nil?
10
+ v = value.is_a?(::String) ? value : value.to_s
11
+ unless matchers(key).call(v, options[key])
12
+ errors.add(attribute, message, key => options[key])
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def matchers(key)
20
+ m = {
21
+ extremum: lambda do |value, length|
22
+ value.length == length
23
+ end,
24
+
25
+ max: lambda do |value, length|
26
+ value.length <= length
27
+ end,
28
+
29
+ min: lambda do |value, length|
30
+ value.length >= length
31
+ end,
32
+
33
+ in: lambda do |value, length|
34
+ length.cover?(value.length)
35
+ end
36
+ }
37
+ m.fetch(key, nil)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class NumericValidator < BaseClass
4
+ def validate(attribute, value, options)
5
+ message = options.fetch(:message, :numeric)
6
+
7
+ begin
8
+ Kernel.Integer(value.to_s)
9
+ nil
10
+ rescue
11
+ errors.add(attribute, :integer)
12
+ return
13
+ end
14
+
15
+ options.keys.each do |key|
16
+ matcher = matchers(key)
17
+ next if matcher.nil?
18
+ unless matchers(key).call(value, options[key])
19
+ errors.add(attribute, message, key => options[key])
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def matchers(key)
27
+ m = {
28
+ extremum: lambda do |value, length|
29
+ value == length
30
+ end,
31
+
32
+ max: lambda do |value, length|
33
+ value <= length
34
+ end,
35
+
36
+ min: lambda do |value, length|
37
+ value >= length
38
+ end,
39
+
40
+ in: lambda do |value, length|
41
+ length.cover?(value)
42
+ end
43
+ }
44
+ m.fetch(key, nil)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ module PoroValidator
2
+ module Validators
3
+ class PresenceValidator < BaseClass
4
+ BLANK_STRING_MATCHER = /\A[[:space:]]*\z/.freeze
5
+
6
+ def validate(attribute, value, options)
7
+ allow_blank = options.fetch(:allow_blank, false)
8
+ message = options.fetch(:message, :presence)
9
+
10
+ if value.is_a?(::String)
11
+ if value.gsub(/\s+/, '').match(BLANK_STRING_MATCHER)
12
+ errors.add(attribute, message) unless allow_blank
13
+ return
14
+ end
15
+ end
16
+
17
+ if value.nil? || (value.respond_to?(:empty?) && value.empty?)
18
+ errors.add(attribute, message)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end