mini_defender 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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