can_has_validations 1.0.2 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b18aa1039ebc20678d9781ff0e90d495253b6d40fa39c3ffec9ae69c03b6d298
4
- data.tar.gz: 6657e468172b9f6cdbd7f69f7d68a105c82661b521e5473d50fda8ca37387558
3
+ metadata.gz: 4b8c22fcbf2964d57091e6b566e5d05a1b501f286722ae26e4c63be5d42989f9
4
+ data.tar.gz: c2cdd19af9fa8459007d506f93e4dec5b1311c173d4fdf087c7438a1d387d97f
5
5
  SHA512:
6
- metadata.gz: 129fe04836706f5aa89ba7d1116b857375bd3088f1b3953247c045c8c75c385adc981d2d1e0189810aae40009689c4a18da2f714ac52b79869ba01ea3cd9f823
7
- data.tar.gz: 3454442e03e74108c632ac8f7dfed962850e36a5ce16afd595eb21b1855653edf8c4cefa4181f4eb7f65b308fc85a7d2341afcbab0a7d31ad268f4e0da000e8e
6
+ metadata.gz: 115570baed1e6a8061612de3f6428599ab4b76afab032756db8a7df5d5aa8bf8ec76f91d52849b5a28b154349965028c5f295350e4fd8f62d86b7f298e046cf2
7
+ data.tar.gz: fa601d54efefa5ee3d83299b9838ba6684450009ef3f782e3904de6405ea6ecd05869e79e89daadf42904f8cfe8c791a2f397a1fb24ec08cd9ff1ced1bd269a9
data/README.md CHANGED
@@ -10,6 +10,7 @@ Validations provided:
10
10
  * Existence
11
11
  * Grandparent
12
12
  * Hostname
13
+ * IP address
13
14
  * Ordering
14
15
  * URL
15
16
  * Write Once
@@ -147,6 +148,9 @@ TLD, so as to not fail as ICANN continues to add TLDs.
147
148
  # allows '_abc.example.com'
148
149
  validates :domain, hostname: {allow_underscore: true}
149
150
 
151
+ # allows '4.0/25.3.2.1.example.com'
152
+ validates :domain, hostname: {allow_slash: true}
153
+
150
154
  # allows 'a.example.com', but not 'example.com'
151
155
  validates :domain, hostname: {segments: 3..100}
152
156
 
@@ -158,6 +162,34 @@ TLD, so as to not fail as ICANN continues to add TLDs.
158
162
  # use 4 or 6 for ipv4 or ipv6 only
159
163
 
160
164
 
165
+ ## IP address validator ##
166
+
167
+ Ensures an attribute is generally formatted as a IP or IP block.
168
+
169
+ # allows '1.2.3.4' or '::1'
170
+ validates :ip, ipaddr: true
171
+
172
+ # allows '1.2.3.0/24' or '2001:db8::/64'
173
+ validates :cidr, ipaddr: {allow_block: true}
174
+
175
+ # if an ip block, the attribute must be fully contained within an allowed block.
176
+ # allows '10.0.0.1' and '10.0.0.0/24', but not '10.0.0.0/15'
177
+ validates :private_ip, ipaddr: {
178
+ allow_block: true,
179
+ within: [IPAddr.new('10.0.0.0/16'), '127.0.0.1']
180
+ # allowed IPs and blocks may be IPAddrs or Strings
181
+ }
182
+
183
+ # the inverse of :within
184
+ validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
185
+
186
+ # :within and :without may also be procs or method names
187
+ validates :ip, ipaddr: {
188
+ within: :some_method,
189
+ without: ->(record){ ... }
190
+ }
191
+
192
+
161
193
  ## Ordering validators ##
162
194
 
163
195
  Ensures two attribute values maintain a relative order to one another. This is
@@ -183,7 +215,7 @@ Always skips over nil values; use `:presence` to validate those.
183
215
 
184
216
  ## URL validator ##
185
217
 
186
- Ensure an attribute is generally formatted as a URL. If `addressable/uri` is
218
+ Ensures an attribute is generally formatted as a URL. If `addressable/uri` is
187
219
  already loaded, it will be used to parse IDN's. Additionally, allowed schemes
188
220
  can be specified; they default to ['http','https'].
189
221
 
@@ -205,8 +237,8 @@ can be specified; they default to ['http','https'].
205
237
 
206
238
  ## Write Once validator ##
207
239
 
208
- Ensure that once a value is written, it becomes readonly. There are two uses
209
- for this.
240
+ Ensures that once a value is written, it becomes readonly. There are a few
241
+ uses for this.
210
242
 
