coercive 1.0.1 → 1.5.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 +4 -4
- data/README.md +107 -4
- data/coercive.gemspec +1 -1
- data/lib/coercive.rb +73 -6
- data/lib/coercive/uri.rb +1 -59
- data/test/coercive.rb +134 -1
- data/test/uri.rb +1 -54
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9ce029330ac405fde945a05e6f81c62361027ebe5d03659fd82b067554087e7
|
4
|
+
data.tar.gz: 89cfe35313fde4e0b0cea25d6050b008e75c8457ca440b71cf1c2acca26d13d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5260061302b501653bf64ce29988622f5ea06739a32058778a64106ad25ddd28567773048ac48addf510adc5f08357f0f6258c2877b67b8e2c0a3e3352baf48
|
7
|
+
data.tar.gz: 25e42dec9a930a6cea139f581114abf59d166a95210a5884edb0276a29bbbee7cb365959d51c6caddc4c76c827d0dfecf5bdb71386e262d2e8fccd3b20decacc
|
data/README.md
CHANGED
@@ -128,6 +128,30 @@ CoerceFoo.call("foo" => "DEADBEEF")
|
|
128
128
|
# => {"foo"=>"DEADBEEF"}
|
129
129
|
```
|
130
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
|
+
|
131
155
|
### `any`
|
132
156
|
|
133
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.
|
@@ -147,20 +171,58 @@ CoerceFoo.call("foo" => 4)
|
|
147
171
|
# => Coercive::Error: {"foo"=>"not_valid"}
|
148
172
|
```
|
149
173
|
|
150
|
-
### `
|
174
|
+
### `integer(min:, max:)`
|
151
175
|
|
152
|
-
`
|
176
|
+
`integer` expects an integer value. It supports optional `min` and `max` options to check if the user input is within certain bounds.
|
153
177
|
|
154
178
|
```ruby
|
155
179
|
module CoerceFoo
|
156
180
|
extend Coercive
|
157
181
|
|
158
|
-
attribute :foo,
|
182
|
+
attribute :foo, integer, optional
|
183
|
+
attribute :foo_bounds, integer(min: 1, max: 10), optional
|
159
184
|
end
|
160
185
|
|
186
|
+
CoerceFoo.call("foo" => "1")
|
187
|
+
# => {"foo"=>1}
|
188
|
+
|
161
189
|
CoerceFoo.call("foo" => "bar")
|
162
190
|
# => Coercive::Error: {"foo"=>"not_valid"}
|
163
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
|
+
|
205
|
+
### `float(min:, max:)`
|
206
|
+
|
207
|
+
`float` expects, well, a float value. It supports optional `min` and `max` options to check if the user input is within certain bounds.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
module CoerceFoo
|
211
|
+
extend Coercive
|
212
|
+
|
213
|
+
attribute :foo, float, optional
|
214
|
+
attribute :foo_bounds, float(min: 1.0, max: 5.5), optional
|
215
|
+
end
|
216
|
+
|
217
|
+
CoerceFoo.call("foo" => "bar")
|
218
|
+
# => Coercive::Error: {"foo"=>"not_valid"}
|
219
|
+
|
220
|
+
CoerceFoo.call("foo" => "0.5")
|
221
|
+
# => Coercive::Error: {"foo"=>"too_low"}
|
222
|
+
|
223
|
+
CoerceFoo.call("foo" => 6.5)
|
224
|
+
# => Coercive::Error: {"foo"=>"too_high"}
|
225
|
+
|
164
226
|
CoerceFoo.call("foo" => "0.1")
|
165
227
|
# => {"foo"=>0.1}
|
166
228
|
|
@@ -212,4 +274,45 @@ CoerceFoo.call("foo" => {"barrrr" => "0.1"})
|
|
212
274
|
|
213
275
|
### `uri`
|
214
276
|
|
215
|
-
The `uri` coercion
|
277
|
+
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.
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
module CoerceFoo
|
281
|
+
extend Coercive
|
282
|
+
|
283
|
+
attribute :foo, uri(string), optional
|
284
|
+
end
|
285
|
+
|
286
|
+
CoerceFoo.call("foo" => "http://github.com")
|
287
|
+
# => {"foo"=>"http://github.com"}
|
288
|
+
|
289
|
+
CoerceFoo.call("foo" => "not a url")
|
290
|
+
# => Coercive::Error: {"foo"=>"not_valid"}
|
291
|
+
```
|
292
|
+
|
293
|
+
#### Requiring a specific URI schema
|
294
|
+
|
295
|
+
The `schema_fn` option allows you to compose additional coercion functions to verify the schema.
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
module CoerceFoo
|
299
|
+
extend Coercive
|
300
|
+
|
301
|
+
attribute :foo, uri(string, schema_fn: member(%w{http https})), optional
|
302
|
+
end
|
303
|
+
|
304
|
+
CoerceFoo.call("foo" => "https://github.com")
|
305
|
+
# => {"foo"=>"https://github.com"}
|
306
|
+
|
307
|
+
CoerceFoo.call("foo" => "ftp://github.com")
|
308
|
+
# => Coercive::Error: {"foo"=>"unsupported_schema"}
|
309
|
+
```
|
310
|
+
|
311
|
+
#### Requiring URI elements
|
312
|
+
|
313
|
+
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.
|
314
|
+
|
315
|
+
* `require_path`: for example, `"https://github.com/Theorem"`
|
316
|
+
* `require_port`: for example, `"https://github.com:433"`
|
317
|
+
* `require_user`: for example, `"https://user@github.com"`
|
318
|
+
* `require_password`: for example, `"https://:password@github.com"`
|
data/coercive.gemspec
CHANGED
data/lib/coercive.rb
CHANGED
@@ -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,15 +150,39 @@ 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
|
-
def float
|
174
|
+
def float(min: nil, max: nil)
|
155
175
|
->(input) do
|
156
|
-
begin
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
176
|
+
input = begin
|
177
|
+
Float(input)
|
178
|
+
rescue TypeError, ArgumentError
|
179
|
+
fail Coercive::Error.new("not_numeric")
|
180
|
+
end
|
181
|
+
|
182
|
+
fail Coercive::Error.new("too_low") if min && input < min
|
183
|
+
fail Coercive::Error.new("too_high") if max && input > max
|
184
|
+
|
185
|
+
input
|
161
186
|
end
|
162
187
|
end
|
163
188
|
|
@@ -192,6 +217,48 @@ module Coercive
|
|
192
217
|
end
|
193
218
|
end
|
194
219
|
|
220
|
+
# Public DSL: Return a coercion function to coerce input into a Date.
|
221
|
+
# Used when declaring an attribute. See documentation for attr_coerce_fns.
|
222
|
+
#
|
223
|
+
# format - String following Ruby's `strftime` format to change the parsing behavior. When empty
|
224
|
+
# it will expect the String to be ISO 8601 compatible.
|
225
|
+
def date(format: nil)
|
226
|
+
->(input) do
|
227
|
+
input = begin
|
228
|
+
if format
|
229
|
+
Date.strptime(input, format)
|
230
|
+
else
|
231
|
+
Date.iso8601(input)
|
232
|
+
end
|
233
|
+
rescue ArgumentError
|
234
|
+
fail Coercive::Error.new("not_valid")
|
235
|
+
end
|
236
|
+
|
237
|
+
input
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Public DSL: Return a coercion function to coerce input into a DateTime.
|
242
|
+
# Used when declaring an attribute. See documentation for attr_coerce_fns.
|
243
|
+
#
|
244
|
+
# format - String following Ruby's `strftime` format to change the parsing behavior. When empty
|
245
|
+
# it will expect the String to be ISO 8601 compatible.
|
246
|
+
def datetime(format: nil)
|
247
|
+
->(input) do
|
248
|
+
input = begin
|
249
|
+
if format
|
250
|
+
DateTime.strptime(input, format)
|
251
|
+
else
|
252
|
+
DateTime.iso8601(input)
|
253
|
+
end
|
254
|
+
rescue ArgumentError
|
255
|
+
fail Coercive::Error.new("not_valid")
|
256
|
+
end
|
257
|
+
|
258
|
+
input
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
195
262
|
# Public DSL: Return a coercion function to coerce input to an Array.
|
196
263
|
# Used when declaring an attribute. See documentation for attr_coerce_fns.
|
197
264
|
#
|
data/lib/coercive/uri.rb
CHANGED
@@ -3,27 +3,6 @@ require "uri"
|
|
3
3
|
|
4
4
|
module Coercive
|
5
5
|
module URI
|
6
|
-
# The IP ranges below are considered private and by default not permitted by the `uri`
|
7
|
-
# coercion function. To allow connecting to local services (in development, for example)
|
8
|
-
# users can set the `allow_private_ip` option, which ignores if the URI resolves to a public
|
9
|
-
# address or not.
|
10
|
-
PRIVATE_IP_RANGES = [
|
11
|
-
IPAddr.new("0.0.0.0/8"), # Broadcasting to the current network. RFC 1700.
|
12
|
-
IPAddr.new("10.0.0.0/8"), # Local private network. RFC 1918.
|
13
|
-
IPAddr.new("127.0.0.0/8"), # Loopback addresses to the localhost. RFC 990.
|
14
|
-
IPAddr.new("169.254.0.0/16"), # link-local addresses between two hosts on a single link. RFC 3927.
|
15
|
-
IPAddr.new("172.16.0.0/12"), # Local private network. RFC 1918.
|
16
|
-
IPAddr.new("192.168.0.0/16"), # Local private network. RFC 1918.
|
17
|
-
IPAddr.new("198.18.0.0/15"), # Testing of inter-network communications between two separate subnets. RFC 2544.
|
18
|
-
IPAddr.new("198.51.100.0/24"), # Assigned as "TEST-NET-2" in RFC 5737.
|
19
|
-
IPAddr.new("203.0.113.0/24"), # Assigned as "TEST-NET-3" in RFC 5737.
|
20
|
-
IPAddr.new("240.0.0.0/4"), # Reserved for future use, as specified by RFC 6890
|
21
|
-
IPAddr.new("::1/128"), # Loopback addresses to the localhost. RFC 5156.
|
22
|
-
IPAddr.new("2001:20::/28"), # Non-routed IPv6 addresses used for Cryptographic Hash Identifiers. RFC 7343.
|
23
|
-
IPAddr.new("fc00::/7"), # Unique Local Addresses (ULAs). RFC 1918.
|
24
|
-
IPAddr.new("fe80::/10"), # link-local addresses between two hosts on a single link. RFC 3927.
|
25
|
-
].freeze
|
26
|
-
|
27
6
|
# Public DSL: Return a coercion function to coerce input to a URI.
|
28
7
|
# Used when declaring an attribute. See documentation for attr_coerce_fns.
|
29
8
|
#
|
@@ -34,8 +13,7 @@ module Coercive
|
|
34
13
|
# require_user - set true to make the URI user a required element
|
35
14
|
# require_password - set true to make the URI password a required element
|
36
15
|
def self.coerce_fn(string_coerce_fn, schema_fn: nil, require_path: false,
|
37
|
-
require_port: false, require_user: false, require_password: false
|
38
|
-
allow_private_ip: false)
|
16
|
+
require_port: false, require_user: false, require_password: false)
|
39
17
|
->(input) do
|
40
18
|
uri = begin
|
41
19
|
::URI.parse(string_coerce_fn.call(input))
|
@@ -44,7 +22,6 @@ module Coercive
|
|
44
22
|
end
|
45
23
|
|
46
24
|
fail Coercive::Error.new("no_host") unless uri.host
|
47
|
-
fail Coercive::Error.new("not_resolvable") unless allow_private_ip || resolvable_public_ip?(uri)
|
48
25
|
fail Coercive::Error.new("no_path") if require_path && uri.path.empty?
|
49
26
|
fail Coercive::Error.new("no_port") if require_port && !uri.port
|
50
27
|
fail Coercive::Error.new("no_user") if require_user && !uri.user
|
@@ -61,40 +38,5 @@ module Coercive
|
|
61
38
|
uri.to_s
|
62
39
|
end
|
63
40
|
end
|
64
|
-
|
65
|
-
# Internal: Return true if the given URI is resolvable to a non-private IP.
|
66
|
-
#
|
67
|
-
# uri - the URI to check.
|
68
|
-
def self.resolvable_public_ip?(uri)
|
69
|
-
begin
|
70
|
-
_, _, _, *resolved_addresses = Socket.gethostbyname(uri.host)
|
71
|
-
rescue SocketError
|
72
|
-
return false
|
73
|
-
end
|
74
|
-
|
75
|
-
resolved_addresses.none? do |bytes|
|
76
|
-
ip = ip_from_bytes(bytes)
|
77
|
-
|
78
|
-
ip.nil? || PRIVATE_IP_RANGES.any? { |range| range.include?(ip) }
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Internal: Return an IPAddr built from the given address bytes.
|
83
|
-
#
|
84
|
-
# bytes - the binary-encoded String returned by Socket.gethostbyname.
|
85
|
-
def self.ip_from_bytes(bytes)
|
86
|
-
octets = bytes.unpack("C*")
|
87
|
-
|
88
|
-
string =
|
89
|
-
if octets.length == 4 # IPv4
|
90
|
-
octets.join(".")
|
91
|
-
else # IPv6
|
92
|
-
octets.map { |i| "%02x" % i }.each_slice(2).map(&:join).join(":")
|
93
|
-
end
|
94
|
-
|
95
|
-
IPAddr.new(string)
|
96
|
-
rescue IPAddr::InvalidAddressError
|
97
|
-
nil
|
98
|
-
end
|
99
41
|
end
|
100
42
|
end
|
data/test/coercive.rb
CHANGED
@@ -100,12 +100,57 @@ 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
|
106
150
|
extend Coercive
|
107
151
|
|
108
|
-
attribute :foo, float,
|
152
|
+
attribute :foo, float, optional
|
153
|
+
attribute :bar, float(min: 1.0, max: 5.5), optional
|
109
154
|
end
|
110
155
|
end
|
111
156
|
|
@@ -132,6 +177,16 @@ describe "Coercive" do
|
|
132
177
|
assert_coercion_error(expected_errors) { @coercion.call("foo" => bad) }
|
133
178
|
end
|
134
179
|
end
|
180
|
+
|
181
|
+
it "errors if the input is out of bounds" do
|
182
|
+
expected_errors = { "bar" => "too_low" }
|
183
|
+
|
184
|
+
assert_coercion_error(expected_errors) { @coercion.call("bar" => 0.5) }
|
185
|
+
|
186
|
+
expected_errors = { "bar" => "too_high" }
|
187
|
+
|
188
|
+
assert_coercion_error(expected_errors) { @coercion.call("bar" => 6.0) }
|
189
|
+
end
|
135
190
|
end
|
136
191
|
|
137
192
|
describe "string" do
|
@@ -199,6 +254,84 @@ describe "Coercive" do
|
|
199
254
|
end
|
200
255
|
end
|
201
256
|
|
257
|
+
describe "date" do
|
258
|
+
before do
|
259
|
+
@coercion = Module.new do
|
260
|
+
extend Coercive
|
261
|
+
|
262
|
+
attribute :date, date, optional
|
263
|
+
attribute :american_date, date(format: "%m-%d-%Y"), optional
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
it "coerces a string into a Date object with ISO 8601 format by default" do
|
268
|
+
attributes = { "date" => "1988-05-18" }
|
269
|
+
|
270
|
+
expected = { "date" => Date.new(1988, 5, 18) }
|
271
|
+
|
272
|
+
assert_equal expected, @coercion.call(attributes)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "supports a custom date format" do
|
276
|
+
attributes = { "american_date" => "05-18-1988" }
|
277
|
+
|
278
|
+
expected = { "american_date" => Date.new(1988, 5, 18) }
|
279
|
+
|
280
|
+
assert_equal expected, @coercion.call(attributes)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "errors if the input doesn't parse" do
|
284
|
+
attributes = { "date" => "12-31-1990" }
|
285
|
+
|
286
|
+
expected_errors = { "date" => "not_valid" }
|
287
|
+
|
288
|
+
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "datetime" do
|
293
|
+
before do
|
294
|
+
@coercion = Module.new do
|
295
|
+
extend Coercive
|
296
|
+
|
297
|
+
attribute :datetime, datetime, optional
|
298
|
+
attribute :american_date, datetime(format: "%m-%d-%Y %I:%M:%S %p"), optional
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
it "coerces a string into a DateTime object with ISO 8601 format by default" do
|
303
|
+
attributes = { "datetime" => "1988-05-18T21:00:00Z" }
|
304
|
+
|
305
|
+
expected = { "datetime" => DateTime.new(1988, 5, 18, 21, 00, 00) }
|
306
|
+
|
307
|
+
assert_equal expected, @coercion.call(attributes)
|
308
|
+
end
|
309
|
+
|
310
|
+
it "honors the timezone" do
|
311
|
+
attributes = { "datetime" => "1988-05-18T21:00:00-0300" }
|
312
|
+
|
313
|
+
expected = { "datetime" => DateTime.new(1988, 5, 18, 21, 00, 00, "-03:00") }
|
314
|
+
|
315
|
+
assert_equal expected, @coercion.call(attributes)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "supports a custom date format" do
|
319
|
+
attributes = { "american_date" => "05-18-1988 09:00:00 PM" }
|
320
|
+
|
321
|
+
expected = { "american_date" => DateTime.new(1988, 5, 18, 21, 00, 00) }
|
322
|
+
|
323
|
+
assert_equal expected, @coercion.call(attributes)
|
324
|
+
end
|
325
|
+
|
326
|
+
it "errors if the input doesn't parse" do
|
327
|
+
attributes = { "datetime" => "12-31-1990T21:00:00Z" }
|
328
|
+
|
329
|
+
expected_errors = { "datetime" => "not_valid" }
|
330
|
+
|
331
|
+
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
202
335
|
describe "array" do
|
203
336
|
before do
|
204
337
|
@coercion = Module.new do
|
data/test/uri.rb
CHANGED
@@ -39,10 +39,6 @@ describe "Coercive::URI" do
|
|
39
39
|
attribute :require_password,
|
40
40
|
uri(string(min: 1, max: 255), require_password: true),
|
41
41
|
optional
|
42
|
-
|
43
|
-
attribute :allow_private_ip,
|
44
|
-
uri(string(min: 1, max: 255), allow_private_ip: true),
|
45
|
-
optional
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
@@ -127,61 +123,12 @@ describe "Coercive::URI" do
|
|
127
123
|
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
128
124
|
end
|
129
125
|
|
130
|
-
|
131
|
-
range = range.to_range
|
132
|
-
first = range.first
|
133
|
-
last = range.last
|
134
|
-
first = first.ipv6? ? "[#{first}]" : first.to_s
|
135
|
-
last = last.ipv6? ? "[#{last}]" : last.to_s
|
136
|
-
|
137
|
-
it "errors when the URI host is an IP in the range #{first}..#{last}" do
|
138
|
-
attributes_first = { "schema" => "http://#{first}/path" }
|
139
|
-
attributes_last = { "schema" => "http://#{last}/path" }
|
140
|
-
expected_errors = { "schema" => "not_resolvable" }
|
141
|
-
|
142
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes_first) }
|
143
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes_last) }
|
144
|
-
end
|
145
|
-
|
146
|
-
it "allows overriding private IP address checks" do
|
147
|
-
attributes_first = { "allow_private_ip" => "http://#{first}/path" }
|
148
|
-
attributes_last = { "allow_private_ip" => "http://#{last}/path" }
|
149
|
-
|
150
|
-
assert_equal attributes_first, @coercion.call(attributes_first)
|
151
|
-
assert_equal attributes_last, @coercion.call(attributes_last)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
it "errors when the URI host is not resolvable" do
|
156
|
-
attributes = {
|
157
|
-
"schema" => "http://bogus-host-that-cant-possibly-exist-here/path"
|
158
|
-
}
|
159
|
-
|
160
|
-
expected_errors = { "schema" => "not_resolvable" }
|
161
|
-
|
162
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
163
|
-
end
|
164
|
-
|
165
|
-
it "errors when the URI host resolves to an IP in a private range" do
|
166
|
-
attributes = { "schema" => "http://localhost/path" }
|
167
|
-
|
168
|
-
expected_errors = { "schema" => "not_resolvable" }
|
169
|
-
|
170
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
171
|
-
end
|
172
|
-
|
173
|
-
it "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
|
174
127
|
attributes = { "schema" => "http://8.8.8.8/path" }
|
175
128
|
|
176
129
|
assert_equal attributes, @coercion.call(attributes)
|
177
130
|
end
|
178
131
|
|
179
|
-
it "allows a URI host that resolves to an IP not in a private range" do
|
180
|
-
attributes = { "schema" => "http://www.example.com/path" }
|
181
|
-
|
182
|
-
assert_equal attributes, @coercion.call(attributes)
|
183
|
-
end
|
184
|
-
|
185
132
|
it "allows a URI with no explicit path component" do
|
186
133
|
attributes = { "schema" => "http://www.example.com" }
|
187
134
|
|
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
|
4
|
+
version: 1.5.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-
|
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:
|