JSON-Validator-1.08/000755 000765 000024 00000000000 13217716317 015315 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/run-all-tests.sh000644 000765 000024 00000001555 13217715761 020373 0ustar00jhthorsenstaff000000 000000 #!/bin/sh # Usage: # sh run-all-tests.sh -j8 # PROJECT=json-validator sh run-all-tests.sh -j8 # HASH_ITERATIONS=10 sh run-all-tests.sh -v t/plugin-yaml.t # PERL_HASH_SEED=8 sh run-all-tests.sh -v t/plugin-yaml.t export PERL5LIB="$PWD/lib:$PERL5LIB"; # export SWAGGER2_DEBUG=1; t () { echo "\$ cd ../$PROJECT && prove -l $*"; cd ../$PROJECT && prove -l "$@" || exit $?; } if [ -n "$PERL_HASH_SEED" ]; then export PERL_PERTURB_KEYS=NO; fi HASH_ITERATIONS=${HASH_ITERATIONS:-0} if [ $HASH_ITERATIONS -gt 0 ]; then for i in $(seq 1 $HASH_ITERATIONS); do export HASH_ITERATIONS=0; export PERL_HASH_SEED=$i; echo "\$ export PERL_HASH_SEED=$PERL_HASH_SEED"; sh $0 $@ || break done elif [ "x$PROJECT" != "x" ]; then t "$@"; else PROJECT=json-validator t "$@"; PROJECT=mojolicious-plugin-openapi t "$@"; PROJECT=openapi-client t "$@"; fi exit $?; JSON-Validator-1.08/cpanfile000644 000765 000024 00000000675 13057337744 017036 0ustar00jhthorsenstaff000000 000000 # You can install this projct with curl -L http://cpanmin.us | perl - https://github.com/jhthorsen/json-validator/archive/master.tar.gz requires "Mojolicious" => "7.15"; recommends "Cpanel::JSON::XS" => "3.02"; recommends "Data::Validate::Domain" => "0.10"; recommends "Data::Validate::IP" => "0.24"; recommends "Mojo::JSON::MaybeXS" => "0.01"; recommends "YAML::XS" => "0.59"; test_requires "Test::More" => "1.30"; JSON-Validator-1.08/Changes000644 000765 000024 00000020242 13217716316 016607 0ustar00jhthorsenstaff000000 000000 Revision history for perl distribution JSON-Validator 1.08 2017-12-24T13:25:50+0100 - Fix setting default value from $ref jhthorsen/mojolicious-plugin-openapi#53 - Skip load-from-app.t if "Service Unavailable" 1.07 2017-11-27T17:37:51+0100 - Can load schame from internal app #85 1.06 2017-11-19T20:11:26+0100 - Add JSON::Validator::get() - Add JSON::Validator::bundle() - A $ref is tied hashes, represented by JSON::Validator::Ref 1.05 2017-10-22T20:18:51+0200 - Fix validating headers regardless of case #77 Contributor: Aleksandr Orlenko - Improved boolean handling #76 Contributor: Aleksandr Orlenko - Improved URI validation, fixes #74 - Resolving "$ref" on the fly #65 #75 #79 1.04 2017-10-05T20:36:33+0200 - Avoid autovivification of "patternProperties" in the input schema #47 1.03 2017-09-25T18:37:24+0200 - Fix "uri" format validation, closes #70 1.02 2017-09-01T09:46:20+0200 - Fix validating "type" and "enum" #69 1.01 2017-08-19T19:00:33+0200 - Add support for fetching specification from local application 1.00 2017-06-20T00:02:19+0200 - Removed EXPERIMENTAL from JSON::Validator::OpenAPI (1.00) - Coerce integer numbers into booleans #67 Contributor: @fabzzap 0.99 2017-06-12T20:58:26+0200 - Hopefully fixed some Windows issues #60 0.98 2017-05-21T23:10:39+0200 - Add support for "const" #62 Contributor: Kevin Goess 0.97 2017-03-21T23:31:27+0100 - Require a newer version of Test::More to build 0.96 2017-03-06T20:53:35+0100 - Fix JSON::Validator::load_and_validate_schema() - Add handling of header/formData/query as array #38 - Allow alternative date-time separator #49 - Improved recursion tracking #52 - More tests in t/acceptance.t are ok #52 - Avoid loading the same file multiple times #54 - Swagger2 is deprecated 0.95 2017-03-02T22:22:10+0100 - Add support for format "password" - Add load_and_validate_schema() to JSON::Validator #51 - Started deprecating load_and_validate_spec() 0.94 2017-02-13T09:33:20+0100 - Fix t/issue-27-yaml-syck-false.t - Removed Carp::Always #47 0.93 2017-02-13T01:04:54+0100 - Fix coercing YAML booleans in input specification jhthorsen/mojolicious-plugin-openapi#24 - Replace JSON_VALIDATOR_CACHE_DIR with JSON_VALIDATOR_CACHE_PATH - Remove deprecated cache_dir attribute 0.92 2017-01-18T11:54:03+0000 - Fix infinite recursion when resolving self referencing data structures 0.91 2017-01-10T23:02:36+0100 - Mojo::Util::slurp is DEPRECATED in favor of Mojo::File::slurp 0.90 2016-12-11T11:38:38+0100 - Add support for validating Dancer2 requset/responses #34 - Fix invalidating integer/number path part with letters #37 0.89 2016-11-05T01:36:52+0100 - Fix multipleOf:0.01, closes #35 0.88 2016-11-04T23:18:26+0100 - Fix number coercion #32 Contributor: @melhesedek - Add JSON::Validator::OpenAPI->load_and_validate_spec() 0.87 2016-10-20T22:59:45+0200 - Fix validating data when boolean.pm is loaded 0.86 2016-10-06T18:36:36+0200 - Documented bundled resources 0.85 2016-09-26T22:30:21+0200 - Fix handling of collectionFormat where no input is defined 0.84 2016-08-19T19:11:51+0200 - Removed support for passing $json_path to validate() - Fix guessing type of objects that has TO_JSON() 0.83 2016-08-16T19:37:04+0200 - Fix handling of true/false in schema, when loaded with YAML::Syck #27 - Add EXPERIMENTAL support for passing $json_path to validate() 0.82 2016-08-09T12:45:05+0200 - Fix finding all $ref occurances jhthorsen/swagger2#95 0.81 2016-08-08T10:03:43+0200 - Add support for multiple cache dir search paths - Deperecated cache_dir() - Fix recurring requests with same path part jhthorsen/swagger2#93 - Fix "Use of uninitialized value $schema_type..." warnings 0.80 2016-08-03T19:12:40+0200 - Fix parsing recursive schema 0.79 2016-07-28T21:14:47+0200 - Reverted improved allOf, anyOf and oneOf error messages 0.78 2016-07-28T20:51:31+0200 - Fix recursive dependencies #23 - Add EXPERIMENTAL resolver attribute - Improved allOf, anyOf and oneOf error messages 0.77 2016-07-26T15:03:13+0200 - Avoid duplicate error messages with enum #22 - Fix "false" must be false and not true in OpenAPI 0.76 2016-07-25T14:49:27+0200 - Will write default values into Mojolicious::Controller 0.75 2016-07-02T14:44:31+0200 - Fix uploads must not be slurped - Fix reporting error on missing response status definition - Add warnings on invalid (Perl) regexes 0.74 2016-06-22T19:26:14+0200 - Fix length($data) need be defined in 5.10 0.73 2016-06-22T19:12:10+0200 - Add http://git.io/vcKD4 error schema to cache - Add JSON schema for JSONPatch files - Updated Swagger2 spec to https://github.com/OAI/OpenAPI-Specification/blob/19fed9f0f812ccebe0fc45313fea75bb6656de1c/schemas/v2.0/schema.json 0.72 2016-06-10T19:22:52-0700 - Fix default cache_dir() path - JSON::Validator is no longer EXPERIMENTAL - Move Swagger2::SchemaValidator into JSON::Validator::OpenAPI 0.71 2016-06-07T15:29:35-0500 - Fix setting schema() inside validate() 0.70 2016-05-31T13:38:04+0200 - Fix allowing "id" as property name in objects 0.69 2016-05-26T19:36:53+0200 - Fix failing anyOf logic in t/swagger-validate-response-object.t 0.68 2016-05-25T17:35:26+0100 - Remove _merge_error to clarify anyOf errors #15 0.67 2016-04-11T13:49:55+0200 - Add JSON::Validator::Error class 0.66 2016-02-09T18:32:57+0100 - Fix validating recursive datastructures 0.65 2016-01-07T15:31:37+0100 - Fix t/swagger-validate-response-object.t require Swagger2 0.66 #14 0.64 2015-12-18T16:03:09+0800 - Fix treating JSON::PP::Boolean objects as boolean #13 Contributor: Krasimir Berov - Allow hash reference as arguments to coerce #13 Contributor: Krasimir Berov 0.63 2015-11-28T08:17:59+0100 - Fix skip test in t/booleans.t 0.62 2015-11-27T12:13:08+0100 - Remove support for YAML.pm #jhthorsen/swagger2#50 - Remove support for YAML::Tiny #jhthorsen/swagger2#50 0.61 2015-11-11T21:01:13+0100 - Fix use of TO_JSON() on objects inside arrays #12 0.60 2015-11-09T14:35:56+0000 - Can use TO_JSON() when validating perl objects 0.59 2015-10-14T17:19:53+0200 - Move "collectionFormat" support to Swagger2 0.58 2015-10-13T14:47:25+0200 - Fix string "0" is not detected as boolean 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" - Fix additionalItems are allowed by default - Fix additionalProperties allows a schema which should validate - Fix validating "enum" - Fix validating "array" against "additionalItems" - Fix bugs after running https://github.com/Relequestual/Test-JSON-Schema-Acceptance to validate 0.51 2015-08-24T16:19:06+0200 - Fix "$ref" pointing to a file on disk #1 0.50 2015-08-23T15:07:08+0200 - Fix missing namespace when registering new document - Made cache_dir() public - Bundled spec for json-schema and swagger 0.49 2015-08-23T00:51:08+0200 - Fix loading schema from files 0.48 2015-08-22T21:23:55+0200 - Merged core functionality from Swagger2 and Swagger2::SchemaValidator into this module, JSON::Validator See https://metacpan.org/source/JHTHORSEN/Swagger2-0.47/Changes for previous Changes (<=0.47) - Fix coercing collectionFormat strings into integers and numbers - Add support for reading schemas from __DATA__ section JSON-Validator-1.08/MANIFEST000644 000765 000024 00000005606 13217716317 016455 0ustar00jhthorsenstaff000000 000000 .perltidyrc .ship.conf .travis.yml Changes cpanfile lib/JSON/Validator.pm lib/JSON/Validator/cache/36d1bd12eeed51e86c8695bd8876a9df lib/JSON/Validator/cache/49c95b866e40f788892a7fb3c816b0e8 lib/JSON/Validator/cache/630949337805585c8e52deea27d11419 lib/JSON/Validator/cache/a0f5b4b4e75ea17fc09e88ec0343d148 lib/JSON/Validator/cache/ea34d47d4e060a1c3b12d2287aff89a7 lib/JSON/Validator/cache/eaa832720f36cff0abc20c05236a9cd9 lib/JSON/Validator/Error.pm lib/JSON/Validator/OpenAPI.pm lib/JSON/Validator/OpenAPI/Dancer2.pm lib/JSON/Validator/OpenAPI/Mojolicious.pm lib/JSON/Validator/Ref.pm Makefile.PL MANIFEST This list of files README run-all-tests.sh t/00-basic.t t/acceptance.t t/booleans-xs.t t/booleans.t t/bundle.t t/coerce-args.t t/deep-mixed-ref.t t/definitions/age.json t/definitions/unit.json t/definitions/weight.json t/draft4-tests/additionalItems.json t/draft4-tests/additionalProperties.json t/draft4-tests/allOf.json t/draft4-tests/anyOf.json t/draft4-tests/default.json t/draft4-tests/definitions.json t/draft4-tests/dependencies.json t/draft4-tests/enum.json t/draft4-tests/items.json t/draft4-tests/maximum.json t/draft4-tests/maxItems.json t/draft4-tests/maxLength.json t/draft4-tests/maxProperties.json t/draft4-tests/minimum.json t/draft4-tests/minItems.json t/draft4-tests/minLength.json t/draft4-tests/minProperties.json t/draft4-tests/multipleOf.json t/draft4-tests/not.json t/draft4-tests/oneOf.json t/draft4-tests/optional/bignum.json t/draft4-tests/optional/format.json t/draft4-tests/optional/zeroTerminatedFloats.json t/draft4-tests/pattern.json t/draft4-tests/patternProperties.json t/draft4-tests/properties.json t/draft4-tests/ref.json t/draft4-tests/refRemote.json t/draft4-tests/required.json t/draft4-tests/type.json t/draft4-tests/uniqueItems.json t/Helper.pm t/id-keyword.t t/invalid-ref.t t/issue-22-duplicate-error-messages.t t/issue-27-yaml-syck-false.t t/issue-42-cache-control.t t/issue-59-oneof-blessed-booleans.t t/issue-71-additionalproperties.t t/jv-allof.t t/jv-anyof.t t/jv-array.t t/jv-basic.t t/jv-boolean.t t/jv-const.t t/jv-enum.t t/jv-formats.t t/jv-integer.t t/jv-not.t t/jv-number.t t/jv-object.t t/jv-oneof.t t/jv-required.t t/jv-string.t t/load-and-validate-spec.t t/load-data.t t/load-from-app.t t/load-http.t t/load-json.t t/load-yaml.t t/openapi-compatibility.t t/openapi-formats.t t/openapi-request.t t/openapi-response.t t/openapi.t t/recursion.t t/relative-ref.t t/remotes/folder/folderInteger.json t/remotes/integer.json t/remotes/subSchemas.json t/schema-as-attr.t t/spec/invalid-ref.t t/spec/person.json t/spec/petstore.json t/spec/ref-same-file-at-many-levels.json t/spec/with-deep-mixed-ref.json t/spec/with-relative-ref.json t/to-json.t t/validate-id.t t/validate-json.t t/validate-recursive.t t/validate-schema.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) JSON-Validator-1.08/t/000755 000765 000024 00000000000 13217716317 015560 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/README000644 000765 000024 00000027474 13207037537 016212 0ustar00jhthorsenstaff000000 000000 NAME JSON::Validator - Validate data against a JSON schema VERSION 1.07 SYNOPSIS use JSON::Validator; my $validator = JSON::Validator->new; # Define a schema - http://json-schema.org/examples.html # You can also load schema from disk or web $validator->schema( { type => "object", required => ["firstName", "lastName"], properties => { firstName => {type => "string"}, lastName => {type => "string"}, age => {type => "integer", minimum => 0, description => "Age in years"} } } ); # Validate your data @errors = $validator->validate({firstName => "Jan Henning", lastName => "Thorsen", age => -42}); # Do something if any errors was found die "@errors" if @errors; DESCRIPTION JSON::Validator is a class for validating data against JSON schemas. You might want to use this instead of JSON::Schema if you need to validate data against draft 4 of the specification. This module can be used standalone, but if you want to define a specification for your webserver's API, then have a look at Mojolicious::Plugin::OpenAPI, which will replace Mojolicious::Plugin::Swagger2. Supported schema formats JSON::Validator can load JSON schemas in multiple formats: Plain perl data structured (as shown in "SYNOPSIS") or files on disk/web in the JSON/YAML format. The JSON parsing is done using Mojo::JSON, while the YAML parsing is done with an optional modules which need to be installed manually. JSON::Validator will look for the YAML modules in this order: YAML::XS, YAML::Syck. The order is set by which module that performs the best, so it might change in the future. Resources Here are some resources that are related to JSON schemas and validation: * * * * Swagger2 Bundled specifications This module comes with some JSON specifications bundled, so your application don't have to fetch those from the web. These specifications should be up to date, but please submit an issue if they are not. Files referenced to an URL will automatically be cached if the first element in "cache_paths" is a writable directory. Note that the cache headers for the remote assets are not honored, so you will manually need to remove any cached file, should you need to refresh them. To download and cache an online asset, do this: JSON_VALIDATOR_CACHE_PATH=/some/writable/directory perl myapp.pl Here is the list of the bundled specifications: * JSON schema, draft 4 Web page: $ref: * JSON schema for JSONPatch files Web page: $ref: * Swagger / OpenAPI specification, version 2 Web page: $ref: * Custom error document There is a custom schema used by Mojolicious::Plugin::OpenAPI as a default error document. This document might be extended later, but it will always be backward compatible. Specification: $ref: . * Swagger Petstore This is used for unit tests, and should probably not be relied on by external users. 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" See also 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 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 => "(String is too long: 8/5. String is too short: 8/12)", 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"; @errors = validate_json $data, $schema; This can be useful in web applications: @errors = validate_json $c->req->json, "data://main/spec.json"; See also "validate" and "ERROR OBJECT" for more details. ATTRIBUTES cache_paths $self = $self->cache_paths(\@paths); $array_ref = $self->cache_paths; A list of directories to where cached specifications are stored. Defaults to "JSON_VALIDATOR_CACHE_PATH" environment variable and the specs that is bundled with this distribution. "JSON_VALIDATOR_CACHE_PATH" can be a list of directories, each separated by ":". See "Bundled specifications" for more details. formats $hash_ref = $self->formats; $self = $self->formats(\%hash); Holds a hash-ref, where the keys are supported JSON type "formats", and the values holds a code block which can validate a given format. Note! The modules mentioned below are optional. * date-time An RFC3339 timestamp in UTC time. This is formatted as "YYYY-MM-DDThh:mm:ss.fffZ". The milliseconds portion (".fff") is optional * email Validated against the RFC5322 spec. * hostname Will be validated using Data::Validate::Domain if installed. * ipv4 Will be validated using Data::Validate::IP if installed or fall back to a plain IPv4 IP regex. * ipv6 Will be validated using Data::Validate::IP if installed. * regex EXPERIMENTAL. Will check if the string is a regex, using "qr{...}". * uri Validated against the RFC3986 spec. ua $ua = $self->ua; $self = $self->ua(Mojo::UserAgent->new); Holds a Mojo::UserAgent object, used by "schema" to load a JSON schema from remote location. Note that the default Mojo::UserAgent will detect proxy settings and have "max_redirects" in Mojo::UserAgent set to 3. (These settings are EXPERIMENTAL and might change without a warning) METHODS bundle $schema = $self->bundle(\%args); Used to create a new schema, where the $ref are resolved. %args can have: * "{replace =" 1}> Used if you want to replace the $ref inline in the schema. This currently does not work if you have circular references. The default is to move all the $ref definitions into the main schema with custom names. Here is an example on how a $ref looks before and after: {"$ref":"../some/place.json#/foo/bar"} => {"$ref":"#/definitions/____some_place_json-_foo_bar"} {"$ref":"http://example.com#/foo/bar"} => {"$ref":"#/definitions/_http___example_com-_foo_bar"} * "{schema =" {...}}> Default is to use the value from the "schema" attribute. coerce $self = $self->coerce(booleans => 1, numbers => 1, strings => 1); $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 for more details. get $sub_schema = $self->get("/x/y"); Extract value from "schema" identified by the given JSON Pointer. Will at the same time resolve $ref if found. Example: $self->schema({x => {'$ref' => '#/y'}, y => {'type' => 'string'}}); $self->schema->get('/x') == undef $self->schema->get('/x')->{'$ref'} == '#/y' $self->get('/x') == {type => 'string'} This method is EXPERIMENTAL. load_and_validate_schema $self = $self->load_and_validate_schema($schema, \%args); Will load and validate $schema against the OpenAPI specification. $schema can be anything "schema" in JSON::Validator accepts. The expanded specification will be stored in "schema" in JSON::Validator on success. See "schema" in JSON::Validator for the different version of $url that can be accepted. %args can be used to further instruct the validation process: * schema Defaults to "http://json-schema.org/draft-04/schema#", but can be any structured that can be used to validate $schema. schema $self = $self->schema($json_or_yaml_string); $self = $self->schema($url); $self = $self->schema(\%schema); $schema = $self->schema; Used to set a schema from either a data structure or a URL. $schema will be a Mojo::JSON::Pointer object when loaded, and "undef" by default. The $url can take many forms, but needs to point to a text file in the JSON or YAML format. * http://... or https://... A web resource will be fetched using the Mojo::UserAgent, stored in "ua". * data://Some::Module/file.name This version will use "data_section" in Mojo::Loader to load "file.name" from the module "Some::Module". * /path/to/file An URL (without a recognized scheme) will be loaded from disk. singleton $self = $class->singleton; Returns the JSON::Validator object used by "validate_json". validate @errors = $self->validate($data); @errors = $self->validate($data, $schema); Validates $data against a given JSON "schema". @errors will contain validation error objects or be an empty list on success. See "ERROR OBJECT" for details. $schema is optional, but when specified, it will override schema stored in "schema". Example: $self->validate({hero => "superwoman"}, {type => "object"}); COPYRIGHT AND LICENSE Copyright (C) 2014-2015, Jan Henning Thorsen This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. AUTHOR Jan Henning Thorsen - "jhthorsen@cpan.org" Daniel Böhmer - "post@daniel-boehmer.de" Kevin Goess - "cpan@goess.org" Martin Renvoize - "martin.renvoize@gmail.com" JSON-Validator-1.08/META.yml000664 000765 000024 00000001403 13217716317 016566 0ustar00jhthorsenstaff000000 000000 --- abstract: 'Validate data against a JSON schema' author: - 'Jan Henning Thorsen ' build_requires: Test::More: '1.30' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: JSON-Validator no_index: directory: - t - inc requires: Mojolicious: '7.15' 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: '1.08' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' JSON-Validator-1.08/lib/000755 000765 000024 00000000000 13217716317 016063 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/.ship.conf000644 000765 000024 00000000504 12566023511 017175 0ustar00jhthorsenstaff000000 000000 # Generated by git-ship. See 'git-ship --man' for help or https://github.com/jhthorsen/app-git-ship class = App::git::ship::perl project_name = homepage = https://github.com/jhthorsen/json-validator bugtracker = https://github.com/jhthorsen/json-validator/issues license = artistic_2 build_test_options = # Example: -l -j8 JSON-Validator-1.08/Makefile.PL000644 000765 000024 00000002431 13217716316 017266 0ustar00jhthorsenstaff000000 000000 # Generated by git-ship. See 'git-ship --man' for help or https://github.com/jhthorsen/app-git-ship use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( NAME => 'JSON::Validator', AUTHOR => 'Jan Henning Thorsen ', LICENSE => 'artistic_2', ABSTRACT_FROM => 'lib/JSON/Validator.pm', VERSION_FROM => 'lib/JSON/Validator.pm', EXE_FILES => [qw()], BUILD_REQUIRES => {} , TEST_REQUIRES => { 'Test::More' => '1.30' } , PREREQ_PM => { 'Mojolicious' => '7.15' } , META_MERGE => { 'dynamic_config' => 0, 'meta-spec' => {version => 2}, 'resources' => { bugtracker => {web => 'https://github.com/jhthorsen/json-validator/issues'}, homepage => 'https://github.com/jhthorsen/json-validator', repository => { type => 'git', url => 'https://github.com/jhthorsen/json-validator.git', web => 'https://github.com/jhthorsen/json-validator', }, }, }, test => {S => (-e 'META.yml' ? 't/*.t' : 't/*.t xt/*.t')}, ); unless (eval { ExtUtils::MakeMaker->VERSION('6.63_03') }) { my $test_requires = delete $WriteMakefileArgs{TEST_REQUIRES}; @{$WriteMakefileArgs{PREREQ_PM}}{keys %$test_requires} = values %$test_requires; } WriteMakefile(%WriteMakefileArgs); JSON-Validator-1.08/.perltidyrc000644 000765 000024 00000000714 12674777270 017514 0ustar00jhthorsenstaff000000 000000 -pbp # Start with Perl Best Practices -w # Show all warnings -iob # Ignore old breakpoints -l=100 # Characters per line -mbl=2 # No more than 2 blank lines -i=2 # Indentation is 2 columns -ci=2 # Continuation indentation is 2 columns -vt=0 # Less vertical tightness -pt=2 # High parenthesis tightness -bt=2 # High brace tightness -sbt=2 # High square bracket tightness -isbc # Don't indent comments without leading space JSON-Validator-1.08/.travis.yml000644 000765 000024 00000000443 13057123354 017422 0ustar00jhthorsenstaff000000 000000 language: perl perl: - "5.20" - "5.16" - "5.10" env: - "HARNESS_OPTIONS=j6" install: - "cpanm -n Test::Pod Test::Pod::Coverage" - "cpanm -n Data::Validate::Domain Data::Validate::IP Cpanel::JSON::XS YAML::XS Swagger2" - "cpanm -n --installdeps ." notifications: email: false JSON-Validator-1.08/META.json000664 000765 000024 00000002554 13217716317 016746 0ustar00jhthorsenstaff000000 000000 { "abstract" : "Validate data against a JSON schema", "author" : [ "Jan Henning Thorsen " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "JSON-Validator", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : {} }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Mojolicious" : "7.15" } }, "test" : { "requires" : { "Test::More" : "1.30" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/jhthorsen/json-validator/issues" }, "homepage" : "https://github.com/jhthorsen/json-validator", "repository" : { "type" : "git", "url" : "https://github.com/jhthorsen/json-validator.git", "web" : "https://github.com/jhthorsen/json-validator" } }, "version" : "1.08", "x_serialization_backend" : "JSON::PP version 2.27400_02" } JSON-Validator-1.08/lib/JSON/000755 000765 000024 00000000000 13217716317 016634 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/lib/JSON/Validator.pm000644 000765 000024 00000121674 13217716316 021131 0ustar00jhthorsenstaff000000 000000 package JSON::Validator; use Mojo::Base -base; use B; use Carp 'confess'; use Exporter 'import'; use JSON::Validator::Error; use JSON::Validator::Ref; use Mojo::File 'path'; use Mojo::JSON::Pointer; use Mojo::JSON; use Mojo::Loader; use Mojo::URL; use Mojo::Util 'url_unescape'; use Scalar::Util qw(blessed refaddr); use Time::Local (); use constant COLORS => eval { require Term::ANSIColor }; use constant DEBUG => $ENV{JSON_VALIDATOR_DEBUG}; use constant REPORT => $ENV{JSON_VALIDATOR_REPORT} // $ENV{JSON_VALIDATOR_DEBUG}; use constant RECURSION_LIMIT => $ENV{JSON_VALIDATOR_RECURSION_LIMIT} || 100; use constant SPECIFICATION_URL => 'http://json-schema.org/draft-04/schema#'; use constant VALIDATE_HOSTNAME => eval 'require Data::Validate::Domain;1'; use constant VALIDATE_IP => eval 'require Data::Validate::IP;1'; our $ERR; # ugly hack to improve validation errors our $VERSION = '1.08'; our @EXPORT_OK = 'validate_json'; my $BUNDLED_CACHE_DIR = path(path(__FILE__)->dirname, qw(Validator cache)); my $HTTP_SCHEME_RE = qr{^https?:}; sub D { Data::Dumper->new([@_])->Sortkeys(1)->Indent(0)->Maxdepth(2)->Pair(':')->Useqq(1)->Terse(1)->Dump; } sub E { JSON::Validator::Error->new(@_) } sub S { Mojo::Util::md5_sum(Data::Dumper->new([@_])->Sortkeys(1)->Useqq(1)->Dump) } has cache_paths => sub { my $self = shift; my @paths = split /:/, ($ENV{JSON_VALIDATOR_CACHE_PATH} || ''); if ($ENV{JSON_VALIDATOR_CACHE_DIR}) { warn "JSON_VALIDATOR_CACHE_DIR is deprecated in favor of JSON_VALIDATOR_CACHE_PATH\n" unless $ENV{HARNESS_ACTIVE}; push @paths, split /:/, ($ENV{JSON_VALIDATOR_CACHE_DIR} || ''); } push @paths, $BUNDLED_CACHE_DIR; return \@paths; }; has formats => sub { shift->_build_formats }; has ua => sub { require Mojo::UserAgent; my $ua = Mojo::UserAgent->new; $ua->proxy->detect; $ua->max_redirects(3); $ua; }; sub bundle { my ($self, $args) = @_; my $def_name_cb = $args->{definitions_name} || \&_definitions_name; my @topics = ([undef, my $bundle = {}]); my ($cloner, $tied); $topics[0][0] = $args->{schema} ? $self->_reset->_resolve($args->{schema}) : $self->schema->data; if ($args->{replace}) { $cloner = sub { my $from = shift; my $ref = ref $from; $from = $tied->schema if $ref eq 'HASH' and $tied = tied %$from; my $to = $ref eq 'ARRAY' ? [] : $ref eq 'HASH' ? {} : $from; push @topics, [$from, $to] if $ref; return $to; }; } else { $bundle->{definitions} ||= {%{$topics[0][0]{definitions} || {}}}; $cloner = sub { my $from = shift; my $ref = ref $from; if ($ref eq 'HASH' and my $tied = tied %$from) { return $from if $tied->fqn =~ m!^\#!; my $name = $self->$def_name_cb($tied->fqn); push @topics, [$tied->schema, $bundle->{definitions}{$name} = {}]; tie my %ref, 'JSON::Validator::Ref', $tied->schema, "#/definitions/$name"; return \%ref; } my $to = $ref eq 'ARRAY' ? [] : $ref eq 'HASH' ? {} : $from; push @topics, [$from, $to] if $ref; return $to; }; } while (@topics) { my ($from, $to) = @{shift @topics}; if (ref $from eq 'ARRAY') { for (my $i = 0; $i < @$from; $i++) { $to->[$i] = $cloner->($from->[$i]); } } elsif (ref $from eq 'HASH') { while (my ($key, $value) = each %$from) { $to->{$key} //= $cloner->($from->{$key}); } } } return $bundle; } sub coerce { my $self = shift; return $self->{coerce} ||= {} unless @_; $self->{coerce} = $_[0] eq '1' ? {booleans => 1, numbers => 1, strings => 1} : ref $_[0] ? {%{$_[0]}} : {@_}; $self; } sub get { my ($self, $pointer) = @_; my $data = $self->schema->data; my $tied; return $data unless ref $pointer or $pointer =~ s!^/!!; $pointer = [length $pointer ? (split '/', $pointer, -1) : ($pointer)] unless ref $pointer; for my $p (@$pointer) { $p =~ s!~1!/!g; $p =~ s/~0/~/g; if (ref $data eq 'HASH' and exists $data->{$p}) { $data = $data->{$p}; } elsif (ref $data eq 'ARRAY' and $p =~ /^\d+$/ and @$data > $p) { $data = $data->[$p]; } else { return undef; } $data = $tied->schema if ref $data eq 'HASH' and $tied = tied %$data; } return $data; } sub load_and_validate_schema { my ($self, $spec, $args) = @_; $spec = $self->_reset->_resolve($spec); my @errors = $self->new(%$self)->schema($args->{schema} || SPECIFICATION_URL)->validate($spec); confess join "\n", "Invalid JSON specification $spec:", map {"- $_"} @errors if @errors; $self->{schema} = Mojo::JSON::Pointer->new($spec); $self; } sub schema { my $self = shift; return $self->{schema} unless @_; $self->{schema} = Mojo::JSON::Pointer->new($self->_reset->_resolve(shift)); return $self; } sub singleton { state $validator = shift->new } sub validate { my ($self, $data, $schema) = @_; $schema ||= $self->schema->data; return E '/', 'No validation rules defined.' unless $schema and %$schema; local $self->{grouped} = 0; local $self->{schema} = Mojo::JSON::Pointer->new($schema); local $self->{seen} = {}; $self->{report} = []; my @errors = $self->_validate($data, '', $schema); $self->_report if DEBUG and REPORT; return @errors; } sub validate_json { __PACKAGE__->singleton->schema($_[1])->validate($_[0]); } 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, 'regex' => \&_is_regex, 'uri' => \&_is_uri, }; } sub _load_schema { my ($self, $url) = @_; if ($url =~ m!^https?://!) { warn "[JSON::Validator] Loading schema from URL $url\n" if DEBUG; return $self->_load_schema_from_url(Mojo::URL->new($url)->fragment(undef)), "$url"; } if ($url =~ m!^data://([^/]+)/(.*)!) { my ($module, $file) = ($1, $2); warn "[JSON::Validator] Loading schema from data section: $url\n" if DEBUG; my $text = Mojo::Loader::data_section($module, $file) || confess "$file could not be found in __DATA__ section of $module."; return $self->_load_schema_from_text(\$text), "$url"; } if ($url =~ m!^\s*[\[\{]!) { warn "[JSON::Validator] Loading schema from string.\n" if DEBUG; return $self->_load_schema_from_text(\$url), ''; } my $file = $url; $file =~ s!#$!!; $file = path(split '/', $file); if (-e $file) { $file = $file->realpath; warn "[JSON::Validator] Loading schema from file: $file\n" if DEBUG; return $self->_load_schema_from_text(\$file->slurp), $url; } elsif ($file =~ m!^/!) { warn "[JSON::Validator] Loading schema from URL $url\n" if DEBUG; return $self->_load_schema_from_url(Mojo::URL->new($url)->fragment(undef)), "$url"; } confess "Unable to load schema '$url'"; } sub _load_schema_from_text { my ($self, $text) = @_; my $visit; # JSON return Mojo::JSON::decode_json($$text) if $$text =~ /^\s*\{/s; # YAML $visit = sub { my $v = shift; $visit->($_) for grep { ref $_ eq 'HASH' } values %$v; return $v unless $v->{type} and $v->{type} eq 'boolean' and exists $v->{default}; %$v = (%$v, default => $v->{default} ? Mojo::JSON->true : Mojo::JSON->false); return $v; }; local $YAML::Syck::ImplicitTyping = 1; return $visit->($self->coerce(1)->_yaml_module->can('Load')->($$text)); } sub _load_schema_from_url { my ($self, $url) = @_; my $cache_path = $self->cache_paths->[0]; my $cache_file = Mojo::Util::md5_sum("$url"); my ($err, $tx); for (@{$self->cache_paths}) { my $path = path $_, $cache_file; next unless -r $path; warn "[JSON::Validator] Loading cached file $path\n" if DEBUG; return $self->_load_schema_from_text(\$path->slurp); } $tx = $self->ua->get($url); $err = $tx->error && $tx->error->{message}; confess "GET $url == $err" if DEBUG and $err; die "[JSON::Validator] GET $url == $err" if $err; if ($cache_path and $cache_path ne $BUNDLED_CACHE_DIR and -w $cache_path) { $cache_file = path $cache_path, $cache_file; warn "[JSON::Validator] Caching $url to $cache_file\n" unless $ENV{HARNESS_ACTIVE}; $cache_file->spurt($tx->res->body); } return $self->_load_schema_from_text(\$tx->res->body); } sub _ref_to_schema { my ($self, $schema) = @_; my @guard; while (my $tied = tied %$schema) { push @guard, $tied->ref; confess "Seems like you have a circular reference: @guard" if @guard > RECURSION_LIMIT; $schema = $tied->schema; } return $schema; } sub _register_schema { my ($self, $schema, $fqn) = @_; $fqn =~ s!(.)#$!$1!; $self->{schemas}{$fqn} = $schema; } sub _report { my $table = Mojo::Util::tablify($_[0]->{report}); $table =~ s!^(\W*)(N?OK|<<<)(.*)!{ my ($x, $y, $z) = ($1, $2, $3); my $c = $y eq 'OK' ? 'green' : $y eq '<<<' ? 'blue' : 'magenta'; $c = "$c bold" if $z =~ /\s\w+Of\s/; Term::ANSIColor::colored([$c], "$x$y$z") }!gme if COLORS; warn "---\n$table"; } sub _report_errors { my ($self, $path, $type, $errors) = @_; push @{$self->{report}}, [ ((' ') x $self->{grouped}) . (@$errors ? 'NOK' : 'OK'), $path || '/', $type, join "\n", @$errors ]; } sub _report_schema { my ($self, $path, $type, $schema) = @_; push @{$self->{report}}, [((' ') x $self->{grouped}) . ('<<<'), $path || '/', $type, D $schema]; } sub _reset { delete $_[0]->{schemas}{''}; $_[0]->{level} = 0; $_[0]; } # _resolve() method is used to convert all "id" into absolute URLs and # resolve all the $ref's that we find inside JSON Schema specification. sub _resolve { my ($self, $schema) = @_; my ($id, $resolved, @refs); if (ref $schema eq 'HASH') { $id = $schema->{id} // ''; return $resolved if $resolved = $self->{schemas}{$id}; } elsif ($resolved = $self->{schemas}{$schema // ''}) { return $resolved; } else { ($schema, $id) = $self->_load_schema($schema); $id = $schema->{id} if $schema->{id}; } if (!$self->{level}++ and my $id = $schema->{id}) { confess "Root schema cannot have a fragment in the 'id'. ($id)" if $id =~ /\#./; confess "Root schema cannot have a relative 'id'. ($id)" if $id and $id !~ /^\w+:/; } $self->_register_schema($schema, $id); my @topics = ([$schema, Mojo::URL->new($id)]); while (@topics) { my ($topic, $base) = @{shift @topics}; if (UNIVERSAL::isa($topic, 'ARRAY')) { push @topics, map { [$_, $base] } @$topic; } elsif (UNIVERSAL::isa($topic, 'HASH')) { push @refs, [$topic, $base] and next if $topic->{'$ref'} and !ref $topic->{'$ref'}; if ($topic->{id} and !ref $topic->{id}) { my $fqn = Mojo::URL->new($topic->{id}); $fqn = $fqn->to_abs($base) unless $fqn->is_abs; $self->_register_schema($topic, $fqn->to_string); } push @topics, map { [$_, $base] } values %$topic; } } # Need to register "id":"..." before resolving "$ref":"..." $self->_resolve_ref(@$_) for @refs; return $schema; } sub _resolve_ref { my ($self, $topic, $url) = @_; return if tied %$topic; my $other = $topic; my ($base, $fqn, $pointer, $ref, @guard); while (1) { $ref = $other->{'$ref'}; push @guard, $other->{'$ref'}; confess "Seems like you have a circular reference: @guard" if @guard > RECURSION_LIMIT; last if !$ref or ref $ref; $fqn = Mojo::URL->new($ref =~ m!^/! ? "#$ref" : $ref); $fqn = $fqn->to_abs($url) unless $fqn->is_abs; $url = $fqn; $fqn = $fqn->to_string; $fqn =~ s!(.)#$!$1!; $fqn =~ s!#(.+)!{'#' . url_unescape $1}!e; ($base, $pointer) = split /#/, $fqn, 2; $other = $self->_resolve($base); if (length $pointer) { $other = Mojo::JSON::Pointer->new($other)->get($pointer) or confess qq[Possibly a typo in schema? Could not find "$pointer" in "$base" ($ref)]; } } tie %$topic, 'JSON::Validator::Ref', $other, $topic->{'$ref'}, $fqn; } # This code is from Data::Dumper::format_refaddr() sub _seen { my $self = shift; my $key = join ':', shift, map { pack 'J', refaddr $_ } @_; return $self->{seen}{$key}++; } sub _validate { my ($self, $data, $path, $schema) = @_; my ($type, @errors); $schema = $self->_ref_to_schema($schema) if $schema->{'$ref'}; # Avoid recursion if (ref $data and !_is_blessed_boolean($data) and $self->_seen($schema, $data)) { $self->_report_schema($path || '/', 'seen', $schema) if REPORT; return; } # Make sure we validate plain data and not a perl object $data = $data->TO_JSON if blessed $data and UNIVERSAL::can($data, 'TO_JSON'); $type = $schema->{type} || _guess_schema_type($schema, $data); # Test base schema before allOf, anyOf or oneOf if (ref $type eq 'ARRAY') { push @errors, $self->_validate_any_of($data, $path, [map { +{%$schema, type => $_} } @$type]); } elsif ($type) { my $method = sprintf '_validate_type_%s', $type; $self->_report_schema($path || '/', $type, $schema); @errors = $self->$method($data, $path, $schema); $self->_report_errors($path, $type, \@errors) if REPORT; return @errors if @errors; } if ($schema->{enum}) { @errors = $self->_validate_type_enum($data, $path, $schema); $self->_report_errors($path, 'enum', \@errors) if REPORT; return @errors if @errors; } if (my $rules = $schema->{not}) { push @errors, $self->_validate($data, $path, $rules); $self->_report_errors($path, 'not', \@errors) if REPORT; return @errors ? () : (E $path, 'Should not match.'); } 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 _validate_all_of { my ($self, $data, $path, $rules) = @_; my $type = _guess_data_type($data); my (@errors, @expected); $self->_report_schema($path, 'allOf', $rules) if REPORT; $self->{grouped}++; for my $rule (@$rules) { my @e = $self->_validate($data, $path, $rule) or next; my $schema_type = _guess_schema_type($rule); push @errors, [@e] and next if !$schema_type or $schema_type eq $type; push @expected, $schema_type; } $self->{grouped}--; $self->_report_errors($path, 'allOf', \@errors) if REPORT; my $expected = join ' or ', _uniq(@expected); return E $path, "allOf failed: Expected $expected, not $type." if $expected and @errors + @expected == @$rules; return E $path, sprintf 'allOf failed: %s', _merge_errors(@errors) if @errors; return; } sub _validate_any_of { my ($self, $data, $path, $rules) = @_; my $type = _guess_data_type($data); my (@e, @errors, @expected); $self->_report_schema($path, 'anyOf', $rules) if REPORT; $self->{grouped}++; for my $rule (@$rules) { @e = $self->_validate($data, $path, $rule); if (!@e) { $self->_report_errors($path, 'anyOf', \@errors) if REPORT; return; } my $schema_type = _guess_schema_type($rule); push @errors, [@e] and next if !$schema_type or $schema_type eq $type; push @expected, $schema_type; } $self->{grouped}--; $self->_report_errors($path, 'anyOf', \@errors) if REPORT; my $expected = join ' or ', _uniq(@expected); return E $path, "anyOf failed: Expected $expected, got $type." unless @errors; return E $path, sprintf "anyOf failed: %s", _merge_errors(@errors); } sub _validate_one_of { my ($self, $data, $path, $rules) = @_; my $type = _guess_data_type($data); my (@errors, @expected); $self->_report_schema($path, 'oneOf', $rules) if REPORT; $self->{grouped}++; for my $rule (@$rules) { my @e = $self->_validate($data, $path, $rule) or next; my $schema_type = _guess_schema_type($rule); push @errors, [@e] and next if !$schema_type or $schema_type eq $type; push @expected, $schema_type; } $self->{grouped}--; if (REPORT) { my @e = @errors + @expected + 1 == @$rules ? () : @errors ? @errors : 'All of the oneOf rules match.'; $self->_report_errors($path, 'oneOf', \@e); } return if @errors + @expected + 1 == @$rules; my $expected = join ' or ', _uniq(@expected); return E $path, "All of the oneOf rules match." unless @errors + @expected; return E $path, "oneOf failed: Expected $expected, got $type." unless @errors; return E $path, sprintf 'oneOf failed: %s', _merge_errors(@errors); } sub _validate_type_enum { my ($self, $data, $path, $schema) = @_; my $enum = $schema->{enum}; my $m = S $data; for my $i (@$enum) { return if !(defined $data and $self->_validate_type_boolean($data, $path)) and _is_true($data) == _is_true($i); return if $m eq S $i; } local $" = ', '; return E $path, sprintf 'Not in enum list: %s.', join ', ', map { ref $_ ? Mojo::JSON::encode_json($_) : $_ } @$enum; } sub _validate_type_const { my ($self, $data, $path, $schema) = @_; my $const = $schema->{const}; my $m = S $data; return if $m eq S $const; return E $path, sprintf 'Does not match const: %s.', Mojo::JSON::encode_json($const); } sub _validate_format { my ($self, $value, $path, $schema) = @_; my $code = $self->formats->{$schema->{format}}; local $ERR; return if $code and $code->($value); return do { warn "Format rule for '$schema->{format}' is missing"; return } unless $code; return E $path, $ERR || "Does not match $schema->{format} format."; } sub _validate_type_any { } sub _validate_type_array { my ($self, $data, $path, $schema) = @_; my @errors; if (ref $data ne 'ARRAY') { return E $path, _expected(array => $data); } if (defined $schema->{minItems} and $schema->{minItems} > @$data) { push @errors, E $path, sprintf 'Not enough items: %s/%s.', int @$data, $schema->{minItems}; } if (defined $schema->{maxItems} and $schema->{maxItems} < @$data) { push @errors, E $path, sprintf 'Too many items: %s/%s.', int @$data, $schema->{maxItems}; } if ($schema->{uniqueItems}) { my %uniq; for (@$data) { next if !$uniq{S($_)}++; push @errors, E $path, 'Unique items required.'; last; } } if (ref $schema->{items} eq 'ARRAY') { my $additional_items = $schema->{additionalItems} // {type => 'any'}; my @v = @{$schema->{items}}; if ($additional_items) { push @v, $additional_items while @v < @$data; } if (@v == @$data) { for my $i (0 .. @v - 1) { push @errors, $self->_validate($data->[$i], "$path/$i", $v[$i]); } } elsif (!$additional_items) { push @errors, E $path, sprintf "Invalid number of items: %s/%s.", int(@$data), int(@v); } } elsif (UNIVERSAL::isa($schema->{items}, 'HASH')) { for my $i (0 .. @$data - 1) { push @errors, $self->_validate($data->[$i], "$path/$i", $schema->{items}); } } return @errors; } sub _validate_type_boolean { my ($self, $value, $path, $schema) = @_; return if _is_blessed_boolean($value); if ( defined $value and $self->{coerce}{booleans} and (B::svref_2object(\$value)->FLAGS & (B::SVp_IOK | B::SVp_NOK) or $value =~ /^(true|false)$/)) { $_[1] = $value ? Mojo::JSON->true : Mojo::JSON->false; return; } return E $path, _expected(boolean => $value); } sub _validate_type_integer { my ($self, $value, $path, $schema) = @_; my @errors = $self->_validate_type_number($value, $path, $schema, 'integer'); return @errors if @errors; return if $value =~ /^-?\d+$/; return E $path, "Expected integer - got number."; } sub _validate_type_null { my ($self, $value, $path, $schema) = @_; return E $path, 'Not null.' if defined $value; return; } sub _validate_type_number { my ($self, $value, $path, $schema, $expected) = @_; my @errors; $expected ||= 'number'; if (!defined $value or ref $value) { 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}{numbers} or $value !~ /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/; $_[1] = 0 + $value; # coerce input value } if ($schema->{format}) { push @errors, $self->_validate_format($value, $path, $schema); } if (my $e = _cmp($schema->{minimum}, $value, $schema->{exclusiveMinimum}, '<')) { push @errors, E $path, "$value $e minimum($schema->{minimum})"; } if (my $e = _cmp($value, $schema->{maximum}, $schema->{exclusiveMaximum}, '>')) { push @errors, E $path, "$value $e maximum($schema->{maximum})"; } if (my $d = $schema->{multipleOf}) { if (($value / $d) =~ /\.[^0]+$/) { push @errors, E $path, "Not multiple of $d."; } } return @errors; } sub _validate_type_object { my ($self, $data, $path, $schema) = @_; my %required = map { ($_ => 1) } @{$schema->{required} || []}; my ($additional, @errors, %rules); if (ref $data ne 'HASH') { return E $path, _expected(object => $data); } my @dkeys = sort keys %$data; if (defined $schema->{maxProperties} and $schema->{maxProperties} < @dkeys) { push @errors, E $path, sprintf 'Too many properties: %s/%s.', int @dkeys, $schema->{maxProperties}; } if (defined $schema->{minProperties} and $schema->{minProperties} > @dkeys) { push @errors, E $path, sprintf 'Not enough properties: %s/%s.', int @dkeys, $schema->{minProperties}; } while (my ($k, $r) = each %{$schema->{properties}}) { push @{$rules{$k}}, $r; } while (my ($p, $r) = each %{$schema->{patternProperties} || {}}) { push @{$rules{$_}}, $r for sort grep { $_ =~ /$p/ } @dkeys; } $additional = exists $schema->{additionalProperties} ? $schema->{additionalProperties} : {}; if ($additional) { $additional = {} unless UNIVERSAL::isa($additional, 'HASH'); $rules{$_} ||= [$additional] for @dkeys; } else { # Special case used internally when validating schemas: This module adds "id" # on the top level which might conflict with very strict schemas, so we have to # remove it again unless there's a rule. local $rules{id} = 1 if !$path and exists $data->{id}; if (my @k = grep { !$rules{$_} } @dkeys) { local $" = ', '; return E $path, "Properties not allowed: @k."; } } for my $k (sort keys %required) { next if exists $data->{$k}; push @errors, E _path($path, $k), 'Missing property.'; delete $rules{$k}; } for my $k (sort keys %rules) { for my $r (@{$rules{$k}}) { if (!exists $data->{$k} and (UNIVERSAL::isa($r, 'HASH') and exists $r->{default})) { #$data->{$k} = $r->{default}; # TODO: This seems to fail when using oneOf and friends } elsif (exists $data->{$k}) { my @e = $self->_validate($data->{$k}, _path($path, $k), $r); push @errors, @e; push @errors, $self->_validate_type_enum($data->{$k}, _path($path, $k), $r) if $r->{enum} and !@e; push @errors, $self->_validate_type_const($data->{$k}, _path($path, $k), $r) if $r->{const} and !@e; } } } return @errors; } sub _validate_type_string { my ($self, $value, $path, $schema) = @_; my @errors; if (!defined $value or ref $value) { 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}{strings}; $_[1] = "$value"; # coerce input value } if ($schema->{format}) { push @errors, $self->_validate_format($value, $path, $schema); } if (defined $schema->{maxLength}) { if (length($value) > $schema->{maxLength}) { push @errors, E $path, sprintf "String is too long: %s/%s.", length($value), $schema->{maxLength}; } } if (defined $schema->{minLength}) { if (length($value) < $schema->{minLength}) { push @errors, E $path, sprintf "String is too short: %s/%s.", length($value), $schema->{minLength}; } } if (defined $schema->{pattern}) { my $p = $schema->{pattern}; unless ($value =~ /$p/) { push @errors, E $path, "String does not match '$p'"; } } return @errors; } # FUNCTIONS ================================================================== sub _cmp { return undef if !defined $_[0] or !defined $_[1]; return "$_[3]=" if $_[2] and $_[0] >= $_[1]; return $_[3] if $_[0] > $_[1]; return ""; } sub _definitions_name { local $_ = "$_[1]"; s!\#!-!g; s![^\w-]!_!g; return "_$_"; } sub _expected { my $type = _guess_data_type($_[1]); return "Expected $_[0] - got different $type." if $_[0] =~ /\b$type\b/; return "Expected $_[0] - got $type."; } sub _guess_data_type { local $_ = $_[0]; my $ref = ref; my $blessed = blessed $_; return 'object' if $ref eq 'HASH'; return lc $ref if $ref and !$blessed; return 'null' if !defined; return 'boolean' if $blessed and ("$_" eq "1" or !"$_"); 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 $_[0]->{type} if $_[0]->{type}; 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 'const' if $_[0]->{const}; return undef; } sub _guessed_right { return $_[1] unless defined $_[0]; return _guess_data_type($_[0]) eq $_[1] ? $_[1] : undef; } sub _invalid { $ERR = $_[0]; warn sprintf "[JSON::Validator] Failed validation: $_[0]\n" if DEBUG; return 0; } sub _is_date_time { my @time = $_[0] =~ m!^(\d{4})-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d(?:\.\d+)?)(?:Z|([+-])(\d+):(\d+))?$!io; return 0 unless @time; @time = map { s/^0//; $_ } reverse @time[0 .. 5]; $time[4] -= 1; # month are zero based local $@; return eval { Time::Local::timegm(@time); 1 } || 0; } sub _is_domain { warn "Data::Validate::Domain is not installed"; return; } sub _is_email { state $email_rfc5322_re = do { my $atom = qr;[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+;o; my $quoted_string = qr/"(?:\\[^\r\n]|[^\\"])*"/o; my $domain_literal = qr/\[(?:\\[\x01-\x09\x0B-\x0c\x0e-\x7f]|[\x21-\x5a\x5e-\x7e])*\]/o; my $dot_atom = qr/$atom(?:[.]$atom)*/o; my $local_part = qr/(?:$dot_atom|$quoted_string)/o; my $domain = qr/(?:$dot_atom|$domain_literal)/o; qr/$local_part\@$domain/o; }; return $_[0] =~ $email_rfc5322_re; } sub _is_ipv4 { my (@octets) = $_[0] =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; return 4 == grep { $_ >= 0 && $_ <= 255 && $_ !~ /^0\d{1,2}$/ } @octets; } sub _is_ipv6 { warn "Data::Validate::IP is not installed"; return; } sub _is_true { local $_ = $_[0]; return 0 + $_ if ref $_ and !blessed $_; return 0 if !$_ or /^(n|false|off)/i; return 1; } sub _is_blessed_boolean { return 0 if !blessed $_[0]; return 1 if UNIVERSAL::isa($_[0], 'JSON::PP::Boolean') or "$_[0]" eq "1" or !$_[0]; return 0; } sub _is_regex { eval {qr{$_[0]}}; } sub _is_uri { return unless defined $_[0]; return unless $_[0] =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!; my ($scheme, $auth_host, $path, $query, $fragment) = map { $_ // '' } ($2, $4, $5, $7, $9); return _invalid('Scheme missing from URI.') if length $auth_host and !length $scheme; return _invalid('Scheme, path or fragment are required.') unless length($scheme) + length($path) + length($fragment); return _invalid('Scheme must begin with a letter.') if length $scheme and lc($scheme) !~ m!^[a-z][a-z0-9\+\-\.]*$!; return _invalid('Invalid hex escape.') if $_[0] =~ /%[^0-9a-f]/i; return _invalid('Hex escapes are not complete.') if $_[0] =~ /%[0-9a-f](:?[^0-9a-f]|$)/i; if (defined $auth_host and length $auth_host) { return _invalid('Path cannot be empty or begin with a /') unless !length $path or $path =~ m!^/!; } else { return _invalid('Path cannot not start with //.') if $path =~ m!^//!; } return 1; } sub _merge_errors { join ' ', map { my $e = $_; (@$e == 1) ? $e->[0]{message} : sprintf '(%s)', join '. ', map { $_->{message} } @$e; } @_; } sub _path { local $_ = $_[1]; s!~!~0!g; s!/!~1!g; "$_[0]/$_"; } sub _uniq { my %uniq; grep { !$uniq{$_}++ } @_; } # Please report if you need to manually monkey patch this function # https://github.com/jhthorsen/json-validator/issues sub _yaml_module { state $yaml_module = do { require List::Util; my @modules = qw(YAML::XS YAML::Syck); # subject to change my $module = (List::Util::first { eval "require $_;1" } @modules)[0]; die "Need to install one of these YAML modules: @modules (YAML::XS is recommended)" unless $module; warn "[JSON::Validator] Using $module to parse YAML\n" if DEBUG; $module; }; } 1; =encoding utf8 =head1 NAME JSON::Validator - Validate data against a JSON schema =head1 VERSION 1.08 =head1 SYNOPSIS use JSON::Validator; my $validator = JSON::Validator->new; # Define a schema - http://json-schema.org/examples.html # You can also load schema from disk or web $validator->schema( { type => "object", required => ["firstName", "lastName"], properties => { firstName => {type => "string"}, lastName => {type => "string"}, age => {type => "integer", minimum => 0, description => "Age in years"} } } ); # Validate your data @errors = $validator->validate({firstName => "Jan Henning", lastName => "Thorsen", age => -42}); # Do something if any errors was found die "@errors" if @errors; =head1 DESCRIPTION L is a class for validating data against JSON schemas. You might want to use this instead of L if you need to validate data against L of the specification. This module can be used standalone, but if you want to define a specification for your webserver's API, then have a look at L, which will replace L. =head2 Supported schema formats L can load JSON schemas in multiple formats: Plain perl data structured (as shown in L) or files on disk/web in the JSON/YAML format. The JSON parsing is done using L, while the YAML parsing is done with an optional modules which need to be installed manually. L will look for the YAML modules in this order: L, L. The order is set by which module that performs the best, so it might change in the future. =head2 Resources Here are some resources that are related to JSON schemas and validation: =over 4 =item * L =item * L =item * L =item * L =back =head2 Bundled specifications This module comes with some JSON specifications bundled, so your application don't have to fetch those from the web. These specifications should be up to date, but please submit an issue if they are not. Files referenced to an URL will automatically be cached if the first element in L is a writable directory. Note that the cache headers for the remote assets are B honored, so you will manually need to remove any cached file, should you need to refresh them. To download and cache an online asset, do this: JSON_VALIDATOR_CACHE_PATH=/some/writable/directory perl myapp.pl Here is the list of the bundled specifications: =over 2 =item * JSON schema, draft 4 Web page: L C<$ref>: L =item * JSON schema for JSONPatch files Web page: L C<$ref>: L =item * Swagger / OpenAPI specification, version 2 Web page: L C<$ref>: L =item * Custom error document There is a custom schema used by L as a default error document. This document might be extended later, but it will always be backward compatible. Specification: L C<$ref>: L. =item * Swagger Petstore This is used for unit tests, and should probably not be relied on by external users. =back =head1 ERROR OBJECT =head2 Overview The method L and the function L returns error objects when the input data violates the L. Each of the objects looks like this: bless { message => "Some description", path => "/json/path/to/node", }, "JSON::Validator::Error" See also L. =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 for documented examples of the error cases. Especially look at C, C and C. 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. Example error object: bless { message => "(String is too long: 8/5. String is too short: 8/12)", path => "/json/path/to/node", }, "JSON::Validator::Error" Note that these error messages are subject for change. Any suggestions are most welcome! =head1 FUNCTIONS =head2 validate_json use JSON::Validator "validate_json"; @errors = validate_json $data, $schema; This can be useful in web applications: @errors = validate_json $c->req->json, "data://main/spec.json"; See also L and L for more details. =head1 ATTRIBUTES =head2 cache_paths $self = $self->cache_paths(\@paths); $array_ref = $self->cache_paths; A list of directories to where cached specifications are stored. Defaults to C environment variable and the specs that is bundled with this distribution. C can be a list of directories, each separated by ":". See L for more details. =head2 formats $hash_ref = $self->formats; $self = $self->formats(\%hash); Holds a hash-ref, where the keys are supported JSON type "formats", and the values holds a code block which can validate a given format. Note! The modules mentioned below are optional. =over 4 =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 * email Validated against the RFC5322 spec. =item * hostname Will be validated using L if installed. =item * ipv4 Will be validated using L if installed or fall back to a plain IPv4 IP regex. =item * ipv6 Will be validated using L if installed. =item * regex EXPERIMENTAL. Will check if the string is a regex, using C. =item * uri Validated against the RFC3986 spec. =back =head2 ua $ua = $self->ua; $self = $self->ua(Mojo::UserAgent->new); Holds a L object, used by L to load a JSON schema from remote location. Note that the default L will detect proxy settings and have L set to 3. (These settings are EXPERIMENTAL and might change without a warning) =head1 METHODS =head2 bundle $schema = $self->bundle(\%args); Used to create a new schema, where the C<$ref> are resolved. C<%args> can have: =over 2 =item * C<{replace => 1}> Used if you want to replace the C<$ref> inline in the schema. This currently does not work if you have circular references. The default is to move all the C<$ref> definitions into the main schema with custom names. Here is an example on how a C<$ref> looks before and after: {"$ref":"../some/place.json#/foo/bar"} => {"$ref":"#/definitions/____some_place_json-_foo_bar"} {"$ref":"http://example.com#/foo/bar"} => {"$ref":"#/definitions/_http___example_com-_foo_bar"} =item * C<{schema => {...}}> Default is to use the value from the L attribute. =back =head2 coerce $self = $self->coerce(booleans => 1, numbers => 1, strings => 1); $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 or other JSON parsers. The coercion rules are EXPERIMENTAL and will be tighten/loosen if bugs are reported. See L for more details. =head2 get $sub_schema = $self->get("/x/y"); Extract value from L identified by the given JSON Pointer. Will at the same time resolve C<$ref> if found. Example: $self->schema({x => {'$ref' => '#/y'}, y => {'type' => 'string'}}); $self->schema->get('/x') == undef $self->schema->get('/x')->{'$ref'} == '#/y' $self->get('/x') == {type => 'string'} This method is EXPERIMENTAL. =head2 load_and_validate_schema $self = $self->load_and_validate_schema($schema, \%args); Will load and validate C<$schema> against the OpenAPI specification. C<$schema> can be anything L accepts. The expanded specification will be stored in L on success. See L for the different version of C<$url> that can be accepted. C<%args> can be used to further instruct the validation process: =over 2 =item * schema Defaults to "http://json-schema.org/draft-04/schema#", but can be any structured that can be used to validate C<$schema>. =back =head2 schema $self = $self->schema($json_or_yaml_string); $self = $self->schema($url); $self = $self->schema(\%schema); $schema = $self->schema; Used to set a schema from either a data structure or a URL. C<$schema> will be a L object when loaded, and C by default. The C<$url> can take many forms, but needs to point to a text file in the JSON or YAML format. =over 4 =item * http://... or https://... A web resource will be fetched using the L, stored in L. =item * data://Some::Module/file.name This version will use L to load "file.name" from the module "Some::Module". =item * /path/to/file An URL (without a recognized scheme) will be loaded from disk. =back =head2 singleton $self = $class->singleton; Returns the L object used by L. =head2 validate @errors = $self->validate($data); @errors = $self->validate($data, $schema); Validates C<$data> against a given JSON L. C<@errors> will contain validation error objects or be an empty list on success. See L for details. C<$schema> is optional, but when specified, it will override schema stored in L. Example: $self->validate({hero => "superwoman"}, {type => "object"}); =head1 COPYRIGHT AND LICENSE Copyright (C) 2014-2015, Jan Henning Thorsen This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. =head1 AUTHOR Jan Henning Thorsen - C Daniel Böhmer - C Kevin Goess - C Martin Renvoize - C =cut JSON-Validator-1.08/lib/JSON/Validator/000755 000765 000024 00000000000 13217716317 020561 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/lib/JSON/Validator/Error.pm000644 000765 000024 00000002122 13202727270 022177 0ustar00jhthorsenstaff000000 000000 package JSON::Validator::Error; use Mojo::Base -base; use overload q("") => sub { sprintf '%s: %s', @{$_[0]}{qw(path message)} }, bool => sub {1}, fallback => 1; sub new { my $self = bless {}, shift; @$self{qw(path message)} = ($_[0] || '/', $_[1] || ''); $self; } sub message { shift->{message} } sub path { shift->{path} } sub TO_JSON { {message => $_[0]->{message}, path => $_[0]->{path}} } 1; =encoding utf8 =head1 NAME JSON::Validator::Error - JSON::Validator error object =head1 SYNOPSIS use JSON::Validator::Error; my $err = JSON::Validator::Error->new($path, $message); =head1 DESCRIPTION L is a class representing validation errors from L. =head1 ATTRIBUTES =head2 message $str = $self->message; A human readable description of the error. Defaults to empty string. =head2 path $str = $self->path; A JSON pointer to where the error occurred. Defaults to "/". =head1 METHODS =head2 new $self = JSON::Validator::Error->new($path, $message); Object constructor. =head1 SEE ALSO L. =cut JSON-Validator-1.08/lib/JSON/Validator/OpenAPI.pm000644 000765 000024 00000030464 13217715734 022363 0ustar00jhthorsenstaff000000 000000 package JSON::Validator::OpenAPI; use Carp (); use Mojo::Base 'JSON::Validator'; use Mojo::Util qw(deprecated monkey_patch); use Scalar::Util (); use constant DEBUG => $ENV{JSON_VALIDATOR_DEBUG} || 0; use constant IV_SIZE => eval 'require Config;$Config::Config{ivsize}'; use constant SPECIFICATION_URL => 'http://swagger.io/v2/schema.json'; my %COLLECTION_RE = (pipes => qr{\|}, csv => qr{,}, ssv => qr{\s}, tsv => qr{\t}); has _json_validator => sub { state $v = JSON::Validator->new; }; sub load_and_validate_schema { my ($self, $spec, $args) = @_; $spec = $self->bundle({replace => 1, schema => $spec}) if $args->{allow_invalid_ref}; local $args->{schema} = $args->{schema} || SPECIFICATION_URL; $self->SUPER::load_and_validate_schema($spec, $args); if (my $class = $args->{version_from_class}) { if (UNIVERSAL::can($class, 'VERSION') and $class->VERSION) { $self->schema->data->{info}{version} = $class->VERSION; } } return $self; } # deprecated sub load_and_validate_spec { goto &load_and_validate_schema } sub validate_input { my $self = shift; local $self->{validate_input} = 1; local $self->{root} = $self->schema; $self->validate(@_); } sub validate_request { my ($self, $c, $schema, $input) = @_; my @errors; for my $p (@{$schema->{parameters} || []}) { my ($in, $name, $type) = @$p{qw(in name type)}; my ($exists, $value) = (0, undef); if ($in eq 'body') { $value = $self->_get_request_data($c, $in); $exists = length $value if defined $value; } elsif ($in eq 'formData' and $type eq 'file') { $value = $self->_get_request_uploads($c, $name)->[-1]; $exists = $value ? 1 : 0; } else { my $key = $in eq 'header' ? lc $name : $name; $value = $self->_get_request_data($c, $in); $exists = exists $value->{$key}; $value = $value->{$key}; } if (defined $value and ref $p->{items} eq 'HASH' and $p->{collectionFormat}) { $value = $self->_coerce_by_collection_format($value, $p); } ($exists, $value) = (1, $p->{default}) if !$exists and exists $p->{default}; if ($type and defined $value) { if ($type ne 'array' and ref $value eq 'ARRAY') { $value = $value->[-1]; } if (($type eq 'integer' or $type eq 'number') and Scalar::Util::looks_like_number($value)) { $value += 0; } elsif ($type eq 'boolean') { if (!$value or $value =~ /^(?:false)$/) { $value = Mojo::JSON->false; } elsif ($value =~ /^(?:1|true)$/) { $value = Mojo::JSON->true; } } } if (my @e = $self->_validate_request_value($p, $name => $value)) { push @errors, @e; } elsif ($exists) { $input->{$name} = $value; $self->_set_request_data($c, $in, $name => $value) if defined $value; } } return @errors; } sub validate_response { my ($self, $c, $schema, $status, $data) = @_; my @errors; if (my $blueprint = $schema->{responses}{$status} || $schema->{responses}{default}) { push @errors, $self->_validate_response_headers($c, $blueprint->{headers}) if $blueprint->{headers}; if ($blueprint->{'x-json-schema'}) { warn "[JSON::Validator::OpenAPI] Validate using x-json-schema\n" if DEBUG; push @errors, $self->_json_validator->validate($data, $blueprint->{'x-json-schema'}); } elsif ($blueprint->{schema}) { warn "[JSON::Validator::OpenAPI] Validate using schema\n" if DEBUG; push @errors, $self->validate($data, $blueprint->{schema}); } } else { push @errors, JSON::Validator::E('/' => "No responses rules defined for status $status."); } return @errors; } { my @proxy_methods = qw( _get_request_uploads _get_request_data _set_request_data _get_response_data _set_response_data ); for my $method (@proxy_methods) { monkey_patch(__PACKAGE__, $method => sub { deprecated "Using JSON::Validator::OpenAPI directly is DEPRECATED." . " For the Mojolicious-specific methods use JSON::Validator::OpenAPI::Mojolicious"; require JSON::Validator::OpenAPI::Mojolicious; my $self = shift; bless $self, 'JSON::Validator::OpenAPI::Mojolicious'; return $self->$method(@_); } ); } } sub _resolve_ref { my ($self, $topic, $url) = @_; $topic->{'$ref'} = "#/definitions/$topic->{'$ref'}" if $topic->{'$ref'} =~ /^\w+$/; return $self->SUPER::_resolve_ref($topic, $url); } sub _validate_request_value { my ($self, $p, $name, $value) = @_; my $type = $p->{type} || 'object'; my @e; return if !defined $value and !JSON::Validator::_is_true($p->{required}); my $in = $p->{in}; my $schema = { properties => {$name => $p->{'x-json-schema'} || $p->{schema} || $p}, required => [$p->{required} ? ($name) : ()] }; if ($in eq 'body') { warn "[JSON::Validator::OpenAPI] Validate $in $name\n" if DEBUG; if ($p->{'x-json-schema'}) { return $self->_json_validator->validate({$name => $value}, $schema); } else { return $self->validate_input({$name => $value}, $schema); } } elsif (defined $value) { warn "[JSON::Validator::OpenAPI] Validate $in $name=$value\n" if DEBUG; return $self->validate_input({$name => $value}, $schema); } else { warn "[JSON::Validator::OpenAPI] Validate $in $name=undef\n" if DEBUG; return $self->validate_input({$name => $value}, $schema); } return; } sub _validate_response_headers { my ($self, $c, $schema) = @_; my $input = $self->_get_response_data($c, 'header'); my @errors; for my $name (keys %$schema) { my $p = $schema->{$name}; # jhthorsen: I think that the only way to make a header required, # is by defining "array" and "minItems" >= 1. if ($p->{type} eq 'array') { push @errors, $self->validate($input->{$name}, $p); } elsif ($input->{$name}) { push @errors, $self->validate($input->{$name}[0], $p); $self->_set_response_data($c, 'header', $name => $input->{$name}[0] ? 'true' : 'false') if $p->{type} eq 'boolean' and !@errors; } } return @errors; } sub _validate_type_array { my ($self, $data, $path, $schema) = @_; if (ref $schema->{items} eq 'HASH' and $schema->{items}{collectionFormat}) { $data = $self->_coerce_by_collection_format($data, $schema->{items}); } return $self->SUPER::_validate_type_array($data, $path, $schema); } sub _validate_type_file { my ($self, $data, $path, $schema) = @_; if ($schema->{required} and (not defined $data or not length $data)) { return JSON::Validator::E($path => 'Missing property.'); } return; } sub _validate_type_object { return shift->SUPER::_validate_type_object(@_) unless $_[0]->{validate_input}; my ($self, $data, $path, $schema) = @_; my $properties = $schema->{properties} || {}; my $discriminator = $schema->{discriminator}; my (%ro, @e); for my $p (keys %$properties) { next unless $properties->{$p}{readOnly}; push @e, JSON::Validator::E("$path/$p", "Read-only.") if exists $data->{$p}; $ro{$p} = 1; } if ($discriminator and !$self->{inside_discriminator}) { my $name = $data->{$discriminator} or return JSON::Validator::E($path, "Discriminator $discriminator has no value."); my $dschema = $self->{root}->get("/definitions/$name") or return JSON::Validator::E($path, "No definition for discriminator $name."); local $self->{inside_discriminator} = 1; # prevent recursion return $self->_validate($data, $path, $dschema); } local $schema->{required} = [grep { !$ro{$_} } @{$schema->{required} || []}]; return @e, $self->SUPER::_validate_type_object($data, $path, $schema); } sub _build_formats { my $formats = shift->SUPER::_build_formats; $formats->{byte} = \&_is_byte_string; $formats->{date} = \&_is_date; $formats->{double} = \&Scalar::Util::looks_like_number; $formats->{float} = \&Scalar::Util::looks_like_number; $formats->{int32} = sub { _is_number($_[0], 'l'); }; $formats->{int64} = IV_SIZE >= 8 ? sub { _is_number($_[0], 'q'); } : sub {1}; $formats->{password} = sub {1}; $formats; } sub _coerce_by_collection_format { my ($self, $data, $schema) = @_; my $type = ($schema->{items} ? $schema->{items}{type} : $schema->{type}) || ''; if ($schema->{collectionFormat} eq 'multi') { $data = [$data] unless ref $data eq 'ARRAY'; @$data = map { $_ + 0 } @$data if $type eq 'integer' or $type eq 'number'; return $data; } my $re = $COLLECTION_RE{$schema->{collectionFormat}} || ','; my $single = ref $data eq 'ARRAY' ? 0 : ($data = [$data]); for my $i (0 .. @$data - 1) { my @d = split /$re/, ($data->[$i] // ''); $data->[$i] = ($type eq 'integer' or $type eq 'number') ? [map { $_ + 0 } @d] : \@d; } return $single ? $data->[0] : $data; } sub _confess_invalid_in { Carp::confess( "Unsupported \$in: $_[0]. Please report at https://github.com/jhthorsen/json-validator"); } sub _is_byte_string { $_[0] =~ /^[A-Za-z0-9\+\/\=]+$/ } sub _is_date { $_[0] =~ /^(\d+)-(\d+)-(\d+)$/ } sub _is_number { return unless $_[0] =~ /^-?\d+(\.\d+)?$/; return $_[0] eq unpack $_[1], pack $_[1], $_[0]; } 1; =encoding utf8 =head1 NAME JSON::Validator::OpenAPI - OpenAPI is both a subset and superset of JSON Schema =head1 DESCRIPTION L can validate Open API (also known as "Swagger") requests and responses that is passed through a L powered web application. =head1 ATTRIBUTES L inherits all attributes from L. =head2 formats Open API support the same formats as L, but adds the following to the set: =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 * double Cannot test double values with higher precision then what the "number" type already provides. =item * float Will always be true if the input is a number, meaning there is no difference between L and L. Patches are welcome. =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. =back =head1 METHODS L inherits all attributes from L. =head2 load_and_validate_schema $self = $self->load_and_validate_schema($schema, \%args); Will load and validate C<$schema> against the OpenAPI specification. C<$schema> can be anything L accepts. The expanded specification will be stored in L on success. See L for the different version of C<$url> that can be accepted. C<%args> can be used to further instruct the expansion and validation process: =over 2 =item * allow_invalid_ref Setting this to a true value, will disable the first pass. This is useful if you don't like the restrictions set by OpenAPI, regarding where you can use C<$ref> in your specification. =item * version_from_class Setting this to a module/class name will use the version number from the class and overwrite the version in the specification: { "info": { "version": "1.00" // <-- this value } } =back The validation is done with a two pass process: =over 2 =item 1. First it will check if the C<$ref> is only specified on the correct places. This can be disabled by setting L to a true value. =item 2. Validate the expanded version of the spec, (without any C<$ref>) against the OpenAPI schema. =back =head2 validate_input @errors = $self->validate_input($data, $schema); This method will make sure "readOnly" is taken into account, when validating data sent to your API. =head2 validate_request @errors = $self->validate_request($c, $schema, \%input); Takes an L and a schema definition and returns a list of errors, if any. Validated input parameters are moved into the C<%input> hash. =head2 validate_response @errors = $self->validate_response($c, $schema, $status, $data); =head1 COPYRIGHT AND LICENSE Copyright (C) 2014-2015, Jan Henning Thorsen This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. =head1 SEE ALSO L. L =cut JSON-Validator-1.08/lib/JSON/Validator/OpenAPI/000755 000765 000024 00000000000 13217716317 022014 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/lib/JSON/Validator/Ref.pm000644 000765 000024 00000003205 13217716045 021631 0ustar00jhthorsenstaff000000 000000 package JSON::Validator::Ref; use Mojo::Base -strict; use Tie::Hash (); use base 'Tie::StdHash'; my $private = '%%'; sub fqn { $_[0]->{"${private}fqn"} } sub ref { $_[0]->{'$ref'} } sub schema { $_[0]->{"${private}schema"} } # Make it look like there is only one key in the hash sub EXISTS { exists $_[0]->{$_[1]} || exists $_[0]->{"${private}schema"}{$_[1]} } sub FETCH { exists $_[0]->{$_[1]} ? $_[0]->{$_[1]} : $_[0]->{"${private}schema"}{$_[1]} } sub FIRSTKEY {'$ref'} sub KEYS {'$ref'} sub NEXTKEY {undef} sub SCALAR {1} sub TIEHASH { my ($class, $schema, $ref, $fqn) = @_; bless {'$ref' => $ref, "${private}fqn" => $fqn // $ref, "${private}schema" => $schema}, $class; } # jhthorsen: This cannot return schema() since it might cause circular references sub TO_JSON { {'$ref' => $_[0]->ref} } 1; =encoding utf8 =head1 NAME JSON::Validator::Ref - JSON::Validator $ref representation =head1 SYNOPSIS use JSON::Validator::Ref; my $ref = JSON::Validator::Ref->new({ref => "...", schema => {...}); =head1 DESCRIPTION L is a class representing a C<$ref> inside a JSON Schema. Note that this module should be considered internal to the L project and the API is subject to change. This class is currently EXPERIMENTAL and can be replaced if a bug is discovered, without any warning. =head1 ATTRIBUTES =head2 fqn $str = $self->fqn; The fully qualified version of L. =head2 ref $str = $self->ref; The original C<$ref> from the document. =head2 schema $hash_ref = $self->schema; A reference to the schema that the C points to. =head1 SEE ALSO L. =cut JSON-Validator-1.08/lib/JSON/Validator/OpenAPI/Mojolicious.pm000644 000765 000024 00000004013 13204233275 024635 0ustar00jhthorsenstaff000000 000000 package JSON::Validator::OpenAPI::Mojolicious; use Mojo::Base 'JSON::Validator::OpenAPI'; sub _get_request_data { my ($self, $c, $in) = @_; if ($in eq 'query') { return $c->req->url->query->to_hash(1); } elsif ($in eq 'path') { return $c->match->stack->[-1]; } elsif ($in eq 'formData') { return $c->req->body_params->to_hash(1); } elsif ($in eq 'header') { my $headers = $c->req->headers->to_hash(1); return {map { lc($_) => $headers->{$_} } keys %$headers}; } elsif ($in eq 'body') { return $c->req->json; } else { JSON::Validator::OpenAPI::_confess_invalid_in($in); } } sub _get_request_uploads { my ($self, $c, $name) = @_; return $c->req->every_upload($name); } sub _get_response_data { my ($self, $c, $in) = @_; return $c->res->headers->to_hash(1) if $in eq 'header'; JSON::Validator::OpenAPI::_confess_invalid_in($in); } sub _set_request_data { my ($self, $c, $in, $name => $value) = @_; if ($in eq 'query') { $c->req->url->query([$name => $value]); $c->req->params->merge($name => $value); } elsif ($in eq 'path') { $c->stash($name => $value); } elsif ($in eq 'formData') { $c->req->params->merge($name => $value); $c->req->body_params->merge($name => $value); } elsif ($in eq 'header') { $c->req->headers->header($name => $value); } elsif ($in ne 'body') { # no need to write body back JSON::Validator::OpenAPI::_confess_invalid_in($in); } } sub _set_response_data { my ($self, $c, $in, $name => $value) = @_; return $c->res->headers->header($name => ref $value ? @$value : $value) if $in eq 'header'; JSON::Validator::OpenAPI::_confess_invalid_in($in); } 1; =encoding utf8 =head1 NAME JSON::Validator::OpenAPI::Mojolicious - Request/response adapter for Mojolicious =head1 SYNOPSIS See L. =head1 DESCRIPTION This module contains private methods to get/set request/response data for L. =head1 SEE ALSO L. L. =cut JSON-Validator-1.08/lib/JSON/Validator/OpenAPI/Dancer2.pm000644 000765 000024 00000004504 13166400660 023625 0ustar00jhthorsenstaff000000 000000 package JSON::Validator::OpenAPI::Dancer2; use Hash::MultiValue; use Mojo::Base 'JSON::Validator::OpenAPI'; sub _get_request_data { my ($self, $dsl, $in) = @_; if ($in eq 'query') { return $dsl->query_parameters->as_hashref_mixed; } elsif ($in eq 'path') { return $dsl->route_parameters->as_hashref_mixed; } elsif ($in eq 'formData') { return $dsl->body_parameters->as_hashref_mixed; } elsif ($in eq 'header') { my $headers = $dsl->app->request->headers->flatten; $headers = {map { lc($_) => $headers->{$_} } keys %$headers}; return Hash::MultiValue->new($headers)->as_hashref_mixed; } elsif ($in eq 'body') { return $dsl->app->request->data; } else { JSON::Validator::OpenAPI::_confess_invalid_in($in); } } sub _get_request_uploads { my ($self, $dsl, $name) = @_; return [$dsl->app->request->upload($name)]; } sub _get_response_data { my ($self, $dsl, $in) = @_; if ($in eq 'header') { my @headers = $dsl->response->headers->flatten; return Hash::MultiValue->new(@headers)->as_hashref_mixed; } else { JSON::Validator::OpenAPI::_confess_invalid_in($in); } } sub _set_request_data { my ($self, $dsl, $in, $name => $value) = @_; if ($in eq 'query') { $dsl->query_parameters->set($name => $value); $dsl->app->request->params->{$name} = $value; } elsif ($in eq 'path') { $dsl->route_parameters->set($name => $value); } elsif ($in eq 'formData') { $dsl->app->request->body_parameters->set($name => $value); $dsl->app->request->params->{$name} = $value; } elsif ($in eq 'header') { $dsl->app->request->headers->header($name => $value); } elsif ($in eq 'body') { } # no need to write body back else { JSON::Validator::OpenAPI::_confess_invalid_in($in); } } sub _set_response_data { my ($self, $dsl, $in, $name => $value) = @_; if ($in eq 'header') { $dsl->response->headers->header($name => $value); } else { JSON::Validator::OpenAPI::_confess_invalid_in($in); } } 1; =encoding utf8 =head1 NAME JSON::Validator::OpenAPI::Dancer2 - Request/response adapter for Dancer2 =head1 SYNOPSIS See L. =head1 DESCRIPTION This module contains private methods to get/set request/response data for L. =head1 SEE ALSO L. L. =cut JSON-Validator-1.08/t/openapi-formats.t000644 000765 000024 00000005165 13173160273 021053 0ustar00jhthorsenstaff000000 000000 use lib '.'; use JSON::Validator::OpenAPI::Mojolicious; use t::Helper; use Test::More; $ENV{TEST_VALIDATOR_CLASS} = 'JSON::Validator::OpenAPI::Mojolicious'; my $schema = {type => 'object', properties => {v => {type => 'string'}}}; { $schema->{properties}{v}{format} = 'byte'; validate_ok {v => 'amh0aG9yc2Vu'}, $schema; validate_ok {v => "\0"}, $schema, E('/v', 'Does not match byte format.'); } { $schema->{properties}{v}{format} = 'date'; validate_ok {v => '2014-12-09'}, $schema; validate_ok {v => '2014-12-09T20:49:37Z'}, $schema, E('/v', 'Does not match date format.'); } { $schema->{properties}{v}{format} = 'date-time'; validate_ok {v => '2014-12-09T20:49:37Z'}, $schema; validate_ok {v => '20:46:02'}, $schema, E('/v', 'Does not match date-time format.'); } { local $schema->{properties}{v}{type} = 'number'; local $schema->{properties}{v}{format} = 'double'; local $TODO = "cannot test double, since input is already rounded"; validate_ok {v => 1.1000000238418599085576943252817727625370025634765626}, $schema; } { local $schema->{properties}{v}{format} = 'email'; validate_ok {v => 'jhthorsen@cpan.org'}, $schema; validate_ok {v => 'foo'}, $schema, E('/v', 'Does not match email format.'); } { local $TODO = 'No idea how to test floats'; local $schema->{properties}{v}{type} = 'number'; local $schema->{properties}{v}{format} = 'float'; validate_ok {v => -1.10000002384186}, $schema; validate_ok {v => 1.10000002384186}, $schema; validate_ok {v => 0.10000000000000}, $schema, E('/v', 'Does not match float format.'); } { local $schema->{properties}{v}{format} = 'ipv4'; validate_ok {v => '255.100.30.1'}, $schema; validate_ok {v => '300.0.0.0'}, $schema, E('/v', 'Does not match ipv4 format.'); } { local $schema->{properties}{v}{type} = 'integer'; local $schema->{properties}{v}{format} = 'int32'; validate_ok {v => -2147483648}, $schema; validate_ok {v => 2147483647}, $schema; validate_ok {v => 2147483648}, $schema, E('/v', 'Does not match int32 format.'); } if (JSON::Validator::OpenAPI::IV_SIZE >= 8) { local $schema->{properties}{v}{type} = 'integer'; local $schema->{properties}{v}{format} = 'int64'; validate_ok {v => -9223372036854775808}, $schema; validate_ok {v => 9223372036854775807}, $schema; validate_ok {v => 9223372036854775808}, $schema, E('/v', 'Does not match int64 format.'); } { local $schema->{properties}{v}{format} = 'password'; validate_ok {v => 'whatever'}, $schema; } { local $schema->{properties}{v}{format} = 'unknown'; validate_ok {v => 'whatever'}, $schema; } done_testing; JSON-Validator-1.08/t/jv-required.t000644 000765 000024 00000001164 13057336631 020203 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; 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'}; validate_ok $data1, $schema1; validate_ok $data2, $schema0; # Cannot have required on properties validate_ok $data2, $schema1, E('/mynumber', 'Missing property.'); validate_ok $data1, $schema2; validate_ok $data2, $schema2; done_testing; JSON-Validator-1.08/t/jv-not.t000644 000765 000024 00000000251 13057336631 017157 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $schema = {not => {type => 'string'}}; validate_ok 12, $schema; validate_ok 'str', $schema, E('/', 'Should not match.'); done_testing; JSON-Validator-1.08/t/jv-basic.t000644 000765 000024 00000000423 13166400660 017434 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; sub j { Mojo::JSON::decode_json(Mojo::JSON::encode_json($_[0])); } validate_ok j($_), {type => 'any'} for undef, [], {}, 123, 'foo'; validate_ok j(undef), {type => 'null'}; validate_ok j(1), {type => 'null'}, E('/', 'Not null.'); done_testing; JSON-Validator-1.08/t/acceptance.t000644 000765 000024 00000003274 13203560215 020026 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use JSON::Validator; use Mojo::File 'path'; use Mojo::JSON qw(encode_json false decode_json true); use Test::Mojo; use Test::More; use JSON::Validator 'validate_json'; my $test_suite = path(qw(t draft4-tests)); my $remotes = path(qw(t remotes)); plan skip_all => 'Cannot find test files in t/draft4-tests' unless -d $test_suite; use Mojolicious::Lite; app->static->paths(["$remotes"]); my $t = Test::Mojo->new; $t->get_ok('/integer.json')->status_is(200); my $host_port = $t->ua->server->url->host_port; my $test_only_re = $ENV{TEST_ONLY} || ''; my $todo_re = join('|', 'dependencies', 'change resolution scope - changed scope ref valid', 'remote ref, containing refs itself - remote ref invalid', ); for my $file (sort $test_suite->list->each) { for my $group (@{decode_json($file->slurp)}) { for my $test (@{$group->{tests}}) { my $schema = encode_json $group->{schema}; my $descr = "$group->{description} - $test->{description}"; next if $test_only_re and $descr !~ /$test_only_re/; diag <<"HERE" if $test_only_re; --- description: $descr schema: $schema data: @{[encode_json $test->{data}]} expect_valid: @{[$test->{valid} ? 'Yes' : 'No']} HERE $schema =~ s!http\W+localhost:1234\b!http://$host_port!g; $schema = decode_json $schema; my @errors = eval { JSON::Validator->new->ua($t->ua)->load_and_validate_schema($schema) ->validate($test->{data}); }; my $e = $@ || join ', ', @errors; local $TODO = $descr =~ /$todo_re/ ? 'TODO' : undef; note "ERROR: $e" if $e; is $e ? 'invalid' : 'valid', $test->{valid} ? 'valid' : 'invalid', $descr; } } } done_testing(); JSON-Validator-1.08/t/load-http.t000644 000765 000024 00000000711 13206231153 017624 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; my $validator = JSON::Validator->new; $validator->schema('http://swagger.io/v2/schema.json'); isa_ok($validator->schema, 'Mojo::JSON::Pointer'); like $validator->schema->get('/title'), qr{swagger}i, 'got swagger spec'; ok $validator->schema->get('/patternProperties/^x-/description'), 'resolved vendorExtension $ref'; done_testing; JSON-Validator-1.08/t/issue-71-additionalproperties.t000644 000765 000024 00000000523 13166400660 023537 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; use Test::More; my $schema = { required => ['link'], type => 'object', additionalProperties => false, properties => {link => {format => 'uri'}}, }; validate_ok {haha => 'hehe', link => 'http://a'}, $schema, E('/', 'Properties not allowed: haha.'); done_testing; JSON-Validator-1.08/t/load-from-app.t000644 000765 000024 00000001720 13213206713 020371 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use JSON::Validator::OpenAPI; use Mojolicious; use Test::More; my $validator = JSON::Validator::OpenAPI->new; $validator->ua->server->app(Mojolicious->new); $validator->ua->server->app->routes->get( '/api' => sub { my $c = shift; $c->render( json => { swagger => $c->param('fail') ? undef : '2.0', info => {version => '0.8', title => 'Test client spec'}, schemes => ['http'], host => 'api.example.com', basePath => '/v1', paths => {}, } ); } ); eval { $validator->load_and_validate_schema('/api') }; # Some CPAN testers says: [JSON::Validator] GET http://127.0.0.1:61594/api == Service Unavailable at JSON/Validator.pm line 274. plan skip_all => $@ if $@ =~ /Service Unavailable/i; is $@, '', 'loaded valid schema from app'; eval { $validator->load_and_validate_schema('/api?fail=1') }; like $@, qr{got null}, 'loaded invalido schema from app'; done_testing; JSON-Validator-1.08/t/jv-formats.t000644 000765 000024 00000005272 13173160273 020036 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; use Test::More; my $schema = {type => 'object', properties => {v => {type => 'string'}}}; { $schema->{properties}{v}{format} = 'date-time'; validate_ok {v => $_}, $schema for ( '2017-03-29T23:02:55.831Z', '2017-03-29t23:02:55.01z', '2017-03-29 23:02:55-12:00', '2016-02-29T23:02:55+05:00' ); validate_ok {v => $_}, $schema, E('/v', 'Does not match date-time format.') for ( 'xxxx-xx-xxtxx:xx:xxz', '2017-03-29T23:02:60Z', '2017-03-29T23:61:55Z', '2017-03-29T24:02:55Z', '2017-03-32T23:02:55Z', '2017-02-30T23:02:55Z', '2017-02-29T23:02:55Z', '2017-13-29T23:02:55Z', '2017-03-00T23:02:55Z', '2017-00-29T23:02:55Z', '2017-03-29\t23:02:55-12:00', ); } { local $schema->{properties}{v}{format} = 'email'; validate_ok {v => 'jhthorsen@cpan.org'}, $schema; validate_ok {v => 'foo'}, $schema, E('/v', 'Does not match email format.'); } { local $TODO = JSON::Validator::VALIDATE_HOSTNAME ? undef : 'Install Data::Validate::Domain'; local $schema->{properties}{v}{format} = 'hostname'; validate_ok {v => 'mojolicio.us'}, $schema; validate_ok {v => '[]'}, $schema, E('/v', 'Does not match hostname format.'); } { local $schema->{properties}{v}{format} = 'ipv4'; validate_ok {v => '255.100.30.1'}, $schema; validate_ok {v => '300.0.0.0'}, $schema, E('/v', 'Does not match ipv4 format.'); } { local $TODO = JSON::Validator::VALIDATE_IP ? undef : 'Install Data::Validate::IP'; local $schema->{properties}{v}{format} = 'ipv6'; validate_ok {v => '::1'}, $schema; validate_ok {v => '300.0.0.0'}, $schema, E('/v', 'Does not match ipv6 format.'); } { local $schema->{properties}{v}{format} = 'regex'; validate_ok {v => '(\w+)'}, $schema; validate_ok {v => '(\w'}, $schema, E('/v', 'Does not match regex format.'); } { local $schema->{properties}{v}{format} = 'uri'; validate_ok {v => '//example.com/no-scheme'}, $schema, E('/v', 'Scheme missing from URI.'); validate_ok {v => ''}, $schema, E('/v', 'Scheme, path or fragment are required.'); validate_ok {v => '0://mojolicio.us/?ø=123'}, $schema, E('/v', 'Scheme must begin with a letter.'); validate_ok {v => 'http://example.com/%z'}, $schema, E('/v', 'Invalid hex escape.'); validate_ok {v => 'http://example.com/%a'}, $schema, E('/v', 'Hex escapes are not complete.'); validate_ok {v => 'http:////'}, $schema, E('/v', 'Path cannot not start with //.'); validate_ok {v => 'http://mojolicio.us/?ø=123'}, $schema; validate_ok {v => '/relative-path'}, $schema; validate_ok {v => 'relative-path'}, $schema; } { local $schema->{properties}{v}{format} = 'unknown'; validate_ok {v => 'whatever'}, $schema; } done_testing; JSON-Validator-1.08/t/draft4-tests/000755 000765 000024 00000000000 13217716317 020104 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/deep-mixed-ref.t000644 000765 000024 00000001250 13173160273 020531 0ustar00jhthorsenstaff000000 000000 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(cache_paths => [])->schema($file); my @errors = $validator->validate({age => 1, weight => {mass => 72, unit => 'kg'}, height => 100}); is int(@errors), 0, 'valid input'; 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')); @errors = $validator->validate({age => 'not a number'}); is int(@errors), 1, 'invalid age'; done_testing; JSON-Validator-1.08/t/jv-array.t000644 000765 000024 00000002334 13166751064 017503 0ustar00jhthorsenstaff000000 000000 use lib '.'; use Mojo::Base -strict; use t::Helper; my $simple = {type => 'array', items => {type => 'number'}}; my $length = {type => 'array', minItems => 2, maxItems => 2}; my $unique = {type => 'array', uniqueItems => 1, items => {type => 'integer'}}; my $tuple = { type => 'array', items => [ {type => 'number'}, {type => 'string'}, {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']}, {type => 'string', enum => ['NW', 'NE', 'SW', 'SE']} ] }; validate_ok [1], $simple; validate_ok [1, 'foo'], $simple, E('/1', 'Expected number - got string.'); validate_ok [1], $length, E('/', 'Not enough items: 1/2.'); validate_ok [1, 2], $length; validate_ok [1, 2, 3], $length, E('/', 'Too many items: 3/2.'); validate_ok [123, 124], $unique; validate_ok [1, 2, 1], $unique, E('/', 'Unique items required.'); validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW'], $tuple; validate_ok [24, 'Sussex', 'Drive'], $tuple; validate_ok [10, 'Downing', 'Street'], $tuple; validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $tuple; $tuple->{additionalItems} = Mojo::JSON->false; validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $tuple, E('/', 'Invalid number of items: 5/4.'); done_testing; JSON-Validator-1.08/t/booleans.t000644 000765 000024 00000003050 13203174447 017542 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; use Test::More; my $schema = {properties => {v => {type => 'boolean'}}}; validate_ok {v => '0'}, $schema, E('/v', 'Expected boolean - got string.'); validate_ok {v => 'false'}, $schema, E('/v', 'Expected boolean - got string.'); validate_ok {v => 1}, $schema, E('/v', 'Expected boolean - got number.'); validate_ok {v => 0.5}, $schema, E('/v', 'Expected boolean - got number.'); validate_ok {v => Mojo::JSON->true}, $schema; validate_ok {v => Mojo::JSON->false}, $schema; t::Helper->validator->coerce(booleans => 1); validate_ok {v => !!1}, $schema; validate_ok {v => !!0}, $schema; validate_ok {v => 'false'}, $schema; validate_ok {v => 'true'}, $schema; validate_ok {v => 1}, $schema; validate_ok {v => 0.5}, $schema; validate_ok {v => '1'}, $schema, E('/v', 'Expected boolean - got string.'); validate_ok {v => '0'}, $schema, E('/v', 'Expected boolean - got string.'); validate_ok {v => ''}, $schema, E('/v', 'Expected boolean - got string.'); SKIP: { skip 'YAML::XS is not installed', 1 unless eval 'require YAML::XS;1'; t::Helper->validator->coerce(booleans => 0); # see that _load_schema_from_text() turns it back on my $data = t::Helper->validator->_load_schema_from_text(\"---\nv: true\n"); validate_ok $data, $schema; ok(t::Helper->validator->coerce->{booleans}, 'coerce booleans'); } SKIP: { skip 'boolean not installed', 1 unless eval 'require boolean;1'; validate_ok {type => 'boolean'}, {type => 'object', properties => {type => {type => 'string'}}}; } done_testing; JSON-Validator-1.08/t/bundle.t000644 000765 000024 00000003347 13217716130 017216 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; my $validator = JSON::Validator->new; my $bundled; # Run multiple times to make sure _reset() works for my $n (1 .. 3) { note "[$n] replace=1"; $bundled = $validator->bundle( { replace => 1, schema => {name => {'$ref' => '#/definitions/name'}, definitions => {name => {type => 'string'}}}, } ); is $bundled->{name}{type}, 'string', "[$n] replace=1"; note "[$n] replace=0"; $bundled = $validator->schema( { name => {'$ref' => '#/definitions/name'}, age => {'$ref' => 'b.json#/definitions/age'}, definitions => {name => {type => 'string'}}, B => {id => 'b.json', definitions => {age => {type => 'integer'}}}, } )->bundle; is $bundled->{definitions}{name}{type}, 'string', "[$n] name still in definitions"; is $bundled->{definitions}{'_b_json-_definitions_age'}{type}, 'integer', "[$n] added to definitions"; isnt $bundled->{age}, $validator->schema->get('/age'), "[$n] new age ref"; is $bundled->{name}, $validator->schema->get('/name'), "[$n] same name ref"; is $bundled->{age}{'$ref'}, '#/definitions/_b_json-_definitions_age', "[$n] age \$ref point to /definitions/_b_json-_definitions_age"; is $bundled->{name}{'$ref'}, '#/definitions/name', "[$n] name \$ref point to /definitions/name"; } is $validator->get([qw(name type)]), 'string', 'get /name/$ref'; is $validator->get('/name/type'), 'string', 'get /name/type'; is $validator->get('/name/$ref'), undef, 'get /name/$ref'; is $validator->schema->get('/name/type'), 'string', 'schema get /name/type'; is $validator->schema->get('/name/$ref'), '#/definitions/name', 'schema get /name/$ref'; done_testing; JSON-Validator-1.08/t/booleans-xs.t000644 000765 000024 00000000616 13057373572 020206 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; use Test::More; plan skip_all => 'Cpanel::JSON::XS and Mojo::JSON::MaybeXS is required.' unless eval 'require Cpanel::JSON::XS;require Mojo::JSON::MaybeXS;1'; validate_ok {disabled => Mojo::JSON->true}, {properties => {disabled => {type => 'boolean'}}}; validate_ok {disabled => Mojo::JSON->false}, {properties => {disabled => {type => 'boolean'}}}; done_testing; JSON-Validator-1.08/t/openapi.t000644 000765 000024 00000003743 13217663715 017412 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use JSON::Validator::OpenAPI::Mojolicious; is JSON::Validator::OpenAPI::SPECIFICATION_URL(), 'http://swagger.io/v2/schema.json', 'spec url'; my $openapi = JSON::Validator::OpenAPI::Mojolicious->new; my ($schema, @errors); # standard $schema = {type => 'object', properties => {age => {type => 'integer'}}}; @errors = $openapi->validate_input({age => '42'}, $schema); like "@errors", qr{Expected integer - got string}, 'string != integer'; # readOnly $schema->{properties}{age}{readOnly} = Mojo::JSON->true; @errors = $openapi->validate_input({age => 42}, $schema); like "@errors", qr{Read-only}, 'ro'; # collectionFormat $schema = {type => 'array', items => {collectionFormat => 'csv', type => 'integer'}}; @errors = $openapi->validate_input('1,2,3', $schema); is "@errors", '', 'csv data'; # file $schema = {type => 'file', required => Mojo::JSON->true}; @errors = $openapi->validate_input(undef, $schema); like "@errors", qr{Missing property}, 'file'; # discriminator $openapi->schema( { definitions => { Cat => { type => 'object', required => ['huntingSkill'], properties => { huntingSkill => { default => 'lazy', enum => ['clueless', 'lazy', 'adventurous', 'aggressive'], type => 'string', } } } } } ); $schema = { discriminator => 'petType', properties => {petType => {'type' => 'string'}}, required => ['petType'], type => 'object', }; @errors = $openapi->validate_input({}, $schema); is "@errors", "/: Discriminator petType has no value.", "petType has no value"; @errors = $openapi->validate_input({petType => 'Bat'}, $schema); is "@errors", "/: No definition for discriminator Bat.", "no definition for discriminator"; @errors = $openapi->validate_input({petType => 'Cat'}, $schema); is "@errors", "/huntingSkill: Missing property.", "missing property"; diag join ',', @errors; done_testing; JSON-Validator-1.08/t/jv-oneof.t000644 000765 000024 00000002264 13057336631 017473 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $schema = {oneOf => [{type => 'string', maxLength => 5}, {type => 'number', minimum => 0}]}; validate_ok 'short', $schema; validate_ok 12, $schema; $schema = {oneOf => [{type => 'number', multipleOf => 5}, {type => 'number', multipleOf => 3}]}; validate_ok 10, $schema; validate_ok 9, $schema; validate_ok 15, $schema, E('/', 'All of the oneOf rules match.'); validate_ok 13, $schema, E('/', 'oneOf failed: Not multiple of 5. Not multiple of 3.'); $schema = {oneOf => [{type => 'object'}, {type => 'string', multipleOf => 3}]}; validate_ok 13, $schema, E('/', 'oneOf failed: Expected object or string, got number.'); $schema = {oneOf => [{type => 'object'}, {type => 'number', multipleOf => 3}]}; validate_ok 13, $schema, E('/', 'oneOf failed: Not multiple of 3.'); # Alternative oneOf # http://json-schema.org/latest/json-schema-validation.html#anchor79 $schema = {type => 'object', properties => {x => {type => ['string', 'null'], format => 'date-time'}}}; validate_ok {x => 'foo'}, $schema, E('/x', 'anyOf failed: Does not match date-time format.'); validate_ok {x => '2015-04-21T20:30:43.000Z'}, $schema; validate_ok {x => undef}, $schema; done_testing; JSON-Validator-1.08/t/load-data.t000644 000765 000024 00000001421 13057330026 017560 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; my $validator = JSON::Validator->new; my @errors = $validator->schema('data://Some::Module/spec.json')->validate({firstName => 'yikes!'}); is int(@errors), 1, 'one error'; is $errors[0]->path, '/lastName', 'lastName'; is $errors[0]->message, 'Missing property.', 'required'; is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON'; done_testing; package Some::Module; __DATA__ @@ spec.json { "title": "Example Schema", "type": "object", "required": ["firstName", "lastName"], "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "age": { "type": "integer", "minimum": 0, "description": "Age in years" } } } JSON-Validator-1.08/t/coerce-args.t000644 000765 000024 00000000651 13057327757 020151 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; my $validator = JSON::Validator->new; my %coerce = (booleans => 1); is_deeply($validator->coerce(%coerce)->coerce, {booleans => 1}, 'hash is accepted'); is_deeply($validator->coerce(\%coerce)->coerce, {booleans => 1}, 'hash reference is accepted'); is_deeply($validator->coerce(1)->coerce, {%coerce, numbers => 1, strings => 1}, '1 is accepted'); done_testing; JSON-Validator-1.08/t/jv-const.t000644 000765 000024 00000003365 13173160273 017512 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $faithful = {type => 'object', properties => {constancy => {const => "as the northern star"}}}; my $ambitious = { type => 'object', properties => {constancy => {const => "there is a tide in the affairs of men"}} }; validate_ok {name => "Caesar", constancy => "as the northern star"}, $faithful; validate_ok {name => "Brutus", constancy => "there is a tide in the affairs of men"}, $ambitious; validate_ok {name => "Cassius", constancy => "Cassius from bondage will deliver Cassius"}, $faithful, E('/constancy', q{Does not match const: "as the northern star".}); validate_ok( { name => "Calpurnia", constancy => "Do not go forth today. Call it my fear That keeps you in the house" }, $ambitious, E('/constancy', q{Does not match const: "there is a tide in the affairs of men".}) ); # Now oneOf should work right # before the fix, this failed with: "All of the oneOf rules match." # because "likes: chocolate" vs. "peanutbutter" wasn't being considered my $schema = { type => 'object', properties => { people => { type => 'array', items => { oneOf => [{'$ref' => '#/definitions/chocolate'}, {'$ref' => '#/definitions/peanutbutter'}], }, }, }, definitions => { chocolate => { type => 'object', properties => {name => {type => 'string'}, age => {type => 'number'}, likes => {const => 'chocolate'}}, }, peanutbutter => { type => 'object', properties => { name => {type => 'string'}, age => {type => 'number'}, likes => {const => 'peanutbutter'}, }, }, }, }; validate_ok {people => [{name => 'mr. chocolate fan', age => 42, likes => 'peanutbutter'}]}, $schema; done_testing; JSON-Validator-1.08/t/spec/000755 000765 000024 00000000000 13217716317 016512 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/schema-as-attr.t000644 000765 000024 00000000567 12725063675 020573 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; my $json = JSON::Validator->new; my $schema; no warnings 'redefine'; *JSON::Validator::_validate = sub { $schema = shift->schema }; $json->validate({data => 1}, {type => 'object'}); is_deeply $schema->data, {type => 'object'}, 'schema() localized'; is $json->schema, undef, 'schema() is not set'; done_testing; JSON-Validator-1.08/t/issue-22-duplicate-error-messages.t000644 000765 000024 00000000754 13057336631 024227 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; # https://github.com/jhthorsen/json-validator/issues/22 validate_ok {foo => 'x'}, 'data://main/test.schema', E('/foo', 'Not in enum list: bar, baz.'); validate_ok {foo => 123}, 'data://main/test.schema', E('/foo', 'Expected string - got number.'); done_testing; __DATA__ @@ test.schema { "$schema": "http://json-schema.org/draft-04/schema#", "title": "test", "type": "object", "properties": { "foo": {"type": "string", "enum": ["bar", "baz"]} } } JSON-Validator-1.08/t/validate-recursive.t000644 000765 000024 00000002332 13203560215 021530 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use JSON::Validator 'validate_json'; { use Mojolicious::Lite; post '/' => sub { my $c = shift; my @errors = validate_json $c->req->json, 'data://main/spec.json'; $c->render(status => @errors ? 400 : 200, json => \@errors); }; } my $t = Test::Mojo->new; $t->post_ok('/', json => {})->status_is(400)->content_like(qr{/person}); $t->post_ok('/', json => {person => {name => 'superwoman'}})->status_is(200); $t->post_ok('/', json => {person => {name => 'superwoman', children => [{name => 'batboy'}]}}) ->status_is(200); $t->post_ok('/', json => {person => {name => 'superwoman', children => [{}]}})->status_is(400) ->json_is('/0/path' => '/person/children/0/name'); done_testing; __DATA__ @@ spec.json { "type": "object", "properties": { "person": { "$ref": "#/definitions/person" } }, "required": [ "person" ], "definitions": { "person": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string" }, "children": { "type": "array", "items": { "$ref": "#/definitions/person" } } } } } } JSON-Validator-1.08/t/jv-anyof.t000644 000765 000024 00000002600 13057336631 017473 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $validator = JSON::Validator->new; my $schema = {anyOf => [{type => "string", maxLength => 5}, {type => "number", minimum => 0}]}; validate_ok 'short', $schema; validate_ok 'too long', $schema, E('/', 'anyOf failed: String is too long: 8/5.'); validate_ok 12, $schema; validate_ok - 1, $schema, E('/', 'anyOf failed: -1 < minimum(0)'); validate_ok {}, $schema, E('/', 'anyOf failed: Expected string or number, got object.'); # anyOf with explicit integer (where _guess_data_type returns 'number') my $schemaB = {anyOf => [{type => "integer"}, {minimum => 2}]}; validate_ok 1, $schemaB; validate_ok( {type => 'string'}, { properties => { type => { anyOf => [ {'$ref' => '#/definitions/simpleTypes'}, { type => 'array', items => {'$ref' => '#/definitions/simpleTypes'}, minItems => 1, uniqueItems => Mojo::JSON::true, } ] }, }, definitions => {simpleTypes => {enum => [qw(array boolean integer null number object string)]}} } ); validate_ok( {age => 6}, { '$schema' => 'http://json-schema.org/draft-04/schema#', type => 'object', title => 'test', description => 'test', properties => {age => {type => 'number', anyOf => [{multipleOf => 5}, {multipleOf => 3}]}} } ); done_testing; JSON-Validator-1.08/t/load-and-validate-spec.t000644 000765 000024 00000003470 13203560262 022136 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator::OpenAPI; my $validator = JSON::Validator::OpenAPI->new; is $validator->load_and_validate_spec('data://main/echo.json'), $validator, 'load_and_validate_spec no options'; is $validator->schema->get('/info/version'), '42.0', 'version'; eval { $validator->load_and_validate_spec('data://main/swagger2/issues/89.json') }; like $@, qr{/definitions/\$ref}si, 'ref in the wrong place'; eval { $validator->load_and_validate_spec('data://main/swagger2/issues/89.json', {allow_invalid_ref => 1, version_from_class => 'JSON::Validator'}); is $validator->schema->get('/info/version'), JSON::Validator->VERSION, 'version_from_class'; is_deeply $validator->schema->get('/definitions/foo/properties'), {}, 'allow_invalid_ref'; } or diag $@; done_testing; __DATA__ @@ echo.json { "swagger" : "2.0", "info" : { "version": "42.0", "title" : "Pets" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/echo" : { "post" : { "x-mojo-name" : "echo", "parameters" : [ { "in": "body", "name": "body", "schema": { "type" : "object" } } ], "responses" : { "200": { "description": "Echo response", "schema": { "type": "object" } }, "400": { "description": "Echo response", "schema": { "type": "object" } } } } } } } @@ swagger2/issues/89.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test auto response" }, "paths" : { "$ref": "#/x-def/paths" }, "definitions": { "$ref": "#/x-def/defs" }, "x-def": { "defs": { "foo": { "properties": {} } }, "paths": { "/auto" : { "post" : { "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } } } } JSON-Validator-1.08/t/00-basic.t000644 000765 000024 00000002325 13057327744 017251 0ustar00jhthorsenstaff000000 000000 use Test::More; use File::Find; if (($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/) { plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/'; } if (!eval 'use Test::Pod; 1') { *Test::Pod::pod_file_ok = sub { SKIP: { skip "pod_file_ok(@_) (Test::Pod is required)", 1 } }; } if (!eval 'use Test::Pod::Coverage; 1') { *Test::Pod::Coverage::pod_coverage_ok = sub { SKIP: { skip "pod_coverage_ok(@_) (Test::Pod::Coverage is required)", 1 } }; } if (!eval 'use Test::CPAN::Changes; 1') { *Test::CPAN::Changes::changes_file_ok = sub { SKIP: { skip "changes_ok(@_) (Test::CPAN::Changes is required)", 4 } }; } find( {wanted => sub { /\.pm$/ and push @files, $File::Find::name }, no_chdir => 1}, -e 'blib' ? 'blib' : 'lib', ); unless (eval 'require Hash::MultiValue;1') { @files = grep { !/Dancer/ } @files; } plan tests => @files * 3 + 4; for my $file (@files) { my $module = $file; $module =~ s,\.pm$,,; $module =~ s,.*/?lib/,,; $module =~ s,/,::,g; ok eval "use $module; 1", "use $module" or diag $@; Test::Pod::pod_file_ok($file); Test::Pod::Coverage::pod_coverage_ok($module, {also_private => ['load_and_validate_spec', qr/^[A-Z_]+$/]}); } Test::CPAN::Changes::changes_file_ok(); JSON-Validator-1.08/t/jv-boolean.t000644 000765 000024 00000002163 13203175030 017765 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; sub j { Mojo::JSON::decode_json(Mojo::JSON::encode_json($_[0])); } my $schema = {type => 'object', properties => {nick => {type => 'boolean'}}}; validate_ok {nick => true}, $schema; validate_ok {nick => 1000}, $schema, E('/nick', 'Expected boolean - got number.'); validate_ok {nick => 0.5}, $schema, E('/nick', 'Expected boolean - got number.'); validate_ok {nick => 'nick'}, $schema, E('/nick', 'Expected boolean - got string.'); validate_ok {nick => bless({}, 'BoolTestOk')}, $schema; validate_ok {nick => bless({}, 'BoolTestFail')}, $schema, E('/nick', 'Expected boolean - got BoolTestFail.'); validate_ok j(Mojo::JSON->false), {type => 'boolean'}; validate_ok j(Mojo::JSON->true), {type => 'boolean'}; validate_ok j('foo'), {type => 'boolean'}, E('/', 'Expected boolean - got string.'); validate_ok undef, {properties => {}}, E('/', 'Expected object - got null.'); t::Helper->validator->coerce(1); validate_ok {nick => 1000}, $schema; validate_ok {nick => 0.5}, $schema; done_testing; package BoolTestOk; use overload '""' => sub {1}; package BoolTestFail; use overload '""' => sub {2}; JSON-Validator-1.08/t/invalid-ref.t000644 000765 000024 00000000502 13203560215 020127 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; eval { JSON::Validator->new->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" } } } JSON-Validator-1.08/t/issue-59-oneof-blessed-booleans.t000644 000765 000024 00000001203 13167203732 023643 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use Mojo::JSON; use JSON::Validator; my $validator = JSON::Validator->new->schema('data://main/spec.json'); my @errors = $validator->validate({prop1 => Mojo::JSON->false, prop2 => Mojo::JSON->false}); is "@errors", ""; done_testing; __DATA__ @@ spec.json { "type": "object", "properties": { "prop1": { "$ref": "data://main/defs.json#/definitions/item" }, "prop2": { "$ref": "data://main/defs.json#/definitions/item" } } } @@ defs.json { "definitions": { "item": { "oneOf": [ {"type": "object"}, {"type": "boolean"} ] } } } JSON-Validator-1.08/t/load-json.t000644 000765 000024 00000001700 13166523636 017634 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; use Mojo::File 'path'; 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!'}); is int(@errors), 1, 'one error'; is $errors[0]->path, '/lastName', 'lastName'; is $errors[0]->message, 'Missing property.', 'required'; is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON'; my $spec = path($file)->slurp; $spec =~ s!"#!"person.json#! or die "Invalid spec: $spec"; path("$file.2")->spurt($spec); ok eval { JSON::Validator->new->schema("$file.2") }, 'test issue #1 where $ref could not point to a file' or diag $@; unlink "$file.2"; # load from cache is(eval { JSON::Validator->new->schema('http://swagger.io/v2/schema.json'); 42 }, 42, 'loaded from cache') or diag $@; done_testing; JSON-Validator-1.08/t/remotes/000755 000765 000024 00000000000 13217716317 017236 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/relative-ref.t000644 000765 000024 00000000762 13204313737 020332 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $file = File::Spec->catfile(File::Basename::dirname(__FILE__), 'spec', 'with-relative-ref.json'); my $validator = t::Helper->validator->cache_paths([]); validate_ok {age => -1}, $file, E('/age', '-1 < minimum(0)'); use Mojolicious::Lite; push @{app->static->paths}, File::Basename::dirname(__FILE__); $validator->ua(app->ua); validate_ok {age => -2}, app->ua->server->url->clone->path('/spec/with-relative-ref.json'), E('/age', '-2 < minimum(0)'); done_testing; JSON-Validator-1.08/t/jv-object.t000644 000765 000024 00000006277 13170375323 017640 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; use Test::More; my $schema; { $schema = {type => 'object'}; validate_ok {mynumber => 1}, $schema; validate_ok [1], $schema, E('/', 'Expected object - got array.'); } { $schema->{properties} = { number => {type => 'number'}, street_name => {type => 'string'}, street_type => {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']} }; local $schema->{patternProperties} = {'^S_' => {type => 'string'}, '^I_' => {type => 'integer'}}; validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue'}, $schema; validate_ok {number => '1600', street_name => 'Pennsylvania', street_type => 'Avenue'}, $schema, E('/number', 'Expected number - got string.'); validate_ok {number => 1600, street_name => 'Pennsylvania'}, $schema; validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW'}, $schema; validate_ok {'S_25' => 'This is a string', 'I_0' => 42}, $schema; validate_ok {'S_0' => 42}, $schema, E('/S_0', 'Expected string - got number.'); } { local $TODO = 't/openapi-set-request.t fails because of some oneOf logic'; my $data = {}; validate_ok $data, {type => 'object', properties => {number => {type => 'number', default => 42}}}; is $data->{number}, 42, 'default value was set'; } { local $schema->{additionalProperties} = 0; validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW'}, $schema, E('/', 'Properties not allowed: direction.'); $schema->{additionalProperties} = {type => 'string'}; validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW'}, $schema; } { local $schema->{required} = ['number', 'street_name']; validate_ok {number => 1600, street_type => 'Avenue'}, $schema, E('/street_name', 'Missing property.'); } { $schema = {type => 'object', minProperties => 1}; validate_ok {}, $schema, E('/', 'Not enough properties: 0/1.'); $schema = {type => 'object', minProperties => 2, maxProperties => 3}; validate_ok {a => 1}, $schema, E('/', 'Not enough properties: 1/2.'); validate_ok {a => 1, b => 2}, $schema; validate_ok {a => 1, b => 2, c => 3, d => 4}, $schema, E('/', 'Too many properties: 4/3.'); } { local $TODO = 'Add support for dependencies'; $schema = { type => 'object', properties => { name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'} }, required => ['name'], dependencies => {credit_card => ['billing_address']} }; validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, E('/credit_card', 'Missing billing_address.', 'credit_card'); } { my $schema = {type => 'object', properties => {name => {type => 'string'}}}; validate_ok {}, $schema; # does not matter ok !$schema->{patternProperties}, 'patternProperties was not added issue#47'; } sub TO_JSON { return {age => shift->{age}} } my $obj = bless {age => 'not_a_string'}, 'main'; validate_ok $obj, {properties => {age => {type => 'integer'}}}, E('/age', 'Expected integer - got string.', 'age is not a string'); done_testing; JSON-Validator-1.08/t/id-keyword.t000644 000765 000024 00000005237 13204322432 020015 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::JSON 'encode_json'; use Test::Mojo; use Test::More; use JSON::Validator; my ($base_url, $jv, $t, @e); use Mojolicious::Lite; get '/invalid-fragment' => 'invalid-fragment'; get '/invalid-relative' => 'invalid-relative'; get '/relative-to-the-root' => 'relative-to-the-root'; $t = Test::Mojo->new; $jv = JSON::Validator->new(ua => $t->ua); $t->get_ok('/relative-to-the-root.json')->status_is(200); $base_url = $t->tx->req->url->to_abs->path('/'); like $base_url, qr{^http}, 'got base_url to web server'; eval { $jv->load_and_validate_schema("${base_url}relative-to-the-root.json") }; ok !$@, "${base_url}relative-to-the-root.json" or diag $@; my $schema = $jv->schema; is $schema->get('/id'), 'http://example.com/relative-to-the-root.json', 'get /id'; is $schema->get('/definitions/B/id'), 'b.json', 'id /definitions/B/id'; is $schema->get('/definitions/B/definitions/X/id'), '#bx', 'id /definitions/B/definitions/X/id'; is $schema->get('/definitions/B/definitions/Y/id'), 't/inner.json', 'id /definitions/B/definitions/Y/id'; is $schema->get('/definitions/C/definitions/X/id'), 'urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f', 'id /definitions/C/definitions/X/id'; is $schema->get('/definitions/C/definitions/Y/id'), '#cy', 'id /definitions/C/definitions/Y/id'; my $ref = $schema->get('/definitions/R1'); ok $ref->{$_}, "got $_" for qw($ref %%fqn %%schema); is encode_json($ref), '{"$ref":"b.json#bx"}', 'ref encode_json'; $ref = tied %$ref; is $ref->ref, 'b.json#bx', 'ref ref'; is $ref->fqn, 'http://example.com/b.json#bx', 'ref fqn'; ok $ref->schema->{definitions}{Y}, 'ref schema'; eval { $jv->load_and_validate_schema("${base_url}invalid-fragment.json") }; like $@, qr{cannot have a fragment}, 'Root id cannot have a fragment' or diag $@; eval { $jv->load_and_validate_schema("${base_url}invalid-relative.json") }; like $@, qr{cannot have a relative}, 'Root id cannot be relative' or diag $@; done_testing; __DATA__ @@ invalid-fragment.json.ep {"id": "http://example.com/invalid-fragment.json#cannot_be_here"} @@ invalid-relative.json.ep {"id": "whatever"} @@ relative-to-the-root.json.ep { "id": "http://example.com/relative-to-the-root.json", "definitions": { "A": { "id": "#a" }, "B": { "id": "b.json", "definitions": { "X": { "id": "#bx" }, "Y": { "id": "t/inner.json" } } }, "C": { "id": "c.json", "definitions": { "X": { "id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f" }, "Y": { "id": "#cy" } } }, "R1": { "$ref": "b.json#bx" }, "R2": { "$ref": "#a" }, "R3": { "$ref": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f" } } } JSON-Validator-1.08/t/openapi-request.t000644 000765 000024 00000014607 13166400660 021070 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojo::JSON qw(false true); use Mojolicious::Controller; use JSON::Validator::OpenAPI::Mojolicious; my $t = Test::Mojo->new; my $c = Mojolicious::Controller->new(tx => Mojo::Transaction::HTTP->new); my $openapi = JSON::Validator::OpenAPI::Mojolicious->new; my $input = {}; # formData make_request( qq(multipart/form-data; boundary=---------------------------9051914041544843365972754266), qq(-----------------------------9051914041544843365972754266\x0d\x0a), qq(Content-Disposition: form-data; name="age"\x0d\x0a\x0d\x0a), qq(42\x0d\x0a), qq(-----------------------------9051914041544843365972754266\x0d\x0a), qq(Content-Disposition: form-data; name="too_cool"\x0d\x0a\x0d\x0a), qq(false\x0d\x0a), qq(-----------------------------9051914041544843365972754266\x0d\x0a), qq(Content-Disposition: form-data; name="age"\x0d\x0a\x0d\x0a), qq(34\x0d\x0a), qq(-----------------------------9051914041544843365972754266--), ); validate_request( { parameters => [ { name => 'age', type => 'array', collectionFormat => 'multi', items => {type => 'number'}, in => 'formData' }, {name => 'too_cool', type => 'boolean', in => 'formData'}, {name => 'x' => type => 'number', required => 1, default => 33, in => 'formData'}, ] }, sub { is "@_", "", "valid form"; is_deeply $input, {age => [42, 34], too_cool => false, x => 33}, 'formData'; } ); validate_request( {parameters => [{name => 'filex', type => 'file', in => 'formData', required => true}]}, sub { like "@_", qr{/filex: Missing property}, 'missing filex property'; is_deeply $input, {}, 'nothing in input'; } ); # collectionFormat make_request('application/json', qq([])); validate_request( { parameters => [ {name => 'x', in => 'query', type => 'integer'}, { name => 'c', in => 'query', type => 'array', collectionFormat => 'csv', items => {type => 'integer'} }, ] }, sub { is_deeply $input, {x => 0, c => [3, 1, 2]}, 'collectionFormat' } ); # json body make_request('application/json', qq([1,2,3])); validate_request( {parameters => [{name => 'body', in => 'body', schema => {type => 'object'}}]}, sub { like "@_", qr{/body: Expected object - got array}, 'expected object'; } ); make_request('application/json', qq({"age":42})); validate_request( {parameters => [{name => 'body', in => 'body', schema => {type => 'object'}}]}, sub { is_deeply $input, {body => {age => 42}}, 'json body'; is "@_", "", "valid json"; } ); # upload make_request( qq(multipart/form-data; boundary=---------------------------9051914041544843365972754266), qq(-----------------------------9051914041544843365972754266\x0d\x0a), qq(Content-Disposition: form-data; name="data"; filename="a.txt"\x0d\x0a), qq(Content-Type: text/plain\x0d\x0a\x0d\x0a), qq(binarydata\x0d\x0a), qq(-----------------------------9051914041544843365972754266--), ); validate_request( {parameters => [{name => 'data', type => 'file', in => 'formData', required => true}]}, sub { is "@_", "", "valid upload"; ok UNIVERSAL::can($input->{data}, 'slurp'), 'data can slurp' or diag $input->{data}; } ); validate_request( {parameters => [{name => 'filex', type => 'file', in => 'formData', required => true}]}, sub { like "@_", qr{/filex: Missing property}, 'missing filex property'; is_deeply $input, {}, 'nothing in input'; } ); # query make_request('application/json', qq([])); validate_request( {parameters => [{name => 'x', in => 'query', type => 'boolean'}]}, sub { is_deeply $input, {x => false}, 'query with boolean' } ); # colliding parameters should be resolved in M::P::OpenAPI validate_request( { todo => 'colliding parameters can be resolved by extracting them manually', parameters => [ {name => 'x' => type => 'number', required => 1, default => 33, in => 'formData'}, {name => 'x' => type => 'number', required => 1, in => 'query'}, ] }, sub { is_deeply $input, {x => 33}, 'colliding parameters in form/query'; } ); # headers $c->tx->req(Mojo::Message::Request->new)->req->headers->header('X-Custom' => 123) ->header('X-DATA' => 'something'); validate_request( { parameters => [ {name => 'x-custom', type => 'integer', required => 1, in => 'header'}, {name => 'X-Data', type => 'string', required => 1, in => 'header'}, ] }, sub { is "@_", "", "valid headers"; is_deeply $input, {'x-custom' => 123, 'X-Data' => 'something'}, 'headers are correct and case insensitive'; } ); # missing header make_request('application/json', qq([])); validate_request( {parameters => [{name => 'x-custom', type => 'string', required => 1, in => 'header'}]}, sub { like "@_", qr{\Q/x-custom: Expected string - got null.\E}, "the header is missing"; } ); # Valid booleans $c->tx->req(Mojo::Message::Request->new) ->req->url->parse('http://127.0.0.1/?a=&b=0&c=false&x=true&y=1&z=0'); validate_request( {parameters => [map { +{name => $_, type => 'boolean', in => 'query'} } qw(a b c x y z),],}, sub { is "@_", "", "valid request"; is_deeply $input, {a => false, b => false, c => false, x => true, y => true, z => false}, 'valid booleans'; } ); # Invalid booleans $c->tx->req(Mojo::Message::Request->new) ->req->url->parse('http://127.0.0.1/?a=something&b=2&c=TRUE'); validate_request( {parameters => [map { +{name => $_, type => 'boolean', in => 'query'} } qw(a b c),],}, sub { like "@_", qr{\Q/a: Expected boolean - got string. /b: Expected boolean - got string. /c: Expected boolean - got string.\E}, "invalid request"; is_deeply $input, {}, 'nothing in input'; } ); done_testing; sub make_request { my ($content_type) = shift; my $length = length join '', @_; my $req = $c->tx->req(Mojo::Message::Request->new)->req; $req->parse(qq(POST /whatever?c=3,1,2&x=0 HTTP/1.1\x0d\x0a)); $req->parse(qq(Content-Type: $content_type\x0d\x0a)); $req->parse(qq(Content-Length: $length\x0d\x0a\x0d\x0a)); $req->parse($_) for @_; ok $req->is_finished, "request $length is finished"; } sub validate_request { my ($schema, $cb) = @_; local $TODO = delete $schema->{todo}; $input = {}; $cb->($openapi->validate_request($c, $schema, $input)); } JSON-Validator-1.08/t/load-yaml.t000644 000765 000024 00000001452 13162230052 017610 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; plan skip_all => $@ unless eval { JSON::Validator::_yaml_module() }; my $validator = JSON::Validator->new; my @errors = $validator->schema('data://Some::Module/s_pec-/-ficaTion')->validate({firstName => 'yikes!'}); is int(@errors), 1, 'one error'; is $errors[0]->path, '/lastName', 'lastName'; is $errors[0]->message, 'Missing property.', 'required'; is_deeply $errors[0]->TO_JSON, {path => '/lastName', message => 'Missing property.'}, 'TO_JSON'; done_testing; package Some::Module; __DATA__ @@ s_pec-/-ficaTion --- title: Example Schema type: object required: - firstName - lastName properties: firstName: type: string lastName: type: string age: type: integer minimum: 0 description: Age in years JSON-Validator-1.08/t/jv-allof.t000644 000765 000024 00000001035 13057336631 017455 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $schema = {allOf => [{type => 'string', maxLength => 5}, {type => 'string', minLength => 3}]}; validate_ok 'short', $schema; validate_ok 12, $schema, E('/', 'allOf failed: Expected string, not number.'); $schema = {allOf => [{type => 'string', maxLength => 7}, {type => 'string', maxLength => 5}]}; validate_ok 'superlong', $schema, E('/', 'allOf failed: String is too long: 9/7. String is too long: 9/5.'); validate_ok 'toolong', $schema, E('/', 'allOf failed: String is too long: 7/5.'); done_testing; JSON-Validator-1.08/t/issue-27-yaml-syck-false.t000644 000765 000024 00000001400 13057327772 022322 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; plan skip_all => $@ unless eval 'require YAML::Syck;1'; use JSON::Validator; Mojo::Util::monkey_patch('JSON::Validator' => _yaml_module => sub {'YAML::Syck'}); my $validator = JSON::Validator->new->schema('data://main/yaml-syck.yml'); my @errors = $validator->validate({firstName => 'Jan Henning', lastName => 'Thorsen', age => 42}); ok $INC{'YAML/Syck.pm'}, 'YAML::Syck is loaded'; ok !$INC{'YAML/XS.pm'}, 'YAML::XS is not loaded'; is "@errors", "/: Properties not allowed: age.", "additionalProperties: false"; done_testing; __DATA__ @@ yaml-syck.yml --- type: object required: [firstName, lastName] additionalProperties: false properties: firstName: { type: string } lastName: { type: string } JSON-Validator-1.08/t/jv-enum.t000644 000765 000024 00000002274 13166400660 017325 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $male = {type => 'object', properties => {chromosomes => {enum => [[qw(X Y)], [qw(Y X)]]}}}; my $female = {type => 'object', properties => {chromosomes => {enum => [[qw(X X)]]}}}; validate_ok {name => "Kate", chromosomes => [qw(X X)]}, $female; validate_ok {name => "Dave", chromosomes => [qw(X Y)]}, $male; validate_ok {name => "Arnie", chromosomes => [qw(Y X)]}, $male; validate_ok {name => "Kate", chromosomes => [qw(X X)]}, $male, E('/chromosomes', 'Not in enum list: ["X","Y"], ["Y","X"].'); validate_ok {name => "Eddie", chromosomes => [qw(X YY )]}, $male, E('/chromosomes', 'Not in enum list: ["X","Y"], ["Y","X"].'); validate_ok {name => "Steve", chromosomes => 'XY'}, $male, E('/chromosomes', 'Not in enum list: ["X","Y"], ["Y","X"].'); # https://github.com/jhthorsen/json-validator/issues/69 validate_ok( {some_prop => ['foo']}, { type => 'object', required => ['some_prop'], properties => { some_prop => { type => 'array', minItems => 1, maxItems => 1, items => [{type => 'string', enum => [qw(x y)]}], }, }, }, E('/some_prop/0', 'Not in enum list: x, y.') ); done_testing; JSON-Validator-1.08/t/Helper.pm000644 000765 000024 00000002430 13173160273 017327 0ustar00jhthorsenstaff000000 000000 package t::Helper; use Mojo::Base -base; use Mojo::JSON 'encode_json'; use Mojo::Util 'monkey_patch'; use JSON::Validator; use Test::More; $ENV{TEST_VALIDATOR_CLASS} = 'JSON::Validator'; sub dump_validator { my $class = shift; my $jv = shift || $class->validator; local $Data::Dumper::Indent = 1; local $jv->{ua} = 'Mojo::UserAgent'; Test::More::note(Data::Dumper::Dumper($jv)); } sub validate_ok { my ($data, $schema, @expected) = @_; my $description ||= @expected ? "errors: @expected" : "valid: " . encode_json($data); my @errors = validator()->schema($schema)->validate($data); Test::More::is_deeply([map { $_->TO_JSON } sort { $a->path cmp $b->path } @errors], [map { $_->TO_JSON } sort { $a->path cmp $b->path } @expected], $description) or Test::More::diag(encode_json(\@errors)); } sub validator { state $obj = $ENV{TEST_VALIDATOR_CLASS}->new } sub import { my $class = shift; my $caller = caller; strict->import; warnings->import; monkey_patch $caller => E => \&JSON::Validator::E; monkey_patch $caller => done_testing => \&Test::More::done_testing; monkey_patch $caller => false => \&Mojo::JSON::false; monkey_patch $caller => true => \&Mojo::JSON::true; monkey_patch $caller => validate_ok => \&validate_ok; } 1; JSON-Validator-1.08/t/openapi-response.t000644 000765 000024 00000003265 13170376102 021232 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojo::JSON qw(false true); use Mojolicious::Controller; use JSON::Validator::OpenAPI::Mojolicious; my $t = Test::Mojo->new; my $c = Mojolicious::Controller->new(tx => Mojo::Transaction::HTTP->new); my $openapi = JSON::Validator::OpenAPI::Mojolicious->new; my ($schema, @errors); { local $TODO = "No idea why this changes to 'No validation rules defined' when running with prove"; $schema = {responses => {200 => {}}}; @errors = $openapi->validate_response($c, $schema, 404, {}); is "@errors", "/: No responses rules defined for status 404.", "no rules"; } $schema = {responses => {default => {}}}; @errors = $openapi->validate_response($c, $schema, 404, {}); is "@errors", "", "default rules"; $schema = {responses => {200 => {schema => {type => 'array'}}}}; @errors = $openapi->validate_response($c, $schema, 200, {}); is "@errors", "/: Expected array - got object.", "invalid response"; @errors = $openapi->validate_response($c, $schema, 200, [1, 2, 3]); is "@errors", "", "valid response"; $schema = {responses => {200 => {'x-json-schema' => {type => 'array'}}}}; @errors = $openapi->validate_response($c, $schema, 200, {}); is "@errors", "/: Expected array - got object.", "invalid x-json-schema response"; $schema = {responses => {200 => {headers => {'X-Location' => {type => 'string'}}}}}; $c->res->headers->header('X-Location' => 42); @errors = $openapi->validate_response($c, $schema, 200, {}); is "@errors", "/: Expected string - got number.", "invalid header"; $c->res->headers->header('X-Location' => 'where wifi is'); @errors = $openapi->validate_response($c, $schema, 200, {}); is "@errors", "", "valid header"; done_testing; JSON-Validator-1.08/t/validate-id.t000644 000765 000024 00000000321 12723273403 020117 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; my $validator = JSON::Validator->new; my @errors = $validator->validate({id => 1}, {type => 'object'}); is "@errors", "", "object"; done_testing; JSON-Validator-1.08/t/issue-42-cache-control.t000644 000765 000024 00000002153 13057336631 022037 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; use File::Temp; plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; $ENV{JSON_VALIDATOR_CACHE_DIR} = '/tmp/whatever'; my $validator = JSON::Validator->new; my @old_files = get_cached_files($validator); is $validator->cache_paths->[0], '/tmp/whatever', 'back compat env'; shift @{$validator->cache_paths}; $validator->schema('https://za.payprop.com/api/docs/api_spec.yaml'); my @new_files = get_cached_files($validator); ok @old_files == @new_files, 'remote file not cached in default cache dir'; my $tmp_dir = File::Temp->newdir; $ENV{JSON_VALIDATOR_CACHE_PATH} = join ':', $tmp_dir->dirname, '/tmp/whatever'; $validator = JSON::Validator->new; is $validator->cache_paths->[0], $tmp_dir->dirname, 'env'; $validator->schema('https://za.payprop.com/api/docs/api_spec.yaml'); @new_files = get_cached_files($validator); ok @new_files > @old_files, 'remote file cached when cache_paths not the default' or diag join "\n", @new_files; done_testing; sub get_cached_files { my ($validator) = @_; return sort map { glob "$_/*" } @{$validator->cache_paths}; } JSON-Validator-1.08/t/recursion.t000644 000765 000024 00000003561 13203177044 017754 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use JSON::Validator 'validate_json'; use JSON::Validator::OpenAPI; my $data = {}; $data->{rec} = $data; $SIG{ALRM} = sub { die 'Recursion!' }; alarm 2; my @errors = ('i_will_be_removed'); eval { @errors = validate_json {top => $data}, 'data://main/spec.json' }; is $@, '', 'no error'; is_deeply(\@errors, [], 'avoided recursion'); # This part of the test checks that we don't go into an infite loop my $validator = JSON::Validator::OpenAPI->new; is $validator->load_and_validate_spec('data://main/user.json'), $validator, 'load_and_validate_spec no recursion'; is $validator->schema($validator->schema->data), $validator, 'schema() handles $schema with recursion'; done_testing; __DATA__ @@ spec.json { "properties": { "top": { "$ref": "#/definitions/again" } }, "definitions": { "again": { "anyOf": [ {"type": "string"}, { "type": "object", "properties": { "rec": {"$ref": "#/definitions/again"} } } ] } } } @@ user.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "User schema" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/user" : { "post" : { "operationId" : "User", "parameters": [{ "name": "data", "in": "body", "required": true, "schema": { "$ref": "#/definitions/user" } }], "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } }, "definitions": { "user": { "type": "object", "properties": { "name": { "type": "string" }, "siblings": { "type": "array", "items": { "$ref": "#/definitions/user" } } } } } } JSON-Validator-1.08/t/validate-schema.t000644 000765 000024 00000002211 13203222766 020763 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator; use lib '.'; use t::Helper (); my $should_fail = JSON::Validator->new->schema('data://main/invalid.json'); my $json_schema = JSON::Validator->new->schema('http://json-schema.org/draft-04/schema#'); my @errors; # The schema is invalid... @errors = $json_schema->validate($should_fail->schema->data); is $errors[0], '/properties/should_fail: Expected object - got array.', 'invalid property element'; # ...but can still be used to validate data. @errors = $should_fail->validate({foo => 123}); is int(@errors), 0, 'data is valid'; # Can also use load_and_validate_schema() to do the same as above eval { JSON::Validator->new->load_and_validate_schema('data://main/invalid.json') }; like $@, qr{Expected object - got array}, 'invalid schema'; done_testing; __DATA__ @@ invalid.json { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Example Schema That Should Fail To Load", "description": "There is an array as the value of an object property, which should not be allowed.", "type": "object", "properties": { "foo": { "type": "integer" }, "should_fail": [] } } JSON-Validator-1.08/t/jv-string.t000644 000765 000024 00000001506 13057336631 017671 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; use Test::More; use utf8; my $schema = { type => 'object', properties => {nick => {type => 'string', minLength => 3, maxLength => 10, pattern => qr{^\w+$}}} }; validate_ok {nick => 'batman'}, $schema; validate_ok {nick => 1000}, $schema, E('/nick', 'Expected string - got number.'); validate_ok {nick => '1000'}, $schema; validate_ok {nick => 'aa'}, $schema, E('/nick', 'String is too short: 2/3.'); validate_ok {nick => 'a' x 11}, $schema, E('/nick', 'String is too long: 11/10.'); like +join('', t::Helper->validator->validate({nick => '[nick]'})), qr{/nick: String does not match}, 'String does not match'; delete $schema->{properties}{nick}{pattern}; validate_ok {nick => 'Déjà vu'}, $schema; t::Helper->validator->coerce(1); validate_ok {nick => 1000}, $schema; done_testing; JSON-Validator-1.08/t/definitions/000755 000765 000024 00000000000 13217716317 020073 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/jv-integer.t000644 000765 000024 00000001727 13057336631 020025 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $schema = {type => 'object', properties => {mynumber => {type => 'integer', minimum => 1, maximum => 4}}}; validate_ok {mynumber => 1}, $schema; validate_ok {mynumber => 4}, $schema; validate_ok {mynumber => 2}, $schema; validate_ok {mynumber => 0}, $schema, E('/mynumber', '0 < minimum(1)'); validate_ok {mynumber => -1}, $schema, E('/mynumber', '-1 < minimum(1)'); validate_ok {mynumber => 5}, $schema, E('/mynumber', '5 > maximum(4)'); validate_ok {mynumber => '2'}, $schema, E('/mynumber', 'Expected integer - got string.'); $schema->{properties}{mynumber}{multipleOf} = 2; validate_ok {mynumber => 3}, $schema, E('/mynumber', 'Not multiple of 2.'); t::Helper->validator->coerce(numbers => 1); validate_ok {mynumber => '2'}, $schema; validate_ok {mynumber => '2xyz'}, $schema, E('/mynumber', 'Expected integer - got string.'); $schema->{properties}{mynumber}{minimum} = -3; validate_ok {mynumber => '-2'}, $schema; done_testing; JSON-Validator-1.08/t/jv-number.t000644 000765 000024 00000001355 13057336631 017655 0ustar00jhthorsenstaff000000 000000 use lib '.'; use t::Helper; my $schema = { type => 'object', properties => {mynumber => {type => 'number', minimum => -0.5, maximum => 2.7}} }; validate_ok {mynumber => 1}, $schema; validate_ok {mynumber => '2'}, $schema, E('/mynumber', 'Expected number - got string.'); t::Helper->validator->coerce(numbers => 1); validate_ok {mynumber => '-0.3'}, $schema; validate_ok {mynumber => '0.1e+1'}, $schema; validate_ok {mynumber => '2xyz'}, $schema, E('/mynumber', 'Expected number - got string.'); validate_ok {mynumber => '.1'}, $schema, E('/mynumber', 'Expected number - got string.'); validate_ok {validNumber => 2.01}, {type => 'object', properties => {validNumber => {type => 'number', multipleOf => 0.01}}}; done_testing; JSON-Validator-1.08/t/validate-json.t000644 000765 000024 00000001123 13023455230 020467 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use JSON::Validator 'validate_json'; { use Mojolicious::Lite; post '/' => sub { my $c = shift; my @errors = validate_json $c->req->json, 'data://main/spec.json'; $c->render(status => @errors ? 400 : 200, text => "@errors"); }; } my $t = Test::Mojo->new; $t->post_ok('/', json => {})->status_is(400)->content_like(qr{/name}); $t->post_ok('/', json => {name => "foo"})->status_is(200); done_testing; __DATA__ @@ spec.json { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] } JSON-Validator-1.08/t/to-json.t000644 000765 000024 00000002236 13002227031 017317 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use JSON::Validator 'validate_json'; my @errors = validate_json(bless({path => '', message => 'yikes'}, 'JSON::Validator::Error'), 'data://main/spec.json'); ok !@errors, 'TO_JSON on objects' or diag join ', ', @errors; @errors = validate_json( { valid => Mojo::JSON->false, errors => [ bless({path => '', message => 'foo'}, 'JSON::Validator::Error'), bless({path => '', message => 'bar'}, 'JSON::Validator::Error') ] }, 'data://main/spec_array.json' ); ok !@errors, 'TO_JSON on objects inside arrays' or diag join ', ', @errors; done_testing; __DATA__ @@ spec.json { "type": "object", "properties": { "message": { "type": "string" } }, "required": ["message"] } @@ spec_array.json { "type": "object", "properties": { "valid": { "type": "boolean" }, "errors": { "type": "array", "items": { "type": "object", "properaties": { "message": { "type": "string" }, "path": { "type": "string" } }, "required": [ "message" ] } } }, "required": [ "errors" ] } JSON-Validator-1.08/t/openapi-compatibility.t000644 000765 000024 00000002100 13057334305 022233 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use File::Spec; use Mojo::Util; use Test::More; ok( module_calls_deprecated('JSON::Validator::OpenAPI'), "JSON::Validator::OpenAPI->validate_request is deprecated" ); if (eval 'require Hash::MultiValue;1') { ok( !module_calls_deprecated('JSON::Validator::OpenAPI::Dancer2'), "JSON::Validator::OpenAPI::Dancer2->validate_request isn't deprecated" ); } else { ok 1, 'Skipping JSON::Validator::OpenAPI::Dancer2 test'; } ok( !module_calls_deprecated('JSON::Validator::OpenAPI::Mojolicious'), "JSON::Validator::OpenAPI::Mojolicious->validate_request isn't deprecated" ); done_testing; sub module_calls_deprecated { my $module = shift; my $called = 0; { no warnings 'redefine'; *Mojo::Util::deprecated = sub { ++$called }; } my $c = {}; my $schema = {parameters => [{name => 'foo', in => 'query', type => 'integer'}]}; my $input = {}; eval "require $module" or die $@; eval { # we don't care if this actually works out $module->new->validate_request($c, $schema, $input); }; return $called; } JSON-Validator-1.08/t/definitions/weight.json000644 000765 000024 00000000235 13167236175 022260 0ustar00jhthorsenstaff000000 000000 { "type": "object", "description": "Weight with Units", "properties": { "mass": { "type": "integer" }, "unit": { "$ref": "./unit.json" } } } JSON-Validator-1.08/t/definitions/age.json000644 000765 000024 00000000111 12601743073 021506 0ustar00jhthorsenstaff000000 000000 { "type": "integer", "minimum": 0, "description": "Age in years" } JSON-Validator-1.08/t/definitions/unit.json000644 000765 000024 00000000123 12601750442 021732 0ustar00jhthorsenstaff000000 000000 { "type": "string", "description": "Unit of Mass", "pattern": "^kg|st|lb$" } JSON-Validator-1.08/t/remotes/folder/000755 000765 000024 00000000000 13217716317 020511 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/remotes/subSchemas.json000644 000765 000024 00000000157 13203560215 022216 0ustar00jhthorsenstaff000000 000000 { "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/integer" } } JSON-Validator-1.08/t/remotes/integer.json000644 000765 000024 00000000031 13057123354 021553 0ustar00jhthorsenstaff000000 000000 { "type": "integer" }JSON-Validator-1.08/t/remotes/folder/folderInteger.json000644 000765 000024 00000000031 13057123354 024162 0ustar00jhthorsenstaff000000 000000 { "type": "integer" }JSON-Validator-1.08/t/spec/ref-same-file-at-many-levels.json000644 000765 000024 00000000265 13167204604 024653 0ustar00jhthorsenstaff000000 000000 { "type": "object", "properties": { "age_one": { "$ref": "../definitions/age.json" }, "age_two": { "$ref": "../spec/../definitions/age.json" } } } JSON-Validator-1.08/t/spec/with-deep-mixed-ref.json000644 000765 000024 00000000424 13167236175 023154 0ustar00jhthorsenstaff000000 000000 { "type": "object", "properties": { "age": { "$ref": "../definitions/age.json#" }, "weight": { "$ref": "../definitions/weight.json" }, "height": { "$ref": "#/definitions/height" } }, "definitions": { "height": { "type": "integer", "minimum": 5 } } } JSON-Validator-1.08/t/spec/person.json000644 000765 000024 00000000530 12601015652 020677 0ustar00jhthorsenstaff000000 000000 { "title": "Example Schema", "type": "object", "required": ["firstName", "lastName"], "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "age": { "$ref": "#/definitions/age" } }, "definitions": { "age": { "type": "integer", "minimum": 0, "description": "Age in years" } } } JSON-Validator-1.08/t/spec/invalid-ref.t000644 000765 000024 00000000000 12601742662 021063 0ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/spec/petstore.json000644 000765 000024 00000002264 13157162541 021253 0ustar00jhthorsenstaff000000 000000 { "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" } } } } } JSON-Validator-1.08/t/spec/with-relative-ref.json000644 000765 000024 00000000140 13166757644 022751 0ustar00jhthorsenstaff000000 000000 { "type": "object", "properties": { "age": { "$ref": "../definitions/age.json#" } } } JSON-Validator-1.08/t/draft4-tests/uniqueItems.json000644 000765 000024 00000005065 13057123354 023310 0ustar00jhthorsenstaff000000 000000 [ { "description": "uniqueItems validation", "schema": {"uniqueItems": true}, "tests": [ { "description": "unique array of integers is valid", "data": [1, 2], "valid": true }, { "description": "non-unique array of integers is invalid", "data": [1, 1], "valid": false }, { "description": "numbers are unique if mathematically unequal", "data": [1.0, 1.00, 1], "valid": false }, { "description": "unique array of objects is valid", "data": [{"foo": "bar"}, {"foo": "baz"}], "valid": true }, { "description": "non-unique array of objects is invalid", "data": [{"foo": "bar"}, {"foo": "bar"}], "valid": false }, { "description": "unique array of nested objects is valid", "data": [ {"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : false}}} ], "valid": true }, { "description": "non-unique array of nested objects is invalid", "data": [ {"foo": {"bar" : {"baz" : true}}}, {"foo": {"bar" : {"baz" : true}}} ], "valid": false }, { "description": "unique array of arrays is valid", "data": [["foo"], ["bar"]], "valid": true }, { "description": "non-unique array of arrays is invalid", "data": [["foo"], ["foo"]], "valid": false }, { "description": "1 and true are unique", "data": [1, true], "valid": true }, { "description": "0 and false are unique", "data": [0, false], "valid": true }, { "description": "unique heterogeneous types are valid", "data": [{}, [1], true, null, 1], "valid": true }, { "description": "non-unique heterogeneous types are invalid", "data": [{}, [1], true, null, {}, 1], "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/minItems.json000644 000765 000024 00000001265 13057123354 022563 0ustar00jhthorsenstaff000000 000000 [ { "description": "minItems validation", "schema": {"minItems": 1}, "tests": [ { "description": "longer is valid", "data": [1, 2], "valid": true }, { "description": "exact length is valid", "data": [1], "valid": true }, { "description": "too short is invalid", "data": [], "valid": false }, { "description": "ignores non-arrays", "data": "", "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/additionalProperties.json000644 000765 000024 00000005271 13057123354 025164 0ustar00jhthorsenstaff000000 000000 [ { "description": "additionalProperties being false does not allow other properties", "schema": { "properties": {"foo": {}, "bar": {}}, "patternProperties": { "^v": {} }, "additionalProperties": false }, "tests": [ { "description": "no additional properties is valid", "data": {"foo": 1}, "valid": true }, { "description": "an additional property is invalid", "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, "valid": false }, { "description": "ignores non-objects", "data": [1, 2, 3], "valid": true }, { "description": "patternProperties are not additional properties", "data": {"foo":1, "vroom": 2}, "valid": true } ] }, { "description": "additionalProperties allows a schema which should validate", "schema": { "properties": {"foo": {}, "bar": {}}, "additionalProperties": {"type": "boolean"} }, "tests": [ { "description": "no additional properties is valid", "data": {"foo": 1}, "valid": true }, { "description": "an additional valid property is valid", "data": {"foo" : 1, "bar" : 2, "quux" : true}, "valid": true }, { "description": "an additional invalid property is invalid", "data": {"foo" : 1, "bar" : 2, "quux" : 12}, "valid": false } ] }, { "description": "additionalProperties can exist by itself", "schema": { "additionalProperties": {"type": "boolean"} }, "tests": [ { "description": "an additional valid property is valid", "data": {"foo" : true}, "valid": true }, { "description": "an additional invalid property is invalid", "data": {"foo" : 1}, "valid": false } ] }, { "description": "additionalProperties are allowed by default", "schema": {"properties": {"foo": {}, "bar": {}}}, "tests": [ { "description": "additional properties are allowed", "data": {"foo": 1, "bar": 2, "quux": true}, "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/dependencies.json000644 000765 000024 00000006103 13057123354 023420 0ustar00jhthorsenstaff000000 000000 [ { "description": "dependencies", "schema": { "dependencies": {"bar": ["foo"]} }, "tests": [ { "description": "neither", "data": {}, "valid": true }, { "description": "nondependant", "data": {"foo": 1}, "valid": true }, { "description": "with dependency", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "missing dependency", "data": {"bar": 2}, "valid": false }, { "description": "ignores non-objects", "data": "foo", "valid": true } ] }, { "description": "multiple dependencies", "schema": { "dependencies": {"quux": ["foo", "bar"]} }, "tests": [ { "description": "neither", "data": {}, "valid": true }, { "description": "nondependants", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "with dependencies", "data": {"foo": 1, "bar": 2, "quux": 3}, "valid": true }, { "description": "missing dependency", "data": {"foo": 1, "quux": 2}, "valid": false }, { "description": "missing other dependency", "data": {"bar": 1, "quux": 2}, "valid": false }, { "description": "missing both dependencies", "data": {"quux": 1}, "valid": false } ] }, { "description": "multiple dependencies subschema", "schema": { "dependencies": { "bar": { "properties": { "foo": {"type": "integer"}, "bar": {"type": "integer"} } } } }, "tests": [ { "description": "valid", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "no dependency", "data": {"foo": "quux"}, "valid": true }, { "description": "wrong type", "data": {"foo": "quux", "bar": 2}, "valid": false }, { "description": "wrong type other", "data": {"foo": 2, "bar": "quux"}, "valid": false }, { "description": "wrong type both", "data": {"foo": "quux", "bar": "quux"}, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/anyOf.json000644 000765 000024 00000003110 13057123354 022041 0ustar00jhthorsenstaff000000 000000 [ { "description": "anyOf", "schema": { "anyOf": [ { "type": "integer" }, { "minimum": 2 } ] }, "tests": [ { "description": "first anyOf valid", "data": 1, "valid": true }, { "description": "second anyOf valid", "data": 2.5, "valid": true }, { "description": "both anyOf valid", "data": 3, "valid": true }, { "description": "neither anyOf valid", "data": 1.5, "valid": false } ] }, { "description": "anyOf with base schema", "schema": { "type": "string", "anyOf" : [ { "maxLength": 2 }, { "minLength": 4 } ] }, "tests": [ { "description": "mismatch base schema", "data": 3, "valid": false }, { "description": "one anyOf valid", "data": "foobar", "valid": true }, { "description": "both anyOf invalid", "data": "foo", "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/not.json000644 000765 000024 00000004332 13057221364 021574 0ustar00jhthorsenstaff000000 000000 [ { "description": "not", "schema": { "not": {"type": "integer"} }, "tests": [ { "description": "allowed", "data": "foo", "valid": true }, { "description": "disallowed", "data": 1, "valid": false } ] }, { "description": "not multiple types", "schema": { "not": {"type": ["integer", "boolean"]} }, "tests": [ { "description": "valid", "data": "foo", "valid": true }, { "description": "mismatch", "data": 1, "valid": false }, { "description": "other mismatch", "data": true, "valid": false } ] }, { "description": "not more complex schema", "schema": { "not": { "type": "object", "properties": { "foo": { "type": "string" } } } }, "tests": [ { "description": "match", "data": 1, "valid": true }, { "description": "other match", "data": {"foo": 1}, "valid": true }, { "description": "mismatch", "data": {"foo": "bar"}, "valid": false } ] }, { "description": "forbidden property", "schema": { "properties": { "foo": { "not": {} } } }, "tests": [ { "description": "property present", "data": {"foo": 1, "bar": 2}, "valid": false }, { "description": "property absent", "data": {"bar": 1, "baz": 2}, "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/items.json000644 000765 000024 00000002160 13057123354 022112 0ustar00jhthorsenstaff000000 000000 [ { "description": "a schema given for items", "schema": { "items": {"type": "integer"} }, "tests": [ { "description": "valid items", "data": [ 1, 2, 3 ], "valid": true }, { "description": "wrong type of items", "data": [1, "x"], "valid": false }, { "description": "ignores non-arrays", "data": {"foo" : "bar"}, "valid": true } ] }, { "description": "an array of schemas for items", "schema": { "items": [ {"type": "integer"}, {"type": "string"} ] }, "tests": [ { "description": "correct types", "data": [ 1, "foo" ], "valid": true }, { "description": "wrong types", "data": [ "foo", 1 ], "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/enum.json000644 000765 000024 00000003667 13057123354 021752 0ustar00jhthorsenstaff000000 000000 [ { "description": "simple enum validation", "schema": {"enum": [1, 2, 3]}, "tests": [ { "description": "one of the enum is valid", "data": 1, "valid": true }, { "description": "something else is invalid", "data": 4, "valid": false } ] }, { "description": "heterogeneous enum validation", "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, "tests": [ { "description": "one of the enum is valid", "data": [], "valid": true }, { "description": "something else is invalid", "data": null, "valid": false }, { "description": "objects are deep compared", "data": {"foo": false}, "valid": false } ] }, { "description": "enums in properties", "schema": { "type":"object", "properties": { "foo": {"enum":["foo"]}, "bar": {"enum":["bar"]} }, "required": ["bar"] }, "tests": [ { "description": "both properties are valid", "data": {"foo":"foo", "bar":"bar"}, "valid": true }, { "description": "missing optional property is valid", "data": {"bar":"bar"}, "valid": true }, { "description": "missing required property is invalid", "data": {"foo":"foo"}, "valid": false }, { "description": "missing all properties is invalid", "data": {}, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/minProperties.json000644 000765 000024 00000001325 13057123354 023633 0ustar00jhthorsenstaff000000 000000 [ { "description": "minProperties validation", "schema": {"minProperties": 1}, "tests": [ { "description": "longer is valid", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "exact length is valid", "data": {"foo": 1}, "valid": true }, { "description": "too short is invalid", "data": {}, "valid": false }, { "description": "ignores non-objects", "data": "", "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/properties.json000644 000765 000024 00000005501 13057123354 023167 0ustar00jhthorsenstaff000000 000000 [ { "description": "object properties validation", "schema": { "properties": { "foo": {"type": "integer"}, "bar": {"type": "string"} } }, "tests": [ { "description": "both properties present and valid is valid", "data": {"foo": 1, "bar": "baz"}, "valid": true }, { "description": "one property invalid is invalid", "data": {"foo": 1, "bar": {}}, "valid": false }, { "description": "both properties invalid is invalid", "data": {"foo": [], "bar": {}}, "valid": false }, { "description": "doesn't invalidate other properties", "data": {"quux": []}, "valid": true }, { "description": "ignores non-objects", "data": [], "valid": true } ] }, { "description": "properties, patternProperties, additionalProperties interaction", "schema": { "properties": { "foo": {"type": "array", "maxItems": 3}, "bar": {"type": "array"} }, "patternProperties": {"f.o": {"minItems": 2}}, "additionalProperties": {"type": "integer"} }, "tests": [ { "description": "property validates property", "data": {"foo": [1, 2]}, "valid": true }, { "description": "property invalidates property", "data": {"foo": [1, 2, 3, 4]}, "valid": false }, { "description": "patternProperty invalidates property", "data": {"foo": []}, "valid": false }, { "description": "patternProperty validates nonproperty", "data": {"fxo": [1, 2]}, "valid": true }, { "description": "patternProperty invalidates nonproperty", "data": {"fxo": []}, "valid": false }, { "description": "additionalProperty ignores property", "data": {"bar": []}, "valid": true }, { "description": "additionalProperty validates others", "data": {"quux": 3}, "valid": true }, { "description": "additionalProperty invalidates others", "data": {"quux": "foo"}, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/maxLength.json000644 000765 000024 00000001600 13057123354 022716 0ustar00jhthorsenstaff000000 000000 [ { "description": "maxLength validation", "schema": {"maxLength": 2}, "tests": [ { "description": "shorter is valid", "data": "f", "valid": true }, { "description": "exact length is valid", "data": "fo", "valid": true }, { "description": "too long is invalid", "data": "foo", "valid": false }, { "description": "ignores non-strings", "data": 100, "valid": true }, { "description": "two supplementary Unicode code points is long enough", "data": "\uD83D\uDCA9\uD83D\uDCA9", "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/minimum.json000644 000765 000024 00000002047 13057123354 022450 0ustar00jhthorsenstaff000000 000000 [ { "description": "minimum validation", "schema": {"minimum": 1.1}, "tests": [ { "description": "above the minimum is valid", "data": 2.6, "valid": true }, { "description": "below the minimum is invalid", "data": 0.6, "valid": false }, { "description": "ignores non-numbers", "data": "x", "valid": true } ] }, { "description": "exclusiveMinimum validation", "schema": { "minimum": 1.1, "exclusiveMinimum": true }, "tests": [ { "description": "above the minimum is still valid", "data": 1.2, "valid": true }, { "description": "boundary point is invalid", "data": 1.1, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/optional/000755 000765 000024 00000000000 13217716317 021731 5ustar00jhthorsenstaff000000 000000 JSON-Validator-1.08/t/draft4-tests/oneOf.json000644 000765 000024 00000003107 13057123354 022041 0ustar00jhthorsenstaff000000 000000 [ { "description": "oneOf", "schema": { "oneOf": [ { "type": "integer" }, { "minimum": 2 } ] }, "tests": [ { "description": "first oneOf valid", "data": 1, "valid": true }, { "description": "second oneOf valid", "data": 2.5, "valid": true }, { "description": "both oneOf valid", "data": 3, "valid": false }, { "description": "neither oneOf valid", "data": 1.5, "valid": false } ] }, { "description": "oneOf with base schema", "schema": { "type": "string", "oneOf" : [ { "minLength": 2 }, { "maxLength": 4 } ] }, "tests": [ { "description": "mismatch base schema", "data": 3, "valid": false }, { "description": "one oneOf valid", "data": "foobar", "valid": true }, { "description": "both oneOf valid", "data": "foo", "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/additionalItems.json000644 000765 000024 00000004352 13057123354 024110 0ustar00jhthorsenstaff000000 000000 [ { "description": "additionalItems as schema", "schema": { "items": [{}], "additionalItems": {"type": "integer"} }, "tests": [ { "description": "additional items match schema", "data": [ null, 2, 3, 4 ], "valid": true }, { "description": "additional items do not match schema", "data": [ null, 2, 3, "foo" ], "valid": false } ] }, { "description": "items is schema, no additionalItems", "schema": { "items": {}, "additionalItems": false }, "tests": [ { "description": "all items match schema", "data": [ 1, 2, 3, 4, 5 ], "valid": true } ] }, { "description": "array of items with no additionalItems", "schema": { "items": [{}, {}, {}], "additionalItems": false }, "tests": [ { "description": "no additional items present", "data": [ 1, 2, 3 ], "valid": true }, { "description": "additional items are not permitted", "data": [ 1, 2, 3, 4 ], "valid": false } ] }, { "description": "additionalItems as false without items", "schema": {"additionalItems": false}, "tests": [ { "description": "items defaults to empty schema so everything is valid", "data": [ 1, 2, 3, 4, 5 ], "valid": true }, { "description": "ignores non-arrays", "data": {"foo" : "bar"}, "valid": true } ] }, { "description": "additionalItems are allowed by default", "schema": {"items": [{"type": "integer"}]}, "tests": [ { "description": "only the first item is validated", "data": [1, "foo", false], "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/pattern.json000644 000765 000024 00000001531 13057123354 022447 0ustar00jhthorsenstaff000000 000000 [ { "description": "pattern validation", "schema": {"pattern": "^a*$"}, "tests": [ { "description": "a matching pattern is valid", "data": "aaa", "valid": true }, { "description": "a non-matching pattern is invalid", "data": "abc", "valid": false }, { "description": "ignores non-strings", "data": true, "valid": true } ] }, { "description": "pattern is not anchored", "schema": {"pattern": "a+"}, "tests": [ { "description": "matches a substring", "data": "xxaayy", "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/definitions.json000644 000765 000024 00000001526 13057123354 023311 0ustar00jhthorsenstaff000000 000000 [ { "description": "valid definition", "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, "tests": [ { "description": "valid definition schema", "data": { "definitions": { "foo": {"type": "integer"} } }, "valid": true } ] }, { "description": "invalid definition", "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, "tests": [ { "description": "invalid definition schema", "data": { "definitions": { "foo": {"type": 1} } }, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/maxProperties.json000644 000765 000024 00000001367 13057123354 023643 0ustar00jhthorsenstaff000000 000000 [ { "description": "maxProperties validation", "schema": {"maxProperties": 2}, "tests": [ { "description": "shorter is valid", "data": {"foo": 1}, "valid": true }, { "description": "exact length is valid", "data": {"foo": 1, "bar": 2}, "valid": true }, { "description": "too long is invalid", "data": {"foo": 1, "bar": 2, "baz": 3}, "valid": false }, { "description": "ignores non-objects", "data": "foobar", "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/required.json000644 000765 000024 00000001633 13057123354 022615 0ustar00jhthorsenstaff000000 000000 [ { "description": "required validation", "schema": { "properties": { "foo": {}, "bar": {} }, "required": ["foo"] }, "tests": [ { "description": "present required property is valid", "data": {"foo": 1}, "valid": true }, { "description": "non-present required property is invalid", "data": {"bar": 1}, "valid": false } ] }, { "description": "required default validation", "schema": { "properties": { "foo": {} } }, "tests": [ { "description": "not required by default", "data": {}, "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/type.json000644 000765 000024 00000022122 13057123354 021752 0ustar00jhthorsenstaff000000 000000 [ { "description": "integer type matches integers", "schema": {"type": "integer"}, "tests": [ { "description": "an integer is an integer", "data": 1, "valid": true }, { "description": "a float is not an integer", "data": 1.1, "valid": false }, { "description": "a string is not an integer", "data": "foo", "valid": false }, { "description": "an object is not an integer", "data": {}, "valid": false }, { "description": "an array is not an integer", "data": [], "valid": false }, { "description": "a boolean is not an integer", "data": true, "valid": false }, { "description": "null is not an integer", "data": null, "valid": false } ] }, { "description": "number type matches numbers", "schema": {"type": "number"}, "tests": [ { "description": "an integer is a number", "data": 1, "valid": true }, { "description": "a float is a number", "data": 1.1, "valid": true }, { "description": "a string is not a number", "data": "foo", "valid": false }, { "description": "an object is not a number", "data": {}, "valid": false }, { "description": "an array is not a number", "data": [], "valid": false }, { "description": "a boolean is not a number", "data": true, "valid": false }, { "description": "null is not a number", "data": null, "valid": false } ] }, { "description": "string type matches strings", "schema": {"type": "string"}, "tests": [ { "description": "1 is not a string", "data": 1, "valid": false }, { "description": "a float is not a string", "data": 1.1, "valid": false }, { "description": "a string is a string", "data": "foo", "valid": true }, { "description": "an object is not a string", "data": {}, "valid": false }, { "description": "an array is not a string", "data": [], "valid": false }, { "description": "a boolean is not a string", "data": true, "valid": false }, { "description": "null is not a string", "data": null, "valid": false } ] }, { "description": "object type matches objects", "schema": {"type": "object"}, "tests": [ { "description": "an integer is not an object", "data": 1, "valid": false }, { "description": "a float is not an object", "data": 1.1, "valid": false }, { "description": "a string is not an object", "data": "foo", "valid": false }, { "description": "an object is an object", "data": {}, "valid": true }, { "description": "an array is not an object", "data": [], "valid": false }, { "description": "a boolean is not an object", "data": true, "valid": false }, { "description": "null is not an object", "data": null, "valid": false } ] }, { "description": "array type matches arrays", "schema": {"type": "array"}, "tests": [ { "description": "an integer is not an array", "data": 1, "valid": false }, { "description": "a float is not an array", "data": 1.1, "valid": false }, { "description": "a string is not an array", "data": "foo", "valid": false }, { "description": "an object is not an array", "data": {}, "valid": false }, { "description": "an array is an array", "data": [], "valid": true }, { "description": "a boolean is not an array", "data": true, "valid": false }, { "description": "null is not an array", "data": null, "valid": false } ] }, { "description": "boolean type matches booleans", "schema": {"type": "boolean"}, "tests": [ { "description": "an integer is not a boolean", "data": 1, "valid": false }, { "description": "a float is not a boolean", "data": 1.1, "valid": false }, { "description": "a string is not a boolean", "data": "foo", "valid": false }, { "description": "an object is not a boolean", "data": {}, "valid": false }, { "description": "an array is not a boolean", "data": [], "valid": false }, { "description": "a boolean is a boolean", "data": true, "valid": true }, { "description": "null is not a boolean", "data": null, "valid": false } ] }, { "description": "null type matches only the null object", "schema": {"type": "null"}, "tests": [ { "description": "an integer is not null", "data": 1, "valid": false }, { "description": "a float is not null", "data": 1.1, "valid": false }, { "description": "a string is not null", "data": "foo", "valid": false }, { "description": "an object is not null", "data": {}, "valid": false }, { "description": "an array is not null", "data": [], "valid": false }, { "description": "a boolean is not null", "data": true, "valid": false }, { "description": "null is null", "data": null, "valid": true } ] }, { "description": "multiple types can be specified in an array", "schema": {"type": ["integer", "string"]}, "tests": [ { "description": "an integer is valid", "data": 1, "valid": true }, { "description": "a string is valid", "data": "foo", "valid": true }, { "description": "a float is invalid", "data": 1.1, "valid": false }, { "description": "an object is invalid", "data": {}, "valid": false }, { "description": "an array is invalid", "data": [], "valid": false }, { "description": "a boolean is invalid", "data": true, "valid": false }, { "description": "null is invalid", "data": null, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/default.json000644 000765 000024 00000002371 13057123354 022421 0ustar00jhthorsenstaff000000 000000 [ { "description": "invalid type for default", "schema": { "properties": { "foo": { "type": "integer", "default": [] } } }, "tests": [ { "description": "valid when property is specified", "data": {"foo": 13}, "valid": true }, { "description": "still valid when the invalid default is used", "data": {}, "valid": true } ] }, { "description": "invalid string value for default", "schema": { "properties": { "bar": { "type": "string", "minLength": 4, "default": "bad" } } }, "tests": [ { "description": "valid when property is specified", "data": {"bar": "good"}, "valid": true }, { "description": "still valid when the invalid default is used", "data": {}, "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/multipleOf.json000644 000765 000024 00000002765 13057123354 023124 0ustar00jhthorsenstaff000000 000000 [ { "description": "by int", "schema": {"multipleOf": 2}, "tests": [ { "description": "int by int", "data": 10, "valid": true }, { "description": "int by int fail", "data": 7, "valid": false }, { "description": "ignores non-numbers", "data": "foo", "valid": true } ] }, { "description": "by number", "schema": {"multipleOf": 1.5}, "tests": [ { "description": "zero is multiple of anything", "data": 0, "valid": true }, { "description": "4.5 is multiple of 1.5", "data": 4.5, "valid": true }, { "description": "35 is not multiple of 1.5", "data": 35, "valid": false } ] }, { "description": "by small number", "schema": {"multipleOf": 0.0001}, "tests": [ { "description": "0.0075 is multiple of 0.0001", "data": 0.0075, "valid": true }, { "description": "0.00751 is not multiple of 0.0001", "data": 0.00751, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/patternProperties.json000644 000765 000024 00000006445 13057123354 024535 0ustar00jhthorsenstaff000000 000000 [ { "description": "patternProperties validates properties matching a regex", "schema": { "patternProperties": { "f.*o": {"type": "integer"} } }, "tests": [ { "description": "a single valid match is valid", "data": {"foo": 1}, "valid": true }, { "description": "multiple valid matches is valid", "data": {"foo": 1, "foooooo" : 2}, "valid": true }, { "description": "a single invalid match is invalid", "data": {"foo": "bar", "fooooo": 2}, "valid": false }, { "description": "multiple invalid matches is invalid", "data": {"foo": "bar", "foooooo" : "baz"}, "valid": false }, { "description": "ignores non-objects", "data": 12, "valid": true } ] }, { "description": "multiple simultaneous patternProperties are validated", "schema": { "patternProperties": { "a*": {"type": "integer"}, "aaa*": {"maximum": 20} } }, "tests": [ { "description": "a single valid match is valid", "data": {"a": 21}, "valid": true }, { "description": "a simultaneous match is valid", "data": {"aaaa": 18}, "valid": true }, { "description": "multiple matches is valid", "data": {"a": 21, "aaaa": 18}, "valid": true }, { "description": "an invalid due to one is invalid", "data": {"a": "bar"}, "valid": false }, { "description": "an invalid due to the other is invalid", "data": {"aaaa": 31}, "valid": false }, { "description": "an invalid due to both is invalid", "data": {"aaa": "foo", "aaaa": 31}, "valid": false } ] }, { "description": "regexes are not anchored by default and are case sensitive", "schema": { "patternProperties": { "[0-9]{2,}": { "type": "boolean" }, "X_": { "type": "string" } } }, "tests": [ { "description": "non recognized members are ignored", "data": { "answer 1": "42" }, "valid": true }, { "description": "recognized members are accounted for", "data": { "a31b": null }, "valid": false }, { "description": "regexes are case sensitive", "data": { "a_x_3": 3 }, "valid": true }, { "description": "regexes are case sensitive, 2", "data": { "a_X_3": 3 }, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/refRemote.json000644 000765 000024 00000003651 13057123354 022727 0ustar00jhthorsenstaff000000 000000 [ { "description": "remote ref", "schema": {"$ref": "http://localhost:1234/integer.json"}, "tests": [ { "description": "remote ref valid", "data": 1, "valid": true }, { "description": "remote ref invalid", "data": "a", "valid": false } ] }, { "description": "fragment within remote ref", "schema": {"$ref": "http://localhost:1234/subSchemas.json#/integer"}, "tests": [ { "description": "remote fragment valid", "data": 1, "valid": true }, { "description": "remote fragment invalid", "data": "a", "valid": false } ] }, { "description": "ref within remote ref", "schema": { "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" }, "tests": [ { "description": "ref within ref valid", "data": 1, "valid": true }, { "description": "ref within ref invalid", "data": "a", "valid": false } ] }, { "description": "change resolution scope", "schema": { "id": "http://localhost:1234/", "items": { "id": "folder/", "items": {"$ref": "folderInteger.json"} } }, "tests": [ { "description": "changed scope ref valid", "data": [[1]], "valid": true }, { "description": "changed scope ref invalid", "data": [["a"]], "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/allOf.json000644 000765 000024 00000005721 13057123354 022034 0ustar00jhthorsenstaff000000 000000 [ { "description": "allOf", "schema": { "allOf": [ { "properties": { "bar": {"type": "integer"} }, "required": ["bar"] }, { "properties": { "foo": {"type": "string"} }, "required": ["foo"] } ] }, "tests": [ { "description": "allOf", "data": {"foo": "baz", "bar": 2}, "valid": true }, { "description": "mismatch second", "data": {"foo": "baz"}, "valid": false }, { "description": "mismatch first", "data": {"bar": 2}, "valid": false }, { "description": "wrong type", "data": {"foo": "baz", "bar": "quux"}, "valid": false } ] }, { "description": "allOf with base schema", "schema": { "properties": {"bar": {"type": "integer"}}, "required": ["bar"], "allOf" : [ { "properties": { "foo": {"type": "string"} }, "required": ["foo"] }, { "properties": { "baz": {"type": "null"} }, "required": ["baz"] } ] }, "tests": [ { "description": "valid", "data": {"foo": "quux", "bar": 2, "baz": null}, "valid": true }, { "description": "mismatch base schema", "data": {"foo": "quux", "baz": null}, "valid": false }, { "description": "mismatch first allOf", "data": {"bar": 2, "baz": null}, "valid": false }, { "description": "mismatch second allOf", "data": {"foo": "quux", "bar": 2}, "valid": false }, { "description": "mismatch both", "data": {"bar": 2}, "valid": false } ] }, { "description": "allOf simple types", "schema": { "allOf": [ {"maximum": 30}, {"minimum": 20} ] }, "tests": [ { "description": "valid", "data": 25, "valid": true }, { "description": "mismatch one", "data": 35, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/ref.json000644 000765 000024 00000010416 13057123354 021550 0ustar00jhthorsenstaff000000 000000 [ { "description": "root pointer ref", "schema": { "properties": { "foo": {"$ref": "#"} }, "additionalProperties": false }, "tests": [ { "description": "match", "data": {"foo": false}, "valid": true }, { "description": "recursive match", "data": {"foo": {"foo": false}}, "valid": true }, { "description": "mismatch", "data": {"bar": false}, "valid": false }, { "description": "recursive mismatch", "data": {"foo": {"bar": false}}, "valid": false } ] }, { "description": "relative pointer ref to object", "schema": { "properties": { "foo": {"type": "integer"}, "bar": {"$ref": "#/properties/foo"} } }, "tests": [ { "description": "match", "data": {"bar": 3}, "valid": true }, { "description": "mismatch", "data": {"bar": true}, "valid": false } ] }, { "description": "relative pointer ref to array", "schema": { "items": [ {"type": "integer"}, {"$ref": "#/items/0"} ] }, "tests": [ { "description": "match array", "data": [1, 2], "valid": true }, { "description": "mismatch array", "data": [1, "foo"], "valid": false } ] }, { "description": "escaped pointer ref", "schema": { "tilda~field": {"type": "integer"}, "slash/field": {"type": "integer"}, "percent%field": {"type": "integer"}, "properties": { "tilda": {"$ref": "#/tilda~0field"}, "slash": {"$ref": "#/slash~1field"}, "percent": {"$ref": "#/percent%25field"} } }, "tests": [ { "description": "slash invalid", "data": {"slash": "aoeu"}, "valid": false }, { "description": "tilda invalid", "data": {"tilda": "aoeu"}, "valid": false }, { "description": "percent invalid", "data": {"percent": "aoeu"}, "valid": false }, { "description": "slash valid", "data": {"slash": 123}, "valid": true }, { "description": "tilda valid", "data": {"tilda": 123}, "valid": true }, { "description": "percent valid", "data": {"percent": 123}, "valid": true } ] }, { "description": "nested refs", "schema": { "definitions": { "a": {"type": "integer"}, "b": {"$ref": "#/definitions/a"}, "c": {"$ref": "#/definitions/b"} }, "$ref": "#/definitions/c" }, "tests": [ { "description": "nested ref valid", "data": 5, "valid": true }, { "description": "nested ref invalid", "data": "a", "valid": false } ] }, { "description": "remote ref, containing refs itself", "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, "tests": [ { "description": "remote ref valid", "data": {"minLength": 1}, "valid": true }, { "description": "remote ref invalid", "data": {"minLength": -1}, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/maximum.json000644 000765 000024 00000002047 13057123354 022452 0ustar00jhthorsenstaff000000 000000 [ { "description": "maximum validation", "schema": {"maximum": 3.0}, "tests": [ { "description": "below the maximum is valid", "data": 2.6, "valid": true }, { "description": "above the maximum is invalid", "data": 3.5, "valid": false }, { "description": "ignores non-numbers", "data": "x", "valid": true } ] }, { "description": "exclusiveMaximum validation", "schema": { "maximum": 3.0, "exclusiveMaximum": true }, "tests": [ { "description": "below the maximum is still valid", "data": 2.2, "valid": true }, { "description": "boundary point is invalid", "data": 3.0, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/minLength.json000644 000765 000024 00000001566 13057123354 022727 0ustar00jhthorsenstaff000000 000000 [ { "description": "minLength validation", "schema": {"minLength": 2}, "tests": [ { "description": "longer is valid", "data": "foo", "valid": true }, { "description": "exact length is valid", "data": "fo", "valid": true }, { "description": "too short is invalid", "data": "f", "valid": false }, { "description": "ignores non-strings", "data": 1, "valid": true }, { "description": "one supplementary Unicode code point is not long enough", "data": "\uD83D\uDCA9", "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/maxItems.json000644 000765 000024 00000001302 13057123354 022555 0ustar00jhthorsenstaff000000 000000 [ { "description": "maxItems validation", "schema": {"maxItems": 2}, "tests": [ { "description": "shorter is valid", "data": [1], "valid": true }, { "description": "exact length is valid", "data": [1, 2], "valid": true }, { "description": "too long is invalid", "data": [1, 2, 3], "valid": false }, { "description": "ignores non-arrays", "data": "foobar", "valid": true } ] } ] JSON-Validator-1.08/t/draft4-tests/optional/zeroTerminatedFloats.json000644 000765 000024 00000000600 13057123354 026760 0ustar00jhthorsenstaff000000 000000 [ { "description": "some languages do not distinguish between different types of numeric value", "schema": { "type": "integer" }, "tests": [ { "description": "a float is not an integer even without fractional part", "data": 1.0, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/optional/bignum.json000644 000765 000024 00000006003 13057123354 024077 0ustar00jhthorsenstaff000000 000000 [ { "description": "integer", "schema": {"type": "integer"}, "tests": [ { "description": "a bignum is an integer", "data": 12345678910111213141516171819202122232425262728293031, "valid": true } ] }, { "description": "number", "schema": {"type": "number"}, "tests": [ { "description": "a bignum is a number", "data": 98249283749234923498293171823948729348710298301928331, "valid": true } ] }, { "description": "integer", "schema": {"type": "integer"}, "tests": [ { "description": "a negative bignum is an integer", "data": -12345678910111213141516171819202122232425262728293031, "valid": true } ] }, { "description": "number", "schema": {"type": "number"}, "tests": [ { "description": "a negative bignum is a number", "data": -98249283749234923498293171823948729348710298301928331, "valid": true } ] }, { "description": "string", "schema": {"type": "string"}, "tests": [ { "description": "a bignum is not a string", "data": 98249283749234923498293171823948729348710298301928331, "valid": false } ] }, { "description": "integer comparison", "schema": {"maximum": 18446744073709551615}, "tests": [ { "description": "comparison works for high numbers", "data": 18446744073709551600, "valid": true } ] }, { "description": "float comparison with high precision", "schema": { "maximum": 972783798187987123879878123.18878137, "exclusiveMaximum": true }, "tests": [ { "description": "comparison works for high numbers", "data": 972783798187987123879878123.188781371, "valid": false } ] }, { "description": "integer comparison", "schema": {"minimum": -18446744073709551615}, "tests": [ { "description": "comparison works for very negative numbers", "data": -18446744073709551600, "valid": true } ] }, { "description": "float comparison with high precision on negative numbers", "schema": { "minimum": -972783798187987123879878123.18878137, "exclusiveMinimum": true }, "tests": [ { "description": "comparison works for very negative numbers", "data": -972783798187987123879878123.188781371, "valid": false } ] } ] JSON-Validator-1.08/t/draft4-tests/optional/format.json000644 000765 000024 00000011000 13057123354 024077 0ustar00jhthorsenstaff000000 000000 [ { "description": "validation of date-time strings", "schema": {"format": "date-time"}, "tests": [ { "description": "a valid date-time string", "data": "1963-06-19T08:30:06.283185Z", "valid": true }, { "description": "an invalid date-time string", "data": "06/19/1963 08:30:06 PST", "valid": false }, { "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350T01:01:01", "valid": false } ] }, { "description": "validation of URIs", "schema": {"format": "uri"}, "tests": [ { "description": "a valid URI", "data": "http://foo.bar/?baz=qux#quux", "valid": true }, { "description": "a valid protocol-relative URI", "data": "//foo.bar/?baz=qux#quux", "valid": true }, { "description": "an invalid URI", "data": "\\\\WINDOWS\\fileshare", "valid": false }, { "description": "an invalid URI though valid URI reference", "data": "abc", "valid": false } ] }, { "description": "validation of e-mail addresses", "schema": {"format": "email"}, "tests": [ { "description": "a valid e-mail address", "data": "joe.bloggs@example.com", "valid": true }, { "description": "an invalid e-mail address", "data": "2962", "valid": false } ] }, { "description": "validation of IP addresses", "schema": {"format": "ipv4"}, "tests": [ { "description": "a valid IP address", "data": "192.168.0.1", "valid": true }, { "description": "an IP address with too many components", "data": "127.0.0.0.1", "valid": false }, { "description": "an IP address with out-of-range values", "data": "256.256.256.256", "valid": false }, { "description": "an IP address without 4 components", "data": "127.0", "valid": false }, { "description": "an IP address as an integer", "data": "0x7f000001", "valid": false } ] }, { "description": "validation of IPv6 addresses", "schema": {"format": "ipv6"}, "tests": [ { "description": "a valid IPv6 address", "data": "::1", "valid": true }, { "description": "an IPv6 address with out-of-range values", "data": "12345::", "valid": false }, { "description": "an IPv6 address with too many components", "data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1", "valid": false }, { "description": "an IPv6 address containing illegal characters", "data": "::laptop", "valid": false } ] }, { "description": "validation of host names", "schema": {"format": "hostname"}, "tests": [ { "description": "a valid host name", "data": "www.example.com", "valid": true }, { "description": "a host name starting with an illegal character", "data": "-a-host-name-that-starts-with--", "valid": false }, { "description": "a host name containing illegal characters", "data": "not_a_valid_host_name", "valid": false }, { "description": "a host name with a component too long", "data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component", "valid": false } ] } ]