coercive 1.0.0 → 1.4.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: d1b6a873b4a86ca962a22d60630a4de5355b278b529fccbd2615a794a7a94448
4
- data.tar.gz: 62f47d1f35c9d931935138872dace70a7c39d3eb090529f1f2886448dca2e4ad
3
+ metadata.gz: f5ece0274ec3187ab9ae2407837048143d6c1c516bd4ddcfc953a208e5bee6ed
4
+ data.tar.gz: 6b9ffb6fa9c18a0d9049193c7e352201f559b86a98cd4258d49b756ca8bf4df9
5
5
  SHA512:
6
- metadata.gz: d2d5f401306d119e14a761aa1937f6c5777f69458d9db1367d35bd2a02fcea8a2d41876db4331a0086af8ec49db00b3f892d3c76dbcbacd6d297edc836cfb3db
7
- data.tar.gz: e5e4ffc4e9e7874807a2b3af33fb3f846752acce0e9748d06c79247e54f7120ffb38ac5a764c33a629ee05009c62154864f41f975a7fa19985f77b96caa5d53e
6
+ metadata.gz: d036ba5c9a7b0526903330fe17aff3e34f47f0c0a338769fb0762fed5da8ca7d0210143b0f87533dba2010637feed320cbf454d78d629ffd5d3ebb13a8ab18fb
7
+ data.tar.gz: c5eb1b8af3fb355081d15108bf037fd4f5ec9b85be892f875500b419a343a8d4136c26ce0ff3ca0c8fca8bf3bb91f7ac4acf8439369009fbec14dfaf98e5e043
data/README.md CHANGED
@@ -1,10 +1,20 @@
1
- # coercive
1
+ # Coercive
2
+
3
+ # Install
4
+
5
+ ```
6
+ $ gem install coercive
7
+ ```
8
+
9
+ # Usage
2
10
 
3
11
  `Coercive` is a Ruby library to validate and coerce user input.
4
12
 
5
13
  Define your coercion modules like this:
6
14
 
7
15
  ```ruby
16
+ require "coercive"
17
+
8
18
  module CoerceFoo
9
19
  extend Coercive
10
20
 
@@ -118,6 +128,30 @@ CoerceFoo.call("foo" => "DEADBEEF")
118
128
  # => {"foo"=>"DEADBEEF"}
119
129
  ```
120
130
 
131
+ ### `date` and `datetime`
132
+
133
+ The `date` and `datetime` coercion functions will receive a `String` and give you `Date` and `DateTime` objects, respectively.
134
+
135
+ By default they expect an ISO 8601 string, but they provide a `format` option in case you need to parse something different, following the `strftime` format.
136
+
137
+ ```ruby
138
+ module CoerceFoo
139
+ extend Coercive
140
+
141
+ attribute :date_foo, date, optional
142
+ attribute :american_date, date(format: "%m-%d-%Y"), optional
143
+ attribute :datetime_foo, datetime, optional
144
+ end
145
+
146
+ CoerceFoo.call("date_foo" => "1988-05-18", "datetime_foo" => "1988-05-18T21:00:00Z", "american_date" => "05-18-1988")
147
+ # => {"date_foo"=>#<Date: 1988-05-18 ((2447300j,0s,0n),+0s,2299161j)>,
148
+ # "american_date"=>#<Date: 1988-05-18 ((2447300j,0s,0n),+0s,2299161j)>,
149
+ # "datetime_foo"=>#<DateTime: 1988-05-18T21:00:00+00:00 ((2447300j,75600s,0n),+0s,2299161j)>}
150
+
151
+ CoerceFoo.call("date_foo" => "18th May 1988")
152
+ # => Coercive::Error: {"date_foo"=>"not_valid"}
153
+ ```
154
+
121
155
  ### `any`
122
156
 
123
157
  The `any` coercion function lets anything pass through. It's commonly used with the `optional` fetch function when an attribute may or many not be a part of the input.
@@ -137,6 +171,37 @@ CoerceFoo.call("foo" => 4)
137
171
  # => Coercive::Error: {"foo"=>"not_valid"}
