lite-validators 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.fasterer.yml +19 -0
- data/.gitignore +11 -0
- data/.rspec +4 -0
- data/.rubocop.yml +31 -0
- data/.travis.yml +24 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +128 -0
- data/LICENSE.txt +21 -0
- data/README.md +88 -0
- data/Rakefile +8 -0
- data/_config.yml +1 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +23 -0
- data/docs/ALPHA.md +29 -0
- data/docs/ALPHA_NUMERIC.md +29 -0
- data/docs/BASE64.md +18 -0
- data/docs/BOOLEAN.md +19 -0
- data/docs/COMPARE.md +30 -0
- data/docs/COORDINATE.md +27 -0
- data/docs/CREDIT_CARD.md +29 -0
- data/docs/CSV.md +44 -0
- data/docs/CURRENCY.md +18 -0
- data/docs/CUSIP.md +20 -0
- data/docs/EMAIL.md +26 -0
- data/docs/FILE_CONTENT_TYPE.md +26 -0
- data/docs/FILE_EXTENSION.md +25 -0
- data/docs/FILE_SIZE.md +38 -0
- data/docs/HEX.md +18 -0
- data/docs/IMEI.md +20 -0
- data/docs/IP_ADDRESS.md +26 -0
- data/docs/ISBN.md +18 -0
- data/docs/ISIN.md +20 -0
- data/docs/MAC_ADDRESS.md +21 -0
- data/docs/NAME.md +18 -0
- data/docs/PASSWORD.md +26 -0
- data/docs/PHONE_NUMBER.md +18 -0
- data/docs/SEDOL.md +20 -0
- data/docs/SLUG.md +18 -0
- data/docs/SSN.md +18 -0
- data/docs/TIME_ZONE.md +17 -0
- data/docs/TYPE.md +28 -0
- data/docs/URL.md +28 -0
- data/docs/USERNAME.md +18 -0
- data/docs/UUID.md +28 -0
- data/lib/lite/validators/alpha_numeric_validator.rb +11 -0
- data/lib/lite/validators/alpha_validator.rb +34 -0
- data/lib/lite/validators/base64_validator.rb +9 -0
- data/lib/lite/validators/base_validator.rb +54 -0
- data/lib/lite/validators/boolean_validator.rb +15 -0
- data/lib/lite/validators/compare_validator.rb +41 -0
- data/lib/lite/validators/coordinate_validator.rb +37 -0
- data/lib/lite/validators/credit_card_validator.rb +144 -0
- data/lib/lite/validators/csv_validator.rb +73 -0
- data/lib/lite/validators/currency_validator.rb +7 -0
- data/lib/lite/validators/cusip_validator.rb +27 -0
- data/lib/lite/validators/email_validator.rb +19 -0
- data/lib/lite/validators/file_content_type_validator.rb +38 -0
- data/lib/lite/validators/file_extension_validator.rb +41 -0
- data/lib/lite/validators/file_size_validator.rb +75 -0
- data/lib/lite/validators/hex_validator.rb +7 -0
- data/lib/lite/validators/imei_validator.rb +27 -0
- data/lib/lite/validators/ip_address_validator.rb +39 -0
- data/lib/lite/validators/isbn_validator.rb +16 -0
- data/lib/lite/validators/isin_validator.rb +36 -0
- data/lib/lite/validators/mac_address_validator.rb +18 -0
- data/lib/lite/validators/name_validator.rb +7 -0
- data/lib/lite/validators/password_validator.rb +29 -0
- data/lib/lite/validators/phone_number_validator.rb +7 -0
- data/lib/lite/validators/sedol_validator.rb +23 -0
- data/lib/lite/validators/slug_validator.rb +7 -0
- data/lib/lite/validators/ssn_validator.rb +7 -0
- data/lib/lite/validators/time_zone_validator.rb +13 -0
- data/lib/lite/validators/type_validator.rb +41 -0
- data/lib/lite/validators/url_validator.rb +61 -0
- data/lib/lite/validators/username_validator.rb +7 -0
- data/lib/lite/validators/uuid_validator.rb +37 -0
- data/lib/lite/validators/version.rb +9 -0
- data/lib/lite/validators.rb +12 -0
- data/lite-validators.gemspec +51 -0
- metadata +265 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BaseValidator < ActiveModel::EachValidator
|
4
|
+
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
assign_attr_readers(record, attribute, value)
|
7
|
+
return if valid?
|
8
|
+
|
9
|
+
record.errors.add(attribute, *error_message)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :record, :attribute, :value
|
15
|
+
|
16
|
+
def assign_attr_readers(record, attribute, value)
|
17
|
+
@record = record
|
18
|
+
@attribute = attribute
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
# rubocop:disable Metrics/LineLength
|
23
|
+
def assert_valid_option!(name, collection, option: nil)
|
24
|
+
option ||= send(name)
|
25
|
+
|
26
|
+
[*option].each do |option_value|
|
27
|
+
next if collection.include?(option_value)
|
28
|
+
|
29
|
+
raise ArgumentError, "Unknown #{name}: #{option_value.inspect}. Valid options are: #{collection.map(&:inspect).join(', ')}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# rubocop:enable Metrics/LineLength
|
33
|
+
|
34
|
+
def error_message
|
35
|
+
[options[:message] || :invalid]
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
valid_length? && valid_attr?
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid_attr?
|
43
|
+
valid_regexp?
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid_length?
|
47
|
+
!value.to_s.strip.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid_regexp?
|
51
|
+
value.to_s =~ self.class::REGEXP
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CompareValidator < BaseValidator
|
4
|
+
|
5
|
+
CHECKS ||= {
|
6
|
+
less_than: :<,
|
7
|
+
less_than_or_equal_to: :<=,
|
8
|
+
greater_than: :>,
|
9
|
+
greater_than_or_equal_to: :>=,
|
10
|
+
equal_to: :==,
|
11
|
+
not_equal_to: :!=
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def validate_each(record, attribute, value)
|
15
|
+
assert_valid_to!
|
16
|
+
assert_valid_check!
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def assert_valid_check!
|
23
|
+
assert_valid_option!(:check, CHECKS.keys)
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert_valid_to!
|
27
|
+
return if options.key?(:to)
|
28
|
+
|
29
|
+
raise ArgumentError, 'Missing ":to" attribute for comparison.'
|
30
|
+
end
|
31
|
+
|
32
|
+
def check
|
33
|
+
options[:check] || :equal_to
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_attr?
|
37
|
+
other = record.send(options[:to])
|
38
|
+
value.send(CHECKS[check], other)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CoordinateValidator < BaseValidator
|
4
|
+
|
5
|
+
BOUNDARIES ||= {
|
6
|
+
latitude: 90.0,
|
7
|
+
longitude: 180.0
|
8
|
+
}.freeze
|
9
|
+
|
10
|
+
def validate_each(record, attribute, value)
|
11
|
+
assert_valid_boundary!
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def assert_valid_boundary!
|
18
|
+
assert_valid_option!(:boundary, BOUNDARIES.keys.push(:pair))
|
19
|
+
end
|
20
|
+
|
21
|
+
def boundary
|
22
|
+
options[:boundary] || :pair
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_attr?
|
26
|
+
case boundary
|
27
|
+
when :latitude then valid_boundary?(:latitude)
|
28
|
+
when :longitude then valid_boundary?(:longitude)
|
29
|
+
else valid_boundary?(:latitude, value.first) && valid_boundary?(:longitude, value.last)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_boundary?(key, coordinate = nil)
|
34
|
+
(coordinate || value).to_f.abs <= BOUNDARIES[key]
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreditCardValidator < BaseValidator
|
4
|
+
|
5
|
+
# NOTE: https://en.wikipedia.org/wiki/Payment_card_value#Issuer_identification_value_.28IIN.29
|
6
|
+
|
7
|
+
PROVIDERS ||= {
|
8
|
+
american_express: {
|
9
|
+
sizes: [15],
|
10
|
+
prefixes: [34, 37]
|
11
|
+
},
|
12
|
+
dankort: {
|
13
|
+
sizes: [16],
|
14
|
+
prefixes: [4571, 5019]
|
15
|
+
},
|
16
|
+
diners_club: {
|
17
|
+
sizes: (14..19),
|
18
|
+
prefixes: [36, (38..39), (54..55), (300..305), 3095]
|
19
|
+
},
|
20
|
+
discover: {
|
21
|
+
sizes: (16..19),
|
22
|
+
prefixes: [(64..65), 6011, (622_126..622_925), (624_000..626_999), (628_200..628_899)]
|
23
|
+
},
|
24
|
+
interpayment: {
|
25
|
+
sizes: (16..19),
|
26
|
+
prefixes: [636]
|
27
|
+
},
|
28
|
+
rupay: {
|
29
|
+
sizes: [16],
|
30
|
+
prefixes: [60, (6521..6522)]
|
31
|
+
},
|
32
|
+
jcb: {
|
33
|
+
sizes: (16..19),
|
34
|
+
prefixes: [(3528..3589)]
|
35
|
+
},
|
36
|
+
maestro: {
|
37
|
+
sizes: (12..19),
|
38
|
+
prefixes: [50, (56..69)]
|
39
|
+
},
|
40
|
+
maestro_uk: {
|
41
|
+
sizes: (12..19),
|
42
|
+
prefixes: [6759, 676_770, 676_774]
|
43
|
+
},
|
44
|
+
mastercard: {
|
45
|
+
sizes: [16],
|
46
|
+
prefixes: [(51..55), (2221..2720)]
|
47
|
+
},
|
48
|
+
mir: {
|
49
|
+
sizes: [16],
|
50
|
+
prefixes: [(2200..2204)]
|
51
|
+
},
|
52
|
+
nps_pridnestrovie: {
|
53
|
+
sizes: [16],
|
54
|
+
prefixes: [(6_054_740..6_054_744)]
|
55
|
+
},
|
56
|
+
troy: {
|
57
|
+
sizes: [16],
|
58
|
+
prefixes: [(979_200..979_289)]
|
59
|
+
},
|
60
|
+
uatp: {
|
61
|
+
sizes: [16],
|
62
|
+
prefixes: [1]
|
63
|
+
},
|
64
|
+
unionpay: {
|
65
|
+
sizes: (16..19),
|
66
|
+
prefixes: [62]
|
67
|
+
},
|
68
|
+
verve: {
|
69
|
+
sizes: [16, 19],
|
70
|
+
prefixes: [(506_099..506_198), (650_002..650_027)]
|
71
|
+
},
|
72
|
+
visa: {
|
73
|
+
sizes: [16],
|
74
|
+
prefixes: [4]
|
75
|
+
}
|
76
|
+
}.freeze
|
77
|
+
|
78
|
+
def validate_each(record, attribute, value)
|
79
|
+
assert_valid_provider!
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def assert_valid_provider!
|
86
|
+
assert_valid_option!(:provider, PROVIDERS.keys.push(:all))
|
87
|
+
end
|
88
|
+
|
89
|
+
def checksum(value)
|
90
|
+
values = digits(value).reverse.map.with_index { |n, i| i.even? ? n * 2 : n }
|
91
|
+
total = values.reverse.inject(0) { |a, b| a + digits(b).inject(:+) }
|
92
|
+
checksum = 10 - (total % 10)
|
93
|
+
checksum == 10 ? 0 : checksum
|
94
|
+
end
|
95
|
+
|
96
|
+
def digits(value)
|
97
|
+
value.to_s.chars.map(&:to_i)
|
98
|
+
end
|
99
|
+
|
100
|
+
def encompasses?(subject, value)
|
101
|
+
case subject
|
102
|
+
when Array then subject.include?(value)
|
103
|
+
when Range then subject.cover?(value)
|
104
|
+
else subject == value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def prefix(subject, value)
|
109
|
+
nums = case subject
|
110
|
+
when Array, Range then subject.first
|
111
|
+
else subject
|
112
|
+
end
|
113
|
+
|
114
|
+
value.to_s[0..(nums.to_s.size - 1)].to_i
|
115
|
+
end
|
116
|
+
|
117
|
+
def provider
|
118
|
+
options[:provider] || :all
|
119
|
+
end
|
120
|
+
|
121
|
+
def valid_attr?
|
122
|
+
valid_size?(value) && valid_prefix?(value) && valid_checksum?
|
123
|
+
end
|
124
|
+
|
125
|
+
def valid_checksum?
|
126
|
+
nums = digits(value)
|
127
|
+
nums.pop == checksum(nums.join)
|
128
|
+
end
|
129
|
+
|
130
|
+
def valid_prefix?(value)
|
131
|
+
prefixes = PROVIDERS.dig(provider, :prefixes) || PROVIDERS.flat_map { |_, h| h[:prefixes] }
|
132
|
+
|
133
|
+
prefixes.any? do |subject|
|
134
|
+
iin = prefix(subject, value)
|
135
|
+
encompasses?(subject, iin)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def valid_size?(value)
|
140
|
+
sizes = PROVIDERS.dig(provider, :sizes) || (12..19)
|
141
|
+
encompasses?(sizes, value.to_s.size)
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
class CsvValidator < FileSizeValidator
|
6
|
+
|
7
|
+
DIMENSIONS ||= %i[
|
8
|
+
columns rows
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
assert_valid_dimensions!
|
13
|
+
assert_valid_checks!
|
14
|
+
assign_attr_readers(record, attribute, csv_dimensions(value.path))
|
15
|
+
valid?
|
16
|
+
rescue CSV::MalformedCSVError
|
17
|
+
record.errors.add(attribute, *error_message)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# rubocop:disable Metrics/LineLength, Style/GuardClause
|
23
|
+
def assert_valid_dimensions!
|
24
|
+
if dimensions.empty?
|
25
|
+
raise ArgumentError, "Missing atleast one dimension of #{DIMENSIONS.map(&:inspect).join(', ')} attribute for comparison."
|
26
|
+
else
|
27
|
+
assert_valid_option!(:dimensions, DIMENSIONS)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
# rubocop:enable Metrics/LineLength, Style/GuardClause
|
31
|
+
|
32
|
+
def csv_dimensions(path)
|
33
|
+
dimension = { columns: 0, rows: 0 }
|
34
|
+
|
35
|
+
CSV.foreach(path) do |row|
|
36
|
+
dimension[:columns] = row.size if dimension[:rows].zero?
|
37
|
+
dimension[:rows] += 1
|
38
|
+
end
|
39
|
+
|
40
|
+
dimension
|
41
|
+
end
|
42
|
+
|
43
|
+
def checks
|
44
|
+
dimension_options = options.slice(:columns, :rows).values
|
45
|
+
dimension_options.flat_map(&:keys) & CHECKS.keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def dimensions
|
49
|
+
options.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
def error_message_for(dimension, check, check_value)
|
53
|
+
options[:message] || I18n.t(
|
54
|
+
"errors.messages.csv.#{check}",
|
55
|
+
error_options(check_value).merge(dimension: dimension)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid_attr?
|
60
|
+
options.slice(*DIMENSIONS).each do |dimension, dimension_value|
|
61
|
+
dimension_value.each do |check, check_value|
|
62
|
+
validate_check(dimension, check, check_value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_check(dimension, check, check_value)
|
68
|
+
return if valid_size?(value[dimension], check, check_value)
|
69
|
+
|
70
|
+
record.errors.add(attribute, error_message_for(dimension, check, check_value))
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CusipValidator < BaseValidator
|
4
|
+
|
5
|
+
REGEXP ||= /^[0-9A-Z]{9}$/.freeze
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def valid_attr?
|
10
|
+
valid_regexp? && valid_checksum?
|
11
|
+
end
|
12
|
+
|
13
|
+
# rubocop:disable Metrics/AbcSize
|
14
|
+
def valid_checksum?
|
15
|
+
digits = value.chars.map { |chr| /[A-Z]/.match?(chr) ? (chr.ord - 55) : chr.to_i }
|
16
|
+
|
17
|
+
even_values = digits.values_at(*digits.each_index.select(&:even?))
|
18
|
+
odd_values = digits.values_at(*digits.each_index.select(&:odd?))
|
19
|
+
|
20
|
+
values = odd_values.map { |int| int * 2 }.zip(even_values).flatten
|
21
|
+
values = values.inject(0) { |sum, int| sum + (int / 10) + int % 10 }
|
22
|
+
|
23
|
+
((10 - values) % 10) % 10
|
24
|
+
end
|
25
|
+
# rubocop:enable Metrics/AbcSize
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class EmailValidator < BaseValidator
|
4
|
+
|
5
|
+
REGEXP ||= /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.freeze
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def valid_attr?
|
10
|
+
valid_regexp? && valid_domain?
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_domain?
|
14
|
+
return true unless options.key?(:domain)
|
15
|
+
|
16
|
+
[options[:domain]].flatten.any? { |domain| value.downcase.end_with?(".#{domain.downcase}") }
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FileContentTypeValidator < BaseValidator
|
4
|
+
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
assert_valid_include_or_exclude!
|
7
|
+
assign_attr_readers(record, attribute, value)
|
8
|
+
valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def assert_valid_include_or_exclude!
|
14
|
+
return if options.key?(:include) || options.key?(:exclude)
|
15
|
+
|
16
|
+
raise ArgumentError, 'Missing ":include" or ":exclude" attribute for comparison.'
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid_attr?
|
20
|
+
options.slice(:include, :exclude).each do |option, option_value|
|
21
|
+
validate_check(option, option_value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# rubocop:disable Style/CaseEquality
|
26
|
+
def valid_content_type?(option, option_value)
|
27
|
+
check = [*option_value].any? { |type| type === value.content_type }
|
28
|
+
option == :exclude ? !check : check
|
29
|
+
end
|
30
|
+
# rubocop:enable Style/CaseEquality
|
31
|
+
|
32
|
+
def validate_check(option, option_value)
|
33
|
+
return if valid_content_type?(option, option_value)
|
34
|
+
|
35
|
+
record.errors.add(attribute, *error_message)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FileExtensionValidator < BaseValidator
|
4
|
+
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
assert_valid_include_or_exclude!
|
7
|
+
assign_attr_readers(record, attribute, value)
|
8
|
+
valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def assert_valid_include_or_exclude!
|
14
|
+
return if options.key?(:include) || options.key?(:exclude)
|
15
|
+
|
16
|
+
raise ArgumentError, 'Missing ":include" or ":exclude" attribute for comparison.'
|
17
|
+
end
|
18
|
+
|
19
|
+
def check
|
20
|
+
options.key?(:exclude) ? :exclude : :include
|
21
|
+
end
|
22
|
+
|
23
|
+
def include_or_exclude
|
24
|
+
values = options[:include] || options[:exclude]
|
25
|
+
[*values]
|
26
|
+
end
|
27
|
+
|
28
|
+
# rubocop:disable Metrics/AbcSize
|
29
|
+
def valid_attr?
|
30
|
+
extension = File.extname(value.original_filename).tr('.', '')
|
31
|
+
return false if extension.empty?
|
32
|
+
|
33
|
+
included = include_or_exclude.any? { |ext| ext.to_s == extension }
|
34
|
+
included = !included if check == :exclude
|
35
|
+
return if included
|
36
|
+
|
37
|
+
record.errors.add(attribute, *error_message)
|
38
|
+
end
|
39
|
+
# rubocop:enable Metrics/AbcSize
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FileSizeValidator < BaseValidator
|
4
|
+
|
5
|
+
CHECKS ||= {
|
6
|
+
in: :===,
|
7
|
+
less_than: :<,
|
8
|
+
less_than_or_equal_to: :<=,
|
9
|
+
greater_than: :>,
|
10
|
+
greater_than_or_equal_to: :>=,
|
11
|
+
equal_to: :==,
|
12
|
+
not_equal_to: :!=
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def validate_each(record, attribute, value)
|
16
|
+
assert_valid_checks!
|
17
|
+
assign_attr_readers(record, attribute, value)
|
18
|
+
valid?
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# rubocop:disable Metrics/LineLength, Style/GuardClause
|
24
|
+
def assert_valid_checks!
|
25
|
+
if checks.empty?
|
26
|
+
raise ArgumentError, "Missing atleast one check of #{CHECKS.keys.map(&:inspect).join(', ')} attribute for comparison."
|
27
|
+
else
|
28
|
+
assert_valid_option!(:checks, CHECKS.keys)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# rubocop:enable Metrics/LineLength, Style/GuardClause
|
32
|
+
|
33
|
+
def checks
|
34
|
+
options.keys & CHECKS.keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def error_message_for(option, option_value)
|
38
|
+
options[:message] || I18n.t(
|
39
|
+
"errors.messages.file_size.#{option}",
|
40
|
+
error_options(option_value)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def error_options(option_value)
|
45
|
+
if option_value.is_a?(Range)
|
46
|
+
{ min: option_value.min, max: option_value.max }
|
47
|
+
else
|
48
|
+
{ count: option_value }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_attr?
|
53
|
+
options.slice(*CHECKS.keys).each do |option, option_value|
|
54
|
+
validate_check(option, option_value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_size?(size, option, option_value)
|
59
|
+
return false if size.nil?
|
60
|
+
|
61
|
+
if option_value.is_a?(Range)
|
62
|
+
option_value.send(CHECKS[option], size)
|
63
|
+
else
|
64
|
+
size.send(CHECKS[option], option_value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_check(option, option_value)
|
69
|
+
size = value.respond_to?(:byte_size) ? value.byte_size : value.size
|
70
|
+
return if valid_size?(size, option, option_value)
|
71
|
+
|
72
|
+
record.errors.add(attribute, error_message_for(option, option_value))
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ImeiValidator < BaseValidator
|
4
|
+
|
5
|
+
REGEXP ||= /\A[\d\.\:\-\s]+\z/i.freeze
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def valid_attr?
|
10
|
+
valid_regexp? && valid_checksum?
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_checksum?
|
14
|
+
number = value.to_s.gsub(/\D/, '').reverse
|
15
|
+
|
16
|
+
total = 0
|
17
|
+
number.chars.each_with_index do |chr, idx|
|
18
|
+
result = chr.to_i
|
19
|
+
result *= 2 if idx.odd?
|
20
|
+
result = (1 + (result - 10)) if result >= 10
|
21
|
+
total += result
|
22
|
+
end
|
23
|
+
|
24
|
+
(total % 10).zero?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'resolv'
|
4
|
+
|
5
|
+
class IpAddressValidator < BaseValidator
|
6
|
+
|
7
|
+
REGEXP ||= {
|
8
|
+
ipv4: Resolv::IPv4::Regex,
|
9
|
+
ipv6: Resolv::IPv6::Regex
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def validate_each(record, attribute, value)
|
13
|
+
assert_valid_protocol!
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def assert_valid_protocol!
|
20
|
+
assert_valid_option!(:protocol, REGEXP.keys.push(:any))
|
21
|
+
end
|
22
|
+
|
23
|
+
def protocol
|
24
|
+
options[:protocol] || :any
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_attr?
|
28
|
+
case protocol
|
29
|
+
when :ipv4 then valid_regexp?(:ipv4)
|
30
|
+
when :ipv6 then valid_regexp?(:ipv6)
|
31
|
+
else valid_regexp?(:ipv4) || valid_regexp?(:ipv6)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def valid_regexp?(key)
|
36
|
+
value.to_s =~ REGEXP[key]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class IsbnValidator < BaseValidator
|
4
|
+
|
5
|
+
CHARACTERS ||= %w[
|
6
|
+
0 1 2 3 4 5 6 7 8 9 0 x
|
7
|
+
].freeze
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def valid_attr?
|
12
|
+
values = value.to_s.gsub(/-| /, '').downcase.chars
|
13
|
+
[10, 13].include?(values.size) && values.all? { |chr| CHARACTERS.include?(chr) }
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|