mini_defender 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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +70 -0
  5. data/LICENSE.md +21 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +33 -0
  8. data/RULES.md +92 -0
  9. data/Rakefile +12 -0
  10. data/lib/mini_defender/extensions/enumerable.rb +34 -0
  11. data/lib/mini_defender/extensions/hash.rb +28 -0
  12. data/lib/mini_defender/rule.rb +77 -0
  13. data/lib/mini_defender/rules/accepted.rb +17 -0
  14. data/lib/mini_defender/rules/accepted_if.rb +26 -0
  15. data/lib/mini_defender/rules/alpha.rb +15 -0
  16. data/lib/mini_defender/rules/alpha_dash.rb +15 -0
  17. data/lib/mini_defender/rules/alpha_num.rb +15 -0
  18. data/lib/mini_defender/rules/array.rb +15 -0
  19. data/lib/mini_defender/rules/bail.rb +19 -0
  20. data/lib/mini_defender/rules/between.rb +29 -0
  21. data/lib/mini_defender/rules/boolean.rb +26 -0
  22. data/lib/mini_defender/rules/confirmed.rb +24 -0
  23. data/lib/mini_defender/rules/country_code.rb +23 -0
  24. data/lib/mini_defender/rules/credit_card.rb +13 -0
  25. data/lib/mini_defender/rules/date.rb +27 -0
  26. data/lib/mini_defender/rules/date_eq.rb +43 -0
  27. data/lib/mini_defender/rules/date_format.rb +38 -0
  28. data/lib/mini_defender/rules/date_gt.rb +24 -0
  29. data/lib/mini_defender/rules/date_gte.rb +24 -0
  30. data/lib/mini_defender/rules/date_lt.rb +24 -0
  31. data/lib/mini_defender/rules/date_lte.rb +24 -0
  32. data/lib/mini_defender/rules/declined.rb +17 -0
  33. data/lib/mini_defender/rules/declined_if.rb +26 -0
  34. data/lib/mini_defender/rules/default.rb +33 -0
  35. data/lib/mini_defender/rules/different.rb +31 -0
  36. data/lib/mini_defender/rules/digits.rb +26 -0
  37. data/lib/mini_defender/rules/digits_between.rb +29 -0
  38. data/lib/mini_defender/rules/distinct.rb +18 -0
  39. data/lib/mini_defender/rules/email.rb +19 -0
  40. data/lib/mini_defender/rules/ending_with.rb +31 -0
  41. data/lib/mini_defender/rules/equal.rb +9 -0
  42. data/lib/mini_defender/rules/excluded.rb +15 -0
  43. data/lib/mini_defender/rules/excluded_if.rb +28 -0
  44. data/lib/mini_defender/rules/excluded_unless.rb +28 -0
  45. data/lib/mini_defender/rules/excluded_with.rb +27 -0
  46. data/lib/mini_defender/rules/excluded_without.rb +27 -0
  47. data/lib/mini_defender/rules/exists.rb +29 -0
  48. data/lib/mini_defender/rules/expiry_date.rb +28 -0
  49. data/lib/mini_defender/rules/file.rb +17 -0
  50. data/lib/mini_defender/rules/filled.rb +22 -0
  51. data/lib/mini_defender/rules/greater_than.rb +34 -0
  52. data/lib/mini_defender/rules/greater_than_or_equal.rb +34 -0
  53. data/lib/mini_defender/rules/hash.rb +15 -0
  54. data/lib/mini_defender/rules/image.rb +19 -0
  55. data/lib/mini_defender/rules/in.rb +27 -0
  56. data/lib/mini_defender/rules/in_field.rb +40 -0
  57. data/lib/mini_defender/rules/integer.rb +19 -0
  58. data/lib/mini_defender/rules/ip.rb +43 -0
  59. data/lib/mini_defender/rules/ipv4.rb +43 -0
  60. data/lib/mini_defender/rules/ipv6.rb +43 -0
  61. data/lib/mini_defender/rules/json.rb +19 -0
  62. data/lib/mini_defender/rules/less_than.rb +34 -0
  63. data/lib/mini_defender/rules/less_than_or_equal.rb +34 -0
  64. data/lib/mini_defender/rules/luhn.rb +33 -0
  65. data/lib/mini_defender/rules/mac_address.rb +39 -0
  66. data/lib/mini_defender/rules/max.rb +9 -0
  67. data/lib/mini_defender/rules/max_digits.rb +32 -0
  68. data/lib/mini_defender/rules/mime_types.rb +37 -0
  69. data/lib/mini_defender/rules/min.rb +9 -0
  70. data/lib/mini_defender/rules/min_digits.rb +32 -0
  71. data/lib/mini_defender/rules/national_id.rb +13 -0
  72. data/lib/mini_defender/rules/not_ending_with.rb +31 -0
  73. data/lib/mini_defender/rules/not_in.rb +27 -0
  74. data/lib/mini_defender/rules/not_regex.rb +27 -0
  75. data/lib/mini_defender/rules/not_starting_with.rb +31 -0
  76. data/lib/mini_defender/rules/numeric.rb +19 -0
  77. data/lib/mini_defender/rules/present.rb +19 -0
  78. data/lib/mini_defender/rules/prohibited.rb +15 -0
  79. data/lib/mini_defender/rules/prohibited_if.rb +32 -0
  80. data/lib/mini_defender/rules/prohibited_unless.rb +32 -0
  81. data/lib/mini_defender/rules/regex.rb +27 -0
  82. data/lib/mini_defender/rules/required.rb +32 -0
  83. data/lib/mini_defender/rules/required_if.rb +26 -0
  84. data/lib/mini_defender/rules/required_unless.rb +26 -0
  85. data/lib/mini_defender/rules/required_with.rb +27 -0
  86. data/lib/mini_defender/rules/required_with_all.rb +27 -0
  87. data/lib/mini_defender/rules/required_without.rb +27 -0
  88. data/lib/mini_defender/rules/required_without_all.rb +27 -0
  89. data/lib/mini_defender/rules/size.rb +45 -0
  90. data/lib/mini_defender/rules/starting_with.rb +31 -0
  91. data/lib/mini_defender/rules/string.rb +15 -0
  92. data/lib/mini_defender/rules/timezone.rb +19 -0
  93. data/lib/mini_defender/rules/unique.rb +37 -0
  94. data/lib/mini_defender/rules/url.rb +17 -0
  95. data/lib/mini_defender/rules/uuid.rb +37 -0
  96. data/lib/mini_defender/rules.rb +8 -0
  97. data/lib/mini_defender/rules_expander.rb +19 -0
  98. data/lib/mini_defender/rules_factory.rb +36 -0
  99. data/lib/mini_defender/validates_input.rb +12 -0
  100. data/lib/mini_defender/validation_error.rb +10 -0
  101. data/lib/mini_defender/validator.rb +127 -0
  102. data/lib/mini_defender/version.rb +5 -0
  103. data/lib/mini_defender.rb +24 -0
  104. data/mini_defender.gemspec +38 -0
  105. data/sig/mini_defender.rbs +4 -0
  106. metadata +206 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::NotIn < MiniDefender::Rule
