dato_json_schema 0.20.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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