138
172
  ```
139
173
 
174
+ ### `integer(min:, max:)`
175
+
176
+ `integer` expects an integer value. It supports optional `min` and `max` options to check if the user input is within certain bounds.
177
+
178
+ ```ruby
179
+ module CoerceFoo
180
+ extend Coercive
181
+
182
+ attribute :foo, integer, optional
183
+ attribute :foo_bounds, integer(min: 1, max: 10), optional
184
+ end
185
+
186
+ CoerceFoo.call("foo" => "1")
187
+ # => {"foo"=>1}
188
+
189
+ CoerceFoo.call("foo" => "bar")
190
+ # => Coercive::Error: {"foo"=>"not_valid"}
191
+
192
+ CoerceFoo.call("foo" => "1.5")
193
+ # => Coercive::Error: {"foo"=>"not_numeric"}
194
+
195
+ CoerceFoo.call("foo" => 1.5)
196
+ # => Coercive::Error: {"foo"=>"float_not_permitted"}
197
+
198
+ CoerceFoo.call("foo_bounds" => 0)
199
+ # => Coercive::Error: {"foo_bounds"=>"too_low"}
200
+
201
+ CoerceFoo.call("foo_bounds" => 11)
202
+ # => Coercive::Error: {"foo_bounds"=>"too_high"}
203
+ ```
204
+
140
205
  ### `float`
141
206
 
142
207
  `float` expects, well, a float value.
@@ -184,7 +249,7 @@ CoerceFoo.call("foo" => [BasicObject.new])
184
249
 
185
250
  ### `hash`
186
251
 
187
- `hash` coercion let's you manipulate the key and values, similarly to how `array` does
252
+ `hash` coercion let's you manipulate the key and values, similarly to how `array` does.
188
253
 
189
254
  ```ruby
190
255
  module CoerceFoo
@@ -199,3 +264,48 @@ CoerceFoo.call("foo" => {"bar" => "0.1"})
199
264
  CoerceFoo.call("foo" => {"barrrr" => "0.1"})
200
265
  # => Coercive::Error: {"foo"=>{"barrrr"=>"too_long"}}
