File Coverage

blib/lib/Types/JSONSchema.pm
Criterion Covered Total %
statement 295 344 85.7
branch 170 220 77.2
condition 67 87 77.0
subroutine 26 27 96.3
pod 2 3 66.6
total 560 681 82.2


line stmt bran cond sub pod time code
1 2     2   718113 use 5.036;
  2         9  
2 2     2   12 use strict;
  2         6  
  2         88  
3 2     2   12 use warnings;
  2         4  
  2         323  
4              
5             package Types::JSONSchema;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.001000';
9              
10 2     2   15 use constant { true => !!1, false => !!0 };
  2         5  
  2         413  
11              
12             use Type::Library
13 2         37 -extends => [ 'Types::JSONSchema::PrimativeTypes' ],
14             -declare => qw/
15             JSRef
16             JSScope
17            
18             JAllOf
19             JAnyOf
20             JOneOf
21             JNot
22             JIf
23             JThen
24             JElse
25             JDependentSchema
26            
27             JEnum
28             JConst
29            
30             JMultipleOf
31             JMaximum
32             JExclusiveMaximum
33             JMinimum
34             JExclusiveMinimum
35            
36             JMaxLength
37             JMinLength
38             JPattern
39            
40             JMaxItems
41             JMinItems
42             JUniqueItems
43             JItems
44            
45             JMaxProperties
46             JMinProperties
47             JRequired
48             JDependentRequired
49             JProperties
50             JPropertyNames
51            
52             FmtDateTime
53             FmtDate
54             FmtTime
55             FmtDuration
56             FmtEmail
57             FmtIdnEmail
58             FmtHostname
59             FmtIdnHostname
60             FmtIpv4
61             FmtIpv6
62             FmtUri
63             FmtUriReference
64             FmtIri
65             FmtIriReference
66             FmtUuid
67             FmtUriTemplate
68             FmtJsonPointer
69             FmtRelativeJsonPointer
70             FmtRegex
71 2     2   1377 /;
  2         113358  
72 2     2   26274 use Types::Common -all;
  2         4  
  2         16  
73 2     2   111047 use Types::Standard::ArrayRef Strings => { of => Str };
  2         3  
  2         9  
74 2     2   4847 use Type::Utils;
  2         5  
  2         13  
75              
76 2     2   3664 use List::Util qw( all any );
  2         5  
  2         226  
77 2     2   982 use Regexp::Common qw( URI net time Email::Address );
  2         5510  
  2         9  
78 2     2   435836 use Regexp::Util qw( :all );
  2         3472  
  2         46  
79 2     2   3159 use Scalar::Util ();
  2         5  
  2         47  
80 2     2   862 use URI::Escape qw( uri_escape );
  2         3778  
  2         20085  