4
+ def initialize(values)
5
+ raise ArgumentError, 'Expected an array of values.' unless values.is_a?(Array)
6
+
7
+ @values = values
8
+ end
9
+
10
+ def self.signature
11
+ 'not_in'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected at least one argument.' unless args.length > 0
16
+
17
+ new(args)
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ @values.include?(value)
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ "The value should not be any of the following #{@values.to_sentence}."
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::NotRegex < MiniDefender::Rule
4
+ def initialize(pattern)
5
+ raise ArgumentError, 'Expected a Regexp instance.' unless pattern.is_a?(Regexp)
6
+
7
+ @pattern = pattern
8
+ end
9
+
10
+ def self.signature
11
+ 'not_regex'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected a pattern as input.' unless args.length == 1
16
+
17
+ new(Regexp.compile(args[0]))
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ !value.to_s.match?(@pattern)
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ "The value must not match #{@pattern.to_s}."
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::NotStartingWith < MiniDefender::Rule
4
+ def initialize(fragments)
5
+ unless fragments.is_a?(Array) && !fragments.empty? && fragments.all? { |f| f.is_a?(String) }
6
+ raise ArgumentError, 'Expected an array of strings.'
7
+ end
8
+
9
+ @fragments = fragments
10
+ end
11
+
12
+ def self.signature
13
+ 'not_starting_with'
14
+ end
15
+
16
+ def self.make(args)
17
+ new(args)
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ @fragments.any? { |f| !value.to_s.start_with?(f) }
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ if @fragments.length == 1
26
+ "The value should not start with #{@fragments[0]}."
27
+ else
28
+ "The value should not start with one of the following #{@fragments.join(', ')}."
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Numeric < MiniDefender::Rule
4
+ def self.signature
5
+ 'numeric'
6
+ end
7
+
8
+ def coerce(value)
9
+ value.is_a?(Numeric) ? value : Float(value.to_s)
10
+ end
11
+
12
+ def passes?(attribute, value, validator)
13
+ value.is_a?(Numeric) || Float(value.to_s) rescue false
14
+ end
15
+
16
+ def message(attribute, value, validator)
17
+ "The field must contain a numeric value."
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Present < MiniDefender::Rule
4
+ def self.signature
5
+ 'present'
6
+ end
7
+
8
+ def implicit?(validator)
9
+ true
10
+ end
11
+
12
+ def passes?(attribute, value, validator)
13
+ true
14
+ end
15
+
16
+ def message(attribute, value, validator)
17
+ "The field should be present."
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Prohibited < MiniDefender::Rule
4
+ def self.signature
5
+ 'prohibited'
6
+ end
7
+
8
+ def passes?(attribute, value, validator)
9
+ value.blank?
10
+ end
11
+
12
+ def message(attribute, value, validator)
13
+ "This field is prohibited."
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::ProhibitedIf < MiniDefender::Rule
4
+ def initialize(target, value)
5
+ raise ArgumentError, 'Target must be a string' unless target.is_a?(String)
6
+
7
+ @target = target
8
+ @value = value
9
+ end
10
+
11
+ def self.signature
12
+ 'prohibited_if'
13
+ end
14
+
15
+ def self.make(args)
16
+ raise ArgumentError, 'Target and expected value are required.' unless args.length == 2
17
+
18
+ self.new(args[0], args[1])
19
+ end
20
+
21
+ def active?(validator)
22
+ validator.data.key?(@target) && validator.data[@target] == @value
23
+ end
24
+
25
+ def passes?(attribute, value, validator)
26
+ value.blank?
27
+ end
28
+
29
+ def message(attribute, value, validator)
30
+ "This field is prohibited."
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::ProhibitedUnless < MiniDefender::Rule
4
+ def initialize(target, value)
5
+ raise ArgumentError, 'Target must be a string' unless target.is_a?(String)
6
+
7
+ @target = target
8
+ @value = value
9
+ end
10
+
11
+ def self.signature
12
+ 'prohibited_unless'
13
+ end
14
+
15
+ def self.make(args)
16
+ raise ArgumentError, 'Target and expected value are required.' unless args.length == 2
17
+
18
+ self.new(args[0], args[1])
19
+ end
20
+
21
+ def active?(validator)
22
+ validator.data.key?(@target) && validator.data[@target] != @value
23
+ end
24
+
25
+ def passes?(attribute, value, validator)
26
+ value.blank?
27
+ end
28
+
29
+ def message(attribute, value, validator)
30
+ "This field is prohibited."
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Regex < MiniDefender::Rule
4
+ def initialize(pattern)
5
+ raise ArgumentError, 'Expected a Regexp instance.' unless pattern.is_a?(Regexp)
6
+
7
+ @pattern = pattern
8
+ end
9
+
10
+ def self.signature
11
+ 'regex'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected a pattern as input.' unless args.length == 1
16
+
17
+ new(Regexp.compile(args[0]))
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ value.to_s.match?(@pattern)
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ "The value must match #{@pattern.to_s}."
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+
5
+ class MiniDefender::Rules::Required < MiniDefender::Rule
6
+ def self.signature
7
+ 'required'
8
+ end
9
+
10
+ def implicit?(validator)
11
+ true
12
+ end
13
+
14
+ def passes?(attribute, value, validator)
15
+ case value
16
+ when nil
17
+ false
18
+ when String
19
+ value.strip.length > 0
20
+ when Enumerable
21
+ value.length > 0
22
+ when ActionDispatch::Http::UploadedFile
23
+ value.path && value.path.length > 0 && value.size > 0
24
+ else
25
+ true
26
+ end
27
+ end
28
+
29
+ def message(attribute, value, validator)
30
+ "This field is required."
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'required'
4
+
5
+ class MiniDefender::Rules::RequiredIf < MiniDefender::Rules::Required
6
+ def initialize(target, value)
7
+ raise ArgumentError, 'Target must be a string' unless target.is_a?(String)
8
+
9
+ @target = target
10
+ @value = value
11
+ end
12
+
13
+ def self.signature
14
+ 'required_if'
15
+ end
16
+
17
+ def self.make(args)
18
+ raise ArgumentError, 'Target and expected value are required.' unless args.length == 2
19
+
20
+ self.new(args[0], args[1])
21
+ end
22
+
23
+ def implicit?(validator)
24
+ validator.data.key?(@target) && validator.data[@target] == @value
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'required'
4
+
5
+ class MiniDefender::Rules::RequiredUnless < MiniDefender::Rules::Required
6
+ def initialize(target, value)
7
+ raise ArgumentError, 'Target must be a string' unless target.is_a?(String)
8
+
9
+ @target = target
10
+ @value = value
11
+ end
12
+
13
+ def self.signature
14
+ 'required_unless'
15
+ end
16
+
17
+ def self.make(args)
18
+ raise ArgumentError, 'Target and expected value are required.' unless args.length == 2
19
+
20
+ self.new(args[0], args[1])
21
+ end
22
+
23
+ def implicit?(validator)
24
+ validator.data.key?(@target) && validator.data[@target] != @value
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'required'
4
+
5
+ class MiniDefender::Rules::RequiredWith < MiniDefender::Rules::Required
6
+ def initialize(targets)
7
+ unless targets.is_a?(Array) && targets.all?{ |t| t.is_a?(String) }
8
+ raise ArgumentError, 'Expected an array of strings.'
9
+ end
10
+
11
+ @targets = targets
12
+ end
13
+
14
+ def self.signature
15
+ 'required_with'
16
+ end
17
+
18
+ def self.make(args)
19
+ raise ArgumentError, 'Expected at least one argument.' unless args.length >= 1
20
+
21
+ self.new(args)
22
+ end
23
+
24
+ def implicit?(validator)
25
+ @targets.any? { |t| validator.data.key?(t) }
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'required'
4
+
5
+ class MiniDefender::Rules::RequiredWithAll < MiniDefender::Rules::Required
6
+ def initialize(targets)
7
+ unless targets.is_a?(Array) && targets.all?{ |t| t.is_a?(String) }
8
+ raise ArgumentError, 'Expected an array of strings.'
9
+ end
10
+
11
+ @targets = targets
12
+ end
13
+
14
+ def self.signature
15
+ 'required_with_all'
16
+ end
17
+
18
+ def self.make(args)
19
+ raise ArgumentError, 'Expected at least one argument.' unless args.length >= 1
20
+
21
+ self.new(args)
22
+ end
23
+
24
+ def implicit?(validator)
25
+ @targets.all? { |t| validator.data.key?(t) }
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'required'
4
+
5
+ class MiniDefender::Rules::RequiredWithout < MiniDefender::Rules::Required
6
+ def initialize(targets)
7
+ unless targets.is_a?(Array) && targets.all?{ |t| t.is_a?(String) }
8
+ raise ArgumentError, 'Expected an array of strings.'
9
+ end
10
+
11
+ @targets = targets
12
+ end
13
+
14
+ def self.signature
15
+ 'required_without'
16
+ end
17
+
18
+ def self.make(args)
19
+ raise ArgumentError, 'Expected at least one argument.' unless args.length >= 1
20
+
21
+ self.new(args)
22
+ end
23
+
24
+ def implicit?(validator)
25
+ !@targets.any? { |t| validator.data.key?(t) }
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'required'
4
+
5
+ class MiniDefender::Rules::RequiredWithoutAll < MiniDefender::Rules::Required
6
+ def initialize(targets)
7
+ unless targets.is_a?(Array) && targets.all?{ |t| t.is_a?(String) }
8
+ raise ArgumentError, 'Expected an array of strings.'
9
+ end
10
+
11
+ @targets = targets
12
+ end
13
+
14
+ def self.signature
15
+ 'required_without_all'
16
+ end
17
+
18
+ def self.make(args)
19
+ raise ArgumentError, 'Expected at least one argument.' unless args.length >= 1
20
+
21
+ self.new(args)
22
+ end
23
+
24
+ def implicit?(validator)
25
+ @targets.none? { |t| validator.data.key?(t) }
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+
5
+ class MiniDefender::Rules::Size < MiniDefender::Rule
6
+ def initialize(size)
7
+ raise ArgumentError, 'Size must be an integer.' unless size.is_a?(Integer)
8
+
9
+ @size = size
10
+ end
11
+
12
+ def self.signature
13
+ 'size'
14
+ end
15
+
16
+ def self.make(args)
17
+ raise ArgumentError, 'Expected exactly one argument.' unless args.length == 1
18
+
19
+ self.new(args[0].to_i)
20
+ end
21
+
22
+ def passes?(attribute, value, validator)
23
+ case value
24
+ when String, Array, Hash
25
+ value.length == @size
26
+ when ActionDispatch::Http::UploadedFile
27
+ value.size == @size
28
+ when Numeric
29
+ value == @size
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def message(attribute, value, validator)
36
+ case value
37
+ when ActionDispatch::Http::UploadedFile
38
+ "The file size must be equal to #{@size} bytes."
39
+ when Numeric
40
+ "The value must be equal to #{@size}."
41
+ else
42
+ "The value length must be equal to #{@size}."
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::StartingWith < MiniDefender::Rule
4
+ def initialize(fragments)
5
+ unless fragments.is_a?(Array) && !fragments.empty? && fragments.all? { |f| f.is_a?(String) }
6
+ raise ArgumentError, 'Expected an array of strings.'
7
+ end
8
+
9
+ @fragments = fragments
10
+ end
11
+
12
+ def self.signature
13
+ 'starting_with'
14
+ end
15
+
16
+ def self.make(args)
17
+ new(args)
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ @fragments.any? { |f| value.to_s.start_with?(f) }
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ if @fragments.length == 1
26
+ "The value should start with #{@fragments[0]}."
27
+ else
28
+ "The value should start with one of the following #{@fragments.join(', ')}."
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::String < MiniDefender::Rule
4
+ def self.signature
5
+ 'string'
6
+ end
7
+
8
+ def passes?(attribute, value, validator)
9
+ value.is_a?(String)
10
+ end
11
+
12
+ def message(attribute, value, validator)
13
+ "The value must be a string."
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tzinfo'
4
+
5
+ class MiniDefender::Rules::Timezone < MiniDefender::Rule
6
+ def self.signature
7
+ 'timezone'
8
+ end
9
+
10
+ def passes?(attribute, value, validator)
11
+ value.is_a?(String) && !!TZInfo::Timezone.get(value)
12
+ rescue TZInfo::InvalidTimezoneIdentifier
13
+ false
14
+ end
15
+
16
+ def message(attribute, value, validator)
17
+ 'The field should contain a valid time zone value.'
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Unique < MiniDefender::Rule
4
+ def initialize(model, column)
5
+ raise ArgumentError, 'model name must be a string or ActiveRecord::Base' unless model.is_a?(String)
6
+ raise ArgumentError, 'Column name must be a string' unless column.is_a?(String)
7
+
8
+ @model = model.camelcase.constantize
9
+ @column = column
10
+ @ignore = nil
11
+ end
12
+
13
+ def self.signature
14
+ 'unique'
15
+ end
16
+
17
+ def should_ignore(value)
18
+ @ignore = value
19
+ self
20
+ end
21
+
22
+ def self.make(args)
23
+ raise ArgumentError, 'Model and column are required.' unless args.length == 2
24
+
25
+ self.new(args[0], args[1])
26
+ end
27
+
28
+ def passes?(attribute, value, validator)
29
+ query = @model.where(@column => value)
30
+ query = query.where.not(@column, @ignore) unless @ignore.nil?
31
+ query.exists?
32
+ end
33
+
34
+ def message(attribute, value, validator)
35
+ "The value already exists."
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ class MiniDefender::Rules::Url < MiniDefender::Rule
6
+ def self.signature
7
+ 'url'
8
+ end
9
+
10
+ def passes?(attribute, value, validator)
11
+ value.is_a?(String) && URI.regexp(%w[http https]).match?(value)
12
+ end
13
+
14
+ def message(attribute, value, validator)
15
+ 'The field must contain a valid URL.'
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Uuid < MiniDefender::Rule
4
+ def initialize(version = nil)
5
+ raise ArgumentError, 'Expected version to be an integer or nil' unless version.nil? || version.is_a?(Integer)
6
+
7
+ @version = version
8
+ end
9
+
10
+ def self.signature
11
+ 'uuid'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected exactly one or zero argument' unless args.length <= 1
16
+
17
+ new(args[0]&.to_i)
18
+ end
19
+
20
+ def coerce(value)
21
+ value.downcase
22
+ end
23
+
24
+ def passes?(attribute, value, validator)
25
+ value.is_a?(String) &&
26
+ /^\h{8}-(\h{4}-){3}\h{12}$/i.match?(value) &&
27
+ (@version.nil? || value[14].to_i(16) == @version)
28
+ end
29
+
30
+ def message(attribute, value, validator)
31
+ if @version
32
+ "The value should be a valid UUID v#{@version}."
33
+ else
34
+ 'The value should be a valid UUID.'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniDefender::Rules
4
+ end
5
+
6
+ Dir["#{__dir__}/rules/*.rb"].each do |path|
7
+ require_relative path
8
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal
2
+
3
+ module MiniDefender
4
+ class RulesExpander
5
+ # @param [Hash] rules
6
+ # @param [Hash] flat_data
7
+ # @return [Hash]
8
+ def expand(rules, flat_data)
9
+ rules
10
+ .map { |k, v| [Regexp.compile('\A' + k.gsub(/\*/, '\d+') + '\Z'), v] }.to_h
11
+ .map { |p, set|
12
+ data_rules = flat_data.filter { |k, _| p.match? k }.map { |k, _| [k, set] }
13
+ data_rules.length > 0 ? data_rules : [[p.source.gsub(/\\[AZ]/, '').gsub('\d+', '0'), set]]
14
+ }
15
+ .flatten(1)
16
+ .to_h
17
+ end
18
+ end
19
+ end