lite-validators 1.0.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.
- 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
|