201
266
  ```
267
+
268
+ ### `uri`
269
+
270
+ The `uri` coercion function really showcases how it's very easy to build custom logic to validate and coerce any kind of input. `uri` is meant to verify IP and URLs and has a variety of options.
271
+
272
+ ```ruby
273
+ module CoerceFoo
274
+ extend Coercive
275
+
276
+ attribute :foo, uri(string), optional
277
+ end
278
+
279
+ CoerceFoo.call("foo" => "http://github.com")
280
+ # => {"foo"=>"http://github.com"}
281
+
282
+ CoerceFoo.call("foo" => "not a url")
283
+ # => Coercive::Error: {"foo"=>"not_valid"}
284
+ ```
285
+
286
+ #### Requiring a specific URI schema
287
+
288
+ The `schema_fn` option allows you to compose additional coercion functions to verify the schema.
289
+
290
+ ```ruby
291
+ module CoerceFoo
292
+ extend Coercive
293
+
294
+ attribute :foo, uri(string, schema_fn: member(%w{http https})), optional
295
+ end
296
+
297
+ CoerceFoo.call("foo" => "https://github.com")
298
+ # => {"foo"=>"https://github.com"}
299
+
300
+ CoerceFoo.call("foo" => "ftp://github.com")
301
+ # => Coercive::Error: {"foo"=>"unsupported_schema"}
302
+ ```
303
+
304
+ #### Requiring URI elements
305
+
306
+ There's a number of boolean options to enforce the presence of parts of a URI to be present. By default they're all false.
307
+
308
+ * `require_path`: for example, `"https://github.com/Theorem"`
309
+ * `require_port`: for example, `"https://github.com:433"`
310
+ * `require_user`: for example, `"https://user@github.com"`
311
+ * `require_password`: for example, `"https://:password@github.com"`
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "coercive"
3
+ s.version = "1.4.0"
4
+ s.summary = "Coercive is a library to validate and coerce user input"
5
+ s.description = s.summary
6
+ s.authors = ["Joe McIlvain", "Lucas Tolchinsky"]
7
+ s.email = ["joe.eli.mac@gmail.com", "tonchis@protonmail.com"]
8
+ s.homepage = "https://github.com/Theorem/coercive"
9
+ s.license = "MIT"
10
+
11
+ s.files = `git ls-files`.split("\n")
12
+ end
@@ -1,3 +1,4 @@
1
+ require "date"
1
2
  require_relative "coercive/uri"
2
3
 
3
4
  # Public: The Coercive module implements a succinct DSL for declaring callable
@@ -149,6 +150,25 @@ module Coercive
149
150
  end
150
151
  end
151
152
 
153
+ # Public DSL: Return a coerce function to coerce input to an Integer.
154
+ # Used when declaring an attribute. See documentation for attr_coerce_fns.
155
+ def integer(min: nil, max: nil)
156
+ ->(input) do
157
+ fail Coercive::Error.new("float_not_permitted") if input.is_a?(Float)
158
+
159
+ input = begin
160
+ Integer(input)
161
+ rescue TypeError, ArgumentError
162
+ fail Coercive::Error.new("not_numeric")
163
+ end
164
+
165
+ fail Coercive::Error.new("too_low") if min && input < min
166
+ fail Coercive::Error.new("too_high") if max && input > max
167
+
168
+ input
169
+ end
170
+ end
171
+
152
172
  # Public DSL: Return a coerce function to coerce input to a Float.
153
173
  # Used when declaring an attribute. See documentation for attr_coerce_fns.
154
174
  def float
@@ -192,6 +212,48 @@ module Coercive
192
212
  end
193
213
  end
194
214
 
215
+ # Public DSL: Return a coercion function to coerce input into a Date.
216
+ # Used when declaring an attribute. See documentation for attr_coerce_fns.
217
+ #
218
+ # format - String following Ruby's `strftime` format to change the parsing behavior. When empty
219
+ # it will expect the String to be ISO 8601 compatible.
220
+ def date(format: nil)
221
+ ->(input) do
222
+ input = begin
223
+ if format
224
+ Date.strptime(input, format)
225
+ else
226
+ Date.iso8601(input)
227
+ end
228
+ rescue ArgumentError
229
+ fail Coercive::Error.new("not_valid")
230
+ end
231
+
232
+ input
233
+ end
234
+ end
235
+
236
+ # Public DSL: Return a coercion function to coerce input into a DateTime.
237
+ # Used when declaring an attribute. See documentation for attr_coerce_fns.
238
+ #
239
+ # format - String following Ruby's `strftime` format to change the parsing behavior. When empty
240
+ # it will expect the String to be ISO 8601 compatible.
241
+ def datetime(format: nil)
242
+ ->(input) do
243
+ input = begin
244
+ if format
245
+ DateTime.strptime(input, format)
246
+ else
247
+ DateTime.iso8601(input)
248
+ end
249
+ rescue ArgumentError
250
+ fail Coercive::Error.new("not_valid")
251
+ end
252
+
253
+ input
254
+ end
255
+ end
256
+
195
257
  # Public DSL: Return a coercion function to coerce input to an Array.
196
258
  # Used when declaring an attribute. See documentation for attr_coerce_fns.
197
259
  #
@@ -1,33 +1,8 @@
1
1
  require "ipaddr"
2
2
  require "uri"
3
3
 
4
- module Coercion
4
+ module Coercive
5
5
  module URI
6
- # Setting this `true` allows outbound connections to private IP addresses,
7
- # bypassing the security check that the IP address is public. This is designed
8
- # to be used in devlopment so that the tests can connect to local services.
9
- #
10
- # This SHOULD NOT be set in PRODUCTION.
11
- ALLOW_PRIVATE_IP_CONNECTIONS =
12
- ENV.fetch("ALLOW_PRIVATE_IP_CONNECTIONS", "").downcase == "true"
13
-
14
- PRIVATE_IP_RANGES = [
15
- IPAddr.new("0.0.0.0/8"), # Broadcasting to the current network. RFC 1700.
16
- IPAddr.new("10.0.0.0/8"), # Local private network. RFC 1918.
17
- IPAddr.new("127.0.0.0/8"), # Loopback addresses to the localhost. RFC 990.
18
- IPAddr.new("169.254.0.0/16"), # link-local addresses between two hosts on a single link. RFC 3927.
19
- IPAddr.new("172.16.0.0/12"), # Local private network. RFC 1918.
20
- IPAddr.new("192.168.0.0/16"), # Local private network. RFC 1918.
21
- IPAddr.new("198.18.0.0/15"), # Testing of inter-network communications between two separate subnets. RFC 2544.
22
- IPAddr.new("198.51.100.0/24"), # Assigned as "TEST-NET-2" in RFC 5737.
23
- IPAddr.new("203.0.113.0/24"), # Assigned as "TEST-NET-3" in RFC 5737.
24
- IPAddr.new("240.0.0.0/4"), # Reserved for future use, as specified by RFC 6890
25
- IPAddr.new("::1/128"), # Loopback addresses to the localhost. RFC 5156.
26
- IPAddr.new("2001:20::/28"), # Non-routed IPv6 addresses used for Cryptographic Hash Identifiers. RFC 7343.
27
- IPAddr.new("fc00::/7"), # Unique Local Addresses (ULAs). RFC 1918.
28
- IPAddr.new("fe80::/10"), # link-local addresses between two hosts on a single link. RFC 3927.
29
- ].freeze
30
-
31
6
  # Public DSL: Return a coercion function to coerce input to a URI.
32
7
  # Used when declaring an attribute. See documentation for attr_coerce_fns.
33
8
  #
@@ -43,61 +18,25 @@ module Coercion
43
18
  uri = begin
44
19
  ::URI.parse(string_coerce_fn.call(input))
45
20
  rescue ::URI::InvalidURIError
46
- fail Coercion::Error.new("not_valid")
21
+ fail Coercive::Error.new("not_valid")
47
22
  end
48
23
 
49
- fail Coercion::Error.new("no_host") unless uri.host
50
- fail Coercion::Error.new("not_resolvable") unless resolvable_public_ip?(uri) || ALLOW_PRIVATE_IP_CONNECTIONS
51
- fail Coercion::Error.new("no_path") if require_path && uri.path.empty?
52
- fail Coercion::Error.new("no_port") if require_port && !uri.port
53
- fail Coercion::Error.new("no_user") if require_user && !uri.user
54
- fail Coercion::Error.new("no_password") if require_password && !uri.password
24
+ fail Coercive::Error.new("no_host") unless uri.host
25
+ fail Coercive::Error.new("no_path") if require_path && uri.path.empty?
26
+ fail Coercive::Error.new("no_port") if require_port && !uri.port
27
+ fail Coercive::Error.new("no_user") if require_user && !uri.user
28
+ fail Coercive::Error.new("no_password") if require_password && !uri.password
55
29
 
56
30
  if schema_fn
57
31
  begin
58
32
  schema_fn.call(uri.scheme)
59
- rescue Coercion::Error
60
- fail Coercion::Error.new("unsupported_schema")
33
+ rescue Coercive::Error
34
+ fail Coercive::Error.new("unsupported_schema")
61
35
  end
62
36
  end
63
37
 
64
38
  uri.to_s
65
39
  end
66
40
  end
67
-
68
- # Internal: Return true if the given URI is resolvable to a non-private IP.
69
- #
70
- # uri - the URI to check.
71
- def self.resolvable_public_ip?(uri)
72
- begin
73
- _, _, _, *resolved_addresses = Socket.gethostbyname(uri.host)
74
- rescue SocketError
75
- return false
76
- end
77
-
78
- resolved_addresses.none? do |bytes|
79
- ip = ip_from_bytes(bytes)
80
-
81
- ip.nil? || PRIVATE_IP_RANGES.any? { |range| range.include?(ip) }
82
- end
83
- end
84
-
85
- # Internal: Return an IPAddr built from the given address bytes.
86
- #
87
- # bytes - the binary-encoded String returned by Socket.gethostbyname.
88
- def self.ip_from_bytes(bytes)
89
- octets = bytes.unpack("C*")
90
-
91
- string =
92
- if octets.length == 4 # IPv4
93
- octets.join(".")
94
- else # IPv6
95
- octets.map { |i| "%02x" % i }.each_slice(2).map(&:join).join(":")
96
- end
97
-
98
- IPAddr.new(string)
99
- rescue IPAddr::InvalidAddressError
100
- nil
101
- end
102
41
  end
103
42
  end
@@ -100,6 +100,50 @@ describe "Coercive" do
100
100
  end
101
101
  end
102
102
 
103
+ describe "integer" do
104
+ before do
105
+ @coercion = Module.new do
106
+ extend Coercive
107
+
108
+ attribute :foo, integer, optional
109
+ attribute :bar, integer, optional
110
+ attribute :baz, integer(min: 1, max: 10), optional
111
+ end
112
+ end
113
+
114
+ it "coerces the input value to an integer" do
115
+ attributes = { "foo" => "100", "bar" => 100 }
116
+
117
+ expected = { "foo" => 100, "bar" => 100 }
118
+
119
+ assert_equal expected, @coercion.call(attributes)
120
+ end
121
+
122
+ it "doesn't allow Float" do
123
+ expected_errors = { "foo" => "float_not_permitted" }
124
+
125
+ assert_coercion_error(expected_errors) { @coercion.call("foo" => 100.5) }
126
+ end
127
+
128
+ it "errors if the input can't be coerced into an Integer" do
129
+ ["nope", "100.5", "1e5"].each do |value|
130
+ expected_errors = { "foo" => "not_numeric" }
131
+
132
+ assert_coercion_error(expected_errors) { @coercion.call("foo" => value) }
133
+ end
134
+ end
135
+
136
+ it "errors if the input is out of bounds" do
137
+ expected_errors = { "baz" => "too_low" }
138
+
139
+ assert_coercion_error(expected_errors) { @coercion.call("baz" => 0) }
140
+
141
+ expected_errors = { "baz" => "too_high" }
142
+
143
+ assert_coercion_error(expected_errors) { @coercion.call("baz" => 11) }
144
+ end
145
+ end
146
+
103
147
  describe "float" do
104
148
  before do
105
149
  @coercion = Module.new do
@@ -113,7 +157,7 @@ describe "Coercive" do
113
157
  fixnum = 2
114
158
  rational = 2 ** -2
115
159
  bignum = 2 ** 64
116
- bigdecimal = BigDecimal.new("0.1")
160
+ bigdecimal = BigDecimal("0.1")
117
161
 
118
162
  [fixnum, rational, bignum, bigdecimal].each do |value|
119
163
  attributes = { "foo" => value }
@@ -199,6 +243,84 @@ describe "Coercive" do
199
243
  end
200
244
  end
201
245
 
246
+ describe "date" do
247
+ before do
248
+ @coercion = Module.new do
249
+ extend Coercive
250
+
251
+ attribute :date, date, optional
252
+ attribute :american_date, date(format: "%m-%d-%Y"), optional
253
+ end
254
+ end
255
+
256
+ it "coerces a string into a Date object with ISO 8601 format by default" do
257
+ attributes = { "date" => "1988-05-18" }
258
+
259
+ expected = { "date" => Date.new(1988, 5, 18) }
260
+
261
+ assert_equal expected, @coercion.call(attributes)
262
+ end
263
+
264
+ it "supports a custom date format" do
265
+ attributes = { "american_date" => "05-18-1988" }
266
+
267
+ expected = { "american_date" => Date.new(1988, 5, 18) }
268
+
269
+ assert_equal expected, @coercion.call(attributes)
270
+ end
271
+
272
+ it "errors if the input doesn't parse" do
273
+ attributes = { "date" => "12-31-1990" }
274
+
275
+ expected_errors = { "date" => "not_valid" }
276
+
277
+ assert_coercion_error(expected_errors) { @coercion.call(attributes) }
278
+ end
279
+ end
280
+
281
+ describe "datetime" do
282
+ before do
283
+ @coercion = Module.new do
284
+ extend Coercive
285
+
286
+ attribute :datetime, datetime, optional
287
+ attribute :american_date, datetime(format: "%m-%d-%Y %I:%M:%S %p"), optional
288
+ end
289
+ end
290
+
291
+ it "coerces a string into a DateTime object with ISO 8601 format by default" do
292
+ attributes = { "datetime" => "1988-05-18T21:00:00Z" }
293
+
294
+ expected = { "datetime" => DateTime.new(1988, 5, 18, 21, 00, 00) }
295
+
296
+ assert_equal expected, @coercion.call(attributes)
297
+ end
298
+
299
+ it "honors the timezone" do
300
+ attributes = { "datetime" => "1988-05-18T21:00:00-0300" }
301
+
302
+ expected = { "datetime" => DateTime.new(1988, 5, 18, 21, 00, 00, "-03:00") }
303
+
304
+ assert_equal expected, @coercion.call(attributes)
305
+ end
306
+
307
+ it "supports a custom date format" do
308
+ attributes = { "american_date" => "05-18-1988 09:00:00 PM" }
309
+
310
+ expected = { "american_date" => DateTime.new(1988, 5, 18, 21, 00, 00) }
311
+
312
+ assert_equal expected, @coercion.call(attributes)
313
+ end
314
+
315
+ it "errors if the input doesn't parse" do
316
+ attributes = { "datetime" => "12-31-1990T21:00:00Z" }
317
+
318
+ expected_errors = { "datetime" => "not_valid" }
319
+
320
+ assert_coercion_error(expected_errors) { @coercion.call(attributes) }
321
+ end
322
+ end
323
+
202
324
  describe "array" do
203
325
  before do
204
326
  @coercion = Module.new do
@@ -11,7 +11,7 @@ describe "Coercive::URI" do
11
11
  assert_equal errors, e.errors
12
12
  end
13
13
 
14
- setup do
14
+ before do
15
15
  @coercion = Module.new do
16
16
  extend Coercive
17
17
 
@@ -42,7 +42,7 @@ describe "Coercive::URI" do
42
42
  end
43
43
  end
44
44
 
45
- test "coerces a valid string to a URI" do
45
+ it "coerces a valid string to a URI" do
46
46
  attributes = {
47
47
  "any" => "http://user:pass@www.example.com:1234/path"
48
48
  }
@@ -50,7 +50,7 @@ describe "Coercive::URI" do
50
50
  assert_equal attributes, @coercion.call(attributes)
51
51
  end
52
52
 
53
- test "errors if input is an invalid URI" do
53
+ it "errors if input is an invalid URI" do
54
54
  attributes = { "any" => "%" }
55
55
 
56
56
  expected_errors = { "any" => "not_valid" }
@@ -58,7 +58,7 @@ describe "Coercive::URI" do
58
58
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
59
59
  end
60
60
 
61
- test "errors if the input is longer than the declared maximum size" do
61
+ it "errors if the input is longer than the declared maximum size" do
62
62
  attributes = {
63
63
  "min" => "http://foo.com",
64
64
  "max" => "http://long.url.com",
@@ -70,7 +70,7 @@ describe "Coercive::URI" do
70
70
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
71
71
  end
72
72
 
73
- test "errors if the input is shorter than the declared minimum size" do
73
+ it "errors if the input is shorter than the declared minimum size" do
74
74
  attributes = {
75
75
  "min" => "http://a.com",
76
76
  "max" => "http://bar.com",
@@ -82,14 +82,14 @@ describe "Coercive::URI" do
82
82
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
83
83
  end
84
84
 
85
- test "errors if the URI is an empty string" do
85
+ it "errors if the URI is an empty string" do
86
86
  attributes = { "schema" => "" }
87
87
  expected_errors = { "schema" => "is_empty" }
88
88
 
89
89
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
90
90
  end
91
91
 
92
- test "errors if no host" do
92
+ it "errors if no host" do
93
93
  attributes = { "any" => "http://" }
94
94
 
95
95
  expected_errors = { "any" => "no_host" }
@@ -97,7 +97,7 @@ describe "Coercive::URI" do
97
97
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
98
98
  end
99
99
 
100
- test "errors if schema is not supported" do
100
+ it "errors if schema is not supported" do
101
101
  attributes = { "schema" => "foo://example.com" }
102
102
 
103
103
  expected_errors = { "schema" => "unsupported_schema" }
@@ -105,7 +105,7 @@ describe "Coercive::URI" do
105
105
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
106
106
  end
107
107
 
108
- test "errors if required elements are not provided" do
108
+ it "errors if required elements are not provided" do
109
109
  attributes = {
110
110
  "require_path" => "foo://example.com",
111
111
  "require_port" => "foo://example.com",
@@ -123,67 +123,26 @@ describe "Coercive::URI" do
123
123
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
124
124
  end
125
125
 
126
- Coercive::URI::PRIVATE_IP_RANGES.each do |range|
127
- range = range.to_range
128
- first = range.first
129
- last = range.last
130
- first = first.ipv6? ? "[#{first}]" : first.to_s
131
- last = last.ipv6? ? "[#{last}]" : last.to_s
132
-
133
- test "errors when the URI host is an IP in the range #{first}..#{last}" do
134
- attributes_first = { "schema" => "http://#{first}/path" }
135
- attributes_last = { "schema" => "http://#{last}/path" }
136
- expected_errors = { "schema" => "not_resolvable" }
137
-
138
- assert_coercion_error(expected_errors) { @coercion.call(attributes_first) }
139
- assert_coercion_error(expected_errors) { @coercion.call(attributes_last) }
140
- end
141
- end
142
-
143
- test "errors when the URI host is not resolvable" do
144
- attributes = {
145
- "schema" => "http://bogus-host-that-cant-possibly-exist-here/path"
146
- }
147
-
148
- expected_errors = { "schema" => "not_resolvable" }
149
-
150
- assert_coercion_error(expected_errors) { @coercion.call(attributes) }
151
- end
152
-
153
- test "errors when the URI host resolves to an IP in a private range" do
154
- attributes = { "schema" => "http://localhost/path" }
155
-
156
- expected_errors = { "schema" => "not_resolvable" }
157
-
158
- assert_coercion_error(expected_errors) { @coercion.call(attributes) }
159
- end
160
-
161
- test "allows a URI host to be IP that isn't in a private range" do
126
+ it "allows a URI host to be an IP" do
162
127
  attributes = { "schema" => "http://8.8.8.8/path" }
163
128
 
164
129
  assert_equal attributes, @coercion.call(attributes)
165
130
  end
166
131
 
167
- test "allows a URI host that resolves to an IP not in a private range" do
168
- attributes = { "schema" => "http://www.example.com/path" }
169
-
170
- assert_equal attributes, @coercion.call(attributes)
171
- end
172
-
173
- test "allows a URI with no explicit path component" do
132
+ it "allows a URI with no explicit path component" do
174
133
  attributes = { "schema" => "http://www.example.com" }
175
134
 
176
135
  assert_equal attributes, @coercion.call(attributes)
177
136
  end
178
137
 
179
- test "errors for a string that does not pass URI.parse" do
138
+ it "errors for a string that does not pass URI.parse" do
180
139
  attributes = { "schema" => "\\" }
181
140
  expected_errors = { "schema" => "not_valid" }
182
141
 
183
142
  assert_coercion_error(expected_errors) { @coercion.call(attributes) }
184
143
  end
185
144
 
186
- test "errors for a URL that passes URI.parse, but is ill-formed" do
145
+ it "errors for a URL that passes URI.parse, but is ill-formed" do
187
146
  attributes = { "schema" => "http:example.com/path" }
188
147
 
189
148
  begin
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coercive
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe McIlvain
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-04 00:00:00.000000000 Z
12
+ date: 2020-09-03 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Coercive is a library to validate and coerce user input
15
15
  email:
@@ -21,6 +21,7 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - LICENSE
23
23
  - README.md
24
+ - coercive.gemspec
24
25
  - lib/coercive.rb
25
26
  - lib/coercive/uri.rb
26
27
  - test/coercive.rb