File Coverage

blib/lib/JSON/Schema/Draft201909/Document.pm
Criterion Covered Total %
statement 64 65 98.4
branch 6 6 100.0
condition 6 6 100.0
subroutine 19 20 95.0
pod 2 4 50.0
total 97 101 96.0


line stmt bran cond sub pod time code
1 20     20   165 use strict;
  20         51  
  20         715  
2 20     20   112 use warnings;
  20         49  
  20         1103  
3             package JSON::Schema::Draft201909::Document;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: One JSON Schema document
6              
7             our $VERSION = '0.028';
8              
9 20     20   453 use 5.016;
  20         76  
10 20     20   131 no if "$]" >= 5.031009, feature => 'indirect';
  20         46  
  20         229  
11 20     20   1222 no if "$]" >= 5.033001, feature => 'multidimensional';
  20         64  
  20         122  
12 20     20   988 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  20         51  
  20         111  
13 20     20   888 use strictures 2;
  20         192  
  20         865  
14 20     20   4454 use Mojo::URL;
  20         46  
  20         284  
15 20     20   859 use Carp 'croak';
  20         72  
  20         1478  
16 20     20   218 use List::Util 1.29 'pairs';
  20         564  
  20         1435  
17 20     20   148 use Safe::Isa;
  20         67  
  20         3000  
18 20     20   158 use Moo;
  20         45  
  20         189  
19 20     20   9184 use MooX::TypeTiny;
  20         56  
  20         162  
20 20     20   17038 use MooX::HandlesVia;
  20         57  
  20         162  
21 20     20   2399 use Types::Standard qw(InstanceOf HashRef Str Dict ArrayRef);
  20         53  
  20         198  
22 20     20   25009 use namespace::clean;
  20         52  
  20         188  
