[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