package JSONSchema::Validator::Constraints::OAS30;
# ABSTRACT: OpenAPI 3.0 specification constraints
use strict;
use warnings;
use URI;
use Carp 'croak';
use JSONSchema::Validator::JSONPointer 'json_pointer';
use JSONSchema::Validator::Error 'error';
use JSONSchema::Validator::Util 'detect_type';
use parent 'JSONSchema::Validator::Constraints::Draft4';
sub type {
my ($self, $instance, $type, $schema, $instance_path, $schema_path, $data) = @_;
if ($self->check_type($instance, 'null')) {
return $self->nullable( $instance,
$schema->{nullable} // 0,
$schema,
$instance_path,
$schema_path,
$data);
}
my $result = 1;
$result = 0 unless $self->check_type($instance, $type);
# # items must be present if type eq array
# if ($result && $type eq 'array') {
# $result = 0 unless exists $schema->{items};
# }
return 1 if $result;
my $actual_type = detect_type($instance);
push @{$data->{errors}}, error(
message => "type mismatch (expecting: $type, found: $actual_type)",
instance_path => $instance_path,
schema_path => $schema_path
);
return 0;
}
sub items {
my ($self, $instance, $items, $schema, $instance_path, $schema_path, $data) = @_;
return 1 unless $self->check_type($instance, 'array');
# items is object and NOT array
my $result = 1;
for my $i (0 .. $#{$instance}) {
my $item = $instance->[$i];
my $ipath = json_pointer->append($instance_path, $i);
my $r = $self->validator->_validate_schema($item, $items, $ipath, $schema_path, $data);
$result = 0 unless $r;
}
return $result;
}
sub nullable {
my ($self, $instance, $nullable, $schema, $instance_path, $schema_path, $data) = @_;
# A true value adds "null" to the allowed type specified by the type keyword, only if type is explicitly defined within the same Schema Object.
return 1 unless $schema->{type};
return 1 if $nullable;
unless (defined $instance) {
push @{$data->{errors}}, error(
message => 'instance is nullable',
instance_path => $instance_path,
schema_path => $schema_path
);
return 0;
}
return 1;
}
sub readOnly {
my ($self, $instance, $readOnly, $schema, $instance_path, $schema_path, $data) = @_;
return 1 unless $readOnly;
return 1 if $data->{direction} eq 'response';
push @{$data->{errors}}, error(
message => 'instance is invalid in request because of readOnly property',
instance_path => $instance_path,
schema_path => $schema_path
);
return 0;
}
sub writeOnly {
my ($self, $instance, $writeOnly, $schema, $instance_path, $schema_path, $data) = @_;
return 1 unless $writeOnly;
return 1 if $data->{direction} eq 'request';
push @{$data->{errors}}, error(
message => "instance is invalid in response because of writeOnly property",
instance_path => $instance_path,
schema_path => $schema_path
);
return 0;
}
sub required {
my ($self, $instance, $required, $schema, $instance_path, $schema_path, $data) = @_;
return 1 unless $self->check_type($instance, 'object');
my $result = 1;
for my $idx (0 .. $#{$required}) {
my $prop = $required->[$idx];
next if exists $instance->{$prop};
if ($schema->{properties} && $schema->{properties}{$prop}) {
my $prop = $schema->{properties}{$prop};
my $read_only = $prop->{readOnly} // 0;
my $write_only = $prop->{writeOnly} // 0;
my $direction = $data->{direction};
next if $direction eq 'request' && $read_only;
next if $direction eq 'response' && $write_only;
}
push @{$data->{errors}}, error(
message => qq{instance does not have required property "${prop}"},
instance_path => $instance_path,
schema_path => json_pointer->append($schema_path, $idx)
);
$result = 0;
}
return $result;
}
sub discriminator {
my ($self, $instance, $discriminator, $origin_schema, $instance_path, $schema_path, $data) = @_;
return 1 unless $self->check_type($instance, 'object');
my $path = $instance_path;
my $property_name = $discriminator->{propertyName};
my $mapping = $discriminator->{mapping} // {};
my $type = $instance->{$property_name};
my $ref = $mapping->{$type};
$ref = $self->__detect_discriminator_ref($ref || $type);
# status == 1 needs to prevent recursion
$data->{discriminator}{$path} = 1;
my $scope = $self->validator->scope;
$ref = URI->new($ref);
$ref = $ref->abs($scope) if $scope;
my ($current_scope, $schema) = $self->validator->resolver->resolve($ref);
croak "schema not resolved by ref $ref" unless $schema;
push @{$self->validator->scopes}, $current_scope;
my $result = eval {
$self->validator->_validate_schema($instance, $schema, $instance_path, $schema_path, $data, apply_scope => 0);
};
if ($@) {
$result = 0;
push @{$data->{errors}}, error(
message => "exception: $@",
instance_path => $instance_path,
schema_path => $schema_path
);
}
pop @{$self->validator->scopes};
delete $data->{discriminator}{$path};
return $result;
}
sub deprecated {
my ($self, $instance, $deprecated, $schema, $instance_path, $schema_path, $data) = @_;
return 1 unless $deprecated;
push @{$data->{warnings}}, error(
message => 'instance is deprecated',
instance_path => $instance_path,
schema_path => $schema_path
);
return 1;
}
# Additional properties defined by the JSON Schema specification that are not mentioned in OAS30 are strictly unsupported.
sub dependencies { 1 }
sub additionalItems { 1 }
sub patternProperties { 1 }
sub __detect_discriminator_ref {
my ($self, $ref) = @_;
# heuristic
return $ref if $ref =~ m|/|;
return $ref if $ref =~ m/\.json$/;
return '#/components/schemas/' . $ref;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSONSchema::Validator::Constraints::OAS30 - OpenAPI 3.0 specification constraints
=head1 VERSION
version 0.011
=head1 AUTHORS
=over 4
=item *
Alexey Stavrov <logioniz@ya.ru>
=item *
Ivan Putintsev <uid@rydlab.ru>
=item *
Anton Fedotov <tosha.fedotov.2000@gmail.com>
=item *
Denis Ibaev <dionys@gmail.com>
=item *
Andrey Khozov <andrey@rydlab.ru>
=back
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2021 by Alexey Stavrov.
This is free software, licensed under:
The MIT (X11) License
=cut