dato_json_schema 0.20.8

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.
@@ -0,0 +1,606 @@
1
+ require "uri"
2
+
3
+ module JsonSchema
4
+ class Validator
5
+ attr_accessor :errors
6
+
7
+ def initialize(schema)
8
+ @schema = schema
9
+ end
10
+
11
+ def validate(data, fail_fast: false)
12
+ @errors = []
13
+ @visits = {}
14
+ @fail_fast = fail_fast
15
+
16
+ # This dynamically creates the "strict_or_fast_and" method which is used
17
+ # throughout the validator to combine the previous validation result with
18
+ # another validation check.
19
+ # Logic wise, we could simply define this method without meta programming
20
+ # and decide every time to either call fast_and or strict_end.
21
+ # Unfortunately this has a small overhead, that adds up over the runtime
22
+ # of the validator – about 5% if we check @fail_fast everytime.
23
+ # For more details, please see https://github.com/brandur/json_schema/pull/96
24
+ and_operation = method(@fail_fast ? :fast_and : :strict_and)
25
+ define_singleton_method(:strict_or_fast_and, and_operation)
26
+
27
+ catch(:fail_fast) do
28
+ validate_data(@schema, data, @errors, ['#'])
29
+ end
30
+ @errors.size == 0
31
+ end
32
+
33
+ def validate!(data, fail_fast: false)
34
+ if !validate(data, fail_fast: fail_fast)
35
+ raise AggregateError.new(@errors)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def first_visit(schema, errors, path)
42
+ true
43
+ # removed until more comprehensive testing can be performed .. this is
44
+ # currently causing validation loop detections to go off on all non-trivial
45
+ # schemas
46
+ =begin
47
+ key = "#{schema.object_id}-#{schema.pointer}-#{path.join("/")}"
48
+ if !@visits.key?(key)
49
+ @visits[key] = true
50
+ true
51
+ else
52
+ message = %{Validation loop detected.}
53
+ errors << ValidationError.new(schema, path, message, :loop_detected)
54
+ false
55
+ end
56
+ =end
57
+ end
58
+
59
+ # for use with additionalProperties and strictProperties
60
+ def get_extra_keys(schema, data)
61
+ extra = data.keys - schema.properties.keys
62
+
63
+ if schema.pattern_properties
64
+ schema.pattern_properties.keys.each do |pattern|
65
+ extra -= extra.select { |k| k =~ pattern }
66
+ end
67
+ end
68
+
69
+ extra
70
+ end
71
+
72
+ # works around &&'s "lazy" behavior
73
+ def strict_and(valid_old, valid_new)
74
+ valid_old && valid_new
75
+ end
76
+
77
+ def fast_and(valid_old, valid_new)
78
+ throw :fail_fast, false if !valid_new
79
+ valid_old && valid_new
80
+ end
81
+
82
+ def validate_data(schema, data, errors, path)
83
+ valid = true
84
+ # detect a validation loop
85
+ if !first_visit(schema, errors, path)
86
+ return false
87
+ end
88
+
89
+ # validation: any
90
+ valid = strict_or_fast_and valid, validate_all_of(schema, data, errors, path)
91
+ valid = strict_or_fast_and valid, validate_any_of(schema, data, errors, path)
92
+ valid = strict_or_fast_and valid, validate_enum(schema, data, errors, path)
93
+ valid = strict_or_fast_and valid, validate_one_of(schema, data, errors, path)
94
+ valid = strict_or_fast_and valid, validate_not(schema, data, errors, path)
95
+ valid = strict_or_fast_and valid, validate_type(schema, data, errors, path)
96
+
97
+ # validation: array
98
+ if data.is_a?(Array)
99
+ valid = strict_or_fast_and valid, validate_items(schema, data, errors, path)
100
+ valid = strict_or_fast_and valid, validate_max_items(schema, data, errors, path)
101
+ valid = strict_or_fast_and valid, validate_min_items(schema, data, errors, path)
102
+ valid = strict_or_fast_and valid, validate_unique_items(schema, data, errors, path)
103
+ end
104
+
105
+ # validation: integer/number
106
+ if data.is_a?(Float) || data.is_a?(Integer)
107
+ valid = strict_or_fast_and valid, validate_max(schema, data, errors, path)
108
+ valid = strict_or_fast_and valid, validate_min(schema, data, errors, path)
109
+ valid = strict_or_fast_and valid, validate_multiple_of(schema, data, errors, path)
110
+ end
111
+
112
+ # validation: object
113
+ if data.is_a?(Hash)
114
+ valid = strict_or_fast_and valid, validate_additional_properties(schema, data, errors, path)
115
+ valid = strict_or_fast_and valid, validate_dependencies(schema, data, errors, path)
116
+ valid = strict_or_fast_and valid, validate_max_properties(schema, data, errors, path)
117
+ valid = strict_or_fast_and valid, validate_min_properties(schema, data, errors, path)
118
+ valid = strict_or_fast_and valid, validate_pattern_properties(schema, data, errors, path)
119
+ valid = strict_or_fast_and valid, validate_properties(schema, data, errors, path)
120
+ valid = strict_or_fast_and valid, validate_required(schema, data, errors, path, schema.required)
121
+ valid = strict_or_fast_and valid, validate_strict_properties(schema, data, errors, path)
122
+ end
123
+
124
+ # validation: string
125
+ if data.is_a?(String)
126
+ valid = strict_or_fast_and valid, validate_format(schema, data, errors, path)
127
+ valid = strict_or_fast_and valid, validate_max_length(schema, data, errors, path)
128
+ valid = strict_or_fast_and valid, validate_min_length(schema, data, errors, path)
129
+ valid = strict_or_fast_and valid, validate_pattern(schema, data, errors, path)
130
+ end
131
+
132
+ valid
133
+ end
134
+
135
+ def validate_additional_properties(schema, data, errors, path)
136
+ return true if schema.additional_properties == true
137
+
138
+ # schema indicates that all properties not in `properties` should be
139
+ # validated according to subschema
140
+ if schema.additional_properties.is_a?(Schema)
141
+ extra = get_extra_keys(schema, data)
142
+ validations = extra.map do |key|
143
+ validate_data(schema.additional_properties, data[key], errors, path + [key])
144
+ end
145
+
146
+ # true only if all keys validate
147
+ validations.all?
148
+
149
+ # boolean indicates whether additional properties are allowed
150
+ else
151
+ validate_extra(schema, data, errors, path)
152
+ end
153
+ end
154
+
155
+ def validate_all_of(schema, data, errors, path)
156
+ return true if schema.all_of.empty?
157
+
158
+ # We've kept this feature behind a configuration flag for now because
159
+ # there is some performance implication to producing each sub error.
160
+ # Normally we can short circuit the validation after encountering only
161
+ # one problem, but here we have to evaluate all subschemas every time.
162
+ if JsonSchema.configuration.all_of_sub_errors && !@fail_fast
163
+ sub_errors = []
164
+ valid = schema.all_of.map do |subschema|
165
+ current_sub_errors = []
166
+ sub_errors << current_sub_errors
167
+ validate_data(subschema, data, current_sub_errors, path)
168
+ end.all?
169
+ else
170
+ sub_errors = nil
171
+ valid = schema.all_of.all? do |subschema|
172
+ validate_data(subschema, data, errors, path)
173
+ end
174
+ end
175
+
176
+ message = %{Not all subschemas of "allOf" matched.}
177
+ errors << ValidationError.new(schema, path, message, :all_of_failed,
178
+ sub_errors: sub_errors, data: data) if !valid
179
+ valid
180
+ end
181
+
182
+ def validate_any_of(schema, data, errors, path)
183
+ return true if schema.any_of.empty?
184
+
185
+ sub_errors = schema.any_of.map do |subschema|
186
+ current_sub_errors = []
187
+ valid = catch(:fail_fast) do
188
+ validate_data(subschema, data, current_sub_errors, path)
189
+ end
190
+ return true if valid
191
+ current_sub_errors
192
+ end
193
+
194
+ message = %{No subschema in "anyOf" matched.}
195
+ errors << ValidationError.new(schema, path, message, :any_of_failed,
196
+ sub_errors: sub_errors, data: data)
197
+
198
+ false
199
+ end
200
+
201
+ def validate_dependencies(schema, data, errors, path)
202
+ return true if schema.dependencies.empty?
203
+ result = schema.dependencies.map do |key, obj|
204
+ # if the key is not present, the dependency is fulfilled by definition
205
+ next true unless data[key]
206
+ if obj.is_a?(Schema)
207
+ validate_data(obj, data, errors, path)
208
+ else
209
+ # if not a schema, value is an array of required fields
210
+ validate_required(schema, data, errors, path, obj)
211
+ end
212
+ end
213
+ result.all?
214
+ end
215
+
216
+ def validate_format(schema, data, errors, path)
217
+ return true unless schema.format
218
+ validator = (
219
+ JsonSchema.configuration.custom_formats[schema.format] ||
220
+ DEFAULT_FORMAT_VALIDATORS[schema.format]
221
+ )
222
+ if validator[data]
223
+ true
224
+ else
225
+ message = %{#{data} is not a valid #{schema.format}.}
226
+ errors << ValidationError.new(schema, path, message, :invalid_format, data: data)
227
+ false
228
+ end
229
+ end
230
+
231
+ def validate_enum(schema, data, errors, path)
232
+ return true unless schema.enum
233
+ if schema.enum.include?(data)
234
+ true
235
+ else
236
+ message = %{#{data} is not a member of #{schema.enum}.}
237
+ errors << ValidationError.new(schema, path, message, :invalid_type, data: data)
238
+ false
239
+ end
240
+ end
241
+
242
+ def validate_extra(schema, data, errors, path)
243
+ extra = get_extra_keys(schema, data)
244
+ if extra.empty?
245
+ true
246
+ else
247
+
248
+ message = %{"#{extra.sort.join('", "')}" } +
249
+ (extra.length == 1 ? "is not a" : "are not") +
250
+ %{ permitted key} +
251
+ (extra.length == 1 ? "." : "s.")
252
+ errors << ValidationError.new(schema, path, message, :invalid_keys)
253
+ false
254
+ end
255
+ end
256
+
257
+ def validate_items(schema, data, errors, path)
258
+ return true unless schema.items
259
+ if schema.items.is_a?(Array)
260
+ if data.size < schema.items.count
261
+ message = %{#{schema.items.count} item} +
262
+ (schema.items.count == 1 ? "" : "s") +
263
+ %{ required; only #{data.size} } +
264
+ (data.size == 1 ? "was" : "were") +
265
+ %{ supplied.}
266
+ errors << ValidationError.new(schema, path, message, :min_items_failed, data: data)
267
+ false
268
+ elsif data.size > schema.items.count && !schema.additional_items?
269
+ message = %{No more than #{schema.items.count} item} +
270
+ (schema.items.count == 1 ? " is" : "s are") +
271
+ %{ allowed; #{data.size} } +
272
+ (data.size > 1 ? "were" : "was") +
273
+ %{ supplied.}
274
+ errors << ValidationError.new(schema, path, message, :max_items_failed, data: data)
275
+ false
276
+ else
277
+ valid = true
278
+ if data.size > schema.items.count && schema.additional_items.is_a?(Schema)
279
+ (schema.items.count..data.count - 1).each do |i|
280
+ valid = strict_or_fast_and valid,
281
+ validate_data(schema.additional_items, data[i], errors, path + [i])
282
+ end
283
+ end
284
+ schema.items.each_with_index do |subschema, i|
285
+ valid = strict_or_fast_and valid,
286
+ validate_data(subschema, data[i], errors, path + [i])
287
+ end
288
+ valid
289
+ end
290
+ else
291
+ valid = true
292
+ data.each_with_index do |value, i|
293
+ valid = strict_or_fast_and valid,
294
+ validate_data(schema.items, value, errors, path + [i])
295
+ end
296
+ valid
297
+ end
298
+ end
299
+
300
+ def validate_max(schema, data, errors, path)
301
+ return true unless schema.max
302
+ if schema.max_exclusive? && data < schema.max
303
+ true
304
+ elsif !schema.max_exclusive? && data <= schema.max
305
+ true
306
+ else
307
+ message = %{#{data} must be less than} +
308
+ (schema.max_exclusive? ? "" : " or equal to") +
309
+ %{ #{schema.max}.}
310
+ errors << ValidationError.new(schema, path, message, :max_failed, data: data)
311
+ false
312
+ end
313
+ end
314
+
315
+ def validate_max_items(schema, data, errors, path)
316
+ return true unless schema.max_items
317
+ if data.size <= schema.max_items
318
+ true
319
+ else
320
+ message = %{No more than #{schema.max_items} item} +
321
+ (schema.max_items == 1 ? " is" : "s are") +
322
+ %{ allowed; #{data.size} } +
323
+ (data.size == 1 ? "was" : "were")+
324
+ %{ supplied.}
325
+ errors << ValidationError.new(schema, path, message, :max_items_failed, data: data)
326
+ false
327
+ end
328
+ end
329
+
330
+ def validate_max_length(schema, data, errors, path)
331
+ return true unless schema.max_length
332
+ if data.length <= schema.max_length
333
+ true
334
+ else
335
+ message = %{Only #{schema.max_length} character} +
336
+ (schema.max_length == 1 ? " is" : "s are") +
337
+ %{ allowed; #{data.length} } +
338
+ (data.length == 1 ? "was" : "were") +
339
+ %{ supplied.}
340
+ errors << ValidationError.new(schema, path, message, :max_length_failed, data: data)
341
+ false
342
+ end
343
+ end
344
+
345
+ def validate_max_properties(schema, data, errors, path)
346
+ return true unless schema.max_properties
347
+ if data.keys.size <= schema.max_properties
348
+ true
349
+ else
350
+ message = %{No more than #{schema.max_properties} propert} +
351
+ (schema.max_properties == 1 ? "y is" : "ies are") +
352
+ %{ allowed; #{data.keys.size} } +
353
+ (data.keys.size == 1 ? "was" : "were") +
354
+ %{ supplied.}
355
+ errors << ValidationError.new(schema, path, message, :max_properties_failed, data: data)
356
+ false
357
+ end
358
+ end
359
+
360
+ def validate_min(schema, data, errors, path)
361
+ return true unless schema.min
362
+ if schema.min_exclusive? && data > schema.min
363
+ true
364
+ elsif !schema.min_exclusive? && data >= schema.min
365
+ true
366
+ else
367
+ message = %{#{data} must be greater than} +
368
+ (schema.min_exclusive? ? "" : " or equal to") +
369
+ %{ #{schema.min}.}
370
+ errors << ValidationError.new(schema, path, message, :min_failed, data: data)
371
+ false
372
+ end
373
+ end
374
+
375
+ def validate_min_items(schema, data, errors, path)
376
+ return true unless schema.min_items
377
+ if data.size >= schema.min_items
378
+ true
379
+ else
380
+ message = %{#{schema.min_items} item} +
381
+ (schema.min_items == 1 ? "" : "s") +
382
+ %{ required; only #{data.size} } +
383
+ (data.size == 1 ? "was" : "were") +
384
+ %{ supplied.}
385
+ errors << ValidationError.new(schema, path, message, :min_items_failed, data: data)
386
+ false
387
+ end
388
+ end
389
+
390
+ def validate_min_length(schema, data, errors, path)
391
+ return true unless schema.min_length
392
+ if data.length >= schema.min_length
393
+ true
394
+ else
395
+ message = %{At least #{schema.min_length} character} +
396
+ (schema.min_length == 1 ? " is" : "s are") +
397
+ %{ required; only #{data.length} } +
398
+ (data.length == 1 ? "was" : "were") +
399
+ %{ supplied.}
400
+ errors << ValidationError.new(schema, path, message, :min_length_failed, data: data)
401
+ false
402
+ end
403
+ end
404
+
405
+ def validate_min_properties(schema, data, errors, path)
406
+ return true unless schema.min_properties
407
+ if data.keys.size >= schema.min_properties
408
+ true
409
+ else
410
+ message = %{At least #{schema.min_properties} propert}+
411
+ (schema.min_properties == 1 ? "y is" : "ies are") +
412
+ %{ required; #{data.keys.size} }+
413
+ (data.keys.size == 1 ? "was" : "were") +
414
+ %{ supplied.}
415
+ errors << ValidationError.new(schema, path, message, :min_properties_failed, data: data)
416
+ false
417
+ end
418
+ end
419
+
420
+ def validate_multiple_of(schema, data, errors, path)
421
+ return true unless schema.multiple_of
422
+ if data % schema.multiple_of == 0
423
+ true
424
+ else
425
+ message = %{#{data} is not a multiple of #{schema.multiple_of}.}
426
+ errors << ValidationError.new(schema, path, message, :multiple_of_failed, data: data)
427
+ false
428
+ end
429
+ end
430
+
431
+ def validate_one_of(schema, data, errors, path)
432
+ return true if schema.one_of.empty?
433
+ sub_errors = []
434
+
435
+ num_valid = schema.one_of.count do |subschema|
436
+ current_sub_errors = []
437
+ valid = catch(:fail_fast) do
438
+ validate_data(subschema, data, current_sub_errors, path)
439
+ end
440
+ sub_errors << current_sub_errors
441
+ valid
442
+ end
443
+
444
+ return true if num_valid == 1
445
+
446
+ message =
447
+ if num_valid == 0
448
+ %{No subschema in "oneOf" matched.}
449
+ else
450
+ %{More than one subschema in "oneOf" matched.}
451
+ end
452
+ errors << ValidationError.new(schema, path, message, :one_of_failed,
453
+ sub_errors: sub_errors, data: data)
454
+
455
+ false
456
+ end
457
+
458
+ def validate_not(schema, data, errors, path)
459
+ return true unless schema.not
460
+ # don't bother accumulating these errors, they'll all be worded
461
+ # incorrectly for the inverse condition
462
+ valid = !validate_data(schema.not, data, [], path)
463
+ if !valid
464
+ message = %{Matched "not" subschema.}
465
+ errors << ValidationError.new(schema, path, message, :not_failed, data: data)
466
+ end
467
+ valid
468
+ end
469
+
470
+ def validate_pattern(schema, data, errors, path)
471
+ return true unless schema.pattern
472
+
473
+ if data =~ schema.pattern
474
+ true
475
+ else
476
+ message = %{#{data} does not match #{schema.pattern.inspect}.}
477
+ errors << ValidationError.new(schema, path, message, :pattern_failed, data: data)
478
+ false
479
+ end
480
+ end
481
+
482
+ def validate_pattern_properties(schema, data, errors, path)
483
+ return true if schema.pattern_properties.empty?
484
+ valid = true
485
+ schema.pattern_properties.each do |pattern, subschema|
486
+ data.each do |key, value|
487
+ if key =~ pattern
488
+ valid = strict_or_fast_and valid,
489
+ validate_data(subschema, value, errors, path + [key])
490
+ end
491
+ end
492
+ end
493
+ valid
494
+ end
495
+
496
+ def validate_properties(schema, data, errors, path)
497
+ return true if schema.properties.empty?
498
+ valid = true
499
+ schema.properties.each do |key, subschema|
500
+ next unless data.key?(key)
501
+ valid = strict_or_fast_and valid,
502
+ validate_data(subschema, data[key], errors, path + [key])
503
+ end
504
+ valid
505
+ end
506
+
507
+ def validate_required(schema, data, errors, path, required)
508
+ return true if !required || required.empty?
509
+ if (missing = required - data.keys).empty?
510
+ true
511
+ else
512
+ message = %{"#{missing.sort.join('", "')}" } +
513
+ (missing.length == 1 ? "wasn't" : "weren't") +
514
+ %{ supplied.}
515
+ errors << ValidationError.new(schema, path, message, :required_failed, data: missing)
516
+ false
517
+ end
518
+ end
519
+
520
+ def validate_strict_properties(schema, data, errors, path)
521
+ return true if !schema.strict_properties
522
+
523
+ strict_or_fast_and validate_extra(schema, data, errors, path),
524
+ validate_required(schema, data, errors, path, schema.properties.keys)
525
+ end
526
+
527
+ def validate_type(schema, data, errors, path)
528
+ return true if !schema.type || schema.type.empty?
529
+ if schema.type_parsed.any? { |t| data.is_a?(t) }
530
+ true
531
+ else
532
+ key = find_parent(schema)
533
+ message = %{For '#{key}', #{data.inspect} is not #{ErrorFormatter.to_list(schema.type)}.}
534
+ errors << ValidationError.new(schema, path, message, :invalid_type, data: data)
535
+ false
536
+ end
537
+ end
538
+
539
+ def validate_unique_items(schema, data, errors, path)
540
+ return true unless schema.unique_items?
541
+ if data.size == data.uniq.size
542
+ true
543
+ else
544
+ message = %{Duplicate items are not allowed.}
545
+ errors << ValidationError.new(schema, path, message, :unique_items_failed, data: data)
546
+ false
547
+ end
548
+ end
549
+
550
+ def find_parent(schema)
551
+ fragment = schema.fragment
552
+ key = if fragment =~ /patternProperties/
553
+ split_pointer = schema.pointer.split("/")
554
+ idx = split_pointer.index("patternProperties")
555
+
556
+ # this join mimics the fragment format below in that it's
557
+ # parent + key
558
+ if idx - 2 >= 0
559
+ parts = split_pointer[(idx - 2)..(idx - 1)]
560
+ end
561
+
562
+ # protect against a `nil` that could occur if
563
+ # `patternProperties` has no parent
564
+ parts ? parts.compact.join("/") : nil
565
+ end
566
+ key || fragment
567
+ end
568
+
569
+ DEFAULT_FORMAT_VALIDATORS = {
570
+ "date" => ->(data) { data =~ DATE_PATTERN },
571
+ "date-time" => ->(data) { data =~ DATE_TIME_PATTERN },
572
+ "email" => ->(data) { data =~ EMAIL_PATTERN },
573
+ "hostname" => ->(data) { data =~ HOSTNAME_PATTERN },
574
+ "ipv4" => ->(data) { data =~ IPV4_PATTERN },
575
+ "ipv6" => ->(data) { data =~ IPV6_PATTERN },
576
+ "regex" => ->(data) { Regexp.new(data) rescue false },
577
+ "uri" => ->(data) { URI.parse(data) rescue false },
578
+
579
+ # From the spec: a string instance is valid URI Reference (either a URI
580
+ # or a relative-reference), according to RFC3986.
581
+ #
582
+ # URI.parse will a handle a relative reference as well as an absolute
583
+ # one. Really though we should try to make "uri" more restrictive, and
584
+ # both of these could do to be more robust.
585
+ "uri-reference" => ->(data) { URI.parse(data) rescue false },
586
+
587
+ "uuid" => ->(data) { data =~ UUID_PATTERN },
588
+ }.freeze
589
+
590
+ EMAIL_PATTERN = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i
591
+
592
+ HOSTNAME_PATTERN = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/
593
+
594
+ DATE_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/
595
+
596
+ DATE_TIME_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-2][0-9]:[0-5][0-9]:[0-5][0-9](\.[0-9]+)?(Z|[\-+][0-9]{2}:[0-5][0-9])$/
597
+
598
+ # from: http://stackoverflow.com/a/17871737
599
+ IPV4_PATTERN = /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/
600
+
601
+ # from: http://stackoverflow.com/a/17871737
602
+ IPV6_PATTERN = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:)$/
603
+
604
+ UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
605
+ end
606
+ end