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,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Filled < MiniDefender::Rule
4
+ def self.signature
5
+ 'filled'
6
+ end
7
+
8
+ def passes?(attribute, value, validator)
9
+ case value
10
+ when String
11
+ !value.strip.empty?
12
+ when Array, Hash
13
+ !value.empty?
14
+ else
15
+ !value.nil?
16
+ end
17
+ end
18
+
19
+ def message(attribute, value, validator)
20
+ 'The field should not be empty.'
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+ require_relative 'size'
5
+
6
+ class MiniDefender::Rules::GreaterThan < MiniDefender::Rules::Size
7
+ def self.signature
8
+ 'gt'
9
+ end
10
+
11
+ def passes?(attribute, value, validator)
12
+ case value
13
+ when String, Array, Hash
14
+ value.length > @size
15
+ when ActionDispatch::Http::UploadedFile
16
+ value.size > @size
17
+ when Numeric
18
+ value > @size
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ case value
26
+ when ActionDispatch::Http::UploadedFile
27
+ "The file size must be greater than #{@size} bytes."
28
+ when Numeric
29
+ "The value must be greater than #{@size}."
30
+ else
31
+ "The value length must be greater than #{@size}."
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+ require_relative 'size'
5
+
6
+ class MiniDefender::Rules::GreaterThanOrEqual < MiniDefender::Rules::Size
7
+ def self.signature
8
+ 'gte'
9
+ end
10
+
11
+ def passes?(attribute, value, validator)
12
+ case value
13
+ when String, Array, Hash
14
+ value.length >= @size
15
+ when ActionDispatch::Http::UploadedFile
16
+ value.size >= @size
17
+ when Numeric
18
+ value >= @size
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ case value
26
+ when ActionDispatch::Http::UploadedFile
27
+ "The file size must be greater than or equal to #{@size} bytes."
28
+ when Numeric
29
+ "The value must be greater than or equal to #{@size}."
30
+ else
31
+ "The value length must be greater than or equal to #{@size}."
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Hash < MiniDefender::Rule
4
+ def self.signature
5
+ 'hash'
6
+ end
7
+
8
+ def passes?(attribute, value, validator)
9
+ value.is_a?(Hash)
10
+ end
11
+
12
+ def message(attribute, value, validator)
13
+ "The field must be an object."
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+
5
+ class MiniDefender::Rules::Image < MiniDefender::Rule
6
+ MIMES = %w[image/jpeg image/png image/gif image/bmp image/png image/svg+xml image/webp]
7
+
8
+ def self.signature
9
+ 'image'
10
+ end
11
+
12
+ def passes?(attribute, value, validator)
13
+ value.is_a?(ActionDispatch::Http::UploadedFile) && MIMES.include?(value.content_type)
14
+ end
15
+
16
+ def message(attribute, value, validator)
17
+ "The field should be an image of type jpg, jpeg, png, bmp, gif, svg, or webp."
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::In < 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
+ '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 must be one of #{@values.to_sentence}."
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::InField < MiniDefender::Rule
4
+ def initialize(field)
5
+ raise ArgumentError, 'Field must be a string.' unless field.is_a?(String)
6
+
7
+ @field = field
8
+ end
9
+
10
+ def self.signature
11
+ 'in_field'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected one argument, target field name.' unless args.length == 1
16
+
17
+ new(args[0])
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ @field_value = nil
22
+ return false unless validator.data.key(@field)
23
+
24
+ @field_value = validator.data[@field]
25
+ return false unless field.is_a?(Array)
26
+
27
+ @field_value.include?(value)
28
+ end
29
+
30
+ def message(attribute, value, validator)
31
+ case @field_value
32
+ when nil
33
+ "The field (#{@field}) is missing."
34
+ when Array
35
+ "The field (#{@field}) must be an array."
36
+ else
37
+ "The value must be one of the values found in #{@field}."
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Integer < MiniDefender::Rule
4
+ def self.signature
5
+ 'integer'
6
+ end
7
+
8
+ def coerce(value)
9
+ value.to_i
10
+ end
11
+
12
+ def passes?(attribute, value, validator)
13
+ value.is_a?(Integer) || value.is_a?(String) && value.match?(/^\d+$/)
14
+ end
15
+
16
+ def message(attribute, value, validator)
17
+ "The value must be an integer."
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddr'
4
+
5
+ class MiniDefender::Rules::Ip < MiniDefender::Rule
6
+ MODES = %w[any public private]
7
+
8
+ def initialize(mode = 'any')
9
+ raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
10
+
11
+ @mode = mode
12
+ end
13
+
14
+ def self.signature
15
+ 'ip'
16
+ end
17
+
18
+ def self.make(args)
19
+ new(args[0] || 'any')
20
+ end
21
+
22
+ def passes?(attribute, value, validator)
23
+ ip = IPAddr.new(value.to_s)
24
+ (
25
+ @mode == 'any' ||
26
+ @mode == 'private' && (ip.private? || ip.link_local? || ip.loopback?) ||
27
+ @mode == 'public' && !(ip.private? || ip.link_local? || ip.loopback?)
28
+ )
29
+ rescue IPAddr::InvalidAddressError
30
+ false
31
+ end
32
+
33
+ def message(attribute, value, validator)
34
+ case @mode
35
+ when 'public'
36
+ "The value must be a valid public IP address."
37
+ when 'private'
38
+ "The value must be a valid private IP address."
39
+ else
40
+ "The value must be a valid IP address."
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddr'
4
+
5
+ class MiniDefender::Rules::Ipv4 < MiniDefender::Rule
6
+ MODES = %w[any public private]
7
+
8
+ def initialize(mode = 'any')
9
+ raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
10
+
11
+ @mode = mode
12
+ end
13
+
14
+ def self.signature
15
+ 'ipv4'
16
+ end
17
+
18
+ def self.make(args)
19
+ new(args[0] || 'any')
20
+ end
21
+
22
+ def passes?(attribute, value, validator)
23
+ ip = IPAddr.new(value.to_s)
24
+ ip.ipv4? && (
25
+ @mode == 'any' ||
26
+ @mode == 'private' && (ip.private? || ip.link_local? || ip.loopback?) ||
27
+ @mode == 'public' && !(ip.private? || ip.link_local? || ip.loopback?)
28
+ )
29
+ rescue IPAddr::InvalidAddressError
30
+ false
31
+ end
32
+
33
+ def message(attribute, value, validator)
34
+ case @mode
35
+ when 'public'
36
+ "The value must be a valid public IPv4 address."
37
+ when 'private'
38
+ "The value must be a valid private IPv4 address."
39
+ else
40
+ "The value must be a valid IPv4 address."
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddr'
4
+
5
+ class MiniDefender::Rules::Ipv6 < MiniDefender::Rule
6
+ MODES = %w[any public private]
7
+
8
+ def initialize(mode = 'any')
9
+ raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
10
+
11
+ @mode = mode
12
+ end
13
+
14
+ def self.signature
15
+ 'ipv6'
16
+ end
17
+
18
+ def self.make(args)
19
+ new(args[0] || 'any')
20
+ end
21
+
22
+ def passes?(attribute, value, validator)
23
+ ip = IPAddr.new(value.to_s)
24
+ ip.ipv6? && (
25
+ @mode == 'any' ||
26
+ @mode == 'private' && (ip.private? || ip.link_local? || ip.loopback?) ||
27
+ @mode == 'public' && !(ip.private? || ip.link_local? || ip.loopback?)
28
+ )
29
+ rescue IPAddr::InvalidAddressError
30
+ false
31
+ end
32
+
33
+ def message(attribute, value, validator)
34
+ case @mode
35
+ when 'public'
36
+ "The value must be a valid public IPv6 address."
37
+ when 'private'
38
+ "The value must be a valid private IPv6 address."
39
+ else
40
+ "The value must be a valid IPv6 address."
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ class MiniDefender::Rules::Json < MiniDefender::Rule
6
+ def self.signature
7
+ 'json'
8
+ end
9
+
10
+ def passes?(attribute, value, validator)
11
+ JSON.parse(value)
12
+ rescue JSON::ParserError
13
+ false
14
+ end
15
+
16
+ def message(attribute, value, validator)
17
+ "The value should be a valid JSON string."
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+ require_relative 'size'
5
+
6
+ class MiniDefender::Rules::LessThan < MiniDefender::Rules::Size
7
+ def self.signature
8
+ 'lt'
9
+ end
10
+
11
+ def passes?(attribute, value, validator)
12
+ case value
13
+ when String, Array, Hash
14
+ value.length < @size
15
+ when ActionDispatch::Http::UploadedFile
16
+ value.size < @size
17
+ when Numeric
18
+ value < @size
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ case value
26
+ when ActionDispatch::Http::UploadedFile
27
+ "The file size must be less than #{@size} bytes."
28
+ when Numeric
29
+ "The value must be less than #{@size}."
30
+ else
31
+ "The value length must be less than #{@size}."
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+ require_relative 'size'
5
+
6
+ class MiniDefender::Rules::LessThanOrEqual < MiniDefender::Rules::Size
7
+ def self.signature
8
+ 'lte'
9
+ end
10
+
11
+ def passes?(attribute, value, validator)
12
+ case value
13
+ when String, Array, Hash
14
+ value.length <= @size
15
+ when ActionDispatch::Http::UploadedFile
16
+ value.size <= @size
17
+ when Numeric
18
+ value <= @size
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ case value
26
+ when ActionDispatch::Http::UploadedFile
27
+ "The file size must be less than or equal to #{@size} bytes."
28
+ when Numeric
29
+ "The value must be less than or equal to #{@size}."
30
+ else
31
+ "The value length must be less than or equal to #{@size}."
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::Luhn < MiniDefender::Rule
4
+ def self.signature
5
+ 'luhn'
6
+ end
7
+
8
+ def self.valid_luhn?(value)
9
+ value = value.to_s
10
+ return false unless value.match?(/\A\d+\z/)
11
+
12
+ double_sum = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
13
+ sum = 0
14
+
15
+ value.to_i.digits.each_with_index do |digit, index|
16
+ sum += index.even? ? digit : double_sum[digit]
17
+ end
18
+
19
+ sum % 10 === 0
20
+ end
21
+
22
+ def coerce(value)
23
+ value.to_s
24
+ end
25
+
26
+ def passes?(attribute, value, validator)
27
+ value.is_a?(String) && self.class.valid_luhn?(value)
28
+ end
29
+
30
+ def message(attribute, value, validator)
31
+ 'The value must be a valid Luhn string.'
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::MacAddress < MiniDefender::Rule
4
+ MODES = %w[any local universal]
5
+
6
+ def initialize(mode = 'any')
7
+ raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
8
+
9
+ @mode = mode
10
+ end
11
+
12
+ def self.signature
13
+ 'mac'
14
+ end
15
+
16
+ def self.make(args)
17
+ new(args[0] || 'any')
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ clean = value.to_s.gsub(/[-:]/)
22
+ clean.match?(/[0-9A-F]{12}/i) && (
23
+ @mode == 'any' ||
24
+ @mode == 'local' && (clean.hex & 0x020000000000 > 0) ||
25
+ @mode == 'universal' && (clean.hex & 0x020000000000 == 0)
26
+ )
27
+ end
28
+
29
+ def message(attribute, value, validator)
30
+ case @mode
31
+ when 'local'
32
+ "The value must be a locally administrated MAC address."
33
+ when 'universal'
34
+ "The value must be a universally administrated MAC address."
35
+ else
36
+ "The value must be a properly formatted mac address."
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'less_than_or_equal'
4
+
5
+ class MiniDefender::Rules::Max < MiniDefender::Rules::LessThanOrEqual
6
+ def self.signature
7
+ 'max'
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::MaxDigits < MiniDefender::Rule
4
+ def initialize(limit)
5
+ raise ArgumentError, 'Limit must be a string.' unless limit.is_a?(Integer)
6
+
7
+ @limit = limit
8
+ end
9
+
10
+ def self.signature
11
+ 'max_digits'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected at least one argument for max_digits.' unless args.length == 1
16
+
17
+ new(args[0].to_i)
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ @integers = valid = value.is_a?(Integer) || value.is_a?(String) && value.match?(/^\d+$/)
22
+ valid && value.to_s.length <= @limit
23
+ end
24
+
25
+ def message(attribute, value, validator)
26
+ if @integers
27
+ 'The field should only contain digits.'
28
+ else
29
+ "The field should have at most #{@limit} digits."
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+
5
+ class MiniDefender::Rules::MimeTypes < MiniDefender::Rule
6
+ def initialize(types)
7
+ unless types.is_a?(Array) && types.all?{ |t| t.is_a?(String) }
8
+ raise ArgumentError, 'Expected an array of strings.'
9
+ end
10
+
11
+ @types = types
12
+ @file = false
13
+ end
14
+
15
+ def self.signature
16
+ 'mime'
17
+ end
18
+
19
+ def self.make(args)
20
+ raise ArgumentError, 'Expected at least one MIME type.' unless args.length > 0
21
+
22
+ new(args.split(',').map(&:downcase).map(&:strip))
23
+ end
24
+
25
+ def passes?(attribute, value, validator)
26
+ @file = value.is_a?(ActionDispatch::Http::UploadedFile)
27
+ @file && @types.include?(@file.content_type)
28
+ end
29
+
30
+ def message(attribute, value, validator)
31
+ if @file
32
+ "The file should be one of the following types #{@types.to_sentence}"
33
+ else
34
+ 'The field should be a file.'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'greater_than_or_equal'
4
+
5
+ class MiniDefender::Rules::Min < MiniDefender::Rules::GreaterThanOrEqual
6
+ def self.signature
7
+ 'min'
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::MinDigits < MiniDefender::Rule
4
+ def initialize(limit)
5
+ raise ArgumentError, 'Limit must be a string.' unless limit.is_a?(Integer)
6
+
7
+ @limit = limit
8
+ end
9
+
10
+ def self.signature
11
+ 'min_digits'
12
+ end
13
+
14
+ def self.make(args)
15
+ raise ArgumentError, 'Expected at least one argument for min_digits.' unless args.length == 1
16
+
17
+ new(args[0].to_i)
18
+ end
19
+
20
+ def passes?(attribute, value, validator)
21
+ @integers = valid = value.is_a?(Integer) || value.is_a?(String) && value.match?(/^\d+$/)
22
+ valid && value.to_s.length >= @limit
23
+ end
24
+
25
+ def message(attribute, value, validator)
26
+ if @integers
27
+ 'The field should only contain digits.'
28
+ else
29
+ "The field should have at least #{@limit} digits."
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'luhn'
4
+
5
+ class MiniDefender::Rules::NationalId < MiniDefender::Rules::Luhn
6
+ def self.signature
7
+ 'national_id'
8
+ end
9
+
10
+ def message(attribute, value, validator)
11
+ 'The value must be a proper national ID number.'
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MiniDefender::Rules::NotEndingWith < 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_ending_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.end_with?(f) }
22
+ end
23
+
24
+ def message(attribute, value, validator)
25
+ if @fragments.length == 1
26
+ "The value should not end with #{@fragments[0]}."
27
+ else
28
+ "The value should not end with one of the following #{@fragments.join(', ')}."
29
+ end
30
+ end
31
+ end