23              
24             extends 'Mojo::JSON::Pointer', 'Moo::Object';
25              
26             has schema => (
27             is => 'ro',
28             required => 1,
29             );
30              
31             has canonical_uri => (
32             is => 'rwp',
33             isa => InstanceOf['Mojo::URL'], # always fragmentless
34             lazy => 1,
35             default => sub { Mojo::URL->new },
36             coerce => sub { $_[0]->$_isa('Mojo::URL') ? $_[0] : Mojo::URL->new($_[0]) },
37             clearer => '_clear_canonical_uri',
38             );
39              
40             has resource_index => (
41             is => 'bare',
42             isa => HashRef[Dict[
43             canonical_uri => InstanceOf['Mojo::URL'],
44             path => Str, # always a json pointer, relative to the document root
45             ]],
46             handles_via => 'Hash',
47             handles => {
48             resource_index => 'elements',
49             resource_pairs => 'kv',
50             _add_resources => 'set',
51             _get_resource => 'get',
52             _remove_resource => 'delete',
53             _canonical_resources => 'values',
54             },
55             init_arg => undef,
56             lazy => 1,
57             default => sub { {} },
58             );
59              
60             has canonical_uri_index => (
61             is => 'bare',
62             isa => HashRef[InstanceOf['Mojo::URL']],
63             handles_via => 'Hash',
64             handles => {
65             path_to_canonical_uri => 'get',
66             _add_canonical_uri => 'set',
67             },
68             init_arg => undef,
69             lazy => 1,
70             default => sub { {} },
71             );
72              
73             # for internal use only
74             has _serialized_schema => (
75             is => 'rw',
76             isa => Str,
77             init_arg => undef,
78             );
79              
80             has errors => (
81             is => 'bare',
82             handles_via => 'Array',
83             handles => {
84             errors => 'elements',
85             has_errors => 'count',
86             },
87             writer => '_set_errors',
88             isa => ArrayRef[InstanceOf['JSON::Schema::Draft201909::Error']],
89             lazy => 1,
90             default => sub { [] },
91             );
92              
93             has evaluation_configs => (
94             is => 'rwp',
95             isa => HashRef,
96             default => sub { {} },
97             );
98              
99             around _add_resources => sub {
100             my $orig = shift;
101             my $self = shift;
102             foreach my $pair (pairs @_) {
103             my ($key, $value) = @$pair;
104             if (my $existing = $self->_get_resource($key)) {
105             croak 'uri "'.$key.'" conflicts with an existing schema resource'
106             if $existing->{path} ne $value->{path}
107             or $existing->{canonical_uri} ne $value->{canonical_uri};
108             }
109              
110             # this will never happen, if we parsed $id correctly
111             croak sprintf('a resource canonical uri cannot contain a plain-name fragment (%s)', $value->{canonical_uri})
112             if ($value->{canonical_uri}->fragment // '') =~ m{^[^/]};
113              
114             $self->$orig($key, $value);
115             $self->_add_canonical_uri($value->{path}, $value->{canonical_uri});
116             }
117             };
118              
119             # shims for Mojo::JSON::Pointer
120 1131     1131 1 14907 sub data { goto \&schema }
121 4737     4737 0 65073 sub FOREIGNBUILDARGS { () }
122              
123             # for JSON serializers
124 0     0 1 0 sub TO_JSON { goto \&schema }
125              
126             around BUILDARGS => sub {
127             my ($orig, $class, @args) = @_;
128              
129             my $args = $class->$orig(@args);
130              
131             # evaluator is only needed for traversal in BUILD; a different evaluator may be used for
132             # the actual evaluation.
133             croak '_evaluator is not a JSON::Schema::Draft201909'
134             if exists $args->{_evaluator} and not $args->{_evaluator}->$_isa('JSON::Schema::Draft201909');
135              
136             $args->{_evaluator} //= JSON::Schema::Draft201909->new;
137             return $args;
138             };
139              
140             sub BUILD {
141 4737     4737 0 55951 my ($self, $args) = @_;
142              
143 4737 100       79240 croak 'canonical_uri cannot contain a fragment' if defined $self->canonical_uri->fragment;
144              
145 4736         122021 my $original_uri = $self->canonical_uri->clone;
146 4736         200655 my $state = $args->{_evaluator}->traverse($self->schema,
147             { initial_schema_uri => $self->canonical_uri->clone });
148              
149             # if the schema identified a canonical uri for itself, it overrides the initial value
150 4736         140840 $self->_set_canonical_uri($state->{initial_schema_uri});
151              
152 4736 100       208916 if (@{$state->{errors}}) {
  4736         15178  
153 55         1122 $self->_set_errors($state->{errors});
154 55         1351 return;
155             }
156              
157             # make sure the root schema is always indexed against *something*.
158 4681 100 100     20100 $self->_add_resources($original_uri => { path => '', canonical_uri => $self->canonical_uri })
      100        
159             if (not "$original_uri" and $original_uri eq $self->canonical_uri)
160             or "$original_uri";
161              
162 4681         586198 $self->_add_resources(@{$state->{identifiers}});
  4681         103855  
163              
164             # overlay the resulting configs with those that were provided by the caller
165 4679         37466 $self->_set_evaluation_configs(+{ %{$state->{configs}}, %{$self->evaluation_configs} });
  4679         13974  
  4679         90991  
166             }
167              
168             1;
169              
170             __END__
171              
172             =pod
173              
174             =encoding UTF-8
175              
176             =for stopwords subschema
177              
178             =head1 NAME
179              
180             JSON::Schema::Draft201909::Document - One JSON Schema document
181              
182             =head1 VERSION
183              
184             version 0.028
185              
186             =head1 SYNOPSIS
187              
188             use JSON::Schema::Draft201909::Document;
189              
190             my $document = JSON::Schema::Draft201909::Document->new(
191             canonical_uri => 'https://example.com/v1/schema',
192             schema => $schema,
193             );
194             my $foo_definition = $document->get('/$defs/foo');
195             my %resource_index = $document->resource_index;
196              
197             =head1 DESCRIPTION
198              
199             This class represents one JSON Schema document, to be used by L<JSON::Schema::Draft201909>.
200              
201             =head1 ATTRIBUTES
202              
203             =head2 schema
204              
205             The actual raw data representing the schema.
206              
207             =head2 canonical_uri
208              
209             When passed in during construction, this represents the initial URI by which the document should
210             be known. It is overwritten with the root schema's C<$id> property when one exists, and as such
211             can be considered the canonical URI for the document as a whole.
212              
213             =head2 resource_index
214              
215             An index of URIs to subschemas (json path to reach the location, and the canonical URI of that
216             location) for all identifiable subschemas found in the document. An entry for URI C<''> is added
217             only when no other suitable identifier can be found for the root schema.
218              
219             This attribute should only be used by L<JSON::Schema::Draft201909> and not intended for use
220             externally (you should use the public accessors in L<JSON::Schema::Draft201909> instead).
221              
222             When called as a method, returns the flattened list of tuples (path, uri). You can also use
223             C<resource_pairs> which returns a list of tuples as arrayrefs.
224              
225             =head2 canonical_uri_index
226              
227             An index of json paths (from the document root) to canonical URIs. This is the inversion of
228             L</resource_index> and is constructed as that is built up.
229              
230             =head2 errors
231              
232             A list of L<JSON::Schema::Draft201909::Error> objects that resulted when the schema document was
233             originally parsed. (If a syntax error occurred, usually there will be just one error, as parse
234             errors halt the parsing process.) Documents with errors cannot be evaluated.
235              
236             =head2 evaluation_configs
237              
238             An optional hashref of configuration values that will be provided to the evaluator during
239             evaluation of this document. See the third parameter of L<JSON::Schema::Draft201909/evaluate>.
240             This should never need to be set explicitly. This is sometimes populated automatically after
241             creating a document object, depending on the keywords found in the schema, but they will never
242             override anything you have already explicitly set.
243              
244             =head1 METHODS
245              
246             =for Pod::Coverage FOREIGNBUILDARGS BUILDARGS BUILD
247              
248             =head2 path_to_canonical_uri
249              
250             =for stopwords fragmentless
251              
252             Given a JSON path within this document, returns the canonical URI corresponding to that location.
253             Only fragmentless URIs can be looked up in this manner, so it is only suitable for finding the
254             canonical URI corresponding to a subschema known to have an C<$id> keyword.
255              
256             =head2 contains
257              
258             Check if L</"schema"> contains a value that can be identified with the given JSON Pointer.
259             See L<Mojo::JSON::Pointer/contains>.
260              
261             =head2 get
262              
263             Extract value from L</"schema"> identified by the given JSON Pointer.
264             See L<Mojo::JSON::Pointer/get>.
265              
266             =head2 TO_JSON
267              
268             Returns a data structure suitable for serialization. See L</schema>.
269              
270             =head1 SUPPORT
271              
272             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Draft201909/issues>.
273              
274             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
275              
276             =head1 AUTHOR
277              
278             Karen Etheridge <ether@cpan.org>
279              
280             =head1 COPYRIGHT AND LICENCE
281              
282             This software is copyright (c) 2020 by Karen Etheridge.
283              
284             This is free software; you can redistribute it and/or modify it under
285             the same terms as the Perl 5 programming language system itself.
286              
287             =cut