211
243
  The first is as an equivalent to `attr_readonly :user_id` except that it also
212
244
  produces a validation error instead of silently ignoring the change as
@@ -221,6 +253,10 @@ sorts.
221
253
 
222
254
  validates :user_id, allow_nil: true, write_once: true
223
255
 
256
+ The third use is to allow a nil value, and treat the nil also as write-once.
257
+
258
+ validates :source, write_once: {immutable_nil: true}
259
+
224
260
 
225
261
  ## Error messages
226
262
 
@@ -232,6 +268,9 @@ Default messages are as follows:
232
268
  messages:
233
269
  invalid_email: "is an invalid email"
234
270
  invalid_hostname: "is an invalid hostname"
271
+ invalid_ip: "is an invalid IP"
272
+ ip_not_allowed: "is not an allowed IP"
273
+ single_ip_required: "must be a single IP"
235
274
  invalid_url: "is an invalid URL"
236
275
  unchangeable: "cannot be changed"
237
276
  before: "must be before %{attribute2}"
@@ -1,6 +1,6 @@
1
1
  require 'active_model/validations'
2
2
 
3
- %w(array email existence grandparent hostname ordering url write_once).each do |validator|
3
+ %w(array email existence grandparent hostname ipaddr ordering url write_once).each do |validator|
4
4
  require "can_has_validations/validators/#{validator}_validator"
5
5
  end
6
6
 
@@ -3,8 +3,10 @@ en:
3
3
  messages:
4
4
  invalid_email: "is an invalid email"
5
5
  invalid_hostname: "is an invalid hostname"
6
+ invalid_ip: "is an invalid IP"
7
+ ip_not_allowed: "is not an allowed IP"
8
+ single_ip_required: "must be a single IP"
6
9
  invalid_url: "is an invalid URL"
7
10
  unchangeable: "cannot be changed"
8
11
  before: "must be before %{attribute2}"
9
12
  after: "must be after %{attribute2}"
10
-
@@ -16,7 +16,8 @@ module ActiveModel::Validations
16
16
  if cousin.nil?
17
17
  options[:allow_nil]
18
18
  else
19
- association.send(options[:parent]) == cousin.send(options[:parent])
19
+ association &&
20
+ association.send(options[:parent]) == cousin.send(options[:parent])
20
21
  end
21
22
  end
22
23
  unless all_match
@@ -18,6 +18,8 @@
18
18
  # allows '*.example.com'
19
19
  # validates :domain, hostname: {allow_underscore: true}
20
20
  # allows '_abc.example.com'
21
+ # validates :domain, hostname: {allow_slash: true}
22
+ # allows '4.0/25.3.2.1.example.com' # rfc2317
21
23
  # validates :domain, hostname: {segments: 3..100}
22
24
  # allows 'a.example.com', but not 'example.com'
23
25
  # validates :domain, hostname: {allow_ip: true} # or 4 or 6 for ipv4 or ipv6 only
@@ -30,9 +32,9 @@ require 'resolv'
30
32
  module ActiveModel::Validations
31
33
  class HostnameValidator < ActiveModel::EachValidator
32
34
 
33
- LABEL_REGEXP = /\A([a-zA-Z0-9_]([a-zA-Z0-9_-]+)?)?[a-zA-Z0-9]\z/
34
- FINAL_LABEL_REGEXP = /\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z/
35
- RESERVED_OPTIONS = %i(allow_ip allow_underscore allow_wildcard)
35
+ LABEL_REGEXP = %r{\A([a-zA-Z0-9_]([a-zA-Z0-9_/-]+)?)?[a-zA-Z0-9]\z}
36
+ FINAL_LABEL_REGEXP = %r{\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z}
37
+ RESERVED_OPTIONS = %i(allow_ip allow_slash allow_underscore allow_wildcard)
36
38
 
37
39
  def validate_each(record, attribute, value)
38
40
  case options[:allow_ip]
@@ -55,6 +57,7 @@ module ActiveModel::Validations
55
57
  is_valid &&= value.length <= 255
56
58
  is_valid &&= value !~ /\.\./
57
59
  is_valid &&= value !~ /_/ unless options[:allow_underscore]
60
+ is_valid &&= value !~ %r{/} unless options[:allow_slash]
58
61
  is_valid &&= labels.size.in? segments
59
62
  labels.each_with_index do |label, idx|
60
63
  is_valid &&= label.length <= 63
