package JSON::Validator::Schema; use Mojo::Base 'JSON::Validator'; # TODO: Change this to "use Mojo::Base -base" use Carp 'carp'; use JSON::Validator::Util qw(E is_type); use Mojo::JSON::Pointer; has errors => sub { my $self = shift; my $url = $self->specification || 'http://json-schema.org/draft-04/schema#'; my $validator = $self->new(%$self)->resolve($url); return [$validator->validate($self->resolve->data)]; }; has id => sub { my $data = shift->data; return is_type($data, 'HASH') ? $data->{'$id'} || $data->{id} || '' : ''; }; has moniker => sub { my $self = shift; return "draft$1" if $self->specification =~ m!draft-(\d+)!; return ''; }; has specification => sub { my $data = shift->data; is_type($data, 'HASH') ? $data->{'$schema'} || $data->{schema} || '' : ''; }; sub bundle { my $self = shift; my $params = shift || {}; return $self->new(%$self)->data($self->SUPER::bundle({%$params, schema => $self})); } sub contains { state $p = Mojo::JSON::Pointer->new; return $p->data(shift->{data})->contains(@_); } sub data { my $self = shift; return $self->{data} //= {} unless @_; $self->{data} = shift; delete $self->{errors}; return $self; } sub get { state $p = Mojo::JSON::Pointer->new; return $p->data(shift->{data})->get(@_) if @_ == 2 and ref $_[1] ne 'ARRAY'; return JSON::Validator::Util::schema_extract(shift->data, @_); } sub new { return shift->SUPER::new(@_) if @_ % 2; my ($class, $data) = (shift, shift); return $class->SUPER::new(@_)->resolve($data); } sub resolve { my $self = shift; return $self->data($self->_resolve(@_ ? shift : $self->{data})); } sub validate { my ($self, $data, $schema) = @_; local $self->{schema} = $self; # back compat: set $jv->schema() local $self->{seen} = {}; local $self->{temp_schema} = []; # make sure random-errors.t does not fail return $self->_validate($_[1], '', $schema || $self->data); } # Should not be called on JSON::Validator::Schema for my $method (qw(load_and_validate_schema schema singleton version)) { my $super = "JSON::Validator::$method"; Mojo::Util::monkey_patch(__PACKAGE__, $method => sub { my $class = ref $_[0]; carp "$class\::$method(...) is unsupported and will be removed."; shift->$super(@_); } ); } sub _register_root_schema { my ($self, $id, $schema) = @_; $self->SUPER::_register_root_schema($id => $schema); $self->id($id) unless $self->id; } 1; =encoding utf8 =head1 NAME JSON::Validator::Schema - Base class for JSON::Validator schemas =head1 SYNOPSIS package JSON::Validator::Schema::SomeSchema; use Mojo::Base "JSON::Validator::Schema"; has specification => "https://api.example.com/my/spec.json#"; 1; =head1 DESCRIPTION L<JSON::Validator::Schema> is the base class for L<JSON::Validator::Schema::Draft4>, L<JSON::Validator::Schema::Draft6> and L<JSON::Validator::Schema::Draft7>. L<JSON::Validator::Schema> is currently EXPERIMENTAL, and most probably will change over the next versions as L<https://github.com/mojolicious/json-validator/pull/189> (or a competing PR) evolves. =head1 ATTRIBUTES =head2 errors my $array_ref = $schema->errors; Holds the errors after checking L</data> against L</specification>. C<$array_ref> containing no elements means L</data> is valid. Each element in the array-ref is a L<JSON::Validator::Error> object. This attribute is I<not> changed by L</validate>. It only reflects if the C<$schema> is valid. =head2 id my $str = $schema->id; my $schema = $schema->id($str); Holds the ID for this schema. Usually extracted from C<"$id"> or C<"id"> in L</data>. =head2 moniker $str = $schema->moniker; $schema = $self->moniker("some_name"); Used to get/set the moniker for the given schema. Will be "draft04" if L</specification> points to a JSON Schema draft URL, and fallback to empty string if unable to guess a moniker name. This attribute will (probably) detect more monikers from a given L</specification> or C</id> in the future. =head2 specification my $str = $schema->specification; my $schema = $schema->specification($str); The URL to the specification used when checking for L</errors>. Usually extracted from C<"$schema"> or C<"schema"> in L</data>. =head1 METHODS =head2 bundle my $bundled = $schema->bundle; C<$bundled> is a new L<JSON::Validator::Schema> object where none of the "$ref" will point to external resources. This can be useful, if you want to have a bunch of files locally, but hand over a single file to a client. Mojo::File->new("client.json") ->spurt(Mojo::JSON::to_json($schema->bundle->data)); =head2 coerce my $schema = $schema->coerce("booleans,defaults,numbers,strings"); my $schema = $schema->coerce({booleans => 1}); my $hash_ref = $schema->coerce; Set the given type to coerce. Before enabling coercion this module is very strict when it comes to validating types. Example: The string C<"1"> is not the same as the number C<1>. Note that it will also change the internal data-structure of the validated data: Example: $schema->coerce({numbers => 1}); $schema->data({properties => {age => {type => "integer"}}}); my $input = {age => "42"}; $schema->validate($input); # $input->{age} is now an integer 42 and not the string "42" =head2 contains See L<Mojo::JSON::Pointer/contains>. =head2 data my $hash_ref = $schema->data; my $schema = $schema->data($bool); my $schema = $schema->data($hash_ref); my $schema = $schema->data($url); Will set a structure representing the schema. In most cases you want to use L</resolve> instead of L</data>. =head2 get my $data = $schema->get($json_pointer); my $data = $schema->get($json_pointer, sub { my ($data, $json_pointer) = @_; }); Called with one argument, this method acts like L<Mojo::JSON::Pointer/get>, while if called with two arguments it will work like L<JSON::Validator::Util/schema_extract> instead: JSON::Validator::Util::schema_extract($schema->data, sub { ... }); The second argument can be C<undef()>, if you don't care about the callback. See L<Mojo::JSON::Pointer/get>. =head2 new my $schema = JSON::Validator::Schema->new($data); my $schema = JSON::Validator::Schema->new($data, %attributes); my $schema = JSON::Validator::Schema->new(%attributes); Construct a new L<JSON::Validator::Schema> object. Passing on C<$data> as the first argument will cause L</resolve> to be called, meaning the constructor might throw an exception if the schema could not be successfully resolved. =head2 resolve $schema = $schema->resolve; $schema = $schema->resolve($data); Used to resolve L</data> or C<$data> and store the resolved schema in L</data>. If C<$data> is an C<$url> on contains "$ref" pointing to an URL, then these schemas will be downloaded and resolved as well. =head2 validate my @errors = $schema->validate($any); Will validate C<$any> against the schema defined in L</data>. Each element in C<@errors> is a L<JSON::Validator::Error> object. =head1 SEE ALSO L<JSON::Validator>. =cut