[libjson-validator-perl] 01/01: Imported Upstream version 0.57+dfsg
Julian Maurice
jajm-guest at moszumanska.debian.org
Wed Oct 28 06:20:54 UTC 2015
This is an automated email from the git hooks/post-receive script.
jajm-guest pushed a commit to annotated tag upstream/0.57+dfsg
in repository libjson-validator-perl.
commit 8edaf3646e927971581d3bb23cb58898f99f6c3a
Author: Julian Maurice <julian.maurice at biblibre.com>
Date: Mon Oct 12 11:15:22 2015 +0200
Imported Upstream version 0.57+dfsg
---
.travis.yml | 12 +
Changes | 25 ++
MANIFEST | 17 +-
META.json | 2 +-
META.yml | 2 +-
README | 121 ++++---
lib/JSON/Validator.pm | 640 +++++++++++++++++++++--------------
t/acceptance.t | 14 +-
t/booleans.t | 35 ++
t/deep-mixed-ref.t | 20 ++
t/definitions/age.json | 5 +
t/definitions/unit.json | 5 +
t/definitions/weight.json | 8 +
t/invalid-ref.t | 19 ++
t/jv-allof.t | 6 +-
t/jv-anyof.t | 4 +-
t/jv-basic.t | 3 +
t/jv-formats.t | 58 ----
t/jv-integer.t | 2 +-
t/jv-oneof.t | 4 +-
t/jv-required.t | 19 +-
t/jv-union.t | 112 ------
t/load-json.t | 4 +-
t/petstore.t | 31 ++
t/relative-ref.t | 19 ++
t/spec/invalid-ref.t | 0
t/{spec.json => spec/person.json} | 0
t/spec/petstore.json | 52 +++
t/spec/with-deep-mixed-ref.json | 11 +
t/spec/with-relative-ref.json | 6 +
t/swagger-validate-response-object.t | 82 +++++
t/validate-json.t | 3 +-
32 files changed, 828 insertions(+), 513 deletions(-)
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3c952f3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: perl
+perl:
+ - "5.20"
+ - "5.16"
+ - "5.10"
+env:
+ - "HARNESS_OPTIONS=j6"
+install:
+ - "cpanm -n Test::Pod Test::Pod::Coverage Data::Validate::Domain Data::Validate::IP YAML"
+ - "cpanm -n --installdeps ."
+notifications:
+ email: false
diff --git a/Changes b/Changes
index aafea74..2a1e343 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,30 @@
Revision history for perl distribution JSON-Validator
+0.57 2015-10-11T13:20:45+0200
+ - Trust guesswork if input data is undefined
+
+0.56 2015-09-30T11:43:49+0200
+ - Can read YAML::XS booleans automatically #8
+ - Change coerce() into a method. #8
+ - Remove EXPERIMENTAL coerce attribute #8
+ - Remove EXPERIMENTAL JSON_VALIDATOR_COERCE_VALUES and SWAGGER_COERCE_VALUES #8
+
+0.55 2015-09-29T19:01:05+0200
+ - Fix "required" cannot be a boolean on properties
+ - Improved documentation of error object
+ - Change anyOf/allOf/oneOf error message
+
+0.54 2015-09-27T13:33:02+0200
+ - Add support for $ref to relative path #3 #4 #5
+ - Removed Swagger specific type "file"
+ - Removed Swagger specific formats: "byte", "date", "double", "float", "int32" and "int64".
+
+0.53 2015-09-13T10:52:16+0200
+ - Fix properties, patternProperties, additionalProperties interaction - patternProperty invalidates property
+ - Fix validation for a keyword and instance SHOULD succeed when keywords does not match primitive type
+ - Fix allOf with base schema - mismatch base schema
+ - Fix checking for a boolean "required"
+
0.52 2015-09-05T13:52:39+0200
- Add guessing of schema type, based on other attributes
- More strict on what is validated as "boolean"
diff --git a/MANIFEST b/MANIFEST
index bf254cc..5e13dd2 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,5 +1,6 @@
.perltidyrc
.ship.conf
+.travis.yml
Changes
cpanfile
lib/JSON/Validator.pm
@@ -8,6 +9,12 @@ MANIFEST This list of files
README
t/00-basic.t
t/acceptance.t
+t/booleans.t
+t/deep-mixed-ref.t
+t/definitions/age.json
+t/definitions/unit.json
+t/definitions/weight.json
+t/invalid-ref.t
t/jv-allof.t
t/jv-anyof.t
t/jv-array.t
@@ -20,13 +27,19 @@ t/jv-object.t
t/jv-oneof.t
t/jv-required.t
t/jv-string.t
-t/jv-union.t
t/load-data.t
t/load-http.t
t/load-json.t
t/load-yaml.t
+t/petstore.t
+t/relative-ref.t
t/resolve.t
-t/spec.json
+t/spec/invalid-ref.t
+t/spec/person.json
+t/spec/petstore.json
+t/spec/with-deep-mixed-ref.json
+t/spec/with-relative-ref.json
+t/swagger-validate-response-object.t
t/validate-json.t
META.yml Module YAML meta-data (added by MakeMaker)
META.json Module JSON meta-data (added by MakeMaker)
diff --git a/META.json b/META.json
index 44de833..9eaa5d4 100644
--- a/META.json
+++ b/META.json
@@ -47,5 +47,5 @@
"url" : "https://github.com/jhthorsen/json-validator.git"
}
},
- "version" : "0.52"
+ "version" : "0.57"
}
diff --git a/META.yml b/META.yml
index 85763b9..6e067fc 100644
--- a/META.yml
+++ b/META.yml
@@ -24,4 +24,4 @@ resources:
bugtracker: https://github.com/jhthorsen/json-validator/issues
homepage: https://github.com/jhthorsen/json-validator
repository: https://github.com/jhthorsen/json-validator.git
-version: '0.52'
+version: '0.57'
diff --git a/README b/README
index 98bd843..0e6967c 100644
--- a/README
+++ b/README
@@ -2,7 +2,7 @@ NAME
JSON::Validator - Validate data against a JSON schema
VERSION
- 0.52
+ 0.57
SYNOPSIS
use JSON::Validator;
@@ -62,6 +62,50 @@ DESCRIPTION
* Swagger2
+ERROR OBJECT
+ Overview
+ The method "validate" and the function "validate_json" returns error
+ objects when the input data violates the "schema". Each of the objects
+ looks like this:
+
+ bless {
+ message => "Some description",
+ path => "/json/path/to/node",
+ }, "JSON::Validator::Error"
+
+ Operators
+ The error object overloads the following operators:
+
+ * bool
+
+ Returns a true value.
+
+ * string
+
+ Returns the "path" and "message" part as a string: "$path:
+ $message".
+
+ Special cases
+ Have a look at the test suite
+ <https://github.com/jhthorsen/json-validator/tree/master/t> for
+ documented examples of the error cases. Especially look at "jv-allof.t",
+ "jv-anyof.t" and "jv-oneof.t".
+
+ The special cases for "allOf", "anyOf" and "oneOf" will contain the
+ error messages from all the failing rules below. It can be a bit hard to
+ read, so if the error message is long, then you might want to run a
+ smaller test with "JSON_VALIDATOR_DEBUG=1".
+
+ Example error object:
+
+ bless {
+ message => "[0] String is too long: 8/5. [1] Expected number - got string.",
+ path => "/json/path/to/node",
+ }, "JSON::Validator::Error"
+
+ Note that these error messages are subject for change. Any suggestions
+ are most welcome!
+
FUNCTIONS
validate_json
use JSON::Validator "validate_json";
@@ -71,6 +115,8 @@ FUNCTIONS
@errors = validate_json $c->req->json, "data://main/spec.json";
+ See also "validate" and "ERROR OBJECT" for more details.
+
ATTRIBUTES
cache_dir
$self = $self->cache_dir($path);
@@ -80,15 +126,6 @@ ATTRIBUTES
"JSON_VALIDATOR_CACHE_DIR" or the bundled spec files that are shipped
with this distribution.
- coerce
- $self = $self->coerce(1);
- $bool = $self->coerce;
-
- Set this to true if you want to coerce numbers into string and the other
- way around.
-
- This is EXPERIMENTAL and could be removed without notice!
-
formats
$hash_ref = $self->formats;
$self = $self->formats(\%hash);
@@ -98,48 +135,20 @@ ATTRIBUTES
Note! The modules mentioned below are optional.
- * byte
-
- A padded, base64-encoded string of bytes, encoded with a URL and
- filename safe alphabet. Defined by RFC4648.
-
- * date
-
- An RFC3339 date in the format YYYY-MM-DD
-
* date-time
An RFC3339 timestamp in UTC time. This is formatted as
"YYYY-MM-DDThh:mm:ss.fffZ". The milliseconds portion (".fff") is
optional
- * double
-
- Cannot test double values with higher precision then what the
- "number" type already provides.
-
* email
Validated against the RFC5322 spec.
- * float
-
- Will always be true if the input is a number, meaning there is no
- difference between "float" and "double". Patches are welcome.
-
* hostname
Will be validated using Data::Validate::Domain if installed.
- * int32
-
- A signed 32 bit integer.
-
- * int64
-
- A signed 64 bit integer. Note: This check is only available if Perl
- is compiled to use 64 bit integers.
-
* ipv4
Will be validated using Data::Validate::IP if installed or fall back
@@ -165,6 +174,30 @@ ATTRIBUTES
EXPERIMENTAL and might change without a warning)
METHODS
+ coerce
+ $self = $self->coerce(booleans => 1, numbers => 1, strings => 1);
+ $self = $self->coerce(1) # enable all
+ $hash = $self->coerce;
+
+ Set the given type to coerce. Before enabling coercion this module is
+ very strict when it comes to validating types. Example: The string "1"
+ is not the same as the number 1, unless you have coercion enabled.
+
+ WARNING! Enabling coercion might hide bugs in your api, which would have
+ been detected if you were strict. For example JavaScript is very picky
+ on a number being an actual number. This module tries it best to convert
+ the data on the fly into the proper value, but this means that you unit
+ tests might be ok, but the client side libraries (that care about types)
+ might break.
+
+ Loading a YAML document will enable "booleans" automatically. This
+ feature is experimental, but was added since YAML has no real concept of
+ booleans, such as Mojo::JSON or other JSON parsers.
+
+ The coercion rules are EXPERIMENTAL and will be tighten/loosen if bugs
+ are reported. See <https://github.com/jhthorsen/json-validator/issues/8>
+ for more details.
+
schema
$self = $self->schema(\%schema);
$self = $self->schema($url);
@@ -201,17 +234,9 @@ METHODS
@errors = $self->validate($data);
Validates $data against a given JSON "schema". @errors will contain
- validation error objects. It will be empty on success.
-
- Example error object:
-
- bless {
- message => "Some description",
- path => "/json/path/to/node",
- }, "JSON::Validator::Error"
+ validation error objects or be an empty list on success.
- The error objects are always true in boolean context and will stringify.
- The stringification format is subject to change.
+ See "ERROR OBJECT" for details.
COPYRIGHT AND LICENSE
Copyright (C) 2014-2015, Jan Henning Thorsen
diff --git a/lib/JSON/Validator.pm b/lib/JSON/Validator.pm
index e6894c3..3c1bbd1 100644
--- a/lib/JSON/Validator.pm
+++ b/lib/JSON/Validator.pm
@@ -6,7 +6,7 @@ JSON::Validator - Validate data against a JSON schema
=head1 VERSION
-0.52
+0.57
=head1 SYNOPSIS
@@ -72,6 +72,55 @@ Here are some resources that are related to JSON schemas and validation:
=back
+=head1 ERROR OBJECT
+
+=head2 Overview
+
+The method L</validate> and the function L</validate_json> returns
+error objects when the input data violates the L</schema>. Each of
+the objects looks like this:
+
+ bless {
+ message => "Some description",
+ path => "/json/path/to/node",
+ }, "JSON::Validator::Error"
+
+=head2 Operators
+
+The error object overloads the following operators:
+
+=over 4
+
+=item * bool
+
+Returns a true value.
+
+=item * string
+
+Returns the "path" and "message" part as a string: "$path: $message".
+
+=back
+
+=head2 Special cases
+
+Have a look at the L<test suite|https://github.com/jhthorsen/json-validator/tree/master/t>
+for documented examples of the error cases. Especially look at C<jv-allof.t>,
+C<jv-anyof.t> and C<jv-oneof.t>.
+
+The special cases for "allOf", "anyOf" and "oneOf" will contain the error messages
+from all the failing rules below. It can be a bit hard to read, so if the error message
+is long, then you might want to run a smaller test with C<JSON_VALIDATOR_DEBUG=1>.
+
+Example error object:
+
+ bless {
+ message => "[0] String is too long: 8/5. [1] Expected number - got string.",
+ path => "/json/path/to/node",
+ }, "JSON::Validator::Error"
+
+Note that these error messages are subject for change. Any suggestions are most
+welcome!
+
=cut
use Mojo::Base -base;
@@ -81,21 +130,23 @@ use Mojo::JSON::Pointer;
use Mojo::URL;
use Mojo::Util;
use B;
+use Cwd ();
use File::Basename ();
use File::Spec;
use Scalar::Util;
use constant VALIDATE_HOSTNAME => eval 'require Data::Validate::Domain;1';
use constant VALIDATE_IP => eval 'require Data::Validate::IP;1';
-use constant IV_SIZE => eval 'require Config;$Config::Config{ivsize}';
use constant DEBUG => $ENV{JSON_VALIDATOR_DEBUG} || $ENV{SWAGGER2_DEBUG} || 0;
use constant WARN_ON_MISSING_FORMAT => $ENV{JSON_VALIDATOR_WARN_ON_MISSING_FORMAT}
|| $ENV{SWAGGER2_WARN_ON_MISSING_FORMAT} ? 1 : 0;
-our $VERSION = '0.52';
+our $VERSION = '0.57';
our @EXPORT_OK = qw( validate_json );
+my $HTTP_SCHEME_RE = qr{^https?:};
+
sub E { bless {path => $_[0] || '/', message => $_[1]}, 'JSON::Validator::Error'; }
sub S { Mojo::Util::md5_sum(Data::Dumper->new([@_])->Sortkeys(1)->Useqq(1)->Dump); }
@@ -110,6 +161,8 @@ This can be useful in web applications:
@errors = validate_json $c->req->json, "data://main/spec.json";
+See also L</validate> and L</ERROR OBJECT> for more details.
+
=cut
sub validate_json {
@@ -127,15 +180,6 @@ Path to where downloaded spec files should be cached. Defaults to
C<JSON_VALIDATOR_CACHE_DIR> or the bundled spec files that are shipped
with this distribution.
-=head2 coerce
-
- $self = $self->coerce(1);
- $bool = $self->coerce;
-
-Set this to true if you want to coerce numbers into string and the other way around.
-
-This is EXPERIMENTAL and could be removed without notice!
-
=head2 formats
$hash_ref = $self->formats;
@@ -148,47 +192,19 @@ Note! The modules mentioned below are optional.
=over 4
-=item * byte
-
-A padded, base64-encoded string of bytes, encoded with a URL and filename safe
-alphabet. Defined by RFC4648.
-
-=item * date
-
-An RFC3339 date in the format YYYY-MM-DD
-
=item * date-time
An RFC3339 timestamp in UTC time. This is formatted as
"YYYY-MM-DDThh:mm:ss.fffZ". The milliseconds portion (".fff") is optional
-=item * double
-
-Cannot test double values with higher precision then what
-the "number" type already provides.
-
=item * email
Validated against the RFC5322 spec.
-=item * float
-
-Will always be true if the input is a number, meaning there is no difference
-between L</float> and L</double>. Patches are welcome.
-
=item * hostname
Will be validated using L<Data::Validate::Domain> if installed.
-=item * int32
-
-A signed 32 bit integer.
-
-=item * int64
-
-A signed 64 bit integer. Note: This check is only available if Perl is
-compiled to use 64 bit integers.
-
=item * ipv4
Will be validated using L<Data::Validate::IP> if installed or
@@ -222,24 +238,7 @@ has cache_dir => sub {
$ENV{JSON_VALIDATOR_CACHE_DIR} || File::Spec->catdir(File::Basename::dirname(__FILE__), qw( JSON Validator ));
};
-has coerce => $ENV{JSON_VALIDATOR_COERCE_VALUES} || $ENV{SWAGGER_COERCE_VALUES} || 0; # EXPERIMENTAL!
-
-has formats => sub {
- +{
- 'byte' => \&_is_byte_string,
- 'date' => \&_is_date,
- 'date-time' => \&_is_date_time,
- 'double' => sub {1},
- 'float' => sub {1},
- 'email' => \&_is_email,
- 'hostname' => VALIDATE_HOSTNAME ? \&Data::Validate::Domain::is_domain : \&_is_domain,
- 'int32' => sub { _is_number($_[0], 'l'); },
- 'int64' => IV_SIZE >= 8 ? sub { _is_number($_[0], 'q'); } : sub {1},
- 'ipv4' => VALIDATE_IP ? \&Data::Validate::IP::is_ipv4 : \&_is_ipv4,
- 'ipv6' => VALIDATE_IP ? \&Data::Validate::IP::is_ipv6 : \&_is_ipv6,
- 'uri' => \&_is_uri,
- };
-};
+has formats => sub { shift->_build_formats };
has ua => sub {
require Mojo::UserAgent;
@@ -251,6 +250,46 @@ has ua => sub {
=head1 METHODS
+=head2 coerce
+
+ $self = $self->coerce(booleans => 1, numbers => 1, strings => 1);
+ $self = $self->coerce(1) # enable all
+ $hash = $self->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>, unless you have coercion enabled.
+
+WARNING! Enabling coercion might hide bugs in your api, which would have been
+detected if you were strict. For example JavaScript is very picky on a number
+being an actual number. This module tries it best to convert the data on the
+fly into the proper value, but this means that you unit tests might be ok,
+but the client side libraries (that care about types) might break.
+
+Loading a YAML document will enable "booleans" automatically. This feature is
+experimental, but was added since YAML has no real concept of booleans, such
+as L<Mojo::JSON> or other JSON parsers.
+
+The coercion rules are EXPERIMENTAL and will be tighten/loosen if
+bugs are reported. See L<https://github.com/jhthorsen/json-validator/issues/8>
+for more details.
+
+=cut
+
+sub coerce {
+ my ($self, @args) = @_;
+
+ return $self->{coerce} ||= {} unless @args;
+
+ @args = (booleans => 1, numbers => 1, strings => 1) if $args[0] eq '1'; # back compat
+ while (@args) {
+ my $k = shift @args;
+ $self->{coerce}{$k} = shift @args;
+ }
+
+ return $self;
+}
+
=head2 schema
$self = $self->schema(\%schema);
@@ -291,13 +330,16 @@ sub schema {
return $self->{schema};
}
elsif (ref $schema eq 'HASH') {
- $self->_register_document($schema, $schema->{id} ||= 'http://generated.json.validator.url#');
+ $schema->{id} ||= $self->_default_id($schema);
+ warn "[JSON::Validator] Schema from hash. id=$schema->{id}\n" if DEBUG;
+ $schema = $self->_register_document($schema, $schema->{id});
}
else {
- $schema = $self->_load_schema($schema)->data;
+ $schema = Cwd::abs_path($schema) if -e $schema;
+ $schema = $self->_load_schema($schema);
}
- $self->{schema} = Mojo::JSON::Pointer->new($self->_resolve_schema($schema, $schema->{id}, {}));
+ $self->{schema} = $self->_resolve_schema($schema, $schema->data->{id});
$self;
}
@@ -316,18 +358,9 @@ sub singleton { state $validator = shift->new }
@errors = $self->validate($data);
Validates C<$data> against a given JSON L</schema>. C<@errors> will
-contain validation error objects. It will be
-empty on success.
+contain validation error objects or be an empty list on success.
-Example error object:
-
- bless {
- message => "Some description",
- path => "/json/path/to/node",
- }, "JSON::Validator::Error"
-
-The error objects are always true in boolean context and will stringify. The
-stringification format is subject to change.
+See L</ERROR OBJECT> for details.
=cut
@@ -338,6 +371,17 @@ sub validate {
return $self->_validate($data, '', $schema);
}
+sub _build_formats {
+ return {
+ 'date-time' => \&_is_date_time,
+ 'email' => \&_is_email,
+ 'hostname' => VALIDATE_HOSTNAME ? \&Data::Validate::Domain::is_domain : \&_is_domain,
+ 'ipv4' => VALIDATE_IP ? \&Data::Validate::IP::is_ipv4 : \&_is_ipv4,
+ 'ipv6' => VALIDATE_IP ? \&Data::Validate::IP::is_ipv6 : \&_is_ipv6,
+ 'uri' => \&_is_uri,
+ };
+}
+
sub _coerce_by_collection_format {
my ($self, $schema, $data) = @_;
my $format = $schema->{collectionFormat};
@@ -349,30 +393,42 @@ sub _coerce_by_collection_format {
}
sub _load_schema {
- my ($self, $url) = @_;
+ my ($self, $url, $parent) = @_;
my ($namespace, $scheme) = ("$url", "file");
my $doc;
- if ($namespace =~ m!^https?://!) {
+ if ($namespace =~ $HTTP_SCHEME_RE) {
$url = Mojo::URL->new($url);
- ($namespace, $scheme) = ($url->clone->fragment(undef)->port(undef)->to_string, $url->scheme);
+ ($namespace, $scheme) = ($url->clone->fragment(undef)->to_string, $url->scheme);
}
elsif ($namespace =~ m!^data://(.*)!) {
$scheme = 'data';
}
+ elsif ($parent and $parent =~ $HTTP_SCHEME_RE) {
+ $parent = Mojo::URL->new($parent);
+ $url =~ s!#.*!!;
+ $url = $parent->path($parent->path->merge($url)->canonicalize);
+ ($namespace, $scheme) = ($url->to_string, $url->scheme);
+ }
+ elsif ($parent) {
+ $url =~ s!#.*!!;
+ $url = File::Spec->catfile(File::Basename::dirname($parent), split '/', $url);
+ $namespace = Cwd::abs_path($url) || $url;
+ }
# Make sure we create the correct namespace if not already done by Mojo::URL
$namespace =~ s!#.*$!! if $namespace eq $url;
return $self->{cached}{$namespace} if $self->{cached}{$namespace};
return eval {
- warn "[JSON::Validator] Loading schema from $url ($namespace)\n" if DEBUG;
+ warn "[JSON::Validator] Loading schema $url namespace=$namespace scheme=$scheme\n" if DEBUG;
$doc
= $scheme eq 'file' ? Mojo::Util::slurp($namespace)
: $scheme eq 'data' ? $self->_load_schema_from_data($url, $namespace)
: $self->_load_schema_from_url($url, $namespace);
$self->_register_document($self->_load_schema_from_text($doc), $namespace);
} || do {
+ $doc ||= '';
die "Could not load document from $url: $@ ($doc)" if DEBUG;
die "Could not load document from $url: $@";
};
@@ -386,7 +442,9 @@ sub _load_schema_from_data {
}
sub _load_schema_from_text {
- $_[1] =~ /^\s*\{/s ? Mojo::JSON::decode_json($_[1]) : _load_yaml($_[1]);
+ return Mojo::JSON::decode_json($_[1]) if $_[1] =~ /^\s*\{/s;
+ $_[0]->{coerce}{booleans} = 1; # internal coercion
+ _load_yaml($_[1]);
}
sub _load_schema_from_url {
@@ -399,144 +457,212 @@ sub _load_schema_from_url {
return $doc;
}
+sub _default_id {
+ my $path = Cwd::abs_path($0);
+ state $id = 0;
+ $path = File::Basename::dirname($path) if $path;
+ $path = Cwd::getcwd unless $path;
+ return File::Spec->catfile($path, sprintf 'json-validator-%s.json', ++$id);
+}
+
sub _register_document {
my ($self, $doc, $namespace) = @_;
$doc = Mojo::JSON::Pointer->new($doc);
$namespace = Mojo::URL->new($namespace) unless ref $namespace;
- $namespace->fragment(undef)->port(undef);
-
- warn "[JSON::Validator] Register $namespace\n" if DEBUG;
+ $namespace->fragment(undef);
$self->{cached}{$namespace} = $doc;
$doc->data->{id} ||= "$namespace";
$self->{cached}{$doc->data->{id}} = $doc;
- $doc;
+
+ warn "[JSON::Validator] Register id=$doc->{data}{id} namespace=$namespace\n" if DEBUG;
+ return $doc;
}
-sub _resolve_ref {
- my ($self, $ref, $namespace, $refs) = @_;
+sub _resolve_schema {
+ my ($self, $schema, $namespace) = @_;
+ my (@items, @refs);
+
+ return $self->{resolved}{$namespace} if $self->{resolved}{$namespace};
+
+ warn "[JSON::Validator] Resolving schema $namespace\n" if DEBUG;
+ $self->{resolved}{$namespace} = Mojo::JSON::Pointer->new({%{$schema->data}});
+ @items = ($self->{resolved}{$namespace}->data);
+
+ # First step: Make copy and find $ref
+ while (@items) {
+ my $topic = shift @items;
+ if (ref $topic eq 'HASH') {
+ while (my ($k, $v) = each %$topic) {
+ $topic->{$k} = [@$v] if ref $v eq 'ARRAY';
+ $topic->{$k} = {%$v} if ref $v eq 'HASH';
+ push @refs, $topic if $k eq '$ref' and !ref $v;
+ push @items, $topic->{$k};
+ }
+ }
+ elsif (ref $topic eq 'ARRAY') {
+ push @items, @$topic;
+ }
+ }
- return if !$ref or ref $ref;
- $ref = "#/definitions/$ref" if $ref =~ /^\w+$/;
- $ref = Mojo::URL->new($namespace)->fragment($ref) if $ref =~ s!^\#!!;
- $ref = Mojo::URL->new($ref) unless UNIVERSAL::isa($ref, 'Mojo::URL');
+ # Seconds step: Resolve $ref
+ for my $topic (@refs) {
+ my $ref = $topic->{'$ref'} or next; # already resolved?
+ $ref = "#/definitions/$ref" if $ref =~ /^\w+$/; # TODO: Figure out if this could be removed
+ $ref = Mojo::URL->new($namespace)->fragment($ref) if $ref =~ s!^\#!!;
+ $ref = Mojo::URL->new($ref) unless ref $ref;
- return $refs->{$ref} if $refs->{$ref};
+ warn "[JSON::Validator] Resolving ref $ref defined in $namespace\n" if DEBUG == 2;
+ my $look_in = $self->{resolved}{$ref->clone->fragment(undef)};
- warn "[JSON::Validator] Resolve $ref\n" if DEBUG;
- $refs->{$ref} = {};
- my $doc = $self->_load_schema($ref);
- my $def = $self->_resolve_schema($doc->get($ref->fragment), $doc->data->{id}, $refs);
- delete $def->{id};
- $refs->{$ref}{$_} = $def->{$_} for keys %$def;
- $refs->{$ref};
-}
-
-sub _resolve_schema {
- my ($self, $obj, $namespace, $refs) = @_;
- my $copy = ref $obj eq 'ARRAY' ? [] : {};
- my $ref;
+ if (!$look_in) {
+ $look_in = $self->_load_schema($ref, $namespace);
+ $look_in = $self->_resolve_schema($look_in, $look_in->data->{id} || $namespace);
+ warn "[JSON::Validator] Will look for $ref in $look_in->{data}{id}\n" if DEBUG == 2;
+ }
- if (ref $obj eq 'HASH') {
- $obj = $ref if $ref = $self->_resolve_ref($obj->{'$ref'}, $namespace, $refs);
- $copy->{$_} = $self->_resolve_schema($obj->{$_}, $namespace, $refs) for keys %$obj;
- delete $copy->{'$ref'};
- return $copy;
- }
- elsif (ref $obj eq 'ARRAY') {
- $copy->[$_] = $self->_resolve_schema($obj->[$_], $namespace, $refs) for 0 .. @$obj - 1;
- return $copy;
+ $ref = $look_in->get($ref->fragment || '')
+ || die qq[Could not find "$topic->{'$ref'}" ($ref). Typo in schema "$namespace"?];
+ %$topic = %$ref;
+ delete $topic->{id} unless ref $topic->{id}; # TODO: Is this correct?
}
- return $obj;
+ return $self->{resolved}{$namespace};
}
sub _validate {
my ($self, $data, $path, $schema) = @_;
- my $check_all = grep { $schema->{$_} } qw( allOf oneOf );
- my (@errors, @types);
-
- push @types, map { $schema->{$_} } grep { $schema->{$_} } qw( allOf anyOf oneOf );
- push @types, $schema->{type} || _guess_schema_type($schema) || 'any' unless @types;
+ my $type = $schema->{type} || _guess_schema_type($schema, $data);
+ my @errors;
- #warn Carp::longmess(Data::Dumper::Dumper([$data, $path, $schema]));
- #$SIG{__WARN__} = sub { Carp::confess(Data::Dumper::Dumper($schema)) };
+ # Test base schema before allOf, anyOf or oneOf
+ if (ref $type eq 'ARRAY') {
+ for my $type (@$type) {
+ my $method = sprintf '_validate_type_%s', $type;
+ my @e = $self->$method($data, $path, $schema);
+ warn "[JSON::Validator] type @{[$path||'/']} => $method [@e]\n" if DEBUG == 2;
+ push @errors, @e;
+ next if @e;
+ @errors = ();
+ last;
+ }
+ @errors = E $path, $self->_merge_errors($path, @errors) if @errors;
+ }
+ elsif ($type) {
+ my $method = sprintf '_validate_type_%s', $type;
+ @errors = $self->$method($data, $path, $schema);
+ warn "[JSON::Validator] type @{[$path||'/']} $method [@errors]\n" if DEBUG == 2;
+ return @errors if @errors;
+ }
- if ($schema->{not}) {
- @errors = $self->_validate($data, $path, $schema->{not});
+ if (my $rules = $schema->{not}) {
+ push @errors, $self->_validate($data, $path, $rules);
+ warn "[JSON::Validator] not @{[$path||'/']} == [@errors]\n" if DEBUG == 2;
return @errors ? () : (E $path, 'Should not match.');
}
- for my $t (map { ref $_ eq 'ARRAY' ? @$_ : $_ } @types) {
- $t //= 'null';
- if (ref $t eq 'HASH') {
- push @errors, [$self->_validate($data, $path, $t)];
- return if !$check_all and !@{$errors[-1]}; # valid
+ if (my $rules = $schema->{allOf}) {
+ push @errors, $self->_validate_all_of($data, $path, $rules);
+ }
+ elsif ($rules = $schema->{anyOf}) {
+ push @errors, $self->_validate_any_of($data, $path, $rules);
+ }
+ elsif ($rules = $schema->{oneOf}) {
+ push @errors, $self->_validate_one_of($data, $path, $rules);
+ }
+
+ return @errors;
+}
+
+sub _merge_errors {
+ my ($self, $root) = (shift, quotemeta shift);
+ my $i = 0;
+ my (%err, @messages);
+
+ for my $e (@_) {
+ if ($e and $e->{message} =~ m!Expected ([^\.]+)\ - got ([^\.]+)\.!) {
+ push @{$err{$e->{path}}}, [$i, $e->{message}, $1, $2];
}
- elsif (my $code = $self->can(sprintf '_validate_type_%s', $t)) {
- push @errors, [$self->$code($data, $path, $schema)];
- return if !$check_all and !@{$errors[-1]}; # valid
+ elsif ($e) {
+ push @{$err{$e->{path}}}, [$i, $e->{message}];
}
- elsif ($t eq 'file') {
- return; # Skip validating raw file
+ $i++;
+ }
+
+ for my $p (sort keys %err) {
+ my $prefix = $p;
+ my (@e, %uniq);
+
+ @e = grep { !$uniq{$_->[1]}++ } @{$err{$p}};
+ $prefix =~ s!^$root/?!!;
+ $prefix = "/$prefix: " if $prefix;
+
+ if (@e == grep { defined $_->[2] } @e) {
+ push @messages, sprintf '%sExpected %s - got %s.', $prefix, join(', ', map { $_->[2] } @e), $e[0][3];
}
else {
- return E $path, "Cannot validate type '$t'";
+ push @messages, join ' ', map {"[$_->[0]] $prefix$_->[1]"} @e;
}
}
- if ($schema->{oneOf}) {
- my $n = grep { @$_ == 0 } @errors;
- return if $n == 1; # one match
- return E $path, "Expected only one to match." if $n == @errors;
+ return sprintf '(%s)', join ' ', @messages;
+}
+
+sub _validate_all_of {
+ my ($self, $data, $path, $rules) = @_;
+ my @errors;
+
+ for my $rule (@$rules) {
+ my @e = $self->_validate($data, $path, $rule);
+ push @errors, @e ? @e : (undef);
}
- if (@errors > 1) {
- my %err;
- for my $i (0 .. @errors - 1) {
- for my $e (@{$errors[$i]}) {
- if ($e->{message} =~ m!Expected ([^\.]+)\ - got ([^\.]+)\.!) {
- push @{$err{$e->{path}}}, [$i, $e->{message}, $1, $2];
- }
- else {
- push @{$err{$e->{path}}}, [$i, $e->{message}];
- }
- }
- }
- unshift @errors, [];
- for my $p (sort keys %err) {
- my %uniq;
- my @e = grep { !$uniq{$_->[1]}++ } @{$err{$p}};
- if (@e == grep { defined $_->[2] } @e) {
- push @{$errors[0]}, E $p, sprintf 'Expected %s - got %s.', join(', ', map { $_->[2] } @e), $e[0][3];
- }
- else {
- push @{$errors[0]}, E $p, join ' ', map {"[$_->[0]] $_->[1]"} @e;
- }
- }
+ warn "[JSON::Validator] allOf @{[$path||'/']} == [@errors]\n" if DEBUG == 2;
+ return E $path, sprintf 'allOf failed: %s', $self->_merge_errors($path, @errors) if grep {$_} @errors;
+ return;
+}
+
+sub _validate_any_of {
+ my ($self, $data, $path, $rules) = @_;
+ my $failed = 0;
+ my @errors;
+
+ for my $rule (@$rules) {
+ my @e = $self->_validate($data, $path, $rule);
+ push @errors, @e ? @e : (undef);
+ $failed++ if @e;
}
- return @{$errors[0]};
+ if ($failed < @$rules) {
+ warn "[JSON::Validator] anyOf @{[$path||'/']} == success\n" if DEBUG == 2;
+ return;
+ }
+ else {
+ warn "[JSON::Validator] anyOf @{[$path||'/']} == [@errors]\n" if DEBUG == 2;
+ return E $path, sprintf 'anyOf failed: %s', $self->_merge_errors($path, @errors) if grep {$_} @errors;
+ }
}
-sub _validate_additional_properties {
- my ($self, $data, $path, $schema) = @_;
- my $properties = $schema->{additionalProperties};
+sub _validate_one_of {
+ my ($self, $data, $path, $rules) = @_;
+ my $failed = 0;
my @errors;
- if (ref $properties eq 'HASH') {
- push @errors, $self->_validate($_, $path, $properties) for values %$data;
+ for my $rule (@$rules) {
+ my @e = $self->_validate($data, $path, $rule);
+ push @errors, @e ? (@e) : (undef);
+ $failed++ if @e;
}
- elsif (!$properties) {
- my @keys = grep { $_ !~ /^(description|id|title)$/ } keys %$data;
- if (@keys) {
- local $" = ', ';
- push @errors, E $path, "Properties not allowed: @keys.";
- }
+
+ if ($failed + 1 == @$rules) {
+ warn "[JSON::Validator] oneOf @{[$path||'/']} == success\n" if DEBUG == 2;
+ return;
}
- return @errors;
+ warn "[JSON::Validator] oneOf @{[$path||'/']} == failed=$failed/@{[int @$rules]} / @errors\n" if DEBUG == 2;
+ return E $path, 'All of the oneOf rules match.' unless $failed;
+ return E $path, sprintf 'oneOf failed: %s', $self->_merge_errors($path, @errors) if grep {$_} @errors;
}
sub _validate_type_enum {
@@ -545,6 +671,7 @@ sub _validate_type_enum {
my $m = S $data;
for my $i (@$enum) {
+ return if !$self->_validate_type_boolean($data, $path) and _is_true($data) == _is_true($i);
return if $m eq S $i;
}
@@ -565,53 +692,6 @@ sub _validate_format {
return E $path, "Does not match $schema->{format} format.";
}
-sub _validate_pattern_properties {
- my ($self, $data, $path, $schema) = @_;
- my $properties = $schema->{patternProperties};
- my @errors;
-
- for my $pattern (keys %$properties) {
- my $v = $properties->{$pattern};
- for my $tk (keys %$data) {
- next unless $tk =~ /$pattern/;
- push @errors, $self->_validate(delete $data->{$tk}, _path($path, $tk), $v);
- }
- }
-
- return @errors;
-}
-
-sub _validate_properties {
- my ($self, $data, $path, $schema) = @_;
- my $properties = $schema->{properties};
- my $required = $schema->{required};
- my (@errors, %required);
-
- if ($required and ref $required eq 'ARRAY') {
- $required{$_} = 1 for @$required;
- }
-
- for my $name (keys %$properties) {
- my $p = $properties->{$name};
- if (exists $data->{$name}) {
- my $v = delete $data->{$name};
- push @errors, $self->_validate_type_enum($v, $path, $p) if $p->{enum};
- push @errors, $self->_validate($v, _path($path, $name), $p);
- }
- elsif ($p->{default}) {
- $data->{$name} = $p->{default};
- }
- elsif ($required{$name}) {
- push @errors, E _path($path, $name), "Missing property.";
- }
- elsif (_is_true($p->{required}) eq '1') {
- push @errors, E _path($path, $name), "Missing property.";
- }
- }
-
- return @errors;
-}
-
sub _validate_type_any {
return;
}
@@ -663,8 +743,7 @@ sub _validate_type_array {
elsif (ref $schema->{items} eq 'HASH') {
for my $i (0 .. @$data - 1) {
if ($schema->{items}{properties}) {
- my $input = ref $data->[$i] eq 'HASH' ? {%{$data->[$i]}} : $data->[$i];
- push @errors, $self->_validate_properties($input, "$path/$i", $schema->{items});
+ push @errors, $self->_validate_type_object($data->[$i], "$path/$i", $schema->{items});
}
else {
push @errors, $self->_validate($data->[$i], "$path/$i", $schema->{items});
@@ -678,7 +757,16 @@ sub _validate_type_array {
sub _validate_type_boolean {
my ($self, $value, $path, $schema) = @_;
- return if defined $value and Scalar::Util::blessed($value) and ("$value" eq "1" or "$value" eq "0");
+ if (defined $value) {
+ if (Scalar::Util::blessed($value) and ("$value" eq "1" or !$value)) {
+ return;
+ }
+ if ($self->{coerce}{booleans} and (B::svref_2object(\$value)->FLAGS & B::SVp_NOK or $value =~ /^(true|false)$/)) {
+ $_[1] = $value ? Mojo::JSON->true : Mojo::JSON->false;
+ return;
+ }
+ }
+
return E $path, _expected(boolean => $value);
}
@@ -708,7 +796,7 @@ sub _validate_type_number {
return E $path, _expected($expected => $value);
}
unless (B::svref_2object(\$value)->FLAGS & (B::SVp_IOK | B::SVp_NOK) and 0 + $value eq $value and $value * 0 == 0) {
- return E $path, "Expected $expected - got string." if !$self->coerce or $value =~ /\D/;
+ return E $path, "Expected $expected - got string." if !$self->{coerce}{numbers} or $value =~ /\D/;
$_[1] = 0 + $value; # coerce input value
}
@@ -732,29 +820,55 @@ sub _validate_type_number {
sub _validate_type_object {
my ($self, $data, $path, $schema) = @_;
- my @errors;
+ my %required = map { ($_ => 1) } @{$schema->{required} || []};
+ my ($additional, @errors, %rules);
if (ref $data ne 'HASH') {
return E $path, _expected(object => $data);
}
-
- # make sure _validate_xxx() does not mess up original $data
- $data = {%$data};
-
if (defined $schema->{maxProperties} and $schema->{maxProperties} < keys %$data) {
push @errors, E $path, sprintf 'Too many properties: %s/%s.', int(keys %$data), $schema->{maxProperties};
}
if (defined $schema->{minProperties} and $schema->{minProperties} > keys %$data) {
push @errors, E $path, sprintf 'Not enough properties: %s/%s.', int(keys %$data), $schema->{minProperties};
}
- if ($schema->{properties}) {
- push @errors, $self->_validate_properties($data, $path, $schema);
+
+ while (my ($k, $r) = each %{$schema->{properties}}) {
+ push @{$rules{$k}}, $r if exists $data->{$k} or $required{$k};
+ }
+ while (my ($p, $r) = each %{$schema->{patternProperties}}) {
+ push @{$rules{$_}}, $r for grep { $_ =~ /$p/ } keys %$data;
+ }
+
+ # special case used internally
+ $rules{id} ||= [{type => 'string'}] if !$path and $data->{id};
+ $additional = exists $schema->{additionalProperties} ? $schema->{additionalProperties} : {};
+
+ if ($additional) {
+ $additional = {} unless ref $additional eq 'HASH';
+ $rules{$_} ||= [$additional] for keys %$data;
}
- if ($schema->{patternProperties}) {
- push @errors, $self->_validate_pattern_properties($data, $path, $schema);
+ elsif (my @keys = grep { !$rules{$_} } keys %$data) {
+ local $" = ', ';
+ return E $path, "Properties not allowed: @keys.";
}
- if (exists $schema->{additionalProperties}) {
- push @errors, $self->_validate_additional_properties($data, $path, $schema);
+
+ for my $k (keys %required) {
+ next if exists $data->{$k};
+ push @errors, E _path($path, $k), 'Missing property.';
+ delete $rules{$k};
+ }
+
+ for my $k (keys %rules) {
+ for my $r (@{$rules{$k}}) {
+ if (!exists $data->{$k} and exists $schema->{default}) {
+ $data->{$k} = $r->{default};
+ }
+ else {
+ push @errors, $self->_validate_type_enum($data->{$k}, _path($path, $k), $r) if $r->{enum};
+ push @errors, $self->_validate($data->{$k}, _path($path, $k), $r);
+ }
+ }
}
return @errors;
@@ -768,7 +882,7 @@ sub _validate_type_string {
return E $path, _expected(string => $value);
}
if (B::svref_2object(\$value)->FLAGS & (B::SVp_IOK | B::SVp_NOK) and 0 + $value eq $value and $value * 0 == 0) {
- return E $path, "Expected string - got number." unless $self->coerce;
+ return E $path, "Expected string - got number." unless $self->{coerce}{strings};
$_[1] = "$value"; # coerce input value
}
if ($schema->{format}) {
@@ -817,32 +931,34 @@ sub _guess_data_type {
return lc $ref if $ref and !$blessed;
return 'null' if !defined;
return 'boolean' if $blessed and "$_" eq "1" or "$_" eq "0";
- return 'integer' if /^\d+$/;
return 'number' if B::svref_2object(\$_)->FLAGS & (B::SVp_IOK | B::SVp_NOK) and 0 + $_ eq $_ and $_ * 0 == 0;
return $blessed || 'string';
}
sub _guess_schema_type {
- return 'object' if $_[0]->{additionalProperties};
- return 'object' if $_[0]->{patternProperties};
- return 'object' if $_[0]->{properties};
- return 'object' if defined $_[0]->{maxProperties} or defined $_[0]->{minProperties};
- return 'array' if $_[0]->{additionalItems};
- return 'array' if $_[0]->{items};
- return 'array' if $_[0]->{uniqueItems};
- return 'array' if defined $_[0]->{maxItems} or defined $_[0]->{minItems};
- return 'string' if $_[0]->{pattern};
- return 'string' if defined $_[0]->{maxLength} or defined $_[0]->{minLength};
- return 'number' if $_[0]->{multipleOf};
- return 'number' if defined $_[0]->{maximum} or defined $_[0]->{minimum};
- return 'enum' if $_[0]->{enum};
- return;
+ return _guessed_right($_[1], 'object') if $_[0]->{additionalProperties};
+ return _guessed_right($_[1], 'object') if $_[0]->{patternProperties};
+ return _guessed_right($_[1], 'object') if $_[0]->{properties};
+ return _guessed_right($_[1], 'object') if defined $_[0]->{maxProperties} or defined $_[0]->{minProperties};
+ return _guessed_right($_[1], 'array') if $_[0]->{additionalItems};
+ return _guessed_right($_[1], 'array') if $_[0]->{items};
+ return _guessed_right($_[1], 'array') if $_[0]->{uniqueItems};
+ return _guessed_right($_[1], 'array') if defined $_[0]->{maxItems} or defined $_[0]->{minItems};
+ return _guessed_right($_[1], 'string') if $_[0]->{pattern};
+ return _guessed_right($_[1], 'string') if defined $_[0]->{maxLength} or defined $_[0]->{minLength};
+ return _guessed_right($_[1], 'number') if $_[0]->{multipleOf};
+ return _guessed_right($_[1], 'number') if defined $_[0]->{maximum} or defined $_[0]->{minimum};
+ return 'enum' if $_[0]->{enum};
+ return undef;
}
-sub _is_byte_string { $_[0] =~ /^[A-Za-z0-9\+\/\=]+$/; }
-sub _is_date { $_[0] =~ qr/^(\d+)-(\d+)-(\d+)$/io; }
-sub _is_date_time { $_[0] =~ qr/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+(?:\.\d+)?)(?:Z|([+-])(\d+):(\d+))?$/io; }
-sub _is_domain { warn "Data::Validate::Domain is not installed"; return; }
+sub _guessed_right {
+ return $_[1] unless defined $_[0];
+ return _guess_data_type($_[0]) eq $_[1] ? $_[1] : undef;
+}
+
+sub _is_date_time { $_[0] =~ qr/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+(?:\.\d+)?)(?:Z|([+-])(\d+):(\d+))?$/io; }
+sub _is_domain { warn "Data::Validate::Domain is not installed"; return; }
sub _is_email {
state $email_rfc5322_re = do {
@@ -866,14 +982,10 @@ sub _is_ipv4 {
sub _is_ipv6 { warn "Data::Validate::IP is not installed"; return; }
-sub _is_number {
- return unless $_[0] =~ /^-?\d+(\.\d+)?$/;
- return $_[0] eq unpack $_[1], pack $_[1], $_[0];
-}
-
sub _is_true {
- return $_[0] if ref $_[0] and !Scalar::Util::blessed($_[0]);
- return 0 if !$_[0] or $_[0] =~ /^(n|false|off)/i;
+ local $_ = $_[0];
+ return 0 + $_ if ref $_ and !Scalar::Util::blessed($_);
+ return 0 if !$_ or /^(n|false|off)/i;
return 1;
}
@@ -882,9 +994,11 @@ sub _is_uri { $_[0] =~ qr!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*
# Please report if you need to manually monkey patch this function
# https://github.com/jhthorsen/json-validator/issues
sub _load_yaml {
- my @YAML_MODULES = qw( YAML::XS YAML::Syck YAML::Tiny YAML ); # subject to change
- my $YAML_MODULE = (grep { eval "require $_;1" } @YAML_MODULES)[0];
- die "Need to install a YAML module: @YAML_MODULES" unless $YAML_MODULE;
+ require List::Util;
+ my @YAML_MODULES = qw( YAML::XS YAML::Syck YAML::Tiny YAML ); # subject to change
+ my $YAML_MODULE = (List::Util::first { eval "require $_;1" } @YAML_MODULES)[0];
+ die "Need to install one of these YAML modules: @YAML_MODULES (YAML::XS is recommended)" unless $YAML_MODULE;
+ warn "[JSON::Validator] Using $YAML_MODULE to parse YAML\n" if DEBUG;
Mojo::Util::monkey_patch(__PACKAGE__, _load_yaml => eval "\\\&$YAML_MODULE\::Load");
_load_yaml(@_);
}
diff --git a/t/acceptance.t b/t/acceptance.t
index 330937e..ce30206 100644
--- a/t/acceptance.t
+++ b/t/acceptance.t
@@ -9,18 +9,14 @@ plan skip_all => 'cpanm Test::JSON::Schema::Acceptance' unless eval 'use Test::J
my $opts = {
only_test => $ENV{ACCEPTANCE_TEST},
skip_tests => [
- 'Unicode code point', # Valid unicode won't pass Mojo::JSON
- 'dependencies', # TODO
- 'ignores non', # Does it really..? How is input array a a valid object?
- 'invalid definition schema', # This module does not validate the schema, it only validates data
- 'multiple simultaneous', # TODO
- 'patternProperty invalidates property', # Does it really..?
- 'ref', # No way to fetch http://localhost:1234/...
- 'with base schema', # I don't see how this makes sense
+ 'Unicode code point', # Valid unicode won't pass Mojo::JSON
+ 'dependencies', # TODO
+ 'valid definition schema', # This module does not validate the schema, it only validates data
+ 'ref', # No way to fetch http://localhost:1234/...
],
};
-my @drafts = qw( 4 ); # ( 3 4 )
+my @drafts = qw( 4 ); # ( 3 4 )
for my $draft (@drafts) {
my $accepter = Test::JSON::Schema::Acceptance->new($draft);
diff --git a/t/booleans.t b/t/booleans.t
new file mode 100644
index 0000000..6ebb98f
--- /dev/null
+++ b/t/booleans.t
@@ -0,0 +1,35 @@
+use Mojo::Base -strict;
+use Test::More;
+use JSON::Validator;
+
+my $validator = JSON::Validator->new->schema(
+ {properties => {required => {type => "boolean", enum => [Mojo::JSON->true, Mojo::JSON->false]}}});
+
+$validator->coerce(booleans => 1);
+for my $value (!!1, !!0) {
+ my @errors = $validator->validate({required => $value});
+ ok !@errors, "boolean ($value). (@errors)";
+}
+
+for my $value (1, "1", "0", "") {
+ my @errors = $validator->validate({required => $value});
+ ok @errors, "not boolean ($value). @errors";
+}
+
+for my $value ("true", "false") {
+ my @errors = $validator->validate({required => $value});
+ ok !@errors, "boolean ($value). (@errors)";
+}
+
+
+if (eval 'require YAML::XS;1') {
+ $validator->coerce(booleans => 0); # see that _load_schema_from_text() turns it back on
+ my @errors = $validator->validate($validator->_load_schema_from_text("---\nrequired: true\n"));
+ ok !@errors, "true in YAML::XS is boolean. (@errors)";
+ ok $validator->coerce->{booleans}, 'coerce booleans';
+}
+else {
+ diag "YAML::XS is not installed";
+}
+
+done_testing;
diff --git a/t/deep-mixed-ref.t b/t/deep-mixed-ref.t
new file mode 100644
index 0000000..cf7a033
--- /dev/null
+++ b/t/deep-mixed-ref.t
@@ -0,0 +1,20 @@
+use Mojo::Base -strict;
+use Test::More;
+use JSON::Validator;
+
+my $file = File::Spec->catfile(File::Basename::dirname(__FILE__), 'spec', 'with-deep-mixed-ref.json');
+my $validator = JSON::Validator->new->schema($file);
+is $validator->schema->get('/properties/age/type'), 'integer', 'loaded age.json from disk';
+is $validator->schema->get('/properties/height/minimum'), '5', 'loaded height from file';
+is $validator->schema->get('/properties/weight/properties/unit/type'), 'string', 'loaded weight-unit type from file';
+
+use Mojolicious::Lite;
+push @{app->static->paths}, File::Basename::dirname(__FILE__);
+$validator->ua(app->ua);
+$validator->schema(app->ua->server->url->clone->path('/spec/with-relative-ref.json'));
+is $validator->schema->get('/properties/age/type'), 'integer', 'loaded age.json from http';
+
+my @errors = $validator->validate({age => 'not a number'});
+is int(@errors), 1, 'invalid age';
+
+done_testing;
diff --git a/t/definitions/age.json b/t/definitions/age.json
new file mode 100644
index 0000000..0011815
--- /dev/null
+++ b/t/definitions/age.json
@@ -0,0 +1,5 @@
+{
+ "type": "integer",
+ "minimum": 0,
+ "description": "Age in years"
+}
diff --git a/t/definitions/unit.json b/t/definitions/unit.json
new file mode 100644
index 0000000..dd8092b
--- /dev/null
+++ b/t/definitions/unit.json
@@ -0,0 +1,5 @@
+{
+ "type": "string",
+ "description": "Unit of Mass",
+ "pattern": "^kg|st|lb$"
+}
diff --git a/t/definitions/weight.json b/t/definitions/weight.json
new file mode 100644
index 0000000..15d39ae
--- /dev/null
+++ b/t/definitions/weight.json
@@ -0,0 +1,8 @@
+{
+ "type": "object",
+ "description": "Weight with Units",
+ "properties": {
+ "mass": { "type": "integer" },
+ "unit": { "$ref": "./unit.json" }
+ }
+}
diff --git a/t/invalid-ref.t b/t/invalid-ref.t
new file mode 100644
index 0000000..c23d3e8
--- /dev/null
+++ b/t/invalid-ref.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use Test::More;
+use JSON::Validator;
+
+my $validator = JSON::Validator->new;
+
+eval { $validator->schema('data://main/spec.json') };
+like $@, qr{Could not find "\#/definitions/Pet"}, 'missing definition';
+
+done_testing;
+
+__DATA__
+@@ spec.json
+{
+ "schema": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/Pet" }
+ }
+}
diff --git a/t/jv-allof.t b/t/jv-allof.t
index 0f6724d..c2c0a99 100644
--- a/t/jv-allof.t
+++ b/t/jv-allof.t
@@ -7,13 +7,13 @@ my $schema = {allOf => [{type => "string", maxLength => 5}, {type => "number", m
my @errors;
@errors = $validator->validate("short", $schema);
-is "@errors", "/: Expected number - got string.", "got string";
+is "@errors", "/: allOf failed: (Expected number - got string.)", "got string";
@errors = $validator->validate(12, $schema);
-is "@errors", "/: Expected string - got number.", "got number";
+is "@errors", "/: allOf failed: (Expected string - got number.)", "got number";
$schema = {allOf => [{type => "string", maxLength => 7}, {type => "string", maxLength => 5}]};
@errors = $validator->validate("toolong", $schema);
-is "@errors", "/: [1] String is too long: 7/5.", "too long";
+is "@errors", "/: allOf failed: ([1] String is too long: 7/5.)", "too long";
@errors = $validator->validate("short", $schema);
is "@errors", "", "success";
diff --git a/t/jv-anyof.t b/t/jv-anyof.t
index f26de71..5d74013 100644
--- a/t/jv-anyof.t
+++ b/t/jv-anyof.t
@@ -10,12 +10,12 @@ my @errors;
is "@errors", "", "short";
@errors = $validator->validate("too long", $schema);
-is "@errors", "/: [0] String is too long: 8/5. [1] Expected number - got string.", "too long";
+is "@errors", "/: anyOf failed: ([0] String is too long: 8/5. [1] Expected number - got string.)", "too long";
@errors = $validator->validate(12, $schema);
is "@errors", "", "number";
@errors = $validator->validate(-1, $schema);
-is "@errors", "/: [0] Expected string - got number. [1] -1 < minimum(0)", "negative";
+is "@errors", "/: anyOf failed: ([0] Expected string - got number. [1] -1 < minimum(0))", "negative";
done_testing;
diff --git a/t/jv-basic.t b/t/jv-basic.t
index d8c3bb8..19477c7 100644
--- a/t/jv-basic.t
+++ b/t/jv-basic.t
@@ -25,6 +25,9 @@ is "@errors", "", "boolean true";
@errors = $validator->validate(j("foo"), {type => 'boolean'});
is "@errors", "/: Expected boolean - got string.", "not boolean";
+ at errors = $validator->validate(undef, {properties => {}});
+is "@errors", "/: Expected object - got null.", "undef";
+
done_testing;
sub j {
diff --git a/t/jv-formats.t b/t/jv-formats.t
index 3c019f9..7a19d2e 100644
--- a/t/jv-formats.t
+++ b/t/jv-formats.t
@@ -7,22 +7,6 @@ my $schema = {type => 'object', properties => {v => {type => 'string'}}};
my @errors;
{
- $schema->{properties}{v}{format} = 'byte';
- @errors = $validator->validate({v => 'amh0aG9yc2Vu'}, $schema);
- is "@errors", "", "byte valid";
- @errors = $validator->validate({v => "\0"}, $schema);
- is "@errors", "/v: Does not match byte format.", "byte invalid";
-}
-
-{
- $schema->{properties}{v}{format} = 'date';
- @errors = $validator->validate({v => '2014-12-09'}, $schema);
- is "@errors", "", "date valid";
- @errors = $validator->validate({v => '2014-12-09T20:49:37Z'}, $schema);
- is "@errors", "/v: Does not match date format.", "date invalid";
-}
-
-{
$schema->{properties}{v}{format} = 'date-time';
@errors = $validator->validate({v => '2014-12-09T20:49:37Z'}, $schema);
is "@errors", "", "date-time valid";
@@ -31,14 +15,6 @@ my @errors;
}
{
- local $schema->{properties}{v}{type} = 'number';
- local $schema->{properties}{v}{format} = 'double';
- @errors = $validator->validate({v => 1.1000000238418599085576943252817727625370025634765626}, $schema);
- local $TODO = "cannot test double, since input is already rounded";
- is "@errors", "", "positive double valid";
-}
-
-{
local $schema->{properties}{v}{format} = 'email';
@errors = $validator->validate({v => 'jhthorsen at cpan.org'}, $schema);
is "@errors", "", "email valid";
@@ -46,18 +22,6 @@ my @errors;
is "@errors", "/v: Does not match email format.", "email invalid";
}
-{
- local $TODO = 'No idea how to test floats';
- local $schema->{properties}{v}{type} = 'number';
- local $schema->{properties}{v}{format} = 'float';
- @errors = $validator->validate({v => -1.10000002384186}, $schema);
- is "@errors", "", "negative float valid";
- @errors = $validator->validate({v => 1.10000002384186}, $schema);
- is "@errors", "", "positive float valid";
- @errors = $validator->validate({v => 0.10000000000000}, $schema);
- is "@errors", "/v: Does not match float format.", "float invalid";
-}
-
if (JSON::Validator::VALIDATE_HOSTNAME) {
local $schema->{properties}{v}{format} = 'hostname';
@errors = $validator->validate({v => 'mojolicio.us'}, $schema);
@@ -89,28 +53,6 @@ else {
}
{
- local $schema->{properties}{v}{type} = 'integer';
- local $schema->{properties}{v}{format} = 'int32';
- @errors = $validator->validate({v => -2147483648}, $schema);
- is "@errors", "", "negative int32 valid";
- @errors = $validator->validate({v => 2147483647}, $schema);
- is "@errors", "", "positive int32 valid";
- @errors = $validator->validate({v => 2147483648}, $schema);
- is "@errors", "/v: Does not match int32 format.", "int32 invalid";
-}
-
-if (JSON::Validator::IV_SIZE >= 8) {
- local $schema->{properties}{v}{type} = 'integer';
- local $schema->{properties}{v}{format} = 'int64';
- @errors = $validator->validate({v => -9223372036854775808}, $schema);
- is "@errors", "", "negative int64 valid";
- @errors = $validator->validate({v => 9223372036854775807}, $schema);
- is "@errors", "", "positive int64 valid";
- @errors = $validator->validate({v => 9223372036854775808}, $schema);
- is "@errors", "/v: Does not match int64 format.", "int64 invalid";
-}
-
-{
local $schema->{properties}{v}{format} = 'uri';
@errors = $validator->validate({v => 'http://mojolicio.us/?ø=123'}, $schema);
is "@errors", "", "uri valid";
diff --git a/t/jv-integer.t b/t/jv-integer.t
index 26c14ee..6465457 100644
--- a/t/jv-integer.t
+++ b/t/jv-integer.t
@@ -30,7 +30,7 @@ $schema->{properties}{mynumber}{multipleOf} = 2;
@errors = $validator->validate({mynumber => 3}, $schema);
is "@errors", "/mynumber: Not multiple of 2.", "multipleOf";
-$validator->coerce(1);
+$validator->coerce(numbers => 1);
@errors = $validator->validate({mynumber => "2"}, $schema);
is "@errors", "", "coerced string into integer";
diff --git a/t/jv-oneof.t b/t/jv-oneof.t
index a775d86..a3e9838 100644
--- a/t/jv-oneof.t
+++ b/t/jv-oneof.t
@@ -17,13 +17,13 @@ is "@errors", "", "n:10";
@errors = $validator->validate(9, $schema);
is "@errors", "", "n:9";
@errors = $validator->validate(15, $schema);
-is "@errors", "/: Expected only one to match.", "n:15";
+is "@errors", "/: All of the oneOf rules match.", "n:15";
# Alternative oneOf
# http://json-schema.org/latest/json-schema-validation.html#anchor79
$schema = {type => 'object', properties => {x => {type => ['string', 'null'], format => 'date-time'}}};
@errors = $validator->validate({x => 'foo'}, $schema);
-is "@errors", "/x: [0] Does not match date-time format. [1] Not null.", "foo";
+is "@errors", "/x: ([0] Does not match date-time format. [1] Not null.)", "foo";
@errors = $validator->validate({x => '2015-04-21T20:30:43.000Z'}, $schema);
is "@errors", "", "YYYY-MM-DDThh:mm:ss.fffZ";
diff --git a/t/jv-required.t b/t/jv-required.t
index a2b0af4..6be2b73 100644
--- a/t/jv-required.t
+++ b/t/jv-required.t
@@ -1,24 +1,27 @@
use Mojo::Base -strict;
use Test::More;
-use JSON::Validator;
+use JSON::Validator 'validate_json';
-my $validator = JSON::Validator->new;
-my $schema1 = {type => 'object', properties => {mynumber => {type => 'string', required => 1}}};
-my $schema2 = {type => 'object', properties => {mynumber => {type => 'string', required => 0}}};
+my $schema0 = {type => 'object', properties => {mynumber => {type => 'string', required => 1}}};
+my $schema1 = {type => 'object', properties => {mynumber => {type => 'string'}}, required => ['mynumber']};
+my $schema2 = {type => 'object', properties => {mynumber => {type => 'string'}}};
my $data1 = {mynumber => "yay"};
my $data2 = {mynumbre => "err"};
-my @errors = $validator->validate($data1, $schema1);
+my @errors = validate_json $data1, $schema1;
is "@errors", "", "data1, schema1";
- at errors = $validator->validate($data2, $schema1);
+ at errors = validate_json $data2, $schema0;
+is "@errors", "", "cannot have required on properties";
+
+ at errors = validate_json $data2, $schema1;
is "@errors", "/mynumber: Missing property.", "data2, schema1";
- at errors = $validator->validate($data1, $schema2);
+ at errors = validate_json $data1, $schema2;
is "@errors", "", "data1, schema2";
- at errors = $validator->validate($data2, $schema2);
+ at errors = validate_json $data2, $schema2;
is "@errors", "", "data2, schema2";
done_testing;
diff --git a/t/jv-union.t b/t/jv-union.t
deleted file mode 100644
index 05db74b..0000000
--- a/t/jv-union.t
+++ /dev/null
@@ -1,112 +0,0 @@
-use Mojo::Base -strict;
-use Test::More;
-use JSON::Validator;
-use Mojo::JSON;
-
-my $validator = JSON::Validator->new;
-
-my $schema1 = {
- type => 'object',
- additionalProperties => 0,
- properties => {test => {type => ['boolean', 'integer'], required => 1}}
-};
-
-my $schema2 = {
- type => 'object',
- additionalProperties => 0,
- properties => {
- test => {
- type => [
- {type => "object", additionalProperties => 0, properties => {dog => {type => "string", required => 1}}},
- {
- type => "object",
- additionalProperties => 0,
- properties => {sound => {type => 'string', enum => ["bark", "meow", "squeak"], required => 1}}
- }
- ],
- required => 1
- }
- }
-};
-
-my $schema3 = {
- type => 'object',
- additionalProperties => 0,
- properties => {test => {type => [qw/object array string number integer boolean null/], required => 1}}
-};
-
-my @errors = $validator->validate({test => "strang"}, $schema1);
-is "@errors", "/test: Expected boolean, integer - got string.", 'boolean or integer against string';
-
- at errors = $validator->validate({test => 1}, $schema1);
-is "@errors", "", 'boolean or integer against integer';
-
- at errors = $validator->validate({test => ['array']}, $schema1);
-is "@errors", "/test: Expected boolean, integer - got array.", 'boolean or integer against array';
-
- at errors = $validator->validate({test => {object => 'yipe'}}, $schema1);
-is "@errors", "/test: Expected boolean, integer - got object.", 'boolean or integer against object';
-
- at errors = $validator->validate({test => 1.1}, $schema1);
-is "@errors", "/test: Expected boolean, integer - got number.", 'boolean or integer against number';
-
- at errors = $validator->validate({test => !!1}, $schema1);
-is "@errors", "", 'boolean or integer against boolean';
-
- at errors = $validator->validate({test => undef}, $schema1);
-is "@errors", "/test: Expected boolean, integer - got null.", 'boolean or integer against null';
-
- at errors = $validator->validate({test => {dog => "woof"}}, $schema2);
-is "@errors", "", 'object or object against object a';
-
- at errors = $validator->validate({test => {sound => "meow"}}, $schema2);
-is "@errors", "", 'object or object against object b nested enum pass';
-
- at errors = $validator->validate({test => {sound => "oink"}}, $schema2);
-is $errors[0], '/test: [0] Properties not allowed: sound. [1] Not in enum list: bark, meow, squeak.', '/test';
-is $errors[1], '/test/dog: [0] Missing property.', '/test/dog';
-
- at errors = $validator->validate({test => {audible => "meow"}}, $schema2);
-is $errors[0], '/test: [0] Properties not allowed: audible.', '/test';
-is $errors[1], '/test/dog: [0] Missing property.', '/test/dog';
-is $errors[2], '/test/sound: [1] Missing property.', '/test/sound';
-
- at errors = $validator->validate({test => 2}, $schema2);
-is "@errors", "/test: Expected object - got integer.", "object or object against integer";
-
- at errors = $validator->validate({test => 2.2}, $schema2);
-is "@errors", "/test: Expected object - got number.", "object or object against number";
-
- at errors = $validator->validate({test => Mojo::JSON->true}, $schema2);
-is "@errors", "/test: Expected object - got boolean.", "object or object against boolean";
-
- at errors = $validator->validate({test => undef}, $schema2);
-is "@errors", "/test: Expected object - got null.", "object or object against null";
-
- at errors = $validator->validate({test => {dog => undef}}, $schema2);
-is $errors[0], "/test: [1] Properties not allowed: dog.", "object or object against object a bad inner type";
-is $errors[1], "/test/dog: Expected string - got null.", "object or object against object a bad inner type";
-is $errors[2], "/test/sound: [1] Missing property.", "object or object against object a bad inner type";
-
- at errors = $validator->validate({test => {dog => undef}}, $schema3);
-is "@errors", "", 'all types against object';
-
- at errors = $validator->validate({test => ['dog']}, $schema3);
-is "@errors", "", 'all types against array';
-
- at errors = $validator->validate({test => 'dog'}, $schema3);
-is "@errors", "", 'all types against string';
-
- at errors = $validator->validate({test => 1.1}, $schema3);
-is "@errors", "", 'all types against number';
-
- at errors = $validator->validate({test => 1}, $schema3);
-is "@errors", "", 'all types against integer';
-
- at errors = $validator->validate({test => 1}, $schema3);
-is "@errors", "", 'all types against boolean';
-
- at errors = $validator->validate({test => undef}, $schema3);
-is "@errors", "", 'all types against null';
-
-done_testing;
diff --git a/t/load-json.t b/t/load-json.t
index f3c27ab..355c07e 100644
--- a/t/load-json.t
+++ b/t/load-json.t
@@ -3,7 +3,7 @@ use Test::More;
use JSON::Validator;
use Mojo::Util qw( slurp spurt );
-my $file = File::Spec->catfile(File::Basename::dirname(__FILE__), 'spec.json');
+my $file = File::Spec->catfile(File::Basename::dirname(__FILE__), 'spec', 'person.json');
my $validator = JSON::Validator->new->schema($file);
my @errors = $validator->validate({firstName => 'yikes!'});
@@ -13,7 +13,7 @@ is $errors[0]->message, 'Missing property.', 'required';
is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON';
my $spec = slurp $file;
-$spec =~ s!"#!"$file#! or die "Invalid spec: $spec";
+$spec =~ s!"#!"person.json#! or die "Invalid spec: $spec";
spurt $spec => "$file.2";
ok eval { JSON::Validator->new->schema("$file.2") }, 'test issue #1 where $ref could not point to a file' or diag $@;
unlink "$file.2";
diff --git a/t/petstore.t b/t/petstore.t
new file mode 100644
index 0000000..eeadca8
--- /dev/null
+++ b/t/petstore.t
@@ -0,0 +1,31 @@
+use Mojo::Base -strict;
+use Test::More;
+use JSON::Validator;
+
+my $validator = JSON::Validator->new;
+
+$validator->schema(File::Spec->catfile(qw( t spec petstore.json )));
+
+is_deeply(
+ $validator->schema->get('/paths/~1pets/get/responses/200/schema/items'),
+ {
+ required => ["id", "name"],
+ properties => {id => {type => "integer", format => "int64"}, name => {type => "string"}, tag => {type => "string"}}
+ },
+ 'expanded /paths/~1pets/get/responses/200/schema/items'
+);
+
+ok !find_key($validator->schema->data, '$ref'), 'no $ref in schema';
+
+done_testing;
+
+sub find_key {
+ my ($data, $needle) = @_;
+
+ for my $k (keys %$data) {
+ return 1 if $k eq $needle;
+ return 1 if ref $data->{$k} eq 'HASH' and find_key($data->{$k}, $needle);
+ }
+
+ return 0;
+}
diff --git a/t/relative-ref.t b/t/relative-ref.t
new file mode 100644
index 0000000..e359d4f
--- /dev/null
+++ b/t/relative-ref.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use Test::More;
+use JSON::Validator;
+use Mojo::Util qw( slurp spurt );
+
+my $file = File::Spec->catfile(File::Basename::dirname(__FILE__), 'spec', 'with-relative-ref.json');
+my $validator = JSON::Validator->new->schema($file);
+is $validator->schema->get('/properties/age/type'), 'integer', 'loaded age.json from disk';
+
+use Mojolicious::Lite;
+push @{app->static->paths}, File::Basename::dirname(__FILE__);
+$validator->ua(app->ua);
+$validator->schema(app->ua->server->url->clone->path('/spec/with-relative-ref.json'));
+is $validator->schema->get('/properties/age/type'), 'integer', 'loaded age.json from http';
+
+my @errors = $validator->validate({age => 'not a number'});
+is int(@errors), 1, 'invalid age';
+
+done_testing;
diff --git a/t/spec/invalid-ref.t b/t/spec/invalid-ref.t
new file mode 100644
index 0000000..e69de29
diff --git a/t/spec.json b/t/spec/person.json
similarity index 100%
rename from t/spec.json
rename to t/spec/person.json
diff --git a/t/spec/petstore.json b/t/spec/petstore.json
new file mode 100644
index 0000000..fd81803
--- /dev/null
+++ b/t/spec/petstore.json
@@ -0,0 +1,52 @@
+{
+ "parameters": {
+ "limit": {
+ "name": "limit",
+ "in": "query",
+ "description": "How many items to return at one time (max 100)",
+ "required": false,
+ "type": "integer",
+ "format": "int32"
+ }
+ },
+ "paths": {
+ "/pets": {
+ "get": {
+ "tags": [ "pets" ],
+ "parameters": [
+ { "$ref": "#/parameters/limit" }
+ ],
+ "responses": {
+ "200": {
+ "description": "pet response",
+ "schema": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/Pet" }
+ }
+ },
+ "default": {
+ "description": "unexpected error",
+ "schema": { "$ref": "#/definitions/Error" }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "Pet": {
+ "required": [ "id", "name" ],
+ "properties": {
+ "id": { "type": "integer", "format": "int64" },
+ "name": { "type": "string" },
+ "tag": { "type": "string" }
+ }
+ },
+ "Error": {
+ "required": [ "code", "message" ],
+ "properties": {
+ "code": { "type": "integer", "format": "int32" },
+ "message": { "type": "string" }
+ }
+ }
+ }
+}
diff --git a/t/spec/with-deep-mixed-ref.json b/t/spec/with-deep-mixed-ref.json
new file mode 100644
index 0000000..dcc3f23
--- /dev/null
+++ b/t/spec/with-deep-mixed-ref.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "properties": {
+ "age": { "$ref": "../definitions/age.json#" },
+ "weight": { "$ref": "../definitions/weight.json" },
+ "height": { "$ref": "#/definitions/height" }
+ },
+ "definitions": {
+ "height": { "type": "integer", "minimum": 5 }
+ }
+}
diff --git a/t/spec/with-relative-ref.json b/t/spec/with-relative-ref.json
new file mode 100644
index 0000000..dcc6b40
--- /dev/null
+++ b/t/spec/with-relative-ref.json
@@ -0,0 +1,6 @@
+{
+ "type": "object",
+ "properties": {
+ "age": { "$ref": "../definitions/age.json#" }
+ }
+}
diff --git a/t/swagger-validate-response-object.t b/t/swagger-validate-response-object.t
new file mode 100644
index 0000000..be32c28
--- /dev/null
+++ b/t/swagger-validate-response-object.t
@@ -0,0 +1,82 @@
+use Mojo::Base -strict;
+use Test::Mojo;
+use Test::More;
+
+#
+# This test used to fail with error messages such as:
+# /paths/~1commands/get/responses/default: Expected only one to match.
+# /info/title: Missing property.
+# /definitions/Error/properties/errors/items: [0] Properties not allowed: message, path. [1] Expected array - got object.
+# The message that tripped me off was "Expected only one to match." which
+# made me believe there was a bug in JSON::Validator. The real reason was
+# however that spec.json contained an invalid spec. Such as "required":true,
+# instead of "required":["message"].
+# I think it would be nice to have better error messages, but at right now I
+# don't have the capacity to figure out how to make those.
+#
+# - Jan Henning Thorsen
+#
+
+plan skip_all => 'Swagger2 is required' unless eval 'require Swagger2;1';
+my $swagger = Swagger2->new('data://main/spec.json');
+my @errors = $swagger->validate;
+ok !@errors, 'no errors in spec.json' or diag join "\n", @errors;
+done_testing;
+
+__DATA__
+@@ spec.json
+{
+ "swagger": "2.0",
+ "info": {
+ "title": "(part of) Convos API specification",
+ "version": "0.87"
+ },
+ "host": "demo.convos.by",
+ "basePath": "/1.0",
+ "schemes": [ "http" ],
+ "paths": {
+ "/commands": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "List of commands.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "commands": { "type": "array", "$ref": "#/definitions/Command" }
+ }
+ }
+ },
+ "default": {
+ "description": "Error.",
+ "schema": { "$ref": "#/definitions/Error" }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "Command": {
+ "required": ["id", "command"],
+ "properties": {
+ "id": { "type": "string", "description": "jhthorsen: Cannot remember what 'id' is." },
+ "command": { "type": "string", "description": "A command to be sent to backend" }
+ }
+ },
+ "Error": {
+ "properties": {
+ "errors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["message"],
+ "properties": {
+ "message": { "type": "string", "description": "Human readable description of the error" },
+ "path": { "type": "string", "description": "JSON pointer to the input data where the error occur" }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/t/validate-json.t b/t/validate-json.t
index 92fdd30..8085355 100644
--- a/t/validate-json.t
+++ b/t/validate-json.t
@@ -22,5 +22,6 @@ __DATA__
@@ spec.json
{
"type": "object",
- "properties": { "name": { "required": true, "type": "string" } }
+ "properties": { "name": { "type": "string" } },
+ "required": ["name"]
}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libjson-validator-perl.git
More information about the Pkg-perl-cvs-commits
mailing list