81              
82             sub _croak {
83 0     0   0 my $str = shift;
84 0 0       0 if ( @_ ) {
85 0         0 $str = sprintf( $str, @_ );
86             }
87 0         0 require Carp;
88 0         0 @_ = ( $str );
89 0         0 goto \&Carp::croak;
90             }
91              
92             sub _carp {
93 2     2   24 my $str = shift;
94 2 50       5 if ( @_ ) {
95 2         8 $str = sprintf( $str, @_ );
96             }
97 2         13 require Carp;
98 2         5 @_ = ( $str );
99 2         305 goto \&Carp::carp;
100             }
101              
102             push our @EXPORT_OK, qw(
103             json_eq
104             json_safe_dumper
105             jpointer_escape
106             schema_to_type
107             true
108             false
109             );
110              
111             signature_for json_eq => (
112             method => false,
113             pos => [ Any, Any ],
114             );
115              
116             sub json_eq ( $x, $y ) {
117            
118             if ( is_JNull $x and is_JNull $y ) {
119             return true;
120             }
121              
122             if ( is_JTrue $x and is_JTrue $y ) {
123             return true;
124             }
125              
126             if ( is_JFalse $x and is_JFalse $y ) {
127             return true;
128             }
129              
130             if ( is_JNumber $x and is_JNumber $y ) {
131             return ( $x == $y );
132             }
133              
134             if ( is_JString $x and is_JString $y ) {
135             return ( $x eq $y );
136             }
137              
138             if ( is_JArray $x and is_JArray $y ) {
139             return false unless $x->@* == $y->@*;
140             for my $ix ( 0 .. $#$x ) {
141             return false unless __SUB__->( $x->[$ix], $y->[$ix] );
142             }
143             return true;
144             }
145              
146             if ( is_JObject $x and is_JObject $y ) {
147             return false unless keys($x->%*) == keys($y->%*);
148             for my $k ( keys $x->%* ) {
149             return false unless exists $y->{$k};
150             return false unless __SUB__->( $x->{$k}, $y->{$k} );
151             }
152             return true;
153             }
154              
155             return false;
156             }
157              
158 886     886   5863 sub _params_to_string ( $sep, @args ) {
  886         2068  
  886         1958  
  886         1275  
159             my @parts = map {
160 886 100       1846 !defined() ? 'undef' :
161             is_JTrue($_) ? '!!1' :
162             is_JFalse($_) ? '!!0' :
163             is_JInteger($_) ? int($_) :
164             is_JNumber($_) ? $_ :
165             is_JString($_) ? B::perlstring($_) :
166             is_ArrayRef($_) ? sprintf( '[%s]', _params_to_string( q{,}, $_->@* ) ) :
167 1377 100       47801 is_HashRef($_) ? sprintf( '{%s}', _params_to_string( [ q{,}, q{=>} ], do { my $h = $_; map {; $_ => $h->{$_} } sort keys $h->%* } ) ) :
  12 100       511  
  12 100       63  
  14 100       73  
    100          
    100          
    100          
    100          
168             "$_"
169             } @args or return '';
170 883 100       68673 if ( is_ArrayRef $sep ) {
171 12         68 my $joined = $parts[0];
172 12         50 for my $ix ( 1 .. $#parts ) {
173 16         95 $joined .= $sep->[ $ix % $sep->@* ] . $parts[$ix];
174             }
175 12         125 return $joined;
176             }
177             else {
178 871         6892 return join( $sep, @parts );
179             }
180             }
181              
182             sub params_to_string {
183 712     712 0 2262 unshift @_, q{,};
184 712         3194 goto \&_params_to_string;
185             }
186              
187             sub json_safe_dumper {
188 79     79 1 269 unshift @_, q{, };
189 79         327 goto \&_params_to_string;
190             }
191              
192 23     23 1 75 sub jpointer_escape ( $raw ) {
  23         59  
  23         56  
193 23         159 my $str = uri_escape( $raw );
194 23         742 $str =~ s/~/~0/g;
195 23         83 $str =~ s/%7E/~0/g;
196 23         66 $str =~ s/%2F/~1/g;
197 23         86 return $str;
198             }
199              
200             my $name_generator = sub {
201             my ( $base, @params ) = @_;
202             sprintf( '%s[%s]', $base, params_to_string(@params) );
203             };
204              
205             our %JSREF;
206             declare JSRef,
207             name_generator => sub {
208             my ( $base, @params ) = @_;
209             sprintf( '%s[%s]', $base, B::perlstring($params[0]) );
210             },
211             constraint_generator => sub {
212             my ( $path, $defs ) = @_;
213             return sub {
214             my $value = pop;
215             my $type = $defs->{$path} or die;
216             $type->check( $value );
217             };
218             },
219             inline_generator => sub {
220             my ( $path, $defs ) = @_;
221             return sub {
222             my $self = shift;
223             my $uniq = $self->{uniq};
224             my $varname = pop;
225             $JSREF{$uniq} ||= sub {
226             my $type = $defs->{$path} or _croak(
227             q{Schema referred to by %s not found. We know: %s},
228             B::perlstring($path),
229             Type::Utils::english_list( map B::perlstring($_), sort keys $defs->%* ) || 'nothing',
230             );
231             $type->check( @_ ? $_[0] : $_ );
232             };
233             return sprintf( '$%s::JSREF{%s}->( %s )', __PACKAGE__, B::perlstring($uniq), $varname );
234             };
235             };
236              
237             our ( $EVALUATED_PROPERTIES, $EVALUATED_ITEMS );
238             declare JSScope,
239             name_generator => $name_generator,
240             constraint_generator => sub {
241             @_ == 1 or die;
242             my $inner = shift;
243             return sub {
244             local $EVALUATED_PROPERTIES = {};
245             local $EVALUATED_ITEMS = {};
246             $inner->check( @_ );
247             };
248             },
249             inline_generator => sub {
250             @_ == 1 or die;
251             my $inner = shift;
252             return unless $inner->can_be_inlined;
253             return sub {
254             my $varname = pop;
255             sprintf(
256             'do { local $%s::EVALUATED_PROPERTIES = {}; local $%s::EVALUATED_ITEMS = {}; %s }',
257             __PACKAGE__,
258             __PACKAGE__,
259             $inner->inline_check( $varname ),
260             );
261             };
262             };
263              
264             declare JAllOf,
265             name_generator => $name_generator,
266             constraint_generator => sub {
267             my @constraints = @_;
268             _smiple(\@constraints);
269             return sub {
270             my $value = pop;
271             all { $_->check($value) } @constraints;
272             };
273             },
274             inline_generator => sub {
275             my @constraints = @_;
276             _smiple(\@constraints);
277             $_->can_be_inlined || return for @constraints;
278             return sub {
279             my $varname = pop;
280             if ( @constraints == 1 ) {
281             return $constraints[0]->inline_check($varname);
282             }
283             if ( $varname =~ /\A\$\w+\z/ ) {
284             return sprintf(
285             '( %s )',
286             join( ' and ', map { $_->inline_check( '$varname' ) } @constraints ),
287             );
288             }
289             sprintf(
290             'do { local $_ = %s; %s }',
291             $varname,
292             join( ' and ', map { $_->inline_check( '$_' ) } @constraints ),
293             );
294             };
295             },
296             deep_explanation => sub {
297             my ( $self, $value, $varname ) = @_;
298             return if $self->check( $value );
299             my @constraints = $self->parameters->@*;
300             return [
301             sprintf(
302             '"%s" requires that the value pass %s',
303             $self,
304             Type::Utils::english_list( \"and", map qq["$_"], @constraints ),
305             ),
306             map {
307             $_->get_message( $value ),
308             map( " $_", @{ $_->validate_explain( $value ) || [] } ),
309             }
310             grep {
311             not $_->check( $value );
312             } @constraints,
313             ];
314             };
315              
316             declare JAnyOf,
317             name_generator => $name_generator,
318             constraint_generator => sub {
319             my @constraints = @_;
320             return sub {
321             my $value = pop;
322             any { $_->check($value) } @constraints;
323             };
324             },
325             inline_generator => sub {
326             my @constraints = @_;
327             $_->can_be_inlined || return for @constraints;
328             return sub {
329             my $varname = pop;
330             if ( @constraints == 1 ) {
331             return $constraints[0]->inline_check($varname);
332             }
333             if ( $varname =~ /\A\$\w+\z/ ) {
334             return sprintf(
335             '( %s )',
336             join( ' or ', map { $_->inline_check( '$varname' ) } @constraints ),
337             );
338             }
339             sprintf(
340             'do { local $_ = %s; %s }',
341             $varname,
342             join( ' or ', map { $_->inline_check( '$_' ) } @constraints ),
343             );
344             };
345             },
346             deep_explanation => sub {
347             my ( $self, $value, $varname ) = @_;
348             return if $self->check( $value );
349             my @constraints = $self->parameters->@*;
350             return [
351             sprintf(
352             '"%s" requires that the value pass %s',
353             $self,
354             Type::Utils::english_list( \"or", map qq["$_"], @constraints ),
355             ),
356             map {
357             $_->get_message( $value ),
358             map( " $_", @{ $_->validate_explain( $value ) || [] } ),
359             } @constraints,
360             ];
361             };
362              
363             declare JOneOf,
364             name_generator => $name_generator,
365             constraint_generator => sub {
366             my @constraints = @_;
367             return sub {
368             my $value = pop;
369             1 == grep { $_->check($value) } @constraints;
370             };
371             },
372             inline_generator => sub {
373             my @constraints = @_;
374             $_->can_be_inlined || return for @constraints;
375             return sub {
376             my $varname = pop;
377             if ( $varname =~ /\A\$\w+\z/ ) {
378             return sprintf(
379             'do { my $passes = 0; %s; $passes == 1 }',
380             join( '; ', map { '++$passes if ' . $_->inline_check( $varname ) } @constraints ),
381             );
382             }
383             sprintf(
384             'do { local $_ = %s; my $passes = 0; %s; $passes == 1 }',
385             $varname,
386             join( '; ', map { '++$passes if ' . $_->inline_check( '$_' ) } @constraints ),
387             );
388             };
389             },
390             deep_explanation => sub {
391             my ( $self, $value, $varname ) = @_;
392             my @constraints = $self->parameters->@*;
393             my $count = grep { $_->check($value) } @constraints;
394             return [
395             sprintf(
396             '"%s" requires that the value pass exactly 1 of %s',
397             $self,
398             Type::Utils::english_list( \"or", map qq["$_"], @constraints ),
399             ),
400             (
401             map {
402             $_->get_message( $value ),
403             map( " $_", @{ $_->validate_explain( $value ) || [] } ),
404             } @constraints,
405             ),
406             sprintf( "The value passed %d", $count ),
407             ];
408             };
409              
410             declare JNot,
411             name_generator => $name_generator,
412             constraint_generator => sub {
413             my ( $constraint ) = @_;
414             return sub {
415             my $value = pop;
416             not $constraint->check($value);
417             };
418             },
419             inline_generator => sub {
420             my ( $constraint ) = @_;
421             $constraint->can_be_inlined || return;
422             return sub {
423             my $varname = pop;
424             sprintf( 'not( %s )', $constraint->inline_check($varname) );
425             };
426             },
427             deep_explanation => sub {
428             my ( $self, $value, $varname ) = @_;
429             my $constraint = $self->type_parameter;
430             return [
431             sprintf(
432             '"%s" requires that the value fail "%s" but it does not',
433             $self,
434             $constraint,
435             ),
436             ];
437             };
438              
439             declare JIf,
440             name_generator => $name_generator,
441             constraint_generator => sub {
442             my ( $if, $then, $else ) = @_;
443             $then ||= Any;
444             $else ||= Any;
445             return sub {
446             my $value = pop;
447             $if->check( $value ) ? $then->check($value) : $else->check($value);
448             };
449             },
450             inline_generator => sub {
451             my ( $if, $then, $else ) = @_;
452             $then ||= Any;
453             $else ||= Any;
454             $_->can_be_inlined || return for $if, $then, $else;
455             return sub {
456             my $varname = pop;
457             if ( $varname =~ /\A\$\w+\z/ ) {
458             return sprintf(
459             '( %s ? %s : %s )',
460             $if->inline_check( $varname ),
461             $then->inline_check( $varname ),
462             $else->inline_check( $varname ),
463             );
464             }
465             sprintf(
466             'do { local $_ = %s; %s ? %s : %s }',
467             $varname,
468             $if->inline_check( '$_' ),
469             $then->inline_check( '$_' ),
470             $else->inline_check( '$_' ),
471             );
472             };
473             },
474             deep_explanation => sub {
475             my ( $self, $value, $varname ) = @_;
476             return if $self->check( $value );
477             my ( $if, $then, $else ) = $self->parameters->@*;
478             $then ||= Any;
479             $else ||= Any;
480             if ( $if->check( $value ) ) {
481             return [
482             sprintf(
483             '"%s" requires that that if the value passes "%s", it must also pass "%s"',
484             $self,
485             $if,
486             $then,
487             ),
488             sprintf( 'The value passed "%s"', $if ),
489             map {
490             $_->get_message( $value ),
491             map( " $_", @{ $_->validate_explain( $value ) || [] } ),
492             } $then,
493             ];
494             }
495             else {
496             return [
497             sprintf(
498             '"%s" requires that that if the value fails "%s", it must pass "%s"',
499             $self,
500             $if,
501             $else,
502             ),
503             map {
504             $_->get_message( $value ),
505             map( " $_", @{ $_->validate_explain( $value ) || [] } ),
506             } $if, $else,
507             ];
508             }
509             };
510              
511             declare JThen,
512             name_generator => $name_generator,
513             constraint_generator => sub {
514             my @constraints = @_;
515             return sub {
516             my $value = pop;
517             return all { $_->check($value) } @constraints;
518             };
519             },
520             inline_generator => sub {
521             my @constraints = @_;
522             $_->can_be_inlined || return for @constraints;
523             return sub {
524             my $varname = pop;
525             return sprintf '( %s )', join( ' and ', map { $_->inline_check( $varname ) } @constraints );
526             }
527             },
528             deep_explanation => JAllOf->{deep_explanation};
529              
530             declare JElse,
531             as JThen,
532             name_generator => $name_generator,
533             constraint_generator => sub {
534             my @constraints = @_;
535             return sub {
536             my $value = pop;
537             return all { $_->check($value) } @constraints;
538             };
539             },
540             inline_generator => sub {
541             my @constraints = @_;
542             $_->can_be_inlined || return for @constraints;
543             return sub {
544             my $varname = pop;
545             return sprintf '( %s )', join( ' and ', map { $_->inline_check( $varname ) } @constraints );
546             }
547             },
548             deep_explanation => JAllOf->{deep_explanation};
549              
550             declare JDependentSchema,
551             name_generator => $name_generator,
552             constraint_generator => sub {
553             my ( $k, $then ) = @_;
554             return sub {
555             my $value = pop;
556             exists( $value->{$k} ) ? $then->check($value) : !!1;
557             };
558             },
559             inline_generator => sub {
560             my ( $k, $then ) = @_;
561             $_->can_be_inlined || return for $then;
562             return sub {
563             my $varname = pop;
564             if ( $varname =~ /\A\$\w+\z/ ) {
565             return sprintf(
566             '( !exists(%s->{%s}) or ( %s ) )',
567             $varname,
568             B::perlstring( $k ),
569             $then->inline_check( $varname ),
570             );
571             }
572             sprintf(
573             'do { local $_ = %s; !exists( $_->{%s} ) or ( %s ) }',
574             $varname,
575             B::perlstring( $k ),
576             $then->inline_check( '$_' ),
577             );
578             };
579             },
580             deep_explanation => sub {
581             my ( $self, $value, $varname ) = @_;
582             return if $self->check( $value );
583             my ( $k, $then ) = $self->parameters->@*;
584             if ( exists $value->{$k} ) {
585             return [
586             sprintf(
587             '"%s" requires that that if the hash has key "%s", the hash must pass "%s"',
588             $self,
589             $k,
590             $then,
591             ),
592             sprintf( 'The hash has key "%s"', $k ),
593             map {
594             $_->get_message( $value ),
595             map( " $_", @{ $_->validate_explain( $value ) || [] } ),
596             } $then,
597             ];
598             }
599             else {
600             return;
601             }
602             };
603              
604             declare JEnum,
605             as JAny,
606             name_generator => $name_generator,
607             constraint_generator => sub {
608             my @things = @_;
609             return sub {
610             my $value = pop;
611             for my $thing ( @things ) {
612             return true if json_eq( $value, $thing );
613             }
614             return false;
615             };
616             },
617             inline_generator => sub {
618             my @things = @_;
619             return sub {
620             my $varname = pop;
621             return sprintf(
622             q{( %s )},
623             join q{ || },
624             map sprintf(
625             '%s::json_eq( %s, %s )',
626             __PACKAGE__,
627             $varname,
628             json_safe_dumper( $_ ),
629             ), @things
630             );
631             };
632             },
633             deep_explanation => sub {
634             my ( $self, $value, $varname ) = @_;
635             return if $self->check( $value );
636             my @things = $self->parameters->@*;
637             if ( not @things ) {
638             return [
639             sprintf(
640             '"%s" cannot ever be satisifed!',
641             $self,
642             ),
643             ];
644             }
645             return [
646             sprintf(
647             '"%s" requires that the value be equivalent to %s%s',
648             $self,
649             ( @things == 1 ) ? q{} : q{one of },
650             Type::Utils::english_list( \'or', map { json_safe_dumper($_) } @things ),
651             ),
652             sprintf(
653             "The value is: %s",
654             json_safe_dumper( $value ),
655             ),
656             ];
657             };
658              
659             declare JConst,
660             as JAny,
661             name_generator => $name_generator,
662             constraint_generator => sub {
663             die if @_ != 1;
664             my $thing = $_[0];
665             return sub {
666             my $value = pop;
667             return json_eq( $value, $thing );
668             };
669             },
670             inline_generator => sub {
671             my $thing = $_[0];
672             return sub {
673             my $varname = pop;
674             return sprintf(
675             'do { %s::json_eq(%s, %s) }',
676             __PACKAGE__,
677             $varname,
678             json_safe_dumper( $thing ),
679             );
680             };
681             },
682             deep_explanation => JEnum->{deep_explanation};
683              
684             declare JMultipleOf,
685             name_generator => $name_generator,
686             constraint_generator => sub {
687             assert_PositiveNum( my $base = shift );
688             return sub {
689             my $value = shift;
690             is_Int( $value / $base );
691             };
692             },
693             inline_generator => sub {
694             assert_PositiveNum( my $base = $_[0] );
695             return sub {
696             my $varname = pop;
697             sprintf(
698             'do { my $tmp = %s / %s; %s }',
699             $varname,
700             $base,
701             Int->inline_check( '$tmp' ),
702             );
703             };
704             },
705             deep_explanation => sub {
706             my ( $self, $value, $varname ) = @_;
707             return if $self->check( $value );
708             return [
709             sprintf(
710             '"%s" requires the value to be a multiple of %s',
711             $self,
712             $self->type_parameter,
713             ),
714             sprintf(
715             '%s is not a multiple of %s',
716             json_safe_dumper($value),
717             $self->type_parameter,
718             ),
719             ];
720             };
721              
722             declare JMaximum,
723             name_generator => $name_generator,
724             constraint_generator => sub {
725             assert_Num( my $base = shift );
726             return sub {
727             my $value = shift;
728             $value <= $base;
729             };
730             },
731             inline_generator => sub {
732             assert_Num( my $base = $_[0] );
733             return sub {
734             my $varname = pop;
735             "$varname <= $base";
736             };
737             };
738              
739             declare JExclusiveMaximum,
740             name_generator => $name_generator,
741             constraint_generator => sub {
742             assert_Num( my $base = shift );
743             return sub {
744             my $value = shift;
745             $value < $base;
746             };
747             },
748             inline_generator => sub {
749             assert_Num( my $base = $_[0] );
750             return sub {
751             my $varname = pop;
752             "$varname < $base";
753             };
754             };
755              
756             declare JMinimum,
757             name_generator => $name_generator,
758             constraint_generator => sub {
759             assert_Num( my $base = shift );
760             return sub {
761             my $value = shift;
762             $value >= $base;
763             };
764             },
765             inline_generator => sub {
766             assert_Num( my $base = $_[0] );
767             return sub {
768             my $varname = pop;
769             "$varname >= $base";
770             };
771             };
772              
773             declare JExclusiveMinimum,
774             name_generator => $name_generator,
775             constraint_generator => sub {
776             assert_Num( my $base = shift );
777             return sub {
778             my $value = shift;
779             $value > $base;
780             };
781             },
782             inline_generator => sub {
783             assert_Num( my $base = $_[0] );
784             return sub {
785             my $varname = pop;
786             "$varname > $base";
787             };
788             };
789              
790             declare JMaxLength,
791             name_generator => $name_generator,
792             constraint_generator => sub {
793             assert_PositiveOrZeroInt( my $base = shift );
794             return sub {
795             my $value = shift;
796             length($value) <= $base;
797             };
798             },
799             inline_generator => sub {
800             assert_PositiveOrZeroInt( my $base = $_[0] );
801             return sub {
802             my $varname = pop;
803             "length($varname) <= $base";
804             };
805             },
806             deep_explanation => sub {
807             my ( $self, $value, $varname ) = @_;
808             return if $self->check( $value );
809             return [
810             sprintf(
811             '"%s" requires the value to be at most %d characters long',
812             $self,
813             $self->type_parameter,
814             ),
815             sprintf(
816             '%s is %d characters long',
817             json_safe_dumper($value),
818             length($value),
819             ),
820             ];
821             };
822              
823             declare JMinLength,
824             name_generator => $name_generator,
825             constraint_generator => sub {
826             assert_PositiveOrZeroInt( my $base = shift );
827             return sub {
828             my $value = shift;
829             length($value) >= $base;
830             };
831             },
832             inline_generator => sub {
833             assert_PositiveOrZeroInt( my $base = $_[0] );
834             return sub {
835             my $varname = pop;
836             "length($varname) >= $base";
837             };
838             },
839             deep_explanation => sub {
840             my ( $self, $value, $varname ) = @_;
841             return if $self->check( $value );
842             return [
843             sprintf(
844             '"%s" requires the value to be at least %d characters long',
845             $self,
846             $self->type_parameter,
847             ),
848             sprintf(
849             '%s is %d characters long',
850             json_safe_dumper($value),
851             length($value),
852             ),
853             ];
854             };
855              
856             declare JPattern,
857             name_generator => $name_generator,
858             constraint_generator => sub {
859             my $pattern = shift;
860             return sub {
861             my $value = shift;
862             $value =~ $pattern;
863             };
864             },
865             inline_generator => sub {
866             my $pattern = shift;
867             my $tc = StrMatch[ $pattern ];
868             return unless $tc->can_be_inlined;
869             return $tc->inlined;
870             };
871              
872             declare JMaxItems,
873             name_generator => $name_generator,
874             constraint_generator => sub {
875             assert_PositiveOrZeroInt( my $base = shift );
876             return sub {
877             my $value = shift;
878             @$value <= $base;
879             };
880             },
881             inline_generator => sub {
882             assert_PositiveOrZeroInt( my $base = $_[0] );
883             return sub {
884             my $varname = pop;
885             "\@{ $varname } <= $base";
886             };
887             },
888             deep_explanation => sub {
889             my ( $self, $value, $varname ) = @_;
890             return if $self->check( $value );
891             return [
892             sprintf(
893             '"%s" requires the array to be at most %d elements long',
894             $self,
895             $self->type_parameter,
896             ),
897             sprintf(
898             'The array is %d elements long',
899             scalar($value->@*),
900             ),
901             ];
902             };
903              
904             declare JMinItems,
905             name_generator => $name_generator,
906             constraint_generator => sub {
907             assert_PositiveOrZeroInt( my $base = shift );
908             return sub {
909             my $value = shift;
910             @$value >= $base;
911             };
912             },
913             inline_generator => sub {
914             assert_PositiveOrZeroInt( my $base = $_[0] );
915             return sub {
916             my $varname = pop;
917             "\@{ $varname } >= $base";
918             };
919             },
920             deep_explanation => sub {
921             my ( $self, $value, $varname ) = @_;
922             return if $self->check( $value );
923             return [
924             sprintf(
925             '"%s" requires the array to be at least %d elements long',
926             $self,
927             $self->type_parameter,
928             ),
929             sprintf(
930             'The array is %d elements long',
931             scalar($value->@*),
932             ),
933             ];
934             };
935              
936             declare JUniqueItems,
937             where {
938             my @tmp = @$_;
939             for my $i ( 0 .. $#tmp - 1 ) {
940             for my $j ( $i + 1 .. $#tmp ) {
941             return false if json_eq( $tmp[$i], $tmp[$j] );
942             }
943             }
944             return true;
945             },
946             inline_as {
947             my $varname = pop;
948             sprintf q{
949             do {
950             my @tmp = @{ %s };
951             my $bad = !!0;
952             OUTER: for my $i ( 0 .. $#tmp - 1 ) {
953             for my $j ( $i + 1 .. $#tmp ) {
954             ( ++$bad, last OUTER ) if %s::json_eq( $tmp[$i], $tmp[$j] );
955             }
956             }
957             not $bad;
958             };
959             }, $varname, __PACKAGE__;
960             },
961             message {
962             my $value = $_;
963             for my $i ( 0 .. $#$value - 1 ) {
964             for my $j ( $i + 1 .. $#$value ) {
965             return "@{[ Type::Tiny::_dd($value) ]} has non-unique elements: index $j duplicates index $i"
966             if json_eq( $value->[$i], $value->[$j] );
967             }
968             }
969             return '';
970             };
971              
972             declare JMaxProperties,
973             name_generator => $name_generator,
974             constraint_generator => sub {
975             assert_PositiveOrZeroInt( my $base = shift );
976             return sub {
977             my $value = shift;
978             keys(%$value) <= $base;
979             };
980             },
981             inline_generator => sub {
982             assert_PositiveOrZeroInt( my $base = $_[0] );
983             return sub {
984             my $varname = pop;
985             "keys(\%{ $varname }) <= $base";
986             };
987             },
988             deep_explanation => sub {
989             my ( $self, $value, $varname ) = @_;
990             return if $self->check( $value );
991             return [
992             sprintf(
993             '"%s" requires the hash to have at most %d keys',
994             $self,
995             $self->type_parameter,
996             ),
997             sprintf(
998             'The hash has %d keys',
999             scalar( my @tmp = keys $value->%* ),
1000             ),
1001             ];
1002             };
1003              
1004             declare JMinProperties,
1005             name_generator => $name_generator,
1006             constraint_generator => sub {
1007             assert_PositiveOrZeroInt( my $base = shift );
1008             return sub {
1009             my $value = shift;
1010             keys(%$value) >= $base;
1011             };
1012             },
1013             inline_generator => sub {
1014             assert_PositiveOrZeroInt( my $base = $_[0] );
1015             return sub {
1016             my $varname = pop;
1017             "keys(\%{ $varname }) >= $base";
1018             };
1019             },
1020             deep_explanation => sub {
1021             my ( $self, $value, $varname ) = @_;
1022             return if $self->check( $value );
1023             return [
1024             sprintf(
1025             '"%s" requires the hash to have at least %d keys',
1026             $self,
1027             $self->type_parameter,
1028             ),
1029             sprintf(
1030             'The hash has %d keys',
1031             scalar( my @tmp = keys $value->%* ),
1032             ),
1033             ];
1034             };
1035              
1036             declare JRequired,
1037             name_generator => $name_generator,
1038             constraint_generator => sub {
1039             my @keys = @_;
1040             return sub {} if !@keys;
1041             return sub {
1042             my $value = shift;
1043             all { exists $value->{$_} } @keys;
1044             };
1045             },
1046             inline_generator => sub {
1047             my @keys = @_;
1048             return sub {
1049             my $varname = pop;
1050             return '!!1' if !@keys;
1051             sprintf(
1052             'do { my $tmp = %s; %s }',
1053             $varname,
1054             join( q{ and }, map { sprintf 'exists $tmp->{%s}', B::perlstring($_) } @keys ),
1055             );
1056             }
1057             },
1058             deep_explanation => sub {
1059             my ( $self, $value, $varname ) = @_;
1060             return if $self->check( $value );
1061             my @keys = sort( $self->parameters->@* );
1062             if ( @keys == 1 ) {
1063             return [
1064             sprintf(
1065             '"%s" requires the key %s to exist in the hash',
1066             $self,
1067             B::perlstring( $keys[0] ),
1068             ),
1069             sprintf(
1070             'The key %s does not exist in the hash',
1071             B::perlstring( $keys[0] ),
1072             ),
1073             ];
1074             }
1075             my @missing = grep { not exists $value->{$_} } @keys;
1076             return [
1077             sprintf(
1078             '"%s" requires the keys %s to exist in the hash',
1079             $self,
1080             Type::Utils::english_list( map { B::perlstring($_) } @keys ),
1081             ),
1082             ( @missing == 1 )
1083             ? sprintf(
1084             'The key %s does not exist in the hash',
1085             B::perlstring( $missing[0] ),
1086             )
1087             : sprintf(
1088             'The keys %s do not exist in the hash',
1089             Type::Utils::english_list( map { B::perlstring($_) } @missing ),
1090             ),
1091             ];
1092             };
1093              
1094             declare JDependentRequired,
1095             name_generator => $name_generator,
1096             constraint_generator => sub {
1097             my ( $k, @keys ) = @_;
1098             return sub {
1099             my $value = shift;
1100             !exists $value->{$k} or all { exists $value->{$_} } @keys;
1101             };
1102             },
1103             inline_generator => sub {
1104             my ( $k, @keys ) = @_;
1105             return sub {
1106             my $varname = pop;
1107             sprintf(
1108             'do { my $tmp = %s; !exists $tmp->{%s} or ( %s ) }',
1109             $varname,
1110             B::perlstring( $k ),
1111             join( q{ and }, map { sprintf 'exists $tmp->{%s}', B::perlstring($_) } @keys ),
1112             );
1113             }
1114             },
1115             deep_explanation => sub {
1116             my ( $self, $value, $varname ) = @_;
1117             return if $self->check( $value );
1118             my ( $k, @keys ) = $self->parameters->@*;
1119             if ( exists $value->{$k} ) {
1120             if ( @keys == 1 ) {
1121             return [
1122             sprintf(
1123             '"%s" requires the key %s to exist in the hash if key %s exists',
1124             $self,
1125             B::perlstring( $keys[0] ),
1126             B::perlstring( $k ),
1127             ),
1128             sprintf(
1129             'The key %s exists in the hash',
1130             B::perlstring( $k ),
1131             ),
1132             sprintf(
1133             'The key %s does not exist in the hash',
1134             B::perlstring( $keys[0] ),
1135             ),
1136             ];
1137             }
1138             my @missing = grep { not exists $value->{$_} } @keys;
1139             return [
1140             sprintf(
1141             '"%s" requires the keys %s to exist in the hash if key %s exists',
1142             $self,
1143             Type::Utils::english_list( map { B::perlstring($_) } @keys ),
1144             B::perlstring( $k ),
1145             ),
1146             sprintf(
1147             'The key %s exists in the hash',
1148             B::perlstring( $k ),
1149             ),
1150             ( @missing == 1 )
1151             ? sprintf(
1152             'The key %s does not exist in the hash',
1153             B::perlstring( $missing[0] ),
1154             )
1155             : sprintf(
1156             'The keys %s do not exist in the hash',
1157             Type::Utils::english_list( map { B::perlstring($_) } @missing ),
1158             ),
1159             ];
1160             }
1161             else {
1162             return;
1163             }
1164             };
1165              
1166             # TODO: JItems deep_explanation
1167             declare JItems,
1168             name_generator => $name_generator,
1169             constraint_generator => sub {
1170             my $items = $_[0];
1171             my @prefixItems = ( $_[1] or [] )->@*;
1172             my $unevaluatedItems = $_[2];
1173             my $contains = $_[3];
1174             my $minContains = $_[4];
1175             my $maxContains = $_[5];
1176            
1177             $minContains //= 1 if $contains;
1178            
1179             return sub {
1180             my $value = shift;
1181             my $ident = Scalar::Util::refaddr($value);
1182             my $count = 0;
1183             for my $ix ( 0 .. $#$value ) {
1184             my $seen = 0;
1185             if ( $ix < @prefixItems ) {
1186             ++$seen;
1187             my $type = $prefixItems[$ix];
1188             return false if !$type->check( $value->[$ix] );
1189             }
1190             elsif ( $items ) {
1191             ++$seen;
1192             return false if !$items->check( $value->[$ix] );
1193             }
1194            
1195             if ( $contains and $contains->check( $value->[$ix] ) ) {
1196             ++$seen;
1197             ++$count;
1198             }
1199            
1200             if ( $unevaluatedItems and !$seen and !$EVALUATED_ITEMS->{"$ident//$ix"}) {
1201             ++$seen;
1202             return false if !$unevaluatedItems->check( $value->[$ix] );
1203             }
1204             $EVALUATED_ITEMS->{"$ident//$ix"}++ if $seen;
1205             }
1206             return false if defined($minContains) && $count > $minContains;
1207             return false if defined($maxContains) && $count > $maxContains;
1208             return true;
1209             };
1210             },
1211             inline_generator => sub {
1212             my $items = $_[0];
1213             my @prefixItems = ( $_[1] or [] )->@*;
1214             my $unevaluatedItems = $_[2];
1215             my $contains = $_[3];
1216             my $minContains = $_[4];
1217             my $maxContains = $_[5];
1218            
1219             $minContains //= 1 if $contains;
1220            
1221             $_->can_be_inlined || return for grep defined, $items, @prefixItems, $unevaluatedItems, $contains;
1222            
1223             return sub {
1224             my $varname = pop;
1225              
1226             my $i = 0;
1227             my $simpleCheck = join q{ }, map {
1228             my $type = $_;
1229             sprintf( 'elsif ( $ix eq %d ) { ++$seen; ( $bad++, last ) unless %s }', $i++, $type->inline_check('$val') );
1230             } @prefixItems;
1231             $simpleCheck =~ s/\Aels//;
1232            
1233             if ( $simpleCheck and $items ) {
1234             $simpleCheck .= sprintf( ' else { ++$seen; ( $bad++, last ) unless %s }', $items->inline_check('$val') );
1235             }
1236             elsif ( $items ) {
1237             $simpleCheck .= sprintf( '++$seen; ( $bad++, last ) unless %s;', $items->inline_check('$val') );
1238             }
1239            
1240             my $containsCheck = '';
1241             if ( $contains ) {
1242             $containsCheck .= sprintf( '( $seen++, $cnt++ ) if %s;', $contains->inline_check('$val') );
1243             }
1244              
1245             my $unevaluatedItemsCheck = '';
1246             if ( $unevaluatedItems ) {
1247             $unevaluatedItemsCheck = sprintf( 'if ( !$seen and !$%s::EVALUATED_ITEMS->{"$id//$ix"} ) { ++$seen; ( $bad++, last ) unless %s; }', __PACKAGE__, $unevaluatedItems->inline_check('$val') );
1248             }
1249              
1250             my ( $minContainsCheck, $maxContainsCheck ) = ( '', '' );
1251             $minContainsCheck = "\$bad++ if \$cnt < $minContains;" if defined $minContains;
1252             $maxContainsCheck = "\$bad++ if \$cnt > $maxContains;" if defined $maxContains;
1253            
1254             return sprintf(
1255             q{
1256             do {
1257             my $tmp = %s;
1258             my $id = Scalar::Util::refaddr($tmp);
1259             my $bad = 0;
1260             my $cnt = 0;
1261             for my $ix ( 0 .. $#$tmp ) {
1262             my $val = $tmp->[$ix];
1263             my $seen = 0;
1264             %s
1265             %s
1266             $%s::EVALUATED_ITEMS->{"$id//$ix"}++ if $seen;
1267             }
1268             %s
1269             %s
1270             not $bad;
1271             }
1272             },
1273             $varname,
1274             $simpleCheck,
1275             $containsCheck,
1276             __PACKAGE__,
1277             $minContainsCheck,
1278             $maxContainsCheck,
1279             );
1280             };
1281             };
1282              
1283             # TODO: JProperties deep_explanation
1284             declare JProperties,
1285             name_generator => $name_generator,
1286             constraint_generator => sub {
1287             my %properties = ( $_[0] or [] )->@*;
1288             my %patternProperties = ( $_[1] or [] )->@*;
1289             my $additionalProperties = $_[2];
1290             my $unevaluatedProperties = $_[3];
1291            
1292             return sub {
1293             my $value = shift;
1294             my $ident = Scalar::Util::refaddr($value);
1295             for my $key ( sort keys $value->%* ) {
1296             my $seen = 0;
1297             if ( my $type = $properties{$key} ) {
1298             ++$seen;
1299             return false if !$type->check( $value->{$key} );
1300             }
1301             for my $pattern ( sort keys %patternProperties ) {
1302             if ( $key =~ /$pattern/ ) {
1303             ++$seen;
1304             my $type = $patternProperties{$pattern};
1305             return false if !$type->check( $value->{$key} );
1306             }
1307             }
1308             if ( $additionalProperties and !$seen ) {
1309             ++$seen;
1310             return false if !$additionalProperties->check( $value->{$key} );
1311             }
1312             if ( $unevaluatedProperties and !$seen and !$EVALUATED_PROPERTIES->{"$ident//$key"}) {
1313             ++$seen;
1314             return false if !$unevaluatedProperties->check( $value->{$key} );
1315             }
1316             $EVALUATED_PROPERTIES->{"$ident//$key"}++ if $seen;
1317             }
1318             return true;
1319             };
1320             },
1321             inline_generator => sub {
1322             my %properties = ( $_[0] or [] )->@*;
1323             my %patternProperties = ( $_[1] or [] )->@*;
1324             my $additionalProperties = $_[2];
1325             my $unevaluatedProperties = $_[3];
1326            
1327             $_->can_be_inlined || return for grep defined, values(%properties), values(%patternProperties), $additionalProperties, $unevaluatedProperties;
1328              
1329             return sub {
1330             my $varname = pop;
1331            
1332             my $propertiesCheck = join q{ }, map {
1333             my $property = $_;
1334             my $type = $properties{$property};
1335             sprintf( 'elsif ( $key eq %s ) { ++$seen; ( $bad++, last ) unless %s }', B::perlstring($property), $type->inline_check('$val') );
1336             } sort keys %properties;
1337             $propertiesCheck =~ s/\Aels//;
1338            
1339             my $patternPropertiesCheck = join q{ }, map {
1340             my $pattern = $_;
1341             my $type = $patternProperties{$pattern};
1342             sprintf( 'if ( $key =~ %s ) { ++$seen; ( $bad++, last ) unless %s }', serialize_regexp(qr/$pattern/), $type->inline_check('$val') );
1343             } sort keys %patternProperties;
1344            
1345             my $additionalPropertiesCheck = '';
1346             if ( $additionalProperties ) {
1347             my $type = $additionalProperties;
1348             $additionalPropertiesCheck = sprintf( 'if ( ! $seen ) { ++$seen; ( $bad++, last ) unless %s; }', $type->inline_check('$val') );
1349             }
1350            
1351             my $unevaluatedPropertiesCheck = '';
1352             if ( $unevaluatedProperties ) {
1353             my $type = $unevaluatedProperties;
1354             $unevaluatedPropertiesCheck = sprintf( 'if ( !$seen and !$%s::EVALUATED_PROPERTIES->{"$id//$key"} ) { ++$seen; ( $bad++, last ) unless %s; }', __PACKAGE__, $type->inline_check('$val') );
1355             }
1356            
1357             return sprintf(
1358             q{
1359             do {
1360             my $tmp = %s;
1361             my $id = Scalar::Util::refaddr($tmp);
1362             my $bad = 0;
1363             for my $key ( sort keys %%$tmp ) {
1364             my $val = $tmp->{$key};
1365             my $seen = 0;
1366             %s
1367             %s
1368             %s
1369             %s
1370             $%s::EVALUATED_PROPERTIES->{"$id//$key"}++ if $seen;
1371             }
1372             not $bad;
1373             }
1374             },
1375             $varname,
1376             $propertiesCheck,
1377             $patternPropertiesCheck,
1378             $additionalPropertiesCheck,
1379             $unevaluatedPropertiesCheck,
1380             __PACKAGE__,
1381             );
1382             };
1383             };
1384              
1385             declare JPropertyNames,
1386             name_generator => $name_generator,
1387             constraint_generator => sub {
1388             @_ == 1 or die;
1389             my $constraint = shift;
1390             my $list_constraint = ArrayRef[$constraint];
1391             return sub {
1392             my $value = shift;
1393             $list_constraint->check( [ sort keys $value->%* ] );
1394             };
1395             },
1396             inline_generator => sub {
1397             @_ == 1 or die;
1398             my $constraint = shift;
1399             my $list_constraint = ArrayRef[$constraint];
1400             $list_constraint->can_be_inlined || return;
1401             return sub {
1402             my $varname = pop;
1403             sprintf( 'do { my $keys = [ sort keys %%{ %s } ]; %s }', $varname, $list_constraint->inline_check('$keys') );
1404             };
1405             },
1406             deep_explanation => sub {
1407             my ( $self, $value, $varname ) = @_;
1408             return if $self->check( $value );
1409             my $constraint = $self->type_parameter;
1410             my @fails = $constraint->complementary_type->grep( sort keys $value->%* );
1411             return [
1412             sprintf(
1413             '"%s" requires that each key passes "%s"',
1414             $self,
1415             $constraint,
1416             ),
1417             map {
1418             ;
1419             "Key @{[ B::perlstring($_) ]} did not pass type constraint \"$constraint\"",
1420             map( " $_", @{ $constraint->validate_explain( $_ ) || [] } ),
1421             } @fails,
1422             ];
1423             };
1424              
1425             {
1426 2     2   26 use feature qw(multidimensional);
  2         4  
  2         11567  
1427             declare FmtDateTime, as StrMatch[ qr{\A$RE{time}{iso}\z} ];
1428             declare FmtDate, as StrMatch[ qr{\A$RE{time}{tf}{-pat=>'yyyy-mm-dd'}\z/} ];
1429             declare FmtTime, as StrMatch[ qr{\A(?:$RE{time}{tf}{-pat=>'hh:mm:ss'})|(?:$RE{time}{tf}{-pat=>'hh:mm'})\z} ];
1430             declare FmtDuration, as Str;
1431             declare FmtEmail, as StrMatch[ qr{\A$RE{Email}{Address}\z} ];
1432             declare FmtIdnEmail, as Str;
1433             declare FmtHostname, as StrMatch[ qr{\A$RE{net}{domain}\z} ];
1434             declare FmtIdnHostname, as Str;
1435             declare FmtIpv4, as StrMatch[ qr{\A$RE{net}{IPv4}{strict}\z} ];
1436             declare FmtIpv6, as StrMatch[ qr{\A$RE{net}{IPv6}\z} ];
1437             declare FmtUri, as StrMatch[ qr{\A$RE{URI}\z} ];
1438             declare FmtUriReference, as Str;
1439             declare FmtIri, as Str;
1440             declare FmtIriReference, as Str;
1441             declare FmtUuid, as Str;
1442             declare FmtUriTemplate, as Str;
1443             declare FmtJsonPointer, as Str;
1444             declare FmtRelativeJsonPointer, as Str;
1445             declare FmtRegex, as Str;
1446             }
1447              
1448             signature_for schema_to_type => (
1449             method => false,
1450             pos => [
1451             JObject|JBoolean,
1452             HashRef, { default => {} },
1453             Str, { default => '#' },
1454             ],
1455             returns => TypeTiny,
1456             );
1457              
1458             sub schema_to_type ( $schema, $defs, $path ) {
1459            
1460             return Any if is_JTrue $schema;
1461             return ~Any if is_JFalse $schema;
1462            
1463             for my $xxx ( qw/ $defs definitions / ) {
1464             if ( is_HashRef $schema->{$xxx} ) {
1465             for my $k ( sort keys $schema->{$xxx}->%* ) {
1466             my $newpath = "$path/$xxx/" . jpointer_escape($k);
1467             my $type = schema_to_type( $schema->{$xxx}{$k}, $defs, $newpath );
1468             $defs->{$newpath} = $type;
1469             }
1470             }
1471             elsif ( exists $schema->{$xxx} ) {
1472             _croak "Invalid '%s' at %s: %s", $xxx, $path, $schema->{$xxx};
1473             }
1474             }
1475              
1476             if ( is_Str $schema->{'$ref'} ) {
1477             return JSRef[ $schema->{'$ref'}, $defs ];
1478             }
1479             elsif ( exists $schema->{'$ref'} ) {
1480             _croak "Invalid '\$ref' at %s: %s", $path, $schema->{'$ref'};
1481             }
1482              
1483             my ( @tc, $need_to_scope );
1484             _schema_to_type_basics( \@tc, $schema, $defs, $path ) and ++$need_to_scope;
1485             _schema_to_type_nested( \@tc, $schema, $defs, $path ) and ++$need_to_scope;
1486             _schema_to_type_number( \@tc, $schema, $defs, $path ) and ++$need_to_scope;
1487             _schema_to_type_string( \@tc, $schema, $defs, $path ) and ++$need_to_scope;
1488             _schema_to_type_arrays( \@tc, $schema, $defs, $path ) and ++$need_to_scope;
1489             _schema_to_type_object( \@tc, $schema, $defs, $path ) and ++$need_to_scope;
1490            
1491             _smiple( \@tc );
1492            
1493             my $intersection =
1494             ( @tc == 0 ) ? Any :
1495             ( @tc == 1 ) ? $tc[0] :
1496             do {
1497             require Type::Tiny::Intersection;
1498             Type::Tiny::Intersection->new( type_constraints => \@tc );
1499             };
1500             my $scoped = $need_to_scope ? JSScope[$intersection] : $intersection;
1501            
1502             $defs->{$path} = $scoped;
1503             Scalar::Util::weaken( $defs->{$path} ) unless $path eq '#';
1504             $defs->{'#' . jpointer_escape($schema->{'$anchor'})} = $scoped if is_Str $schema->{'$anchor'};
1505             $defs->{$schema->{'$id'}} = $scoped if is_Str $schema->{'$id'};
1506              
1507             return $scoped;
1508             }
1509              
1510             our $OPTIMIZE = true;
1511              
1512 465     465   763 sub _smiple ( $orig ) {
  465         812  
  465         756  
1513 465 50       1306 return unless $OPTIMIZE;
1514 465         1270 my @tc = $orig->@*;
1515 465         827 my @new;
1516 465         1240 while ( @tc ) {
1517 468         1149 my $got = shift @tc;
1518 468 100 100     2003 if ( @tc and eval { $got->parent->strictly_equals(JIf) } ) {
  64         305  
1519 26         1819 my $next = $tc[0];
1520 26 100 100     64 if ( eval { $next->parent->strictly_equals(JIf) } and $next->type_parameter->strictly_equals( $got->type_parameter ) ) {
  26         95  
1521 23         2490 $next = shift @tc;
1522             my @then =
1523 56 100       10398 map { $_->equals(Any) ? () : $_ }
1524 46 50       1662 map { !$_ ? () : eval { $_->parent->strictly_equals(JThen) } ? $_->parameters->@* : $_ }
  46 50       308  
1525 23         69 map { $_->parameters->[1] }
  46         214  
1526             $got, $next;
1527             my @else =
1528 0 0       0 map { $_->equals(Any) ? () : $_ }
1529 46 0       207 map { !$_ ? () : eval { $_->parent->strictly_equals(JElse) } ? $_->parameters->@* : $_ }
  0 50       0  
1530 23         3500 map { $_->parameters->[2] }
  46         243  
1531             $got, $next;
1532 23 50       86 push @new, JIf[ $got->type_parameter, @then ? JThen[@then] : Any, @else ? JElse[@else] : () ];
    50          
1533 23         23898 next;
1534             }
1535             }
1536 445         4044 push @new, $got;
1537             }
1538            
1539 465         1728 $orig->@* = @new;
1540             }
1541              
1542 421     421   849 sub _schema_to_type_basics ( $tc, $schema, $defs, $path ) {
  421         718  
  421         786  
  421         748  
  421         835  
  421         696  
1543 421         2582 my $T = JSPrimativeName | Enum['integer'];
1544 421 100       274049 if ( $T->check( $schema->{type} ) ) {
    100          
    50          
1545 114         1754 push $tc->@*, to_JSPrimativeType $schema->{type};
1546             }
1547             elsif ( ArrayRef->of( $T )->check( $schema->{type} ) ) {
1548 5 100       3935 if ( $schema->{type}->@* == 1 ) {
1549 1         6 push $tc->@*, to_JSPrimativeType $schema->{type}[0];
1550             }
1551             else {
1552 4         38 require Type::Tiny::Union;
1553             push $tc->@*, Type::Tiny::Union->new(
1554 4         28 type_constraints => [ JSPrimativeType->map( $schema->{type}->@* ) ],
1555             );
1556             }
1557             }
1558             elsif ( exists $schema->{type} ) {
1559 0         0 _croak "Invalid 'type' at %s: %s", $path, $schema->{type};
1560             }
1561            
1562 421 100       247797 if ( is_ArrayRef $schema->{enum} ) {
    50          
1563 17         165 push $tc->@*, JEnum->of( $schema->{enum}->@* );
1564             }
1565             elsif ( exists $schema->{enum} ) {
1566 0         0 _croak "Invalid 'enum' at %s: %s", $path, $schema->{enum};
1567             }
1568            
1569 421 100       13829 if ( exists $schema->{const} ) {
1570 24         257 push $tc->@*, JConst->of( $schema->{const} );
1571             }
1572            
1573 421         16406 return false;
1574             }
1575              
1576 421     421   889 sub _schema_to_type_nested ( $tc, $schema, $defs, $path ) {
  421         745  
  421         742  
  421         744  
  421         781  
  421         669  
1577 421         886 my $need_to_scope = false;
1578            
1579 421         2092 my %basic = (
1580             allOf => JAllOf,
1581             anyOf => JAnyOf,
1582             oneOf => JOneOf,
1583             );
1584            
1585 421         18452 for my $k ( sort keys %basic ) {
1586 1263 100       41636 if ( is_ArrayRef $schema->{$k} ) {
    50          
1587 46         132 my $i = 0;
1588 46         240 my @nested = map { schema_to_type($_, ($defs), "$path/$k/@{[ $i++ ]}" ) } $schema->{$k}->@*;
  78         2154  
  78         656  
1589 46         6003 push $tc->@*, $basic{$k}->of( @nested );
1590             }
1591             elsif ( exists $schema->{$k} ) {
1592 0         0 _croak "Invalid '%s' at %s: %s", $k, $path, $schema->{$k};
1593             }
1594             }
1595            
1596 421 100 100     17365 if ( is_JObject $schema->{not} or is_JBoolean $schema->{not} ) {
    50          
1597 9         85 my $nested = schema_to_type $schema->{not}, ($defs), "$path/not";
1598 9         305 push $tc->@*, JNot[ $nested ];
1599             }
1600             elsif ( exists $schema->{not} ) {
1601 0         0 _croak "Invalid 'not' at %s: %s", $path, $schema->{not};
1602             }
1603            
1604 421 100 100     16890 if ( is_JObject $schema->{if} or is_JBoolean $schema->{if} ) {
    50          
1605 9         88 my $if = schema_to_type $schema->{if}, ($defs), "$path/if";
1606 9 100       599 my $then = exists $schema->{then} ? ( schema_to_type $schema->{then}, ($defs), "$path/then" ) : Any;
1607 9 100       146 my $else = exists $schema->{else} ? ( schema_to_type $schema->{else}, ($defs), "$path/else" ) : Any;
1608            
1609 9         173 push $tc->@*, JIf[ $if, JThen[$then], JElse[$else] ];
1610             }
1611             elsif ( exists $schema->{if} ) {
1612 0         0 _croak "Invalid 'if' at %s: %s", $path, $schema->{if};
1613             }
1614            
1615             # Also support older(?) 'dependencies'.
1616 421         13174 for my $xxx ( qw/ dependentSchemas dependencies / ) {
1617 842 100       3974 if ( is_JObject $schema->{$xxx} ) {
    50          
1618 7         67 my %ds = $schema->{$xxx}->%*;
1619 7         25 my @tc2;
1620            
1621 7         31 for my $k ( sort keys %ds ) {
1622 11 100       1451 next if is_ArrayRef $ds{$k};
1623 6         46 my $nested = schema_to_type $ds{$k}, ($defs), "$path/$xxx/$k";
1624 6         444 push @tc2, JDependentSchema[ $k, JThen[$nested] ];
1625             }
1626            
1627 7 100       3461 if ( @tc2 ) {
1628 4         27 my @primatives = JSPrimativeType->grep( $tc->@* );
1629            
1630 4 50 33     372 if ( @primatives == 1 and $primatives[0] == JObject ) {
1631 0         0 push $tc->@*, @tc2;
1632             }
1633             else {
1634 4         27 push $tc->@*, JIf[ JObject, JThen[@tc2] ];
1635             }
1636             }
1637             }
1638             elsif ( exists $schema->{$xxx} ) {
1639 0         0 _croak "Invalid '%s' at %s: %s", $xxx, $path, $schema->{$xxx};
1640             }
1641             }
1642            
1643             {
1644 421         865 my %H;
1645 421 50       2163 if ( is_ArrayRef $schema->{prefixItems} ) {
    50          
1646 0         0 my $i = 0;
1647 0         0 $H{prefixItems} = [ map { schema_to_type( $_, ($defs), "$path/prefixItems/@{[ $i++ ]}") } $schema->{prefixItems}->@* ];
  0         0  
  0         0  
1648             }
1649             elsif ( exists $schema->{prefixItems} ) {
1650 0         0 _croak "Invalid 'prefixItems' at %s: %s", $path, $schema->{prefixItems};
1651             }
1652            
1653 421 100 100     4072 if ( is_ArrayRef $schema->{items} ) {
    100          
    50          
1654 17   50     94 $H{prefixItems} ||= [];
1655 17         33 my $i = @{ $H{prefixItems} };
  17         40  
1656 17         79 push $H{prefixItems}->@*, map { schema_to_type( $_, ($defs), "$path/items/@{[ $i++ ]}") } $schema->{items}->@*;
  30         5641  
  30         198  
1657             }
1658             elsif ( is_JObject $schema->{items} or is_JBoolean $schema->{items} ) {
1659 11         99 $H{items} = schema_to_type( $schema->{items}, ($defs), "$path/items");
1660             }
1661             elsif ( exists $schema->{items} ) {
1662 0         0 _croak "Invalid 'items' at %s: %s", $path, $schema->{items};
1663             }
1664              
1665 421 100 100     11113 if ( is_JFalse $schema->{additionalItems} and not exists $schema->{items} ) {
    100 100        
    50          
1666 1         20 $H{items} = Any;
1667             }
1668             elsif ( is_JObject $schema->{additionalItems} or is_JBoolean $schema->{additionalItems} ) {
1669 12 100       293 if ( $H{items} ) {
1670 2         11 _carp "Conflicting 'items' and 'additionalItems' at %s", $path;
1671             }
1672             else {
1673 10         49 $H{items} = schema_to_type( $schema->{additionalItems}, ($defs), "$path/additionalItems");
1674             }
1675             }
1676             elsif ( exists $schema->{additionalItems} ) {
1677 0         0 _croak "Invalid 'additionalItems' at %s: %s", $path, $schema->{additionalItems};
1678             }
1679            
1680 421 100 100     15626 if ( is_JObject $schema->{contains} or is_JBoolean $schema->{contains} ) {
    50          
1681 7         69 $H{contains} = schema_to_type( $schema->{contains}, ($defs), "$path/contains");
1682            
1683 7 50       362 if ( is_PositiveOrZeroInt $schema->{maxContains} ) {
    50          
1684 0         0 $H{maxContains} = $schema->{maxContains};
1685             }
1686             elsif ( exists $schema->{maxContains} ) {
1687 0         0 _croak "Invalid 'maxContains' at %s: %s", $path, $schema->{maxContains};
1688             }
1689            
1690 7 50       56 if ( is_PositiveOrZeroInt $schema->{minContains} ) {
    50          
1691 0         0 $H{minContains} = $schema->{minContains};
1692             }
1693             elsif ( exists $schema->{minContains} ) {
1694 0         0 _croak "Invalid 'minContains' at %s: %s", $path, $schema->{minContains};
1695             }
1696             }
1697             elsif ( exists $schema->{contains} ) {
1698 0         0 _croak "Invalid 'contains' at %s: %s", $path, $schema->{contains};
1699             }
1700              
1701 421 50 33     8203 if ( is_JObject $schema->{unevaluatedItems} or is_JBoolean $schema->{unevaluatedItems} ) {
    50          
1702 0         0 $H{unevaluatedItems} = schema_to_type( $schema->{unevaluatedItems}, ($defs), "$path/unevaluatedItems" );
1703             }
1704             elsif ( exists $schema->{unevaluatedItems} ) {
1705 0         0 _croak "Invalid 'contains' at %s: %s", $path, $schema->{unevaluatedItems};
1706             }
1707            
1708 421 100       7401 if ( keys %H ) {
1709             my @args = (
1710             $H{items} || undef,
1711             $H{prefixItems} || undef,
1712             $H{unevaluatedItems} || undef,
1713             $H{contains} || undef,
1714             $H{minContains} || undef,
1715             $H{maxContains} || undef,
1716 36   100     323 );
      100        
      50        
      100        
      50        
      50        
1717 36         550 while ( not defined $args[-1] ) {
1718 142         256 pop @args;
1719             }
1720 36         272 my $tc2 = JItems[@args];
1721 36         30977 my @primatives = JSPrimativeType->grep( $tc->@* );
1722 36 100 66     3880 if ( @primatives == 1 and $primatives[0] == JArray ) {
1723 6         621 push $tc->@*, $tc2;
1724             }
1725             else {
1726 30         194 push $tc->@*, JIf[ JArray, JThen[$tc2] ];
1727             }
1728             }
1729             }
1730            
1731             {
1732 421         4921 my %H;
  421         23339  
  421         847  
1733 421         1064 for my $k ( qw/ properties patternProperties / ) {
1734 842 100       10962 if ( is_JObject $schema->{$k} ) {
    50          
1735 53         338 for my $k2 ( sort keys $schema->{$k}->%* ) {
1736 75   100     2335 push @{ $H{$k} ||= [] }, $k2 => schema_to_type( $schema->{$k}{$k2}, ($defs), "$path/$k/$k2");
  75         992  
1737             }
1738             }
1739             elsif ( exists $schema->{$k} ) {
1740 0         0 _croak "Invalid '%s' at %s: %s", $k, $path, $schema->{$k};
1741             }
1742             }
1743            
1744 421 100 100     3067 if ( is_JObject $schema->{additionalProperties} or is_JBoolean $schema->{additionalProperties} ) {
    50          
1745 9         86 $H{additionalProperties} = schema_to_type( $schema->{additionalProperties}, ($defs), "$path/additionalProperties");
1746             }
1747             elsif ( exists $schema->{additionalProperties} ) {
1748 0         0 _croak "Invalid 'additionalProperties' at %s: %s", $path, $schema->{additionalProperties};
1749             }
1750              
1751 421 50 33     9836 if ( is_JObject $schema->{unevaluatedProperties} or is_JBoolean $schema->{unevaluatedProperties} ) {
    50          
1752 0         0 $H{unevaluatedProperties} = schema_to_type( $schema->{unevaluatedProperties}, ($defs), "$path/unevaluatedProperties");
1753 0         0 $need_to_scope = true;
1754             }
1755             elsif ( exists $schema->{unevaluatedProperties} ) {
1756 0         0 _croak "Invalid 'unevaluatedProperties' at %s: %s", $path, $schema->{unevaluatedProperties};
1757             }
1758              
1759 421 100       6667 if ( keys %H ) {
1760             my @args = (
1761             $H{properties} || undef,
1762             $H{patternProperties} || undef,
1763             $H{additionalProperties} || undef,
1764             $H{unevaluatedProperties} || undef,
1765 54   100     934 );
      100        
      100        
      50        
1766 54         316 while ( not defined $args[-1] ) {
1767 139         361 pop @args;
1768             }
1769 54         430 my $tc2 = JProperties[@args];
1770 54         56164 my @primatives = JSPrimativeType->grep( $tc->@* );
1771 54 100 66     5358 if ( @primatives == 1 and $primatives[0] == JObject ) {
1772 3         357 push $tc->@*, $tc2;
1773             }
1774             else {
1775 51         338 push $tc->@*, JIf[ JObject, JThen[$tc2] ];
1776             }
1777             }
1778             }
1779              
1780 421 100 100     46466 if ( is_JObject $schema->{propertyNames} or is_JBoolean $schema->{propertyNames} ) {
    50          
1781 6         46 my $tc2 = schema_to_type( $schema->{propertyNames}, ($defs), "$path/propertyNames");
1782 6         148 my @primatives = JSPrimativeType->grep( $tc->@* );
1783 6 50 33     417 if ( @primatives == 1 and $primatives[0] == JObject ) {
1784 0         0 push $tc->@*, JPropertyNames[$tc2];
1785             }
1786             else {
1787 6         33 push $tc->@*, JIf[ JObject, JThen[JPropertyNames[$tc2]] ];
1788             }
1789             }
1790             elsif ( exists $schema->{propertyNames} ) {
1791 0         0 _croak "Invalid 'propertyNames' at %s: %s", $path, $schema->{propertyNames};
1792             }
1793              
1794 421         11808 return $need_to_scope;
1795             }
1796              
1797 421     421   702 sub _schema_to_type_number ( $tc, $schema, $defs, $path ) {
  421         933  
  421         958  
  421         756  
  421         920  
  421         843  
1798 421         801 my @tc2;
1799            
1800 421 100       3076 if ( is_PositiveNum $schema->{multipleOf} ) {
    50          
1801 12         184 push @tc2, JMultipleOf->of( $schema->{multipleOf} );
1802             }
1803             elsif ( exists $schema->{multipleOf} ) {
1804 0         0 _croak "Invalid 'multipleOf' at %s: %s", $path, $schema->{multipleOf};
1805             }
1806              
1807 421         11260 my %basic = (
1808             maximum => JMaximum,
1809             exclusiveMaximum => JExclusiveMaximum,
1810             minimum => JMinimum,
1811             exclusiveMinimum => JExclusiveMinimum,
1812             );
1813            
1814 421         23485 for my $k ( sort keys %basic ) {
1815 1684 100       17813 if ( is_Num $schema->{$k} ) {
    50          
1816 20         281 push @tc2, $basic{$k}->of( $schema->{$k} );
1817             }
1818             elsif ( exists $schema->{$k} ) {
1819 0         0 _croak "Invalid '%s' at %s: %s", $k, $path, $schema->{$k};
1820             }
1821             }
1822            
1823 421 100       8414 if ( @tc2 ) {
1824 32         213 my @primatives = JSPrimativeType->grep( $tc->@* );
1825            
1826 32 100 66     3085 if ( @primatives == 1 and $primatives[0] == JNumber ) {
1827 1         80 push $tc->@*, @tc2;
1828             }
1829             else {
1830 31         196 push $tc->@*, JIf[ JNumber, JThen[@tc2] ];
1831             }
1832             }
1833            
1834 421         26044 return false;
1835             }
1836              
1837 421     421   770 sub _schema_to_type_string ( $tc, $schema, $defs, $path ) {
  421         792  
  421         676  
  421         826  
  421         746  
  421         738  
1838 421         670 my @tc2;
1839            
1840 421 100       3560 if ( is_PositiveOrZeroInt $schema->{maxLength} ) {
    50          
1841 6         44 push @tc2, JMaxLength->of( $schema->{maxLength} );
1842             }
1843             elsif ( exists $schema->{maxLength} ) {
1844 0         0 _croak "Invalid 'maxLength' at %s: %s", $path, $schema->{maxLength};
1845             }
1846              
1847 421 100       5080 if ( is_PositiveOrZeroInt $schema->{minLength} ) {
    50          
1848 5         41 push @tc2, JMinLength->of( $schema->{minLength} );
1849             }
1850             elsif ( exists $schema->{minLength} ) {
1851 0         0 _croak "Invalid 'minLength' at %s: %s", $path, $schema->{minLength};
1852             }
1853              
1854 421 50       4938 if ( is_RegexpRef $schema->{pattern} ) {
    100          
    50          
1855 0         0 push @tc2, JPattern->of( $schema->{pattern} );
1856             }
1857             elsif ( is_Str $schema->{pattern} ) {
1858 3         32 my $pattern = $schema->{pattern};
1859 3         20 push @tc2, JPattern->of( qr/$pattern/ );
1860             }
1861             elsif ( exists $schema->{pattern} ) {
1862 0         0 _croak "Invalid 'pattern' at %s: %s", $path, $schema->{pattern};
1863             }
1864            
1865 421         7049 state $formats = {
1866             'date-time' => FmtDateTime,
1867             'date' => FmtDate,
1868             'time' => FmtTime,
1869             'duration' => FmtDuration,
1870             'email' => FmtEmail,
1871             'idn-email' => FmtIdnEmail,
1872             'hostname' => FmtHostname,
1873             'idn-hostname' => FmtIdnHostname,
1874             'ipv4' => FmtIpv4,
1875             'ipv6' => FmtIpv6,
1876             'uri' => FmtUri,
1877             'uri-reference' => FmtUriReference,
1878             'iri' => FmtIri,
1879             'iri-reference' => FmtIriReference,
1880             'uuid' => FmtUuid,
1881             'uri-template' => FmtUriTemplate,
1882             'json-pointer' => FmtJsonPointer,
1883             'relative-json-pointer' => FmtRelativeJsonPointer,
1884             'regex' => FmtRegex,
1885             };
1886            
1887 421 100 66     2232 if ( is_Str $schema->{format} and exists $formats->{$schema->{format}} ) {
1888 17         84 push @tc2, $formats->{$schema->{format}};
1889             }
1890              
1891 421 100       1632 if ( @tc2 ) {
1892 31         207 my @primatives = JSPrimativeType->grep( $tc->@* );
1893            
1894 31 100 66     3003 if ( @primatives == 1 and $primatives[0] == JString ) {
1895 1         105 push $tc->@*, @tc2;
1896             }
1897             else {
1898 30         189 push $tc->@*, JIf[ JString, JThen[@tc2] ];
1899             }
1900             }
1901            
1902 421         31691 return false;
1903             }
1904              
1905 421     421   788 sub _schema_to_type_arrays ( $tc, $schema, $defs, $path ) {
  421         665  
  421         4780  
  421         722  
  421         722  
  421         616  
1906 421         958 my @tc2;
1907            
1908 421 100       2537 if ( is_PositiveOrZeroInt $schema->{maxItems} ) {
    50          
1909 3         24 push @tc2, JMaxItems->of( $schema->{maxItems} );
1910             }
1911             elsif ( exists $schema->{maxItems} ) {
1912 0         0 _croak "Invalid 'maxItems' at %s: %s", $path, $schema->{maxItems};
1913             }
1914            
1915 421 100       3899 if ( is_PositiveOrZeroInt $schema->{minItems} ) {
    50          
1916 3         22 push @tc2, JMinItems->of( $schema->{minItems} );
1917             }
1918             elsif ( exists $schema->{minItems} ) {
1919 0         0 _croak "Invalid 'minItems at %s: %s", $path, $schema->{minItems};
1920             }
1921              
1922 421 100       3928 if ( is_JBoolean $schema->{uniqueItems} ) {
    50          
1923 6 100       76 push @tc2, JUniqueItems if is_JTrue $schema->{uniqueItems};
1924             }
1925             elsif ( exists $schema->{uniqueItems} ) {
1926 0         0 _croak "Invalid 'uniqueItems' at %s: %s", $path, $schema->{uniqueItems};
1927             }
1928            
1929 421 100       6824 if ( @tc2 ) {
1930 9         53 my @primatives = JSPrimativeType->grep( $tc->@* );
1931            
1932 9 100 66     954 if ( @primatives == 1 and $primatives[0] == JArray ) {
1933 1         108 push $tc->@*, @tc2;
1934             }
1935             else {
1936 8         56 push $tc->@*, JIf[ JArray, JThen[@tc2] ];
1937             }
1938             }
1939            
1940 421         5224 return false;
1941             }
1942              
1943 421     421   1317 sub _schema_to_type_object ( $tc, $schema, $defs, $path ) {
  421         705  
  421         855  
  421         708  
  421         717  
  421         642  
1944 421         770 my @tc2;
1945            
1946 421 100       2289 if ( is_PositiveOrZeroInt $schema->{maxProperties} ) {
    50          
1947 3         22 push @tc2, JMaxProperties->of( $schema->{maxProperties} );
1948             }
1949             elsif ( exists $schema->{maxProperties} ) {
1950 0         0 _croak "Invalid 'maxProperties' at %s: %s", $path, $schema->{maxProperties};
1951             }
1952              
1953 421 100       3816 if ( is_PositiveOrZeroInt $schema->{minProperties} ) {
    50          
1954 3         24 push @tc2, JMinProperties->of( $schema->{minProperties} );
1955             }
1956             elsif ( exists $schema->{minProperties} ) {
1957 0         0 _croak "Invalid 'minProperties' at %s: %s", $path, $schema->{minProperties};
1958             }
1959              
1960 421 100       4434 if ( is_Strings $schema->{required} ) {
    50          
1961 20         171 push @tc2, JRequired->of( $schema->{required}->@* );
1962             }
1963             elsif ( exists $schema->{required} ) {
1964 0         0 _croak "Invalid 'required' at %s: %s", $path, $schema->{required};
1965             }
1966              
1967             # Also support older(?) 'dependencies'.
1968 421         9198 for my $xxx ( qw/ dependentRequired dependencies / ) {
1969 842 100       4444 if ( is_HashRef $schema->{$xxx} ) {
    50          
1970 7         38 my %dr = $schema->{$xxx}->%*;
1971 7         30 for my $k ( sort keys %dr ) {
1972 11 100       1281 if ( is_Strings $dr{$k} ) {
1973 5         36 my @r = assert_Strings( $dr{$k} )->@*;
1974 5 100       88 push @tc2, JDependentRequired->of( $k, @r ) if @r;
1975             }
1976             }
1977             }
1978             elsif ( exists $schema->{$xxx} ) {
1979 0         0 _croak "Invalid '%s' at %s: %s", $xxx, $path, $schema->{$xxx};
1980             }
1981             }
1982              
1983 421 100       2550 if ( @tc2 ) {
1984 29         179 my @primatives = JSPrimativeType->grep( $tc->@* );
1985            
1986 29 100 66     6695 if ( @primatives == 1 and $primatives[0] == JObject ) {
1987 2         168 push $tc->@*, @tc2;
1988             }
1989             else {
1990 27         181 push $tc->@*, JIf[ JObject, JThen[@tc2] ];
1991             }
1992             }
1993            
1994 421         19710 return false;
1995             }
1996              
1997             1;
1998              
1999             __END__