@@ -0,0 +1,91 @@
1
+ # Ensure an attribute is generally formatted as a IP or IP block.
2
+ # eg: validates :ip, ipaddr: true
3
+ # validates :cidr, ipaddr: {allow_block: true}
4
+ # validates :private_ip, ipaddr: {within: [IPAddr.new('10.0.0.0/8'), '127.0.0.1']}
5
+ # ip must be within any one of the provided ips/blocks
6
+ # if ip is block, it must be fully contained within any one of the provided blocks
7
+ # validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
8
+ # ip must be outside all of the provided ips/blocks
9
+ # if ip is block, it must be fully outside all of the provided blocks
10
+
11
+ require 'ipaddr'
12
+
13
+ module ActiveModel::Validations
14
+ class IpaddrValidator < ActiveModel::EachValidator
15
+
16
+ def initialize(options)
17
+ options[:within] = normalize_within options[:within], :within
18
+ options[:without] = normalize_within options[:without], :without
19
+ super
20
+ end
21
+
22
+ def validate_each(record, attribute, value)
23
+ allowed_ips = resolve_array record, options[:within]
24
+ disallowed_ips = resolve_array record, options[:without]
25
+
26
+ ip = case value
27
+ when IPAddr
28
+ ip
29
+ when String
30
+ IPAddr.new(value) rescue nil
31
+ end
32
+ unless ip
33
+ record.errors.add(attribute, :invalid_ip, options.merge(value: value))
34
+ return
35
+ end
36
+
37
+ if !options[:allow_block] && (ip.ipv4? && ip.prefix!=32 or ip.ipv6? && ip.prefix!=128)
38
+ record.errors.add(attribute, :single_ip_required, options.merge(value: value))
39
+ end
40
+ if allowed_ips && allowed_ips.none?{|blk| blk.include? ip}
41
+ record.errors.add(attribute, :ip_not_allowed, options.merge(value: value))
42
+ elsif disallowed_ips && disallowed_ips.any?{|blk| blk.include? ip}
43
+ record.errors.add(attribute, :ip_not_allowed, options.merge(value: value))
44
+ end
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def normalize_within(val, key)
51
+ if val.nil? || val.respond_to?(:call) || val.is_a?(Symbol)
52
+ val
53
+ else
54
+ Array(val).flatten.map do |i|
55
+ case i
56
+ when IPAddr
57
+ i
58
+ when String
59
+ IPAddr.new i
60
+ else
61
+ raise "Unexpected value for #{key.inspect} : #{i}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def resolve_array(record, val)
68
+ res = if val.respond_to?(:call)
69
+ val.call(record)
70
+ elsif val.is_a?(Symbol)
71
+ record.send(val)
72
+ else
73
+ val
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ # tests for & fixes broken IPAddr <= 1.2.2
81
+ if IPAddr.new('192.168.2.0/32').include? '192.168.2.0/24'
82
+ # warn 'IPAddr <= 1.2.2 is broken; monkey-patching'
83
+ class IPAddr
84
+ def include?(other)
85
+ range = to_range
86
+ other = coerce_other(other).to_range
87
+ range.begin <= other.begin && range.end >= other.end
88
+ end
89
+ alias === include?
90
+ end
91
+ end
@@ -1,3 +1,3 @@
1
1
  module CanHasValidations
2
- VERSION = '1.0.2'
2
+ VERSION = '1.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: can_has_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thomas morgan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-06 00:00:00.000000000 Z
11
+ date: 2020-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -61,6 +61,7 @@ files:
61
61
  - lib/can_has_validations/validators/existence_validator.rb
62
62
  - lib/can_has_validations/validators/grandparent_validator.rb
63
63
  - lib/can_has_validations/validators/hostname_validator.rb
64
+ - lib/can_has_validations/validators/ipaddr_validator.rb
64
65
  - lib/can_has_validations/validators/ordering_validator.rb
65
66
  - lib/can_has_validations/validators/url_validator.rb
66
67
  - lib/can_has_validations/validators/write_once_validator.rb
@@ -100,7 +101,7 @@ files:
100
101
  homepage: https://github.com/zarqman/can_has_validations
101
102
  licenses: []
102
103
  metadata: {}
103
- post_install_message:
104
+ post_install_message:
104
105
  rdoc_options: []
105
106
  require_paths:
106
107
  - lib
@@ -115,8 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
118
  requirements: []
118
- rubygems_version: 3.0.3
119
- signing_key:
119
+ rubygems_version: 3.0.8
120
+ signing_key:
120
121
  specification_version: 4
121
122
  summary: Assorted Rails 5.x-6.x validators
122
123
  test_files: