JSON-Validator-5.18/0000755000076500000240000000000015211411470014026 5ustar jhthorsenstaffJSON-Validator-5.18/Changes0000644000076500000240000006122715211411444015332 0ustar jhthorsenstaffRevision history for perl distribution JSON-Validator 5.18 2026-06-08T09:53:00 - Bundle OpenAPI v3.0 schema 2021-09-28 as default #295, #286 Contributor: Henrik Andersen - Fix nondeterministic false errors from the recursive_data_protection cache #272, #294 Contributor: Henrik Andersen 5.17 2026-06-04T22:32:00 - Add validate Mojo::Upload as binary string in OpenAPIv3 #288 Contributor: Archey Barrell 5.16 2026-06-04T10:06:00 - Use Mojo::File::spew instead of Mojo::File::spurt #281 Contributor: Emmanuel Seyman - Updated dependency list #283 - Use negotiated content-type to select schema #289 Contributor: Archey Barrell - Updated idn-email format verification to allow periods in local part #290 Contributor: Keith Carangelo - Add string as valid format #293 5.15 2025-03-16T18:47:47 - Make JSON::Validator::Util::is_bool return true when passed perl v5.36+ builtin booleans #275 - Fix wrong resolving of responses component using $ref #277 - Fix array coercion for array parameters with a $ref schema #274 5.14 2023-03-06T13:45:33 - Avoid mutating OpenAPIv2 schema "parameters" when validating 5.13 2022-12-09T09:55:24 - Fix not coercing body parameter for OpenAPIv2 5.12 2022-10-26T20:14:57 - Allow unicode strings in the JSON schema #268 Contributor: Brad Barden - Specified Perl version - Updated basic repository files - Updated contributors list 5.11 2022-08-30T15:41:16+0900 - Fix sorting routes with placeholders on different positions 5.10 2022-08-18T07:39:15+0900 - Add root schema id to "DefaultResponse" to allow external $ref 5.09 2022-08-17T09:13:55+0900 - Add support for default collectionFormat when "type" is "array" 5.08 2022-03-25T10:00:18+0900 - Fix handling OpenAPIv2 headers with collectionFormat 5.07 2022-03-23T07:58:02+0900 - Fix issues with OpenAPIv3 in 5.06 5.06 2022-03-23T07:29:23+0900 - Fix placing OpenAPIv2 bundled path $ref's inside "paths" 5.05 2022-01-06T09:33:30+0900 - Add EXPERIMENTAL support for coercing to array when posting form data 5.04 2021-12-10T07:54:09+0900 - Fix coercing strings and numbers in schema specification #266 5.03 2021-11-20T13:32:42+0900 - Fix OpenAPIv3::add_default_response() will not overwrite nested $ref - Fix finding "readOnly" and "writeOnly" inside nested $ref - Fix joi required() with nested objects Contributor: Tim Stallard 5.02 2021-10-06T09:58:55+0900 - Fix defaults for arrays with collectionFormat #263 Contributor: Ilya Rassadin - Improved documentation for JSON::Validator and JSON::Validator::Schema #230 5.01 2021-10-04T07:13:32+0900 - Fix registering the correct $ref when bundling - Changed get() to resolve $ref #262 - Deprecated $schema->contains() - Deprecated $schema->schema() 5.00 2021-10-02T10:15:16+0900 - Validation is much faster - Fix "$defs" in draft7 is "definitions" - Add benchmark.t to track validation speed * Runtime v4.21: 2.62s (n=200) * Runtime v5.00: 1.52s (n=200) - Add JSON::Validator::Store::resolve() - Add JSON::Validator::URI - Fix incorrect validation error paths when validating references to same object #244 - Changed JSON::Validator::Util::is_type() * Add JSON::Validator::Util::is_bool() * Add JSON::Validator::Util::is_num() - Changed bundled definitions/$defs names - Changed invalid schema id/$id will not croak - Changed internal _validation_xxx() methods to take $state - Removed support for bundle({replace => 1}) #194 - Removed JSON::Validator::OpenAPIv2::allow_invalid_ref() - Removed JSON::Validator::Ref - Removed JSON::Validator::Util::json_pointer() - Removed JSON::Validator::Util::schema_extract() 4.25 2021-09-26T08:41:51+0900 - Fix checking OpenAPI schema for errors if coerce(defaults) is on #254 - YAML::XS is preferred instead of YAML::PP #259 - Cleaned up CPAN distribution files #260 4.24 2021-09-16T08:02:26+0200 - Add OpenAPI v3.1.x support 4.23 2021-08-28T16:44:05+0200 - Fix mishandling of nullable combined witn enum in OpenAPIv3 #256 #257 4.22 2021-08-27T13:12:56+0200 - Fix email checking #258 Contributor: Stephan Hradek 4.21 2021-07-10T15:58:47+0900 - Fix no warnings when negotiating */* #252 - Depends on perl v5.16.0 #250 - Updated x_IRC to irc.libera.chat 4.20 2021-06-18T11:27:02+0900 - Fix compiling array items #249 - Fix coerce() will be passed on to schema() objects - Add is_invalid() to JSON::Validator::Schema - Improved documentation for JSON::Validator and JSON::Validator::Schema - Changed joi validator to default to a JSON::Validator::Schema::Draft7 object - Deprecated validate(..., $schema) - Removed support for coerce(1) - Removed support for subclassing JSON::Validator. Need to subclass a schema class instead. - Simpler inheritance for Schema classes 4.19 2021-06-17T12:37:10+0900 - Fix generating correct base_url() object with host and port 4.18 2021-06-17T11:13:58+0900 - Fix content_type validation for OpenAPIv2 and OpenAPIv3 - Fix validating multipart\/form-data with boundary - Fix validating content-type with charset - Fix parsing OpenAPIv3 spec with "description", "servers" and "summary" in a path specifiation. 4.17 2021-04-28T11:30:56+0900 - Add add_default_response() to OpenAPIv2 and OpenAPIv3 - Add base_url() to OpenAPIv2 and OpenAPIv3 - Fix validating "nullable" for "array" and "object" 4.16 2021-03-24T08:57:46+0900 - Fix handling OpenAPIv2 "responses" $ref when bundling 4.15 2021-03-24T07:57:58+0900 - Fix validating nullable inside anyOf for OpenAPIv3 #241 - Fix t/id-keyword-draft*.t test failures with Mojolicious 9.11 #242 #243 - Removed deprecated functions joi() and validate_json() - Removed deprecated methods singleton() and version() 4.14 2021-02-23T14:58:07+0900 - Add routes() to Schema::OpenAPIv2 and OpenAPIv3 4.13 2021-01-28T18:22:43+0900 - Fix handling offset in RFC3339 date-time #236 - Add CLEAR method to JSON::Validator::Ref #237 4.12 2021-01-25T07:52:34+0900 - Fix not using Mojo::Exception::raise() #235 - Fix uninitialized warning when looking up schema for an internal $ref 4.11 2021-01-24T16:02:02+0900 - Add JSON::Validator::Schema::OpenAPIv2 * Extends JSON::Validator::Schema::Draft4 * Can validate OpenAPIv2 API specifiation * Can validate HTTP request and response * Can validate "Accept" and "Content-Type" * Can handle "discriminator" * Can handle "readOnly" parameters * Can handle collectionFormat * Can handle default values for parameters * Can convert specifiation with invalid "$ref" into a valid OpenAPIv2 specifiation * Will coerce query parameters and headers into arrays if needed - Add JSON::Validator::Schema::OpenAPIv3 * Extends JSON::Validator::Schema::Draft201909 * Can validate OpenAPIv3 API specifiation * Can validate HTTP request and response * Can validate "Accept" and "Content-Type" * Can handle "discriminator" * Can handle "nullable" parameters * Can handle "readOnly" parameters * Can handle "style" and "explode" for arrays and objects parameters * Can handle "writeOnly" parameters * Can handle default values for parameters * Will coerce query parameters and headers into arrays if needed - Add negotiate_content_type() utility function - Fix t/load-file.t on Windows #234 - Fix not checking if input schema is a file if it has a newline #223 #233 Contributor: David Cantrell - Improved error message when loading non-existing file #231 4.10 2020-10-13T10:53:11+0900 - Add JSON::Validator::Store - Changed JSON::Validator->schema() to also load - JSON::Validator->schema() can handle draft 2019-09 schemas - Documented what is not supported in draft 2019-09 4.09 2020-10-12T11:04:16+0900 - Fix handling if "id" and "$id" in subschemas #186 - Add CONTRIBUTING.md #161 4.08 2020-10-12T08:06:29+0900 - Implemented basic support for draft 2019-09 #181 - JSON::Validator::Ref can hold sibling attrs #181 4.07 2020-10-11T11:48:05+0900 - Acceptance tests require Test::JSON::Schema::Acceptance 1.000 4.06 2020-10-10T16:21:51+0900 - Fix caching of schemas across objects #204 #212 Contributor: Karen Etheridge - Fix "contains" can also be boolean "false" - Fix "dependencies" can have boolean subschemas - Fix "if" can be false - Fix "null" is valid if "pattern" is defined, but no "type" - Fix "propertyNames" can be false - Fix JSON::Validator::Ref can hold a boolean schema - Fix not adding "properties" to the input schema - Fix resolving "$ref":"#id" - Fix validating data when "additionalItems" is false, but there are enough rules - Changed internals of JSON::Validator::Ref 4.05 2020-10-06T06:29:31+0900 - Errors are now reported in a predictable order #223 Contributor: David Cantrell 4.04 2020-09-28T10:01:58+0900 - Add "validator" as attribute to JSON::Validator::Joi - Changed YAML::XS to an optional module #205 - Changed column with to be in sync with mojolicious/mojo - Fix constructing schema() from correct class 4.03 2020-09-14T18:14:19+0900 - Add "joi" as exported function to JSON::Validator::Joi - Fix SYNOPSIS for JSON::Validator::Joi 4.02 2020-08-11T13:06:40+0900 - Add recursive_data_protection() #220 Contributor: Jason Cooper 4.01 2020-06-30T17:42:08+0900 - Fix combining "allOf" and "not" #218 4.00 2020-06-08T10:41:55+0900 - JSON::Validator::schema() now holds a JSON::Validator::Schema object instead of Mojo::JSON::Pointer - Add schema classes for Draft4, Draft6 and Draft7 - Add "duration" and "uuid" formats #210 - Fix coercing boolean "false" #215 - Fix not matching "null" should also be a "type" error #217 - Deprecated JSON::Validator::joi() - Deprecated JSON::Validator::singleton() - Deprecated JSON::Validator::validate_json() - Deprecated JSON::Validator::version() - Removed JSON::Validator::generate_definitions_path() - Removed support for JSON::Validator::bundle({ref_key => ...}) 3.25 2020-03-26T07:42:16+0900 - Made "additionalProperties" error message less confusing - Add support for "items" and "contains" can be defined-but-false #207 Contributor: Karen Etheridge - Respect "items" when combined with "contains" #207 Contributor: Karen Etheridge - Require Sereal::Encoder 4.00 to speed up data_checksum() 3.24 2020-03-03T15:46:39+0900 - Optimize checksum generation #202 Contributor: Ere Maijala - Improved "type":[...] error messages #199 Contributor: Karen Etheridge - Fix handle validation of true, false schemas in oneOf #201 #203 Contributor: Karen Etheridge - Fix guessing type:object if "dependencies" is present #206 3.23 2020-02-19T09:37:44+0900 - Using List::Util::uniq() instead of JSON::Validator::Util::uniq() #198 3.22 2020-02-15T08:35:29+0900 - Add support for "dependencies" keyword #192 #197 Contributor: Karen Etheridge - Add support for anyOf/allOf/oneOf at the same time #196 #197 Contributor: Karen Etheridge - Allow if/then/else to be in any sort of schema #190 #197 Contributor: Karen Etheridge 3.21 2020-02-14T10:14:14+0900 - Fix data_section() without a class #193 3.20 2020-02-12T09:47:21+0900 - Fix validating draft6 "false" and "true" schemas #190 Contributor: Karen Etheridge - Add JSON::Validator::Util with E(), data_checksum(), data_section(), data_type(), is_type(), schema_extract(), json_pointer(), prefix_errors(), schema_type() and uniq() - Removed support for JSON_VALIDATOR_REPORT 3.19 2020-02-07T11:10:58+0900 - Add clearer error when more than one oneOf rule matched #184 Contributor: Karen Etheridge - Improved validation of numeric minimum and maximum values 3.18 2020-01-29T10:18:09+0900 - Fix validating constants that are null or the empty string Contributor: Karen Etheridge 3.17 2019-12-29T14:21:53+0900 - Fix for deep recursion on Windows #170 - Fix reading unicode specifiation from __DATA__ #179 3.16 2019-10-28T13:59:47+0900 - Add generate_definitions_path() #175 #177 Contributor: Henrik Andersen, Jan Henning Thorsen 3.15 2019-09-27T09:28:32+0900 - Add JSON::Validator::Error->details() #133 - Reversed the checksum and nice name for generated definitions #173 3.14 2019-08-09T23:52:24+0200 - Fix failing tests #169 3.13 2019-08-08T16:27:29+0200 - Fix extend() should not mutate the source objects #167 3.12 2019-08-08T12:02:14+0200 - Fix bug: Joi->extend(...) will also merge "required" #166 - Fix deprecation warning from $joi->validate #165 Contributor: Mattias Päivärinta - Fix loading Time::Local in the correct module #163 Contributor: Bernhard Graf 3.11 2019-05-07T21:53:16+0700 - Bundle https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v3.0/schema.json #157 3.10 2019-05-05T14:32:15+0700 - Should not add "/definitions" to bundle, if there are no definitions - A bit too soon to deprecate bundle({replace => 1}) in 3.09 3.09 2019-05-04T22:28:55+0700 - Prettier definition names from bundle(). - Changed default bundle() definitions location from "x-bundle" to "definitions". - Deprecated bundle({ref_key => ...}) - Deprecated bundle({replace => ...}) 3.08 2019-04-06T15:07:11+0700 - Add support for "default" in object definitions #155 - Add support for coerce("bool,def,num,str") as alternative to hash - Add support for setting coerce in new() - Deprecated support for coerce(1) 3.07 2019-04-04T23:43:55+0700 - Fix not leaking file names with bundle() 3.06 2019-02-14T18:24:29+0100 - Fix coercing integers and numbers #147 - Changed recursion guard to not keeping tracking of plain scalars #147 3.05 2019-01-31T08:45:14+0900 - Removed testing Mojo::JSON::MaybeXS, since Mojo::JSON loads Cpanel::JSON::XS 3.04 2019-01-21T09:39:50+0900 - Fix "uri" check, so that it only accept ASCII characters. Note that this fix might be an undesired change for your application. If so, then update the "uri" format in your schema to "iri". - Fix "hostname" format check, so it does not require a valid TLD - Fix validating draft-07 schema against itself #144 - Add support for more formats in JSON Schema draft-6 and 7: date, idn-email, idn-hostname, iri, iri-reference, json-pointer, relative-json-pointer, time, uri-reference and uri-template. - Add support for more keywords in draft-07 * 6.4.6. Arrays - contains * 6.5.8. Objects - propertyNames * 6.6.1. Objects - if * 6.6.2. Objects - then * 6.6.3. Objects - else 3.03 2019-01-19T12:11:34+0900 - Add JSON::Validator::Formats with format checks Note that these functions work by returning a string on error, instead of true on sucches, which was a breaking change introduced in the 3.00 release. 3.02 2019-01-07T09:52:31+0900 - Trying to fix more failing test reports from the smokers 3.01 2019-01-06T08:16:33+0900 - Fix t/jv-formats.t #140 3.00 2019-01-05T13:13:49+0900 - Add enum() to Joi - Add support for a list of types passed on to Joi #136 - Add support for file:// scheme in $ref #138 - Fix $ref resolving after fixes applied to Mojo::JSON::Pointer in Mojolicious 8.11 #139 - Fix cases where input was not coerced - Breaking change: format callbacks need to return undef on success and a description on error. - Changed Joi to always coerce values - Removed JSON::Validator::OpenAPI 2.19 2018-12-07 - Fix random errors when "type" is a list #126 - Moved JSON::Validator::OpenAPI::Mojolicious to Mojolicious-Plugin-OpenAPI - Removed JSON::Validator::OpenAPI::Dancer2 2.18 2018-11-15 - Add EXPERIMENTAL support for data:// without a package 2.17 2018-11-14 - Add basic support for OpenAPI v3 2.16 2018-11-14 - Improved openapi "date" format validation #123 Contributor: Jason Cooper 2.15 2018-11-07 - Did not need to bump Mojolicious version in 2.15 #122 Contributor: Dagfinn Ilmari Mannsåker 2.14 2018-10-26 - Fix guessing schema type from "required" key #118 - Fix appending parameters for Mojolicious 8.00 #119 #120 - Improved error return values from allOf, anyOf and oneOf validation - Will not overwrite OpenAPI "/info/version" from "version_from_class" 2.13 2018-10-17 - Compatible with weak attrs in Mojolicious 8.03 2.12 2018-10-03 - Improved error message when $ref is pointing to a non-existing file 2.11 2018-09-30 - Skipping load-from-app.t on "Gateway Timeout" as well 2.10 2018-09-26 - Fix handling of directory name with RFC 3986 reserved chars Contributor: Ed J 2.09 2018-09-26 - Skip "remote ref" tests when running through cpantesters 2.08 2018-06-03 - Fix validating oneOf correctly #103 - Fix validating "id" property #111 - Add support for $id keyword in draft-07 #114 - Bundle JSON Schema draft-06 and draft-07 2.07 2018-04-18 - Fix joi->object->strict() Contributor: Pierre-Aymeric Masse 2.06 2018-04-09 - Fix normalising file names on windows #102 - Prevent "Use of uninitialized value $pointer in length..." for older Perls #104 - Removed warning about coercion now, since it works well 2.05 2018-03-11 - Fix hash randomization problem fot t/get.t #101 2.04 2018-03-10 - Add JSON::Validator::JOI and joi() #63 - Add support for get(|"x", undef, "y"]) - Will catch if more than one parameter has "in":"body" #97 - Fix file-path with ".." gets false negative for same-ref check #99 2.03 2018-02-15 - Will not leak file system information to bundled schema 2.02 2018-01-30 - Will let the user know if YAML::XS 0.67 (or later) need to be installed 2.01 2018-01-26 - Fix bundle method not spotting "local" fqn when schema from URL - Forgot to remove deprecated JSON_VALIDATOR_CACHE_DIR in 2.00 2.00 2018-01-19 - Fix validating against any enum value #22 - Require YAML::XS 0.67 for proper boolean handling - Removed support for parsing YAML with YAML::Syck - Removed deprecated method load_and_validate_spec() 1.08 2017-12-24 - Fix setting default value from $ref jhthorsen/mojolicious-plugin-openapi#53 - Skip load-from-app.t if "Service Unavailable" 1.07 2017-11-27 - Can load schame from internal app #85 1.06 2017-11-19 - Add JSON::Validator::get() - Add JSON::Validator::bundle() - A $ref is tied hashes, represented by JSON::Validator::Ref 1.05 2017-10-22 - 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-05 - Avoid autovivification of "patternProperties" in the input schema #47 1.03 2017-09-25 - Fix "uri" format validation, closes #70 1.02 2017-09-01 - Fix validating "type" and "enum" #69 1.01 2017-08-19 - Add support for fetching specification from local application 1.00 2017-06-20 - Removed EXPERIMENTAL from JSON::Validator::OpenAPI (1.00) - Coerce integer numbers into booleans #67 Contributor: @fabzzap 0.99 2017-06-12 - Hopefully fixed some Windows issues #60 0.98 2017-05-21 - Add support for "const" #62 Contributor: Kevin Goess 0.97 2017-03-21 - Require a newer version of Test::More to build 0.96 2017-03-06 - 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-02 - Add support for format "password" - Add load_and_validate_schema() to JSON::Validator #51 - Started deprecating load_and_validate_spec() 0.94 2017-02-13 - Fix t/issue-27-yaml-syck-false.t - Removed Carp::Always #47 0.93 2017-02-13 - 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-18 - Fix infinite recursion when resolving self referencing data structures 0.91 2017-01-10 - Mojo::Util::slurp is DEPRECATED in favor of Mojo::File::slurp 0.90 2016-12-11 - Add support for validating Dancer2 requset/responses #34 - Fix invalidating integer/number path part with letters #37 0.89 2016-11-05 - Fix multipleOf:0.01, closes #35 0.88 2016-11-04 - Fix number coercion #32 Contributor: @melhesedek - Add JSON::Validator::OpenAPI->load_and_validate_spec() 0.87 2016-10-20 - Fix validating data when boolean.pm is loaded 0.86 2016-10-06 - Documented bundled resources 0.85 2016-09-26 - Fix handling of collectionFormat where no input is defined 0.84 2016-08-19 - Removed support for passing $json_path to validate() - Fix guessing type of objects that has TO_JSON() 0.83 2016-08-16 - 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-09 - Fix finding all $ref occurances jhthorsen/swagger2#95 0.81 2016-08-08 - 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-03 - Fix parsing recursive schema 0.79 2016-07-28 - Reverted improved allOf, anyOf and oneOf error messages 0.78 2016-07-28 - Fix recursive dependencies #23 - Add EXPERIMENTAL resolver attribute - Improved allOf, anyOf and oneOf error messages 0.77 2016-07-26 - Avoid duplicate error messages with enum #22 - Fix "false" must be false and not true in OpenAPI 0.76 2016-07-25 - Will write default values into Mojolicious::Controller 0.75 2016-07-02 - Fix uploads must not be slurped - Fix reporting error on missing response status definition - Add warnings on invalid (Perl) regexes 0.74 2016-06-22 - Fix length($data) need be defined in 5.10 0.73 2016-06-22 - 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-10 - Fix default cache_dir() path - JSON::Validator is no longer EXPERIMENTAL - Move Swagger2::SchemaValidator into JSON::Validator::OpenAPI 0.71 2016-06-07 - Fix setting schema() inside validate() 0.70 2016-05-31 - Fix allowing "id" as property name in objects 0.69 2016-05-26 - Fix failing anyOf logic in t/swagger-validate-response-object.t 0.68 2016-05-25 - Remove _merge_error to clarify anyOf errors #15 0.67 2016-04-11 - Add JSON::Validator::Error class 0.66 2016-02-09 - Fix validating recursive datastructures 0.65 2016-01-07 - Fix t/swagger-validate-response-object.t require Swagger2 0.66 #14 0.64 2015-12-18 - 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-28 - Fix skip test in t/booleans.t 0.62 2015-11-27 - Remove support for YAML.pm #jhthorsen/swagger2#50 - Remove support for YAML::Tiny #jhthorsen/swagger2#50 0.61 2015-11-11 - Fix use of TO_JSON() on objects inside arrays #12 0.60 2015-11-09 - Can use TO_JSON() when validating perl objects 0.59 2015-10-14 - Move "collectionFormat" support to Swagger2 0.58 2015-10-13 - Fix string "0" is not detected as boolean 0.57 2015-10-11 - Trust guesswork if input data is undefined 0.56 2015-09-30 - 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-29 - Fix "required" cannot be a boolean on properties - Improved documentation of error object - Change anyOf/allOf/oneOf error message 0.54 2015-09-27 - 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-13 - 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-05 - 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-24 - Fix "$ref" pointing to a file on disk #1 0.50 2015-08-23 - Fix missing namespace when registering new document - Made cache_dir() public - Bundled spec for json-schema and swagger 0.49 2015-08-23 - Fix loading schema from files 0.48 2015-08-22 - 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-5.18/MANIFEST0000644000076500000240000001700615211411470015163 0ustar jhthorsenstaffChanges CONTRIBUTING.md lib/JSON/Validator.pm lib/JSON/Validator/cache/089e74a6d17f64af17a9efd6d0fa0de6 lib/JSON/Validator/cache/10a5eeb37fcd5d829449028f7ceb0774 lib/JSON/Validator/cache/33912dbbde6e1d936140f1c82b283d01 lib/JSON/Validator/cache/36d1bd12eeed51e86c8695bd8876a9df lib/JSON/Validator/cache/3be3f46eb248daf48925640f8ef057e8 lib/JSON/Validator/cache/3d35aac549d951f4cf9182ff47bff0b4 lib/JSON/Validator/cache/4550dd8afbfee9e71377b45f5fea42ce lib/JSON/Validator/cache/49c95b866e40f788892a7fb3c816b0e8 lib/JSON/Validator/cache/4a31fe43be9e23ca9eb8d9e9faba8892 lib/JSON/Validator/cache/546acf85ddc442761c18517490215b90 lib/JSON/Validator/cache/630949337805585c8e52deea27d11419 lib/JSON/Validator/cache/7fe97ed1a4c3fac607dd276b2b298275 lib/JSON/Validator/cache/a0f5b4b4e75ea17fc09e88ec0343d148 lib/JSON/Validator/cache/a516498b60c53096b2ce2cd83ebe0abc lib/JSON/Validator/cache/c6f188eb288cf986f23db49297b25e83 lib/JSON/Validator/cache/d18065ce8fb1f748e766b2737bae5200 lib/JSON/Validator/cache/d8cf7ae7a0fd14accadf5d18bc84d14f lib/JSON/Validator/cache/ea34d47d4e060a1c3b12d2287aff89a7 lib/JSON/Validator/cache/eaa832720f36cff0abc20c05236a9cd9 lib/JSON/Validator/Error.pm lib/JSON/Validator/Formats.pm lib/JSON/Validator/Joi.pm lib/JSON/Validator/Schema.pm lib/JSON/Validator/Schema/Draft201909.pm lib/JSON/Validator/Schema/Draft4.pm lib/JSON/Validator/Schema/Draft6.pm lib/JSON/Validator/Schema/Draft7.pm lib/JSON/Validator/Schema/OpenAPIv2.pm lib/JSON/Validator/Schema/OpenAPIv3.pm lib/JSON/Validator/Store.pm lib/JSON/Validator/URI.pm lib/JSON/Validator/Util.pm Makefile.PL MANIFEST This list of files t/00-project.t t/benchmark.t t/bundle.t t/coerce-default.t t/coerce.t t/deep-mixed-ref.t t/definitions/age.json 't/definitions/space age.json' t/definitions/unit.json t/definitions/weight.json t/draft2019-09-acceptance.t t/draft2019-09.t t/draft4-acceptance.t t/draft4.t t/draft6-acceptance.t t/draft6.t t/draft7-acceptance.t t/draft7.t t/get.t t/Helper.pm t/id-keyword-draft4.t t/id-keyword-draft7.t t/invalid-ref.t t/issue-103-one-of.t t/issue-158-draf7-coerce-defaults.t t/issue-22-duplicate-error-messages.t t/issue-272-recursive-data-protection.t t/issue-42-cache-control.t t/issue-59-oneof-blessed-booleans.t t/issue-71-additionalproperties.t t/joi.t t/jv-allof-and-not.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-if-then-else.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-data.t t/load-file.t t/load-from-app.t t/load-http.t t/load-json.t t/load-yaml-pp.t t/load-yaml.t t/more-bundle.t t/newline-warnings.t t/openapiv2-basic.t t/openapiv2-bundle.t t/openapiv2-collection-format.t t/openapiv2-default-values.t t/openapiv2-discriminator.t t/openapiv2-file.t t/openapiv2-headers.t t/openapiv2-readonly.t t/openapiv2-routes.t t/openapiv3-basic.t t/openapiv3-bundled-spec.t t/openapiv3-coerce-array.t t/openapiv3-content-types.t t/openapiv3-default-values.t t/openapiv3-discriminator.t t/openapiv3-file.t t/openapiv3-nullable.t t/openapiv3-readonly-writeonly.t t/openapiv3-style-explode.t t/predictable-errors.t t/random-errors.t t/recursive_data_protection.t t/relative-ref.t t/remotes/folder/folderInteger.json t/remotes/integer.json t/remotes/subSchemas.json t/spec/bundle-no-leaking-filename.json t/spec/bundlecheck.json t/spec/missing-ref.json t/spec/more-bundle.yaml t/spec/more-bundle2.yaml t/spec/paths.yaml t/spec/person.json t/spec/remotes/baseUriChange/folderInteger.json t/spec/remotes/baseUriChangeFolder/folderInteger.json t/spec/remotes/baseUriChangeFolderInSubschema/folderInteger.json t/spec/remotes/different-id-ref-string.json t/spec/remotes/draft-next/baseUriChange/folderInteger.json t/spec/remotes/draft-next/baseUriChangeFolder/folderInteger.json t/spec/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json t/spec/remotes/draft-next/extendible-dynamic-ref.json t/spec/remotes/draft-next/format-assertion-false.json t/spec/remotes/draft-next/format-assertion-true.json t/spec/remotes/draft-next/integer.json t/spec/remotes/draft-next/locationIndependentIdentifier.json t/spec/remotes/draft-next/metaschema-no-validation.json t/spec/remotes/draft-next/name-defs.json t/spec/remotes/draft-next/nested/foo-ref-string.json t/spec/remotes/draft-next/nested/string.json t/spec/remotes/draft-next/ref-and-defs.json t/spec/remotes/draft-next/subSchemas-defs.json t/spec/remotes/draft-next/subSchemas.json t/spec/remotes/draft-next/tree.json t/spec/remotes/draft2019-09/baseUriChange/folderInteger.json t/spec/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json t/spec/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json t/spec/remotes/draft2019-09/dependentRequired.json t/spec/remotes/draft2019-09/extendible-dynamic-ref.json t/spec/remotes/draft2019-09/ignore-prefixItems.json t/spec/remotes/draft2019-09/integer.json t/spec/remotes/draft2019-09/locationIndependentIdentifier.json t/spec/remotes/draft2019-09/metaschema-no-validation.json t/spec/remotes/draft2019-09/name-defs.json t/spec/remotes/draft2019-09/nested/foo-ref-string.json t/spec/remotes/draft2019-09/nested/string.json t/spec/remotes/draft2019-09/ref-and-defs.json t/spec/remotes/draft2019-09/subSchemas-defs.json t/spec/remotes/draft2019-09/subSchemas.json t/spec/remotes/draft2019-09/tree.json t/spec/remotes/draft2020-12/baseUriChange/folderInteger.json t/spec/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json t/spec/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json t/spec/remotes/draft2020-12/extendible-dynamic-ref.json t/spec/remotes/draft2020-12/format-assertion-false.json t/spec/remotes/draft2020-12/format-assertion-true.json t/spec/remotes/draft2020-12/integer.json t/spec/remotes/draft2020-12/locationIndependentIdentifier.json t/spec/remotes/draft2020-12/metaschema-no-validation.json t/spec/remotes/draft2020-12/name-defs.json t/spec/remotes/draft2020-12/nested/foo-ref-string.json t/spec/remotes/draft2020-12/nested/string.json t/spec/remotes/draft2020-12/prefixItems.json t/spec/remotes/draft2020-12/ref-and-defs.json t/spec/remotes/draft2020-12/subSchemas-defs.json t/spec/remotes/draft2020-12/subSchemas.json t/spec/remotes/draft2020-12/tree.json t/spec/remotes/draft7/ignore-dependentRequired.json t/spec/remotes/extendible-dynamic-ref.json t/spec/remotes/folder/folderInteger.json t/spec/remotes/integer.json t/spec/remotes/locationIndependentIdentifier.json t/spec/remotes/locationIndependentIdentifierDraft4.json t/spec/remotes/locationIndependentIdentifierPre2019.json t/spec/remotes/name-defs.json t/spec/remotes/name.json t/spec/remotes/nested-absolute-ref-to-string.json t/spec/remotes/nested/foo-ref-string.json t/spec/remotes/nested/string.json t/spec/remotes/ref-and-definitions.json t/spec/remotes/ref-and-defs.json t/spec/remotes/subSchemas-defs.json t/spec/remotes/subSchemas.json t/spec/remotes/tree.json t/spec/remotes/urn-ref-string.json 't/spec/space bundle.json' t/spec/test-definitions-key.json t/spec/v2-bundle.yaml t/spec/v2-petstore.json t/spec/v3-default-response-extra.yaml t/spec/v3-petstore.json t/spec/with-deep-mixed-ref.json t/spec/with-relative-ref.json t/spec/with-unicode-multibyte.json t/spec/with-unicode-multibyte.yml t/stack/Some.pm t/stack/Some/Module.pm t/test/array.pm t/test/number.pm t/test/object.pm t/to-json.t t/unicode-multibyte.t t/uri.t t/util-checksum-yaml-xs.t t/util.t t/validate-draft07.t t/validate-id.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-5.18/t/0000755000076500000240000000000015211411467014277 5ustar jhthorsenstaffJSON-Validator-5.18/t/openapiv3-readonly-writeonly.t0000644000076500000240000000372715210277065022251 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my ($body, @errors); $body = sub { +{exists => 1, value => {birth => '1983-02-24'}} }; @errors = $schema->validate_request([post => '/user'], {body => $body}); is "@errors", '', 'required is ignored on validate_request'; $body = sub { +{exists => 1, value => {age => 42, birth => '1983-02-24'}} }; @errors = $schema->validate_request([post => '/user'], {body => $body}); is "@errors", '/body/age: Read-only.', 'age is read-only for request'; $body = sub { +{exists => 1, value => {}} }; @errors = $schema->validate_response([get => '/user'], {body => $body}); is "@errors", '/body/age: Missing property.', 'age is required in response'; $body = sub { +{exists => 1, value => {age => 42}} }; @errors = $schema->validate_response([get => '/user'], {body => $body}); is "@errors", '', 'age is present in response'; $body = sub { +{exists => 1, value => {age => 42, birth => '1983-02-24'}} }; @errors = $schema->validate_response([get => '/user'], {body => $body}); is "@errors", '/body/birth: Write-only.', 'birth is write-only in response'; done_testing; __DATA__ @@ spec.json { "openapi": "3.0.0", "info": { "title": "Read/write-only", "version": "" }, "paths": { "/user": { "get": { "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } }, "post": { "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } } }, "components": { "schemas": { "User": { "type": "object", "required": ["age", "birth"], "properties": { "age": {"type": "integer", "readOnly": true}, "birth": {"type": "string", "writeOnly": true} } } } } } JSON-Validator-5.18/t/coerce-default.t0000644000076500000240000000216415210277065017354 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::JSON qw(false true); use Test::More; my $jv = JSON::Validator->new(coerce => 'defaults'); is_deeply($jv->coerce, {defaults => 1}, 'coerce defaults'); $jv->coerce('def'); is_deeply($jv->coerce, {defaults => 1}, 'coerce def'); $jv->schema({ '$schema' => 'http://json-schema.org/draft-04/schema#', type => 'object', definitions => {subscribed_to => {type => 'array', default => []}}, properties => {tos => {type => 'boolean', default => false}, subscribed_to => {'$ref' => '#/definitions/subscribed_to'}}, }); my $data = {}; my @errors = $jv->validate($data); is_deeply \@errors, [], 'defaults pass validation'; is_deeply $data, {tos => false, subscribed_to => []}, 'data was mutated'; $data->{tos} = true; @errors = $jv->validate($data); is_deeply $data, {tos => true, subscribed_to => []}, 'only subscribed_to was mutated'; $jv->schema({type => 'object', properties => {age => {type => 'number', default => 'invalid'}}}); @errors = $jv->validate({}); is $errors[0]->message, 'Expected number - got string.', 'default values must be valid'; done_testing; JSON-Validator-5.18/t/jv-required.t0000644000076500000240000000122615210277065016725 0ustar jhthorsenstaffuse 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-5.18/t/util-checksum-yaml-xs.t0000644000076500000240000000216715210277065020642 0ustar jhthorsenstaffBEGIN { unshift @INC, sub { my $file = $_[1]; die "Skipping $file in this test" if $file =~ m!Sereal\W+Encoder\.pm$!; }; } use Mojo::Util 'md5_sum'; use JSON::Validator; use JSON::Validator::Util qw(data_checksum); use Test::More; my $d_hash = {foo => {}, bar => {}}; my $d_hash2 = {bar => {}, foo => {}}; my $d_undef = {foo => undef}; my $d_obj = {foo => JSON::Validator::Error->new}; my $d_array = ['foo', 'bar']; my $d_array2 = ['bar', 'foo']; ok !$INC{'Sereal/Encoder.pm'}, 'Sereal::Encoder was not loaded'; isnt data_checksum($d_array), data_checksum($d_array2), 'data_checksum array'; is data_checksum($d_hash), data_checksum($d_hash2), 'data_checksum hash field order'; isnt data_checksum($d_hash), data_checksum($d_undef), 'data_checksum hash not undef'; isnt data_checksum($d_hash), data_checksum($d_obj), 'data_checksum hash not object'; isnt data_checksum($d_obj), data_checksum($d_undef), 'data_checksum object not undef'; isnt data_checksum(3.14), md5_sum(3.15), 'data_checksum numeric'; is data_checksum(3.14), data_checksum('3.14'), 'data_checksum numeric like string'; done_testing; JSON-Validator-5.18/t/jv-not.t0000644000076500000240000000025115210277065015702 0ustar jhthorsenstaffuse 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-5.18/t/jv-basic.t0000644000076500000240000000112315210277065016162 0ustar jhthorsenstaffuse 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('/', 'Expected null - got number.'); validate_ok($_, {}) foreach (true, false, 1, 1.2, 'a string', {a => 'b'}, [1, 2, 3]); validate_ok($_, true) foreach (true, false, 1, 1.2, 'a string', {a => 'b'}, [1, 2, 3]); validate_ok($_, false, E('/', 'Should not match.')) foreach (true, false, 1, 1.2, 'a string', {a => 'b'}, [1, 2, 3]); done_testing; JSON-Validator-5.18/t/load-http.t0000644000076500000240000000107615210277065016367 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; my $jv = JSON::Validator->new; $jv->schema('http://swagger.io/v2/schema.json'); isa_ok $jv->schema, 'JSON::Validator::Schema'; like $jv->schema->get('/title'), qr{swagger}i, 'got swagger spec'; ok $jv->schema->get('/patternProperties/^x-/description'), 'resolved vendorExtension $ref'; is_deeply [sort keys %{$jv->store->schemas}], ['http://json-schema.org/draft-04/schema', 'http://swagger.io/v2/schema.json'], 'schemas in store'; done_testing; JSON-Validator-5.18/t/id-keyword-draft4.t0000644000076500000240000000550615210277065017735 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::JSON 'encode_json'; use Test::Mojo; use Test::More; my ($base_url, $jv, $t, @e); use Mojolicious::Lite; get '/invalid-fragment' => [format => ['json']] => 'invalid-fragment'; get '/invalid-relative' => [format => ['json']] => 'invalid-relative'; get '/relative-to-the-root' => [format => ['json']] => '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 $@; isa_ok $jv->schema, 'JSON::Validator::Schema::Draft4'; my $schema = $jv->schema; is $schema->moniker, 'draft04', 'moniker'; is $schema->specification, 'http://json-schema.org/draft-04/schema#', 'specification'; 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 $r1 = $schema->get('/definitions/R1'); is encode_json($r1), '{"id":"#bx"}', 'R1 encode_json'; eval { $jv->load_and_validate_schema("${base_url}invalid-fragment.json") }; like $@, qr{Fragment not allowed}, 'Root id cannot have a fragment' or diag $@; eval { $jv->load_and_validate_schema("${base_url}invalid-relative.json") }; like $@, qr{Relative URL not allowed}, '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", "$schema": "http://json-schema.org/draft-04/schema#", "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-5.18/t/openapiv2-discriminator.t0000644000076500000240000000576415210277065021253 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my ($body, @errors); my %cat = (name => 'kit-e-cat', petType => 'Cat', huntingSkill => 'adventurous'); my %dog = (name => 'dog-e-dog', petType => 'Dog', packSize => 4); $body = sub { {exists => 1, value => {%cat, petType => 'Dog'}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body/packSize: /allOf/1 Missing property.', 'invalid dog'; $body = sub { {exists => 1, value => {%cat}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '', 'valid cat'; $body = sub { {exists => 1, value => {%dog, petType => 'Cat'}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body/huntingSkill: /allOf/1 Missing property.', 'invalid cat'; $body = sub { {exists => 1, value => {%dog}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '', 'valid dog'; $body = sub { {exists => 1, value => {%dog, petType => ''}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body: Discriminator petType has no value.', 'discriminator is required'; $body = sub { {exists => 1, value => {%dog, petType => 'Hamster'}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body: No definition for discriminator Hamster.', 'invalid discriminator'; done_testing; __DATA__ @@ spec.json { "swagger": "2.0", "info": {"version": "", "title": "Test discriminator"}, "basePath": "/api", "paths": { "/pets": { "post": { "parameters": [ {"in": "body", "name": "body", "schema": {"$ref": "#/definitions/Pet"}} ], "responses": { "200": { "description": "pet response", "schema": {"type": "object"} } } } } }, "definitions": { "Pet": { "type": "object", "discriminator": "petType", "required": ["name", "petType"], "properties": { "name": {"type": "string"}, "petType": {"type": "string"} } }, "Cat": { "description": "A representation of a cat", "allOf": [ {"$ref": "#/definitions/Pet"}, { "type": "object", "required": ["huntingSkill"], "properties": {"huntingSkill": {"type": "string", "description": "The measured skill for hunting", "default": "lazy", "enum": ["clueless", "lazy", "adventurous", "aggressive"]} } } ] }, "Dog": { "description": "A representation of a dog", "allOf": [ {"$ref": "#/definitions/Pet"}, { "type": "object", "required": ["packSize"], "properties": { "packSize": {"type": "integer", "format": "int32", "description": "the size of the pack the dog is from", "default": 0, "minimum": 0} } } ] } } } JSON-Validator-5.18/t/validate-draft07.t0000644000076500000240000000067215210277065017532 0ustar jhthorsenstaffuse Mojo::Base -strict; use Mojo::File 'path'; use Mojo::JSON 'decode_json'; use Test::More; use JSON::Validator; my $draft07 = path(qw(lib JSON Validator cache 4a31fe43be9e23ca9eb8d9e9faba8892)); plan skip_all => "Cannot open $draft07" unless -r $draft07; my $schema = decode_json($draft07->slurp); my @errors = JSON::Validator->new->validate($schema, $schema); ok !@errors, "validated draft07" or map { diag $_ } @errors; done_testing; JSON-Validator-5.18/t/issue-71-additionalproperties.t0000644000076500000240000000042415210277065022267 0ustar jhthorsenstaffuse lib '.'; use t::Helper; 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-5.18/t/load-from-app.t0000644000076500000240000000215415210277065017127 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojolicious; use Test::More; my $jv = JSON::Validator->new; $jv->ua->server->app(Mojolicious->new); $jv->ua->server->app->log(Mojo::Log->new->level('fatal')); $jv->ua->server->app->routes->get( '/spec' => sub { my $c = shift; die 'not cached' if $c->stash('from_cache'); $c->render(json => {'$ref' => 'http://swagger.io/v2/schema.json'}); } ); # Some CPAN testers says "Service Unavailable" eval { $jv->schema('/spec') }; plan skip_all => $@ if $@ =~ /\bGET\b/i; is $jv->store->ua, $jv->ua, 'shared ua'; is $@, '', 'loaded schema from app'; is $jv->get('/properties/swagger/enum/0'), '2.0', 'loaded schema structure'; is_deeply [sort keys %{$jv->store->schemas}], ['/spec', 'http://json-schema.org/draft-04/schema', 'http://swagger.io/v2/schema.json'], 'schemas in store'; $jv->ua->server->app->defaults(from_cache => 1); ok $jv->schema('/spec'), 'loaded from cache'; $jv->store->schemas({}); eval { $jv->schema('/spec') }; like $@, qr{Internal Server Error}, 'cache cleared'; done_testing; JSON-Validator-5.18/t/draft7.t0000644000076500000240000000251215210277065015656 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use JSON::Validator::Schema::Draft7; t::Helper->schema(JSON::Validator::Schema::Draft7->new); t::Helper->test(number => qw(basic maximum minimum)); t::Helper->test(array => qw(basic items additional_items contains min_max)); t::Helper->test(array => qw(unique unevaluated_items)); t::Helper->test(object => qw(basic properties)); t::Helper->test(object => qw(additional_properties pattern_properties min_max names)); subtest 'exclusiveMaximum' => sub { schema_validate_ok 2.4, {exclusiveMaximum => 2.4}, E('/', '2.4 >= maximum(2.4)'); schema_validate_ok 0, {exclusiveMaximum => 0}, E('/', '0 >= maximum(0)'); }; subtest 'exclusiveMinimum' => sub { schema_validate_ok 4.2, {exclusiveMinimum => 4.2}, E('/', '4.2 <= minimum(4.2)'); schema_validate_ok 0, {exclusiveMinimum => 0}, E('/', '0 <= minimum(0)'); }; subtest 'bundle' => sub { my $bundle = JSON::Validator::Schema::Draft7->new('data://main/spec.json')->bundle; is $bundle->data->{properties}{name}{'$ref'}, '#/definitions/defs_json-name', 'bundle ref'; is $bundle->data->{definitions}{'defs_json-name'}{type}, 'string', 'bundled spec under $defs'; }; done_testing; __DATA__ @@ spec.json {"type":"object","properties":{"name":{"$ref":"data://main/defs.json#/name"}}} @@ defs.json {"name":{"type":"string"}} JSON-Validator-5.18/t/unicode-multibyte.t0000644000076500000240000000212215210277065020126 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use Mojo::File qw(path); use Mojo::Util qw(encode); use Test::More; my $json_file = path(__FILE__)->dirname->child('spec')->child('with-unicode-multibyte.json'); my $yaml_file = path(__FILE__)->dirname->child('spec')->child('with-unicode-multibyte.yml'); my $perl_utf8_str = "foo\x{266b}bar"; my $encoded_bytes = encode('UTF-8', $perl_utf8_str); my $with_replacement_char = "replacement\x{fffd}char"; my $invalid_utf8_1 = "replacement\x{d800}char"; my $invalid_utf8_2 = "replacement\x{d8f0}char"; validate_ok {foo => $perl_utf8_str}, $json_file; validate_ok {foo => $encoded_bytes}, $json_file, E('/foo', "Not in enum list: $perl_utf8_str."); validate_ok {foo => $perl_utf8_str}, $yaml_file; validate_ok {foo => $encoded_bytes}, $yaml_file, E('/foo', "Not in enum list: $perl_utf8_str."); validate_ok {bar => $with_replacement_char}, $json_file; validate_ok {bar => $invalid_utf8_1}, $json_file, E('/bar', "Not in enum list: $with_replacement_char."); validate_ok {bar => $invalid_utf8_2}, $json_file, E('/bar', "Not in enum list: $with_replacement_char."); done_testing; JSON-Validator-5.18/t/jv-formats.t0000644000076500000240000003232315210277065016562 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use Mojo::Util 'decode'; my $schema = {type => 'object', properties => {v => {type => 'string'}}}; subtest 'byte' => sub { local $schema->{properties}{v}{format} = 'byte'; validate_ok {v => 'amh0aG9yc2Vu'}, $schema; validate_ok {v => "\0"}, $schema, E('/v', 'Does not match byte format.'); }; subtest 'date' => sub { local $schema->{properties}{v}{format} = 'date'; validate_ok {v => '2014-12-09'}, $schema; validate_ok {v => '0000-00-00'}, $schema, E('/v', 'Month out of range.'); validate_ok {v => '0000-01-00'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '2014-12-09T20:49:37Z'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '0-0-0'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '09-12-2014'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '09-DEC-2014'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '09/12/2014'}, $schema, E('/v', 'Does not match date format.'); }; subtest 'date-time' => sub { local $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', '2006-01-02 15:04:05+23:59', '2006-01-02 15:04:05-23:59' ); validate_ok {v => "2017-03-29\t23:02:55-12:00"}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29T23:02:55+0:0'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29T23:02:55+123:00'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29\t23:02:55+0:0'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29\t23:02:55+123:00'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29\t23:02:55-12'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29\t23:02:55-12:00'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29T23:02:55-12'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => 'xxxx-xx-xxtxx:xx:xxz'}, $schema, E('/v', 'Does not match date-time format.'); validate_ok {v => '2017-03-29T23:02:60Z'}, $schema, E('/v', 'Second out of range.'); validate_ok {v => '2017-03-29T23:61:55Z'}, $schema, E('/v', 'Minute out of range.'); validate_ok {v => '2017-03-29T24:02:55Z'}, $schema, E('/v', 'Hour out of range.'); validate_ok {v => '2017-03-32T23:02:55Z'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '2017-02-29T23:02:55Z'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '2017-02-30T23:02:55Z'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '2017-03-00T23:02:55Z'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '2017-00-29T23:02:55Z'}, $schema, E('/v', 'Month out of range.'); validate_ok {v => '2017-13-29T23:02:55Z'}, $schema, E('/v', 'Month out of range.'); validate_ok {v => '2017-03-29T23:02:55+23:60'}, $schema, E('/v', 'Time offset minute out of range.'); validate_ok {v => '2017-03-29T23:02:55+24:00'}, $schema, E('/v', 'Time offset hour out of range.'); }; subtest 'double' => sub { local $schema->{properties}{v}{format} = 'double'; local $TODO = 'cannot test double, since input is already rounded'; validate_ok {v => '1.1000000238418599085576943252817727625370025634765626'}, $schema; }; subtest 'duration' => sub { local $schema->{properties}{v}{format} = 'duration'; validate_ok {v => 'foo'}, $schema, E('/v', 'Does not match duration format.'); validate_ok {v => 'P4Y'}, $schema; validate_ok {v => 'PT0S'}, $schema; validate_ok {v => 'P1M'}, $schema; validate_ok {v => 'PT1M'}, $schema; validate_ok {v => 'PT0.5M'}, $schema; validate_ok {v => 'PT0,5M'}, $schema; validate_ok {v => 'P23DT23H'}, $schema; validate_ok {v => 'P3Y6M4DT12H30M5S'}, $schema; }; subtest 'email' => sub { 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.'); validate_ok {v => '用户@例子.广告'}, $schema, E('/v', 'Does not match email format.'); validate_ok {v => 'wrong@jhthorsen@cpan.org'}, $schema, E('/v', 'Does not match email format.'); validate_ok {v => 'wrong jhthorsen@cpan.org'}, $schema, E('/v', 'Does not match email format.'); }; subtest 'float' => sub { local $schema->{properties}{v}{format} = 'float'; validate_ok {v => '-1.10000002384186'}, $schema; validate_ok {v => '1.10000002384186'}, $schema; }; subtest 'int32' => sub { 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.'); }; subtest 'int64' => sub { local $schema->{properties}{v}{format} = 'int64'; local $TODO = 'Not a 64 bit Perl' unless JSON::Validator::Formats::IV_SIZE >= 8; validate_ok {v => '-9223372036854775808'}, $schema; validate_ok {v => '9223372036854775807'}, $schema; validate_ok {v => '9223372036854775808'}, $schema, E('/v', 'Does not match int64 format.'); }; subtest 'hostname' => sub { local $TODO = eval 'require Data::Validate::Domain;1' ? undef : 'Missing module'; local $schema->{properties}{v}{format} = 'hostname'; validate_ok {v => 'mojolicio.us'}, $schema; validate_ok {v => '[]'}, $schema, E('/v', 'Does not match hostname format.'); }; subtest 'idn-email' => sub { validate_ok {v => decode('UTF-8', '用户@例子.广告')}, $schema; local $TODO = eval 'require Net::IDN::Encode;1' ? undef : 'Missing module'; local $schema->{properties}{v}{format} = 'idn-email'; validate_ok {v => decode('UTF-8', '用户@')}, $schema, E('/v', 'Does not match idn-email format.'); # --- Valid IDN Email Addresses (Domain Variations) --- validate_ok {v => decode('UTF-8', 'user@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', 'user@xn--bcher-kva.example')}, $schema; validate_ok {v => decode('UTF-8', 'test.user@δοκιμή.example')}, $schema; validate_ok {v => decode('UTF-8', 'info@例.jp')}, $schema; validate_ok {v => decode('UTF-8', 'john.doe@例子.com')}, $schema; validate_ok {v => decode('UTF-8', 'user@müller.com')}, $schema; validate_ok {v => decode('UTF-8', 'user@test-bücher.com')}, $schema; validate_ok {v => decode('UTF-8', 'user@xn--test-bcher-kvb.com')}, $schema; # --- Valid IDN Email Addresses (Local-Part Variations) --- validate_ok {v => decode('UTF-8', 'simple@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', 'user.name@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', 'user_name@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', 'user-name@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', 'user+alias@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', '!#$%&\'*+-/=?^_{}|~@bücher.example')}, $schema; validate_ok {v => decode('UTF-8', '佐藤@例.jp')}, $schema; validate_ok {v => decode('UTF-8', 'пользователь@пример.рф')}, $schema; validate_ok {v => decode('UTF-8', 'user.佐藤@bücher.example')}, $schema; # --- Invalid IDN Email Addresses (Domain Variations) --- validate_ok {v => decode('UTF-8', 'user@bücher-.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user@-bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user@bücher..example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user@bücher_example.com')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user@bücher.com.')}, $schema, E('/v', 'Does not match idn-email format.'); # --- Invalid IDN Email Addresses (Local-Part Variations) --- validate_ok {v => decode('UTF-8', '.user@bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user..name@bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user name@bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user,@bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', 'user(comment)@bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); validate_ok {v => decode('UTF-8', '"user.name@bücher.example')}, $schema, E('/v', 'Does not match idn-email format.'); }; subtest 'idn-hostname' => sub { local $schema->{properties}{v}{format} = 'idn-hostname'; validate_ok {v => decode('UTF-8', '例子.广告')}, $schema; }; subtest 'iri' => sub { local $schema->{properties}{v}{format} = 'iri'; validate_ok {v => 'http://mojolicio.us/?ø=123'}, $schema; validate_ok {v => decode('UTF-8', 'https://例子.广告/Ῥόδος')}, $schema; validate_ok {v => '/Ῥόδος'}, $schema, E('/v', 'Scheme missing.'); }; subtest 'iri-reference' => sub { local $schema->{properties}{v}{format} = 'iri-reference'; validate_ok {v => '/Ῥόδος'}, $schema; validate_ok {v => 'Ῥόδος'}, $schema; validate_ok {v => 'http:///Ῥόδος'}, $schema,; }; subtest 'ipv4' => sub { local $TODO = eval 'require Data::Validate::IP;1' ? undef : 'Missing module'; 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.'); }; subtest 'ipv6' => sub { local $TODO = eval 'require Data::Validate::IP;1' ? undef : 'Missing module'; 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.'); }; subtest 'json-pointer' => sub { local $schema->{properties}{v}{format} = 'json-pointer'; validate_ok {v => ''}, $schema; validate_ok {v => '/foo/bar'}, $schema; validate_ok {v => 'foo/bar'}, $schema, E('/v', 'Does not match json-pointer format.'); }; subtest 'regex' => sub { local $schema->{properties}{v}{format} = 'regex'; validate_ok {v => '(\w+)'}, $schema; validate_ok {v => '(\w'}, $schema, E('/v', 'Does not match regex format.'); }; subtest 'relative-json-pointer' => sub { local $schema->{properties}{v}{format} = 'relative-json-pointer'; validate_ok {v => '0'}, $schema; validate_ok {v => '42#'}, $schema; validate_ok {v => '100/foo/bar'}, $schema; validate_ok {v => '#'}, $schema, E('/v', 'Relative JSON Pointer must start with a non-negative-integer.'); validate_ok {v => '42foo/bar'}, $schema, E('/v', 'Does not match relative-json-pointer format.'); }; subtest 'time' => sub { local $schema->{properties}{v}{format} = 'time'; validate_ok {v => $_}, $schema for qw(23:02:55.831Z 23:02:55.01z 23:02:55-12:00 23:02:55+05:00); validate_ok {v => 'xx:xx:xxz'}, $schema, E('/v', 'Does not match time format.'); validate_ok {v => '23:02:60Z'}, $schema, E('/v', 'Second out of range.'); validate_ok {v => '23:61:55Z'}, $schema, E('/v', 'Minute out of range.'); validate_ok {v => '24:02:55Z'}, $schema, E('/v', 'Hour out of range.'); }; subtest 'uri' => sub { local $schema->{properties}{v}{format} = 'uri'; validate_ok {v => '//example.com/no-scheme'}, $schema, E('/v', 'Scheme missing.'); validate_ok {v => ''}, $schema, E('/v', 'Scheme, path or fragment are required.'); validate_ok {v => '0://mojolicio.us/?x=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/?x=123'}, $schema; validate_ok {v => '/relative-path'}, $schema; validate_ok {v => 'relative-path'}, $schema; }; subtest 'uri-reference' => sub { local $schema->{properties}{v}{format} = 'uri-reference'; validate_ok {v => 'http:///whatever'}, $schema; validate_ok {v => '/relative-path'}, $schema; validate_ok {v => 'relative-path'}, $schema; }; subtest 'uri-template' => sub { local $schema->{properties}{v}{format} = 'uri-template'; validate_ok {v => 'http://mojolicio.us/?x={x}'}, $schema; }; subtest 'unknown' => sub { my $warn = 'no warnings seen'; local $SIG{__WARN__} = sub { $warn = shift }; local $schema->{properties}{v}{format} = 'unknown'; validate_ok {v => 'whatever'}, $schema; like $warn, qr{Format rule for 'unknown' is missing}, 'unknown format cause a warning'; }; subtest 'uuid' => sub { local $schema->{properties}{v}{format} = 'uuid'; validate_ok {v => '5782165B-6BB6-A72F-B3DD-369D707D6C72'}, $schema, E('/v', 'Does not match uuid format.'); validate_ok {v => '5782165B-6BB6-472F-B3DD-369D707D6C72'}, $schema; }; done_testing; JSON-Validator-5.18/t/deep-mixed-ref.t0000644000076500000240000000121115210277065017255 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::File 'path'; use Test::More; my $workdir = path(__FILE__)->dirname; my $file = path($workdir, 'spec', 'with-deep-mixed-ref.json'); my $jv = JSON::Validator->new(cache_paths => [])->schema($file); my @errors = $jv->validate({age => 1, weight => {mass => 72, unit => 'kg'}, height => 100}); is int(@errors), 0, 'valid input'; use Mojolicious::Lite; push @{app->static->paths}, $workdir; $jv->ua(app->ua); $jv->schema(app->ua->server->url->clone->path('/spec/with-relative-ref.json')); @errors = $jv->validate({age => 'not a number'}); is int(@errors), 1, 'invalid age'; done_testing; JSON-Validator-5.18/t/test/0000755000076500000240000000000015211411467015256 5ustar jhthorsenstaffJSON-Validator-5.18/t/test/object.pm0000644000076500000240000001055615210277065017074 0ustar jhthorsenstaffpackage t::test::object; use t::Helper; sub additional_properties { my $schema = {properties => {number => {type => 'number'}}, additionalProperties => false}; schema_validate_ok {direction => 'NW', foo => 'nope', number => 1600}, $schema, E('/', 'Properties not allowed: direction, foo.'); $schema->{additionalProperties} = {type => 'string'}; schema_validate_ok {number => 1600, foo => 'nope'}, $schema; } sub basic { my $schema = {type => 'object'}; schema_validate_ok {mynumber => 1}, $schema; schema_validate_ok [1], $schema, E('/', 'Expected object - got array.'); } sub dependencies { my $schema = { dependencies => {credit_card => ['billing_address']}, properties => {name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'}}, }; schema_validate_ok {name => 'John Doe'}, $schema; schema_validate_ok {name => 'John Doe', billing_address => '123 Main St'}, $schema; schema_validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, E('/billing_address', 'Missing property. Dependee: credit_card.'); } sub dependent_required { my $schema = { dependentRequired => {credit_card => ['billing_address']}, properties => {name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'}}, }; schema_validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, E('/billing_address', 'Missing property. Dependee: credit_card.'); } sub dependent_schemas { my $schema = { dependentSchemas => {credit_card => ['billing_address']}, properties => {name => {type => 'string'}, credit_card => {type => 'number'}, billing_address => {type => 'string'}}, }; schema_validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, E('/billing_address', 'Missing property. Dependee: credit_card.'); } sub min_max { my $schema = {minProperties => 2, maxProperties => 3}; schema_validate_ok {}, {minProperties => 1}, E('/', 'Not enough properties: 0/1.'); schema_validate_ok {a => 1}, $schema, E('/', 'Not enough properties: 1/2.'); schema_validate_ok {a => 1, b => 2}, $schema; schema_validate_ok {a => 1, b => 2, c => 3}, $schema; schema_validate_ok {a => 1, b => 2, c => 3, d => 4}, $schema, E('/', 'Too many properties: 4/3.'); } sub names { my $schema = {propertyNames => {minLength => 3, maxLength => 5}}; schema_validate_ok {name => 'John', surname => 'Doe'}, $schema, E('/', '/propertyName/surname String is too long: 7/5.'); $schema->{propertyNames}{maxLength} = 7; schema_validate_ok {name => 'John', surname => 'Doe'}, $schema; $schema = { type => 'object', propertyNames => {anyOf => [{type => 'string', enum => ['foo', 'bar', 'baz']}, {type => 'string', enum => ['hello']}]}, }; schema_validate_ok {FOO => 1}, $schema, E('/', '/propertyName/FOO /anyOf/0 Not in enum list: foo, bar, baz.'), E('/', '/propertyName/FOO /anyOf/1 Not in enum list: hello.'); schema_validate_ok {foo => 1}, $schema; } sub pattern_properties { my $schema = {patternProperties => {'^S_' => {type => 'string'}, '^I_' => {type => 'integer'}}}; schema_validate_ok {'S_25' => 'This is a string', 'I_0' => 42}, $schema; schema_validate_ok {'S_0' => 42}, $schema, E('/S_0', 'Expected string - got number.'); } sub properties { my $schema = { properties => { number => {type => 'number'}, street_name => {type => 'string'}, street_type => {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']} } }; schema_validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue'}, $schema; schema_validate_ok {number => '1600'}, $schema, E('/number', 'Expected number - got string.'); schema_validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW'}, $schema; $schema->{required} = ['number', 'street_name']; validate_ok {number => 1600, street_type => 'Avenue'}, $schema, E('/street_name', 'Missing property.'); } sub unevaluated_properties { local $TODO = 'https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.3.2.4'; my $schema = {properties => {number => {type => 'number'}}, unevaluatedProperties => false}; schema_validate_ok {direction => 'NW', foo => 'nope', number => 1600}, $schema, E('/', 'Properties not allowed: direction, foo.'); } 1; JSON-Validator-5.18/t/test/number.pm0000644000076500000240000000066715210277065017120 0ustar jhthorsenstaffpackage t::test::number; use t::Helper; sub basic { schema_validate_ok 1, {type => 'number'}; } sub maximum { schema_validate_ok 0, {maximum => 0}; schema_validate_ok 1, {maximum => 1}; schema_validate_ok - 1, {maximum => -2}, E('/', '-1 > maximum(-2)'); } sub minimum { schema_validate_ok 0, {minimum => 0}; schema_validate_ok 1, {minimum => 1}; schema_validate_ok - 2, {minimum => -1}, E('/', '-2 < minimum(-1)'); } 1; JSON-Validator-5.18/t/test/array.pm0000644000076500000240000000454515210277065016745 0ustar jhthorsenstaffpackage t::test::array; use t::Helper; sub additional_items { my $schema = { type => 'array', additionalItems => false, items => [ {type => 'number'}, {type => 'string'}, {type => 'string', enum => ['Street', 'Avenue', 'Boulevard']}, {type => 'string', enum => ['NW', 'NE', 'SW', 'SE']} ] }; validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $schema, E('/', 'Invalid number of items: 5/4.'); } sub basic { my $schema = {type => 'array'}; schema_validate_ok [], $schema; schema_validate_ok {}, $schema, E('/', 'Expected array - got object.'); } sub contains { my $schema = {type => 'array', contains => {type => 'string', enum => ['NW']}}; schema_validate_ok [1600, 'NW'], $schema; $schema->{contains}{enum} = ['Nope']; schema_validate_ok [1600, 'NW'], $schema, E('/0', 'Expected string - got number.'), E('/1', 'Not in enum list: Nope.'); } sub items { my $schema = {type => 'array', items => {type => 'number'}}; validate_ok [1], $schema; validate_ok [1, 'foo'], $schema, E('/1', 'Expected number - got string.'); $schema->{items} = {}; validate_ok [1, 'foo', 1.2], $schema; $schema->{items} = true; validate_ok [1, 'foo', 1.2], $schema; } sub min_max { my $schema = {type => 'array', minItems => 2, maxItems => 2}; schema_validate_ok [1], $schema, E('/', 'Not enough items: 1/2.'); schema_validate_ok [1, 2], $schema; schema_validate_ok [1, 2, 3], $schema, E('/', 'Too many items: 3/2.'); } sub min_max_contains { my $schema = {type => 'array', contains => {type => 'string'}, maxContains => 3, minContains => 2}; schema_validate_ok [qw(A)], $schema, E('/', 'Contains not enough items: 1/2.'); schema_validate_ok [qw(A B C D)], $schema, E('/', 'Contains too many items: 4/3.'); schema_validate_ok [qw(A B)], $schema; schema_validate_ok [qw(A B C)], $schema; } sub unevaluated_items { local $TODO = 'https://json-schema.org/draft/2019-09/json-schema-core.html#unevaluatedItems'; my $schema = {unevaluatedItems => {}}; validate_ok [1600, 'Pennsylvania', 'Avenue', 'NW', 'Washington'], $schema, E('/', 'Invalid number of items: 5/4.'); } sub unique { my $schema = {type => 'array', uniqueItems => 1, items => {type => 'integer'}}; validate_ok [123, 124], $schema; validate_ok [1, 2, 1], $schema, E('/', 'Unique items required.'); } 1; JSON-Validator-5.18/t/jv-array.t0000644000076500000240000001033315210277065016222 0ustar jhthorsenstaffuse lib '.'; use Mojo::Base -strict; use Mojo::JSON 'encode_json'; 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, E('/2', 'Not in enum list: Street, Avenue, Boulevard.'); 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.'); validate_ok [1600, 'NW'], {type => 'array', contains => {type => 'string', enum => ['NW']}}; validate_ok [1600, 'NW'], {type => 'array', contains => {type => 'string', enum => ['Nope']}}, E('/0', 'Expected string - got number.'), E('/1', 'Not in enum list: Nope.'); # Make sure all similar numbers gets converted from strings my $jv = JSON::Validator->new->coerce('numbers'); my @numbers; $jv->schema({type => 'array', items => {type => 'number'}}); @numbers = qw(1.42 2.3 1.42 1.42); ok !$jv->validate(\@numbers), 'numbers are valid'; is encode_json(\@numbers), encode_json([1.42, 2.3, 1.42, 1.42]), 'coerced into integers'; $jv->schema({type => 'array', items => {type => 'integer'}}); @numbers = qw(1 2 1 1 3 1); ok !$jv->validate(\@numbers), 'integers are valid'; is encode_json(\@numbers), encode_json([1, 2, 1, 1, 3, 1]), 'coerced into numbers'; my $array_constant = {type => 'array', const => [1, 'a', undef]}; validate_ok [1, 'a', undef], $array_constant; validate_ok [1, 'b', undef], $array_constant, E('/', q{Does not match const: [1,"a",null].}); # TODO! true, false are draft 6+ only validate_ok [1, 'foo', 1.2], {type => 'array', items => {}}; validate_ok [1, 'foo', 1.2], {type => 'array', items => true}; validate_ok [1, 'foo', 1.2], {type => 'array', items => [true, true, false]}, E('/2', 'Should not match.'); validate_ok [1, 'foo', 1.2], {type => 'array', items => false}, E('/0', 'Should not match.'), E('/1', 'Should not match.'), E('/2', 'Should not match.'); validate_ok [1, 'foo', 1.2], {definitions => {my_true_ref => true}, type => 'array', items => {'$ref' => '#/definitions/my_true_ref'}}; validate_ok [1, 'foo', 1.2], {definitions => {my_false_ref => false}, type => 'array', items => {'$ref' => '#/definitions/my_false_ref'}}, E('/0', 'Should not match.'), E('/1', 'Should not match.'), E('/2', 'Should not match.'); validate_ok [1, 'foo', 1.2], { definitions => {my_true_ref => true, my_false_ref => false}, type => 'array', items => [ {'$ref' => '#/definitions/my_true_ref'}, {'$ref' => '#/definitions/my_true_ref'}, {'$ref' => '#/definitions/my_false_ref'} ], }, E('/2', 'Should not match.'); validate_ok [], {type => 'array', contains => {const => 'foo'}}, E('/', 'No items contained.'); validate_ok [1], {contains => {const => 'foo'}}, E('/0', 'Does not match const: "foo".'); validate_ok [1], {items => {not => {}}}, E('/0', 'Should not match.'); validate_ok [1], {items => false}, E('/0', 'Should not match.'); validate_ok [1, 2], {contains => {not => {}}}, E('/0', 'Should not match.'), E('/1', 'Should not match.'); validate_ok [1, 2], {contains => false}, E('/0', 'Should not match.'), E('/1', 'Should not match.'); validate_ok [1, 'hello'], {contains => {const => 1}, items => {type => 'number'}}, E('/1', 'Expected number - got string.'); validate_ok [1, 'hello'], {contains => {const => 1}, items => [{type => 'string'}]}, E('/0', 'Expected string - got number.'); done_testing; JSON-Validator-5.18/t/issue-272-recursive-data-protection.t0000644000076500000240000000315215211411416023217 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv3; use Mojo::JSON qw(true); use Test::More; # Regression test for GH #272 ("heisenbug with openapi 3.1.* schema"). # # Validating an OpenAPI 3.1 document whose parameter uses a $ref schema # intermittently produced a false "/components/parameters/Id/$ref: Missing # property." error. Root cause: recursive_data_protection's "seen" cache keyed on # stringified memory addresses ("$schema:$data"); transient merged-schema hashrefs # (built while following the meta-schema's $refs) were freed mid-validation and # their addresses reused, colliding with stale cached error lists. # # It is a heisenbug (address reuse, independent of the hash seed), so we validate # many times and assert the document is *always* valid. sub spec { return { openapi => '3.1.0', info => {version => '1.0', title => 'whatever'}, components => { parameters => { Id => {in => 'path', name => 'id', required => true, schema => {'$ref' => '#/components/schemas/IdType'}}, }, schemas => {IdType => {type => 'integer'}}, }, }; } my $spec_url = 'https://spec.openapis.org/oas/3.1/schema/2021-05-20'; my $iterations = 500; my (%errors, $failures); for (1 .. $iterations) { my $schema = JSON::Validator::Schema::OpenAPIv3->new(spec(), specification => $spec_url); next unless my @e = @{$schema->errors}; $failures++; $errors{"$_"}++ for @e; } is $failures, undef, "valid 3.1 \$ref parameter stays valid across $iterations validations (#272)" or diag "spurious errors: " . join(', ', map {"$_ x$errors{$_}"} sort keys %errors); done_testing; JSON-Validator-5.18/t/bundle.t0000644000076500000240000001005415210277065015740 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use JSON::Validator::Schema::Draft7; use Mojo::File 'path'; use Test::More; my $workdir = path(__FILE__)->to_abs->dirname; my $jv = JSON::Validator->new; subtest 'Run multiple times to make sure _reset() works' => sub { for my $n (1 .. 3) { my $bundled = $jv->schema({ id => 'https://jv.example.com/reset', surname => {'$ref' => '#/definitions/name'}, age => {'$ref' => 'b.json#/definitions/years'}, definitions => {name => {type => 'string'}}, B => {id => 'b.json', definitions => {years => {type => 'integer'}}}, })->bundle; ok $bundled->{definitions}{name}, "[$n] definitions/name still in definitions"; is $bundled->{definitions}{name}{type}, 'string', "[$n] definitions/name/type still in definitions"; is $bundled->{definitions}{'b_json-definitions_years'}{type}, 'integer', "[$n] added to definitions"; isnt $bundled->{age}, $jv->schema->get('/age'), "[$n] new age ref"; is $bundled->{age}{'$ref'}, '#/definitions/b_json-definitions_years', "[$n] age \$ref"; is $bundled->{surname}{'$ref'}, '#/definitions/name', "[$n] surname \$ref"; } }; subtest 'check bundled structure' => sub { is $jv->get([qw(surname type)]), 'string', 'get /surname/$ref'; is $jv->get('/surname/type'), 'string', 'get /surname/type'; is $jv->get('/surname/$ref'), '#/definitions/name', 'get /surname/$ref'; is $jv->schema->get('/surname/type'), 'string', 'schema get /surname/type'; is $jv->schema->data->{surname}{'$ref'}, '#/definitions/name', 'schema get /surname/$ref'; my $bundled = $jv->schema('data://main/bundled.json')->bundle; is_deeply [sort keys %{$bundled->{definitions}}], ['objtype'], 'no dup definitions'; }; subtest 'definitions in disk spec' => sub { for my $path ( ['test-definitions-key.json'], ['with-deep-mixed-ref.json'], ['with-deep-mixed-ref.json'], [File::Spec->updir, 'spec', 'with-deep-mixed-ref.json'], ) { my $file = path $workdir, 'spec', @$path; my @expected = qw(age_json height unit_json weight_json); my $bundled = $jv->schema($file)->bundle; is_deeply [sort keys %{$bundled->{definitions}}], \@expected, "right definitions in disk spec @$path" or diag join ', ', sort keys %{$bundled->{definitions}}; } }; subtest 'ensure filenames with funny characters not mangled by Mojo::URL' => sub { my $file3 = path $workdir, 'spec', 'space bundle.json'; my $bundled = eval { $jv->schema($file3)->bundle }; is $@, '', 'loaded absolute filename with space'; is $bundled->{definitions}{space_age_json}{description}, 'Age in years', 'space_age_json def'; is $bundled->{properties}{age}{'$ref'}, '#/definitions/space_age_json', 'space_age_json ref'; }; subtest 'extract subset of schema' => sub { my $bundled = $jv->schema('data://main/bundled.json')->bundle({schema => $jv->get([qw(paths /withdots get)])}); is_deeply( $bundled, { definitions => {objtype => {properties => {propname => {type => 'string'}}, type => 'object'}}, responses => {200 => {schema => {'$ref' => '#/definitions/objtype'}}} }, 'subset of schema was bundled' ) or diag explain $bundled; }; subtest 'no leaking path' => sub { my $bundled = $jv->schema('data://main/bundled.json')->bundle({schema => $jv->get([qw(paths /withdots get)])}); my $ref_name_prefix = $workdir; $ref_name_prefix =~ s![^\w-]!_!g; $jv->schema(path $workdir, 'spec', 'bundle-no-leaking-filename.json'); my @definitions = keys %{$bundled->{definitions}}; ok @definitions, 'definitions are present'; is_deeply [grep { 0 == index $_, $ref_name_prefix } @definitions], [], 'no leaking of path'; }; done_testing; __DATA__ @@ bundled.json { "definitions": { "objtype": { "type": "object", "properties": {"propname": {"type": "string"}} } }, "paths": { "/withdots": { "get": { "responses": { "200": {"schema": {"$ref": "#/definitions/objtype"}} } } } } } JSON-Validator-5.18/t/openapiv2-readonly.t0000644000076500000240000000271515210277065020212 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my ($body, @errors); $body = sub { +{exists => 1, value => {}} }; @errors = $schema->validate_request([post => '/user'], {body => $body}); is "@errors", '', 'required is ignored on validate_request'; @errors = $schema->validate_response([post => '/user'], {body => $body}); is "@errors", '/body/age: Missing property.', 'age is required in response'; $body = sub { +{exists => 1, value => {age => 42}} }; @errors = $schema->validate_request([post => '/user'], {body => $body}); is "@errors", '/body/age: Read-only.', 'age is read-only for request'; @errors = $schema->validate_response([post => '/user'], {body => $body}); is "@errors", '', 'age is present in response'; done_testing; __DATA__ @@ spec.json { "swagger": "2.0", "info": {"version": "", "title": "Test readonly"}, "basePath": "/api", "paths": { "/user": { "post": { "parameters": [ {"name":"body", "in":"body", "schema": {"$ref": "#/definitions/User"}} ], "responses": { "200": { "description": "ok", "schema": {"$ref": "#/definitions/User"}} } } } }, "definitions": { "age": { "type": "integer", "readOnly": true }, "User": { "type": "object", "required": ["age"], "properties": { "age": {"$ref": "#/definitions/age"} } } } } JSON-Validator-5.18/t/openapiv3-discriminator.t0000644000076500000240000000615015210277065021242 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my ($body, @errors); my %cat = (name => 'kit-e-cat', petType => 'cat', huntingSkill => 'adventurous'); my %dog = (name => 'dog-e-dog', petType => 'dog', packSize => 4); $body = sub { {exists => 1, value => {%cat, petType => 'dog'}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body/packSize: /allOf/1 Missing property.', 'invalid dog'; $body = sub { {exists => 1, value => {%cat}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '', 'valid cat'; $body = sub { {exists => 1, value => {%dog, petType => 'cat'}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body/huntingSkill: /allOf/1 Missing property.', 'invalid cat'; $body = sub { {exists => 1, value => {%dog}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '', 'valid dog'; $body = sub { {exists => 1, value => {%dog, petType => ''}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body: Discriminator petType has no value.', 'discriminator is required'; $body = sub { {exists => 1, value => {%cat, petType => 'Hamster'}} }; @errors = $schema->validate_request([post => '/pets'], {body => $body}); is "@errors", '/body: No definition for discriminator Hamster.', 'invalid discriminator'; done_testing; __DATA__ @@ spec.json { "openapi": "3.0.0", "info": { "title": "Discriminator", "version": "" }, "paths": { "/pets": { "post": { "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } } }, "components": { "schemas": { "Pet": { "type": "object", "discriminator": { "propertyName": "petType", "mapping": { "cat": "#/components/schemas/Cat", "dog": "#/components/schemas/Dog" } }, "required": ["name", "petType"], "properties": { "name": {"type": "string"}, "petType": {"type": "string"} } }, "Cat": { "description": "A representation of a cat", "allOf": [ {"$ref": "#/components/schemas/Pet"}, { "type": "object", "required": ["huntingSkill"], "properties": {"huntingSkill": {"type": "string", "description": "The measured skill for hunting", "default": "lazy", "enum": ["clueless", "lazy", "adventurous", "aggressive"]} } } ] }, "Dog": { "description": "A representation of a dog", "allOf": [ {"$ref": "#/components/schemas/Pet"}, { "type": "object", "required": ["packSize"], "properties": { "packSize": {"type": "integer", "format": "int32", "description": "the size of the pack the dog is from", "default": 0, "minimum": 0} } } ] } } } } JSON-Validator-5.18/t/jv-oneof.t0000644000076500000240000000621715210277065016220 0ustar jhthorsenstaffuse 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/0 Not multiple of 5.'), E('/', '/oneOf/1 Not multiple of 3.'); $schema = {oneOf => [{type => 'object'}, {type => 'string', multipleOf => 3}]}; validate_ok 13, $schema, E('/', '/oneOf Expected object/string - got number.'); $schema = {oneOf => [{type => 'object'}, {type => 'number', multipleOf => 3}]}; validate_ok 13, $schema, E('/', '/oneOf/0 Expected object - got number.'), E('/', '/oneOf/1 Not multiple of 3.'); # Alternative oneOf # https://json-schema.org/draft-07/json-schema-validation.html#rfc.section.7 $schema = {type => 'object', properties => {x => {type => ['string', 'null'], format => 'date-time'}}}; validate_ok {x => 'foo'}, $schema, E('/x', 'Does not match date-time format.'); validate_ok {x => '2015-04-21T20:30:43.000Z'}, $schema; validate_ok {x => undef}, $schema; $schema = {type => 'object', properties => {x => {type => ['null', 'number']}}}; validate_ok {x => 'foo'}, $schema, E('/x', 'Expected null/number - got string.'); validate_ok 1, {oneOf => [{minimum => 1}, {minimum => 2}, {maximum => 3}]}, E('/', 'oneOf rules 0, 2 match.'); validate_ok 'hello', {oneOf => [true, false]}; validate_ok 'hello', {oneOf => [true, true]}, E('/', 'All of the oneOf rules match.'); validate_ok 'hello', {oneOf => [false, false]}, E('/', '/oneOf/0 Should not match.'), E('/', '/oneOf/1 Should not match.'); validate_ok 'hello', {oneOf => [true, {type => ['string', 'boolean']}]}, E('/', 'All of the oneOf rules match.'); validate_ok 'hello', {type => ['integer', 'boolean']}, E('/', 'Expected integer/boolean - got string.'); validate_ok 'hello', {oneOf => [false, {type => ['integer', 'string'], enum => [123, 'HELLO']}]}, E('/', '/oneOf/0 Should not match.'), E('/', '/oneOf/1 Not in enum list: 123, HELLO.'); validate_ok 'hello', {oneOf => [false, {type => ['integer', 'boolean']}]}, E('/', '/oneOf/0 Should not match.'), E('/', '/oneOf/1 Expected integer/boolean - got string.'); validate_ok 'hello', {oneOf => [false, {type => 'integer'}]}, E('/', '/oneOf/0 Should not match.'), E('/', '/oneOf/1 Expected integer - got string.'); validate_ok 'hello', {oneOf => [{type => ['integer', 'boolean']}]}, E('/', '/oneOf/0 Expected integer/boolean - got string.'); validate_ok 'hello', {oneOf => [{oneOf => [{type => 'boolean'}, {type => 'string', maxLength => 2}]}, {type => 'integer'}]}, E('/', '/oneOf/0/oneOf/0 Expected boolean - got string.'), E('/', '/oneOf/0/oneOf/1 String is too long: 5/2.'), E('/', '/oneOf/1 Expected integer - got string.'); validate_ok {foo => 'not an arrayref'}, {oneOf => [{type => 'object', properties => {foo => {type => 'array'}}}, {type => 'boolean'}]}, E('/foo', '/oneOf/0 Expected array - got string.'), E('/', '/oneOf/1 Expected boolean - got object.'); done_testing; JSON-Validator-5.18/t/load-data.t0000644000076500000240000000306115210277065016315 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $jv = JSON::Validator->new; my @errors = $jv->schema('data://main/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'; use Mojo::File 'path'; push @INC, path(path(__FILE__)->dirname, 'stack')->to_string; require Some::Module; eval { Some->validate_age1({age => 1}) }; like $@, qr{age1\.json}, 'could not find age1.json'; ok !Some->validate_age0({age => 1}), 'validate_age0'; ok !Some::Module->validate_age0({age => 1}), 'validate_age0'; ok !Some::Module->validate_age1({age => 1}), 'validate_age1'; eval { Mojolicious::Plugin::TestX->validate('data:///spec.json', {}) }; ok !$@, 'found spec.json in main' or diag $@; @errors = $jv->schema('data://main/spec.json')->validate({}); like "@errors", qr{firstName.*lastName}, 'required is sorted'; package Mojolicious::Plugin::TestX; sub validate { $jv->schema($_[1])->validate($_[2]) } package main; is_deeply [sort keys %{$jv->store->schemas}], [qw(data:///spec.json data://main/spec.json)], 'schemas in store'; done_testing; __DATA__ @@ spec.json { "title": "Example Schema", "type": "object", "required": ["lastName", "firstName"], "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "age": { "type": "integer", "minimum": 0, "description": "トシ" } } } JSON-Validator-5.18/t/uri.t0000644000076500000240000000422315210277065015267 0ustar jhthorsenstaffuse Mojo::Base -strict; use Test::More; use JSON::Validator::URI; subtest 'url https' => sub { my $url = JSON::Validator::URI->new('https://foo.com'); is $url->scheme, 'https', 'scheme'; is $url->host, 'foo.com', 'host'; is $url->nid, undef, 'nid'; is $url->nss, undef, 'nss'; }; subtest 'urn uuid' => sub { my $urn = JSON::Validator::URI->new('urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f'); is $urn->host, undef, 'host'; is $urn->scheme, 'urn', 'scheme'; is $urn->nid, 'uuid', 'nid'; is $urn->nss, 'ee564b8a-7a87-4125-8c96-e9f123d6766f', 'nss'; is $urn->fragment, undef, 'fragment'; }; subtest 'urn jv' => sub { my $urn = JSON::Validator::URI->new('urn:jv:draft4-4242#foo'); ok $urn->is_abs, 'is_abs'; is $urn->host, undef, 'host'; is $urn->scheme, 'urn', 'scheme'; is $urn->nid, 'jv', 'nid'; is $urn->nss, 'draft4-4242', 'nss'; is $urn->fragment, 'foo', 'fragment'; my $clone = $urn->clone; is $clone->host, undef, 'clone host'; is $clone->scheme, 'urn', 'clone scheme'; is $clone->nid, 'jv', 'clone nid'; is $clone->nss, 'draft4-4242', 'clone nss'; is $clone->fragment, 'foo', 'clone fragment'; is $clone->to_string, 'urn:jv:draft4-4242#foo', 'clone to_string'; }; subtest 'urn to_abs' => sub { my $urn = JSON::Validator::URI->new('urn:jv:draft4-4242#foo'); my $abs = $urn->to_abs(JSON::Validator::URI->new('urn:jv:draft4-4242#bar')); is $abs->to_string, $urn->to_string, 'is_abs'; $urn = JSON::Validator::URI->new('#foo'); $abs = $urn->to_abs(JSON::Validator::URI->new('urn:jv:draft4-4242#bar')); is $abs->to_string, 'urn:jv:draft4-4242#foo', 'to_abs'; # TODO: I don't think it's valid for a URN to have a path, so this might get reverted my $rel = JSON::Validator::URI->new('b.json'); is $rel->to_abs($abs)->to_string, 'urn:jv:draft4-4242/b.json#foo', 'b.json to_abs'; }; done_testing; JSON-Validator-5.18/t/draft4-acceptance.t0000644000076500000240000000110215210277065017731 0ustar jhthorsenstaffuse lib '.'; use t::Helper; $ENV{MOJO_LOG_LEVEL} //= 'fatal'; plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; my @todo_tests; push @todo_tests, ['id.json', 'id inside an enum is not a real identifier']; push @todo_tests, ['ref.json', '$ref prevents a sibling id from changing the base uri']; push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; t::Helper->acceptance('JSON::Validator::Schema::Draft4', todo_tests => \@todo_tests); done_testing; JSON-Validator-5.18/t/openapiv2-basic.t0000644000076500000240000002146315210277065017457 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv2; use Mojo::JSON qw(false true); use Test::Deep; use Test::More; my $cwd = Mojo::File->new(__FILE__)->dirname; my $schema = JSON::Validator::Schema::OpenAPIv2->new; my ($body, @errors); subtest 'basic' => sub { is $schema->specification, 'http://swagger.io/v2/schema.json', 'specification'; is_deeply $schema->coerce, {booleans => 1, numbers => 1, strings => 1}, 'default coercion'; eval { my $s = JSON::Validator->new->schema('data://main/spec-resolve-refs.json')->schema; is $s->get([qw(paths /user get responses 200 schema type)]), 'object', 'resolved "User"'; 1; } or do { diag $@; ok 0, 'Could not resolve "User"'; }; $schema = JSON::Validator->new->schema($cwd->child(qw(spec v2-petstore.json)))->schema; isa_ok $schema, 'JSON::Validator::Schema::OpenAPIv2'; is_deeply( $schema->routes->to_array, [ {method => 'get', operation_id => 'showPetById', path => '/pets/{petId}'}, {method => 'get', operation_id => 'listPets', path => '/pets'}, {method => 'post', operation_id => 'createPets', path => '/pets'}, ], 'routes' ); is_deeply $schema->errors, [], 'errors'; }; subtest base_url => sub { is $schema->base_url, 'http://petstore.swagger.io/v1', 'get'; is $schema->base_url('https://api.example.com:8080/api'), $schema, 'set url'; is_deeply $schema->get('/schemes'), ['https'], 'schemes changed'; is $schema->get('/host'), 'api.example.com:8080', 'host changed'; is $schema->get('/basePath'), '/api', 'basePath changed'; is $schema->base_url(Mojo::URL->new('//api2.example.com')), $schema, 'set without scheme'; is_deeply $schema->get('/schemes'), ['https'], 'schemes unchanged'; is $schema->get('/host'), 'api2.example.com', 'host changed'; is $schema->get('/basePath'), '/', 'basePath changed'; is $schema->base_url(Mojo::URL->new('/v1')), $schema, 'set path'; is $schema->base_url->to_string, 'https://api2.example.com/v1', 'get'; my $schema_with_port = JSON::Validator->new->schema($cwd->child(qw(spec bundlecheck.json)))->schema; is $schema_with_port->base_url->host, 'localhost', 'host'; is $schema_with_port->base_url->port, 3000, 'port'; }; subtest 'validate schema' => sub { @errors = @{JSON::Validator->new->schema({swagger => '2.0', paths => {}})->schema->errors}; is "@errors", '/info: Missing property.', 'invalid schema'; }; subtest 'parameters_for_request' => sub { is $schema->parameters_for_request([GET => '/pets/nope']), undef, 'no such path'; cmp_deeply( $schema->parameters_for_request([GET => '/pets']), [{ default => 20, description => 'How many items to return at one time (max 100)', format => 'int32', in => 'query', name => 'limit', required => false, type => 'integer', }], 'parameters_for_request inside path' ); cmp_deeply( $schema->parameters_for_request([post => '/pets']), [{in => 'body', name => 'body', accepts => ['application/json'], required => true, schema => ignore, type => ''}], 'parameters_for_request for body' ); cmp_deeply( $schema->parameters_for_request([get => '/pets/{petId}']), [{ description => 'The id of the pet to retrieve', in => 'path', name => 'petId', required => true, type => 'string', }], 'parameters_for_request inside method' ); is $schema->get([qw(paths /pets post parameters 0 accepts)]), undef, 'accepts was not added to schema by parameters_for_request()'; is $schema->get([qw(paths /pets post parameters 0 type)]), undef, 'type was not added to schema by parameters_for_request()'; }; subtest 'parameters_for_response' => sub { is $schema->parameters_for_response([GET => '/pets/nope']), undef, 'no such path'; cmp_deeply $schema->parameters_for_response([GET => '/pets']), [ superhashof({in => 'header', name => 'x-next'}), superhashof({in => 'body', name => 'body', accepts => ['application/json']}), ], 'parameters_for_request inside path and default response code'; cmp_deeply $schema->parameters_for_response([GET => '/pets', 404]), [superhashof({in => 'body', name => 'body', accepts => ['application/json']})], 'default response'; }; subtest 'validate_request' => sub { @errors = $schema->validate_request([get => '/pets'], {query => {limit => 10, foo => '42'}}); is "@errors", '', 'limit ok, even as string'; @errors = $schema->validate_request([get => '/pets'], {query => {limit => 'foo'}}); is "@errors", '/limit: Expected integer - got string.', 'limit failed'; $body = {exists => 0}; @errors = $schema->validate_request([POST => '/pets'], {body => \&body}); is "@errors", '/body: Missing property.', 'default content type, but missing body'; is_deeply $body, {exists => 0, in => 'body', name => 'body', valid => 0}, 'input was mutated'; $body = {exists => 1, value => {name => 'kitty'}}; @errors = $schema->validate_request([POST => '/pets'], {body => \&body}); is "@errors", '/body/id: Missing property.', 'missing id in body'; $body = {exists => 1, value => {id => 42, name => 'kitty'}}; @errors = $schema->validate_request([POST => '/pets'], {body => \&body}); is "@errors", '', 'valid request body'; is_deeply $body, { content_type => 'application/json', exists => 1, in => 'body', name => 'body', valid => 1, value => $body->{value} }, 'input was mutated'; }; subtest 'validate_response' => sub { $body = {exists => 1, value => {id => 42, name => 'kitty'}}; @errors = $schema->validate_response([POST => '/pets', 201], {}); is "@errors", '', 'valid response body 201'; $body = {exists => 1, value => {code => 42}}; @errors = $schema->validate_response([post => '/pets', 200], {body => \&body}); is "@errors", '/body/message: Missing property.', 'valid response body default'; }; subtest 'validate_response - accept' => sub { $body = {accept => 'text/plain'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '/header/Accept: Expected application/json - got text/plain.', 'invalid accept'; is_deeply $body, {accept => 'text/plain', content_type => '', in => 'body', name => 'body', valid => 0}, 'failed to negotiate content type'; $body = {accept => 'application/*'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '', 'valid accept'; is_deeply $body, {accept => 'application/*', content_type => 'application/json', in => 'body', name => 'body', valid => 1}, 'negotiated content type'; }; subtest 'validate_response - content_type' => sub { $body = {content_type => 'text/plain'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '/body: Expected application/json - got text/plain.', 'invalid content_type'; is_deeply $body, {content_type => 'text/plain', in => 'body', name => 'body', valid => 0}, 'failed to negotiate content type'; $body = {content_type => 'application/json'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '', 'valid content_type'; is_deeply $body, {content_type => 'application/json', content_type => 'application/json', in => 'body', name => 'body', valid => 1}, 'negotiated content type'; }; subtest add_default_response => sub { $schema = JSON::Validator->new->schema($cwd->child(qw(spec v2-petstore.json)))->schema; ok !$schema->get('/definitions/DefaultResponse'), 'default response missing'; ok !$schema->get([paths => '/petss', 'get', 'responses', '400']), 'default response missing for 400'; $schema->add_default_response; ok $schema->get('/definitions/DefaultResponse'), 'default response added'; my $bad_request = $schema->data->{paths}{'/pets'}{get}{responses}{400}; is $bad_request->{description}, 'Default response.', 'bad_request description'; like $bad_request->{schema}{'$ref'}, qr{^file://.*v2-petstore.json\#/definitions/DefaultResponse$}, 'bad_request ref'; for my $status (400, 401, 404, 500, 501) { ok $schema->get([paths => '/pets', 'get', 'responses', $status]), "default response for $status"; } delete $schema->{errors}; is_deeply $schema->errors, [], 'errors'; }; done_testing; sub body {$body} __DATA__ @@ spec-resolve-refs.json { "swagger": "2.0", "info": {"version": "", "title": "Test non standard refs"}, "basePath": "/api", "paths": { "/user": { "get": { "responses": { "200": { "description": "ok", "schema": { "$ref": "User" } } } } } }, "definitions": { "User": { "type": "object", "properties": {} } } } JSON-Validator-5.18/t/jv-if-then-else.t0000644000076500000240000000156615210277065017374 0ustar jhthorsenstaffuse lib '.'; use t::Helper; my $schema; $schema = { if => {properties => {ifx => {type => 'string'}}}, then => {properties => {ifx => {maxLength => 3}}}, else => {properties => {ifx => {type => 'number'}}}, }; validate_ok {ifx => 'foo'}, $schema; validate_ok {ifx => 'foobar'}, $schema, E('/ifx', 'String is too long: 6/3.'); validate_ok {ifx => 42}, $schema; validate_ok {ifx => []}, $schema, E('/ifx', 'Expected number - got array.'); $schema = { type => 'array', if => {maxItems => 5}, then => {items => {pattern => '^[0-9]$'}}, else => {items => {pattern => '^[a-z]$'}}, }; validate_ok [qw(2 4 7)], $schema; validate_ok [qw(a 1)], $schema, E('/0', 'String does not match ^[0-9]$.'); validate_ok [qw(6 q a b 8 z)], $schema, E('/0', 'String does not match ^[a-z]$.'), E('/4', 'String does not match ^[a-z]$.'); done_testing; JSON-Validator-5.18/t/load-file.t0000644000076500000240000000226515210277065016330 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $file = Mojo::File::path(qw(t spec person.json))->to_abs; my $spec = Mojo::URL->new->scheme('file')->host('')->path(join '/', @$file); my $jv = JSON::Validator->new; my $id = File::Spec->case_tolerant ? lc $spec : $spec->to_string; note $spec->to_string; ok eval { $jv->schema($file) }, 'loaded from file://' or diag $@; isa_ok $jv->schema, 'JSON::Validator::Schema'; is $jv->schema->get('/title'), 'Example Schema', 'got example schema'; is $jv->schema->id, $id, 'schema id'; is_deeply [sort keys %{$jv->store->schemas}], [$jv->schema->id], 'schemas in store'; ok eval { $jv->schema($spec->to_string) }, 'loaded from file:// again' or diag $@; is $jv->schema->id, $id, 'schema id again'; is_deeply [sort keys %{$jv->store->schemas}], [$jv->schema->id], 'schemas in store again'; eval { $jv->load_and_validate_schema('no-such-file.json') }; like $@, qr{Unable to load schema "no-such-file\.json"}, 'cannot load no-such-file.json'; eval { $jv->load_and_validate_schema('/no-such-file.json') }; like $@, qr{Unable to load schema "/no-such-file\.json"}, 'avoid loading from app, when $ua->server->app is not present'; done_testing; JSON-Validator-5.18/t/random-errors.t0000644000076500000240000000241415210277065017262 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; # Note that you might have to run this test many times before it fails: # while TEST_RANDOM_ITERATIONS=10000 prove -l t/random-errors.t; do echo "---"; done plan skip_all => 'TEST_RANDOM_ITERATIONS=10000' unless my $iterations = $ENV{TEST_RANDOM_ITERATIONS}; my $jv = JSON::Validator->new->schema({ items => { properties => { prop1 => {type => [qw(string null)]}, prop2 => {type => [qw(string null)], format => 'ipv4'}, prop3 => {type => [qw(string null)], format => 'ipv4'}, prop4 => {type => 'string', enum => [qw(foo bar)]}, prop5 => {type => [qw(string null)]}, prop6 => {type => 'string'}, prop7 => {type => 'string', enum => [qw(foo bar)]}, prop8 => {type => [qw(string null)], format => 'ipv4'}, prop9 => {type => [qw(string null)]}, }, type => 'object', }, type => 'array', }); my @errors; for (1 .. $iterations) { push @errors, $jv->validate([{ prop1 => undef, prop2 => undef, prop3 => undef, prop4 => 'foo', prop5 => undef, prop6 => 'foo', prop7 => 'bar', prop8 => undef, prop9 => undef, }]); last if @errors; } ok !@errors, 'no random error' or diag @errors; done_testing; JSON-Validator-5.18/t/jv-const.t0000644000076500000240000000453615210277065016242 0ustar jhthorsenstaffuse 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; my $null_const = {const => undef}; validate_ok 'foo', $null_const, E('/', q{Does not match const: null.}); validate_ok undef, $null_const; my $empty_const = {const => ''}; validate_ok 'foo', $empty_const, E('/', q{Does not match const: "".}); validate_ok '', $empty_const; my $array_constant = {const => [1, 'a', undef]}; validate_ok [1, 'a', undef], $array_constant; validate_ok [1, 'b', undef], $array_constant, E('/', q{Does not match const: [1,"a",null].}); validate_ok true, {const => true}; validate_ok false, {const => false}; validate_ok false, {const => true}, E('/', 'Does not match const: true.'); validate_ok true, {const => false}, E('/', 'Does not match const: false.'); done_testing; JSON-Validator-5.18/t/spec/0000755000076500000240000000000015211411467015231 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/with-unicode-multibyte.json0000644000076500000240000000022615210277065022542 0ustar jhthorsenstaff{ "type": "object", "properties": { "bar": { "enum": ["replacement�char"] }, "foo": { "enum": ["foo♫bar"] } } } JSON-Validator-5.18/t/spec/bundlecheck.json0000644000076500000240000000224715210277065020403 0ustar jhthorsenstaff{ "basePath": "/api", "consumes": [ "application/json" ], "host": "localhost:3000", "info": { "license": { "name": "Apache License, Version 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, "title": "t-app", "version": "0.1.0" }, "paths": { "/t": { "get": { "operationId": "listT", "responses": { "200": { "description": "Self sufficient", "schema": { "items": { "type": "string" }, "type": "array" } }, "default": { "$ref": "#/responses/error" } }, "tags": [ "t" ], "x-mojo-to": "Controller::OpenAPI::T#list" } } }, "produces": [ "application/json" ], "responses": { "error": { "description": "Self sufficient", "schema": { "additionalProperties": false, "properties": { "error": { "type": "string" } }, "required": [ "error" ], "type": "object" } } }, "swagger": "2.0" } JSON-Validator-5.18/t/spec/v3-petstore.json0000644000076500000240000001066615210277065020333 0ustar jhthorsenstaff{ "openapi": "3.0.0", "info": { "license": { "name": "MIT" }, "title": "Swagger Petstore", "version": "1.0.0" }, "servers": [ { "url": "http://petstore.swagger.io/v1" } ], "paths": { "/pets/{petId}": { "get": { "operationId": "showPetById", "tags": [ "pets" ], "summary": "Info for a specific pet", "parameters": [ { "description": "The id of the pet to retrieve", "in": "path", "name": "petId", "required": true, "schema": { "type": "string" } }, { "description": "Indicates if the age is wanted in the response object", "in": "query", "name": "wantAge", "schema": { "type": "boolean" } } ], "responses": { "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "200": { "description": "Expected response to a valid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } } }, "/pets": { "get": { "operationId": "listPets", "summary": "List all pets", "tags": [ "pets" ], "parameters": [ {"name": "limit", "in": "query", "description": "How many items to return at one time (max 100)", "required": false, "schema": { "type": "integer", "default": 20, "format": "int32" }} ], "responses": { "200": { "description": "An paged array of pets", "headers": { "x-next": { "schema": { "type": "string" }, "description": "A link to the next page of responses"} }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pets" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pets" } } } }, "201": { "$ref": "#/components/responses/Pets" }, "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Error" } } } } } }, "post": { "operationId": "createPets", "summary": "Create a pet", "tags": [ "pets" ], "parameters": [ { "description": "Turn on/off debug", "in": "cookie", "name": "debug", "schema": { "type": "integer", "enum": [0, 1] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Pet" } } } }, "responses": { "201": { "description": "Null response", "content": { "*/*": { "schema": { "type": "string" } } } }, "default": { "description": "unexpected error", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "Pets": { "type": "array", "items": { "$ref": "#/components/schemas/Pet" } }, "Pet": { "required": [ "id", "name" ], "properties": { "tag": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "age": { "type": "integer" } } }, "Error": { "required": [ "code", "message" ], "properties": { "code": { "format": "int32", "type": "integer" }, "message": { "type": "string" } } } }, "responses": { "Pets": { "description": "Reusable Pets response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pets" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pets" } } } } } } } JSON-Validator-5.18/t/spec/with-unicode-multibyte.yml0000644000076500000240000000015115210277065022367 0ustar jhthorsenstaff--- properties: foo: enum: - foo♫bar bar: enum: - replacement�char type: object JSON-Validator-5.18/t/spec/test-definitions-key.json0000644000076500000240000000040615210277065022205 0ustar jhthorsenstaff{ "type": "object", "properties": { "age": { "$ref": "../definitions/age.json#" }, "weight": { "$ref": "../definitions/weight.json" }, "height": { "$ref": "#/definitions/height" } }, "definitions": { "height": { "type": "integer" } } } JSON-Validator-5.18/t/spec/bundle-no-leaking-filename.json0000644000076500000240000000013515210277065023177 0ustar jhthorsenstaff{ "type": "object", "properties": { "$ref": "./with-deep-mixed-ref.json#/properties" } } JSON-Validator-5.18/t/spec/v3-default-response-extra.yaml0000644000076500000240000000220515210277065023046 0ustar jhthorsenstaffopenapi: 3.0.0 info: title: v3-default-response-extra version: 0.0.1 components: schemas: base: type: object required: [status, reason] properties: status: type: integer reason: type: string not_found: type: object allOf: - $ref: '#/components/schemas/base' exception: type: object allOf: - $ref: '#/components/schemas/base' responses: '404': description: Custom 404 content: application/json: schema: $ref: '#/components/schemas/not_found' paths: /item/{id}: get: summary: get a single item description: get a single item from the database x-mojo-name: item responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/base' '404': $ref: '#/components/responses/404' '500': description: Custom 500 content: application/json: schema: $ref: '#/components/schemas/exception' JSON-Validator-5.18/t/spec/v2-bundle.yaml0000644000076500000240000000016615210277065017721 0ustar jhthorsenstaff--- swagger: "2.0" info: title: Bundled version: "1.0" basePath: /api paths: /user: $ref: paths.yaml#/user JSON-Validator-5.18/t/spec/with-deep-mixed-ref.json0000644000076500000240000000042415210277065021673 0ustar jhthorsenstaff{ "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-5.18/t/spec/person.json0000644000076500000240000000053015210277065017433 0ustar jhthorsenstaff{ "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-5.18/t/spec/missing-ref.json0000644000076500000240000000015015210277065020346 0ustar jhthorsenstaff{ "type": "object", "properties": { "missing": { "$ref": "../definitions/missing.json#" } } } JSON-Validator-5.18/t/spec/remotes/0000755000076500000240000000000015211411467016707 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/baseUriChangeFolder/0000755000076500000240000000000015211411467022543 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/baseUriChangeFolder/folderInteger.json0000644000076500000240000000003215210277065026225 0ustar jhthorsenstaff{ "type": "integer" } JSON-Validator-5.18/t/spec/remotes/baseUriChange/0000755000076500000240000000000015211411467021407 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/baseUriChange/folderInteger.json0000644000076500000240000000003215210277065025071 0ustar jhthorsenstaff{ "type": "integer" } JSON-Validator-5.18/t/spec/remotes/locationIndependentIdentifierDraft4.json0000644000076500000240000000026015210277065026641 0ustar jhthorsenstaff{ "definitions": { "refToInteger": { "$ref": "#foo" }, "A": { "id": "#foo", "type": "integer" } } } JSON-Validator-5.18/t/spec/remotes/name.json0000644000076500000240000000040415210277065020523 0ustar jhthorsenstaff{ "definitions": { "orNull": { "anyOf": [ { "type": "null" }, { "$ref": "#" } ] } }, "type": "string" } JSON-Validator-5.18/t/spec/remotes/subSchemas-defs.json0000644000076500000240000000024115210277065022616 0ustar jhthorsenstaff{ "$defs": { "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/$defs/integer" } } } JSON-Validator-5.18/t/spec/remotes/draft2020-12/0000755000076500000240000000000015211411467020533 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2020-12/baseUriChangeFolder/0000755000076500000240000000000015211411467024367 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2020-12/baseUriChangeFolder/folderInteger.json0000644000076500000240000000013115210277065030051 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/baseUriChange/0000755000076500000240000000000015211411467023233 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2020-12/baseUriChange/folderInteger.json0000644000076500000240000000013115210277065026715 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/metaschema-no-validation.json0000644000076500000240000000073715210277065026311 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://localhost:1234/draft2020-12/metaschema-no-validation.json", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/applicator": true, "https://json-schema.org/draft/2020-12/vocab/core": true }, "allOf": [ { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, { "$ref": "https://json-schema.org/draft/2020-12/meta/core" } ] } JSON-Validator-5.18/t/spec/remotes/draft2020-12/format-assertion-true.json0000644000076500000240000000075015210277065025705 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft2020-12/format-assertion-true.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/format-assertion": true }, "allOf": [ { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, { "$ref": "https://json-schema.org/draft/2020-12/meta/format-assertion" } ] } JSON-Validator-5.18/t/spec/remotes/draft2020-12/subSchemas-defs.json0000644000076500000240000000034015210277065024442 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/$defs/integer" } } } JSON-Validator-5.18/t/spec/remotes/draft2020-12/name-defs.json0000644000076500000240000000047515210277065023276 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "orNull": { "anyOf": [ { "type": "null" }, { "$ref": "#" } ] } }, "type": "string" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/prefixItems.json0000644000076500000240000000027315210277065023732 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft2020-12/prefixItems.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "prefixItems": [ {"type": "string"} ] } JSON-Validator-5.18/t/spec/remotes/draft2020-12/subSchemas.json0000644000076500000240000000025515210277065023530 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/integer" } } JSON-Validator-5.18/t/spec/remotes/draft2020-12/baseUriChangeFolderInSubschema/0000755000076500000240000000000015211411467026511 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2020-12/baseUriChangeFolderInSubschema/folderInteger.json0000644000076500000240000000013115210277065032173 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/locationIndependentIdentifier.json0000644000076500000240000000035515210277065027425 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "refToInteger": { "$ref": "#foo" }, "A": { "$anchor": "foo", "type": "integer" } } } JSON-Validator-5.18/t/spec/remotes/draft2020-12/format-assertion-false.json0000644000076500000240000000075215210277065026022 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft2020-12/format-assertion-false.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/format-assertion": false }, "allOf": [ { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, { "$ref": "https://json-schema.org/draft/2020-12/meta/format-assertion" } ] } JSON-Validator-5.18/t/spec/remotes/draft2020-12/extendible-dynamic-ref.json0000644000076500000240000000102415210277065025745 0ustar jhthorsenstaff{ "description": "extendible array", "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://localhost:1234/draft2020-12/extendible-dynamic-ref.json", "type": "object", "properties": { "elements": { "type": "array", "items": { "$dynamicRef": "#elements" } } }, "required": ["elements"], "additionalProperties": false, "$defs": { "elements": { "$dynamicAnchor": "elements" } } } JSON-Validator-5.18/t/spec/remotes/draft2020-12/ref-and-defs.json0000644000076500000240000000045315210277065023666 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://localhost:1234/draft2020-12/ref-and-defs.json", "$defs": { "inner": { "properties": { "bar": { "type": "string" } } } }, "$ref": "#/$defs/inner" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/nested/0000755000076500000240000000000015211411467022015 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2020-12/nested/foo-ref-string.json0000644000076500000240000000023215210277065025551 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "foo": {"$ref": "string.json"} } } JSON-Validator-5.18/t/spec/remotes/draft2020-12/nested/string.json0000644000076500000240000000013015210277065024213 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/integer.json0000644000076500000240000000013115210277065023061 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2020-12/tree.json0000644000076500000240000000063215210277065022371 0ustar jhthorsenstaff{ "description": "tree schema, extensible", "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://localhost:1234/draft2020-12/tree.json", "$dynamicAnchor": "node", "type": "object", "properties": { "data": true, "children": { "type": "array", "items": { "$dynamicRef": "#node" } } } } JSON-Validator-5.18/t/spec/remotes/folder/0000755000076500000240000000000015211411467020162 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/folder/folderInteger.json0000644000076500000240000000000215210277065023641 0ustar jhthorsenstaff{}JSON-Validator-5.18/t/spec/remotes/draft7/0000755000076500000240000000000015211411467020076 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft7/ignore-dependentRequired.json0000644000076500000240000000025515210277065025726 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft7/integer.json", "$schema": "http://json-schema.org/draft-07/schema#", "dependentRequired": { "foo": ["bar"] } }JSON-Validator-5.18/t/spec/remotes/name-defs.json0000644000076500000240000000037615210277065021452 0ustar jhthorsenstaff{ "$defs": { "orNull": { "anyOf": [ { "type": "null" }, { "$ref": "#" } ] } }, "type": "string" } JSON-Validator-5.18/t/spec/remotes/urn-ref-string.json0000644000076500000240000000020415210277065022463 0ustar jhthorsenstaff{ "$id": "urn:uuid:feebdaed-ffff-0000-ffff-0000deadbeef", "$defs": {"bar": {"type": "string"}}, "$ref": "#/$defs/bar" } JSON-Validator-5.18/t/spec/remotes/nested-absolute-ref-to-string.json0000644000076500000240000000031015210277065025373 0ustar jhthorsenstaff{ "$defs": { "bar": { "$id": "http://localhost:1234/the-nested-id.json", "type": "string" } }, "$ref": "http://localhost:1234/the-nested-id.json" } JSON-Validator-5.18/t/spec/remotes/subSchemas.json0000644000076500000240000000015615210277065021704 0ustar jhthorsenstaff{ "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/integer" } } JSON-Validator-5.18/t/spec/remotes/draft-next/0000755000076500000240000000000015211411467020763 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft-next/baseUriChangeFolder/0000755000076500000240000000000015211411467024617 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft-next/baseUriChangeFolder/folderInteger.json0000644000076500000240000000012615210277065030305 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft-next/baseUriChange/0000755000076500000240000000000015211411467023463 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft-next/baseUriChange/folderInteger.json0000644000076500000240000000012615210277065027151 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft-next/metaschema-no-validation.json0000644000076500000240000000071615210277065026536 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "$id": "http://localhost:1234/draft-next/metaschema-no-validation.json", "$vocabulary": { "https://json-schema.org/draft/next/vocab/applicator": true, "https://json-schema.org/draft/next/vocab/core": true }, "allOf": [ { "$ref": "https://json-schema.org/draft/next/meta/applicator" }, { "$ref": "https://json-schema.org/draft/next/meta/core" } ] } JSON-Validator-5.18/t/spec/remotes/draft-next/format-assertion-true.json0000644000076500000240000000072715210277065026141 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft-next/format-assertion-true.json", "$schema": "https://json-schema.org/draft/next/schema", "$vocabulary": { "https://json-schema.org/draft/next/vocab/core": true, "https://json-schema.org/draft/next/vocab/format-assertion": true }, "allOf": [ { "$ref": "https://json-schema.org/draft/next/meta/core" }, { "$ref": "https://json-schema.org/draft/next/meta/format-assertion" } ] } JSON-Validator-5.18/t/spec/remotes/draft-next/subSchemas-defs.json0000644000076500000240000000033515210277065024676 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "$defs": { "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/$defs/integer" } } } JSON-Validator-5.18/t/spec/remotes/draft-next/name-defs.json0000644000076500000240000000047215210277065023523 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "$defs": { "orNull": { "anyOf": [ { "type": "null" }, { "$ref": "#" } ] } }, "type": "string" } JSON-Validator-5.18/t/spec/remotes/draft-next/subSchemas.json0000644000076500000240000000025215210277065023755 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/integer" } } JSON-Validator-5.18/t/spec/remotes/draft-next/baseUriChangeFolderInSubschema/0000755000076500000240000000000015211411470026733 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft-next/baseUriChangeFolderInSubschema/folderInteger.json0000644000076500000240000000012615210277065032427 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft-next/locationIndependentIdentifier.json0000644000076500000240000000035215210277065027652 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "$defs": { "refToInteger": { "$ref": "#foo" }, "A": { "$anchor": "foo", "type": "integer" } } } JSON-Validator-5.18/t/spec/remotes/draft-next/format-assertion-false.json0000644000076500000240000000073115210277065026247 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft-next/format-assertion-false.json", "$schema": "https://json-schema.org/draft/next/schema", "$vocabulary": { "https://json-schema.org/draft/next/vocab/core": true, "https://json-schema.org/draft/next/vocab/format-assertion": false }, "allOf": [ { "$ref": "https://json-schema.org/draft/next/meta/core" }, { "$ref": "https://json-schema.org/draft/next/meta/format-assertion" } ] } JSON-Validator-5.18/t/spec/remotes/draft-next/extendible-dynamic-ref.json0000644000076500000240000000101715210277065026177 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "description": "extendible array", "$id": "http://localhost:1234/draft-next/extendible-dynamic-ref.json", "type": "object", "properties": { "elements": { "type": "array", "items": { "$dynamicRef": "#elements" } } }, "required": ["elements"], "additionalProperties": false, "$defs": { "elements": { "$dynamicAnchor": "elements" } } } JSON-Validator-5.18/t/spec/remotes/draft-next/ref-and-defs.json0000644000076500000240000000044615210277065024120 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "$id": "http://localhost:1234/draft-next/ref-and-defs.json", "$defs": { "inner": { "properties": { "bar": { "type": "string" } } } }, "$ref": "#/$defs/inner" } JSON-Validator-5.18/t/spec/remotes/draft-next/nested/0000755000076500000240000000000015211411467022245 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft-next/nested/foo-ref-string.json0000644000076500000240000000022715210277065026005 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "type": "object", "properties": { "foo": {"$ref": "string.json"} } } JSON-Validator-5.18/t/spec/remotes/draft-next/nested/string.json0000644000076500000240000000012515210277065024447 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "type": "string" } JSON-Validator-5.18/t/spec/remotes/draft-next/integer.json0000644000076500000240000000012615210277065023315 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft-next/tree.json0000644000076500000240000000062515210277065022623 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/next/schema", "description": "tree schema, extensible", "$id": "http://localhost:1234/draft-next/tree.json", "$dynamicAnchor": "node", "type": "object", "properties": { "data": true, "children": { "type": "array", "items": { "$dynamicRef": "#node" } } } } JSON-Validator-5.18/t/spec/remotes/different-id-ref-string.json0000644000076500000240000000020415210277065024217 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/real-id-ref-string.json", "$defs": {"bar": {"type": "string"}}, "$ref": "#/$defs/bar" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/0000755000076500000240000000000015211411467020551 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2019-09/baseUriChangeFolder/0000755000076500000240000000000015211411467024405 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2019-09/baseUriChangeFolder/folderInteger.json0000644000076500000240000000013115210277065030067 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/baseUriChange/0000755000076500000240000000000015211411467023251 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2019-09/baseUriChange/folderInteger.json0000644000076500000240000000013115210277065026733 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/metaschema-no-validation.json0000644000076500000240000000073715210277065026327 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "http://localhost:1234/draft2019-09/metaschema-no-validation.json", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/applicator": true, "https://json-schema.org/draft/2019-09/vocab/core": true }, "allOf": [ { "$ref": "https://json-schema.org/draft/2019-09/meta/applicator" }, { "$ref": "https://json-schema.org/draft/2019-09/meta/core" } ] } JSON-Validator-5.18/t/spec/remotes/draft2019-09/subSchemas-defs.json0000644000076500000240000000034015210277065024460 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$defs": { "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/$defs/integer" } } } JSON-Validator-5.18/t/spec/remotes/draft2019-09/name-defs.json0000644000076500000240000000047515210277065023314 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$defs": { "orNull": { "anyOf": [ { "type": "null" }, { "$ref": "#" } ] } }, "type": "string" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/subSchemas.json0000644000076500000240000000025515210277065023546 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/integer" } } JSON-Validator-5.18/t/spec/remotes/draft2019-09/baseUriChangeFolderInSubschema/0000755000076500000240000000000015211411467026527 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2019-09/baseUriChangeFolderInSubschema/folderInteger.json0000644000076500000240000000013115210277065032211 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/locationIndependentIdentifier.json0000644000076500000240000000035515210277065027443 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$defs": { "refToInteger": { "$ref": "#foo" }, "A": { "$anchor": "foo", "type": "integer" } } } JSON-Validator-5.18/t/spec/remotes/draft2019-09/ignore-prefixItems.json0000644000076500000240000000030215210277065025222 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft2019-09/ignore-prefixItems.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "prefixItems": [ {"type": "string"} ] } JSON-Validator-5.18/t/spec/remotes/draft2019-09/extendible-dynamic-ref.json0000644000076500000240000000102415210277065025763 0ustar jhthorsenstaff{ "description": "extendible array", "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "http://localhost:1234/draft2019-09/extendible-dynamic-ref.json", "type": "object", "properties": { "elements": { "type": "array", "items": { "$dynamicRef": "#elements" } } }, "required": ["elements"], "additionalProperties": false, "$defs": { "elements": { "$dynamicAnchor": "elements" } } } JSON-Validator-5.18/t/spec/remotes/draft2019-09/ref-and-defs.json0000644000076500000240000000045315210277065023704 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "http://localhost:1234/draft2019-09/ref-and-defs.json", "$defs": { "inner": { "properties": { "bar": { "type": "string" } } } }, "$ref": "#/$defs/inner" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/dependentRequired.json0000644000076500000240000000030315210277065025112 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/draft2019-09/dependentRequired.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "dependentRequired": { "foo": ["bar"] } } JSON-Validator-5.18/t/spec/remotes/draft2019-09/nested/0000755000076500000240000000000015211411467022033 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/draft2019-09/nested/foo-ref-string.json0000644000076500000240000000023215210277065025567 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "object", "properties": { "foo": {"$ref": "string.json"} } } JSON-Validator-5.18/t/spec/remotes/draft2019-09/nested/string.json0000644000076500000240000000013015210277065024231 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "string" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/integer.json0000644000076500000240000000013115210277065023077 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "integer" } JSON-Validator-5.18/t/spec/remotes/draft2019-09/tree.json0000644000076500000240000000063215210277065022407 0ustar jhthorsenstaff{ "description": "tree schema, extensible", "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "http://localhost:1234/draft2019-09/tree.json", "$dynamicAnchor": "node", "type": "object", "properties": { "data": true, "children": { "type": "array", "items": { "$dynamicRef": "#node" } } } } JSON-Validator-5.18/t/spec/remotes/baseUriChangeFolderInSubschema/0000755000076500000240000000000015211411467024665 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/baseUriChangeFolderInSubschema/folderInteger.json0000644000076500000240000000003215210277065030347 0ustar jhthorsenstaff{ "type": "integer" } JSON-Validator-5.18/t/spec/remotes/locationIndependentIdentifier.json0000644000076500000240000000025615210277065025601 0ustar jhthorsenstaff{ "$defs": { "refToInteger": { "$ref": "#foo" }, "A": { "$anchor": "foo", "type": "integer" } } } JSON-Validator-5.18/t/spec/remotes/extendible-dynamic-ref.json0000644000076500000240000000071015210277065024122 0ustar jhthorsenstaff{ "description": "extendible array", "$id": "http://localhost:1234/extendible-dynamic-ref.json", "type": "object", "properties": { "elements": { "type": "array", "items": { "$dynamicRef": "#elements" } } }, "required": ["elements"], "additionalProperties": false, "$defs": { "elements": { "$dynamicAnchor": "elements" } } } JSON-Validator-5.18/t/spec/remotes/ref-and-defs.json0000644000076500000240000000033715210277065022043 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/ref-and-defs.json", "$defs": { "inner": { "properties": { "bar": { "type": "string" } } } }, "$ref": "#/$defs/inner" } JSON-Validator-5.18/t/spec/remotes/nested/0000755000076500000240000000000015211411467020171 5ustar jhthorsenstaffJSON-Validator-5.18/t/spec/remotes/nested/foo-ref-string.json0000644000076500000240000000013315210277065023725 0ustar jhthorsenstaff{ "type": "object", "properties": { "foo": {"$ref": "string.json"} } } JSON-Validator-5.18/t/spec/remotes/nested/string.json0000644000076500000240000000003115210277065022367 0ustar jhthorsenstaff{ "type": "string" } JSON-Validator-5.18/t/spec/remotes/ref-and-definitions.json0000644000076500000240000000040315210277065023427 0ustar jhthorsenstaff{ "$id": "http://localhost:1234/ref-and-definitions.json", "definitions": { "inner": { "properties": { "bar": { "type": "string" } } } }, "allOf": [ { "$ref": "#/definitions/inner" } ] } JSON-Validator-5.18/t/spec/remotes/integer.json0000644000076500000240000000003215210277065021235 0ustar jhthorsenstaff{ "type": "integer" } JSON-Validator-5.18/t/spec/remotes/locationIndependentIdentifierPre2019.json0000644000076500000240000000026115210277065026560 0ustar jhthorsenstaff{ "definitions": { "refToInteger": { "$ref": "#foo" }, "A": { "$id": "#foo", "type": "integer" } } } JSON-Validator-5.18/t/spec/remotes/tree.json0000644000076500000240000000051615210277065020546 0ustar jhthorsenstaff{ "description": "tree schema, extensible", "$id": "http://localhost:1234/tree.json", "$dynamicAnchor": "node", "type": "object", "properties": { "data": true, "children": { "type": "array", "items": { "$dynamicRef": "#node" } } } } JSON-Validator-5.18/t/spec/with-relative-ref.json0000644000076500000240000000014015210277065021460 0ustar jhthorsenstaff{ "type": "object", "properties": { "age": { "$ref": "../definitions/age.json#" } } } JSON-Validator-5.18/t/spec/paths.yaml0000644000076500000240000000054215210277065017240 0ustar jhthorsenstaff--- user: get: parameters: - $ref: "#/parameters/id_ref" responses: 200: description: A user schema: $ref: "#/x-def/User" parameters: id_ref: $ref: "#/parameters/id" id: in: path name: id required: true type: string x-def: User: properties: name: type: string JSON-Validator-5.18/t/spec/space bundle.json0000644000076500000240000000014615210277065020455 0ustar jhthorsenstaff{ "type": "object", "properties": { "age": { "$ref": "../definitions/space age.json#" } } } JSON-Validator-5.18/t/spec/more-bundle.yaml0000644000076500000240000000356615210277065020343 0ustar jhthorsenstaff--- $schema: http://json-schema.org/draft-07/schema# definitions: ref1: type: array items: $ref: '#/definitions/ref2' ref2: type: string minLength: 1 ref3: type: integer dupe_name: type: integer i_have_nested_refs: type: object properties: my_key1: $ref: '#/definitions/ref1' my_key2: $ref: '#/definitions/ref1' # actually a person, as in https://json-schema.org/understanding-json-schema/structuring.html i_have_a_recursive_ref: type: object properties: name: type: string children: type: array items: $ref: '#/definitions/i_have_a_recursive_ref' default: [] i_have_a_ref_to_another_file: type: object properties: name: $ref: more-bundle2.yaml#/definitions/my_name address: $ref: more-bundle2.yaml#/definitions/my_address secrets: $ref: '#/definitions/ref1' i_am_a_ref: $ref: '#/definitions/ref1' i_am_a_ref_level_1: $ref: '#/definitions/i_am_a_ref_level_2' i_am_a_ref_level_2: $ref: '#/definitions/ref3' i_am_a_ref_to_another_file: $ref: more-bundle2.yaml#/definitions/i_have_a_ref_to_the_first_filename i_am_a_ref_with_the_same_name: $ref: more-bundle2.yaml#/definitions/i_am_a_ref_with_the_same_name i_have_refs_with_the_same_name: type: object properties: me: $ref: '#/definitions/i_am_a_ref_with_the_same_name' i_contain_refs_to_same_named_definitions: type: object properties: foo: $ref: '#/definitions/dupe_name' bar: $ref: more-bundle2.yaml#/definitions/dupe_name i_have_a_ref_with_the_same_name: type: object properties: name: type: string children: type: array items: $ref: more-bundle2.yaml#/definitions/i_have_a_ref_with_the_same_name default: [] JSON-Validator-5.18/t/spec/more-bundle2.yaml0000644000076500000240000000111415210277065020410 0ustar jhthorsenstaff--- $schema: http://json-schema.org/draft-07/schema# definitions: my_name: type: string minLength: 2 my_address: type: object properties: street: type: string city: # this is a local ref in a secondary file - resolution is extra tricky $ref: '#/definitions/my_name' dupe_name: type: string i_am_a_ref_with_the_same_name: type: string i_have_a_ref_to_the_first_filename: type: object properties: gotcha: $ref: more-bundle.yaml#/definitions/ref3 i_have_a_ref_with_the_same_name: type: string JSON-Validator-5.18/t/spec/v2-petstore.json0000644000076500000240000000550515210277065020326 0ustar jhthorsenstaff{ "swagger": "2.0", "info": { "version": "1.0.0", "title": "Swagger Petstore", "contact": {"name": "OAI", "url": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore.json"}, "license": {"name": "MIT"} }, "host": "petstore.swagger.io", "basePath": "/v1", "schemes": ["http"], "consumes": ["application/json"], "produces": ["application/json"], "paths": { "/pets": { "get": { "summary": "List all pets", "operationId": "listPets", "tags": ["pets"], "parameters": [ {"name": "limit", "in": "query", "default": 20, "description": "How many items to return at one time (max 100)", "required": false, "type": "integer", "format": "int32"} ], "responses": { "200": { "description": "An paged array of pets", "headers": { "x-next": {"type": "string", "description": "A link to the next page of responses"} }, "schema": {"$ref": "#/definitions/Pets"} }, "default": { "description": "unexpected error", "schema": {"$ref": "#/definitions/Error"} } } }, "post": { "summary": "Create a pet", "operationId": "createPets", "tags": ["pets"], "parameters": [ {"in": "body", "name": "body", "required": true, "schema": {"$ref" : "#/definitions/Pet"}} ], "responses": { "201": { "description": "Null response" }, "default": { "description": "unexpected error", "schema": {"$ref": "#/definitions/Error"} } } } }, "/pets/{petId}": { "parameters": [ {"name": "petId", "in": "path", "required": true, "description": "The id of the pet to retrieve", "type": "string"} ], "get": { "summary": "Info for a specific pet", "operationId": "showPetById", "tags": ["pets"], "responses": { "200": { "description": "Expected response to a valid request", "schema": {"$ref": "#/definitions/Pets"} }, "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"} } }, "Pets": { "type": "array", "items": {"$ref": "#/definitions/Pet"} }, "Error": { "required": ["code", "message"], "properties": { "code": {"type": "integer", "format": "int32"}, "message": {"type": "string"} } } } } JSON-Validator-5.18/t/openapiv3-basic.t0000644000076500000240000002107015211411261017437 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv3; use Mojo::File; use Test::Deep; use Test::More; my $cwd = Mojo::File->new(__FILE__)->dirname; my $schema = JSON::Validator::Schema::OpenAPIv3->new; my ($body, $p, @errors); subtest 'basic' => sub { is $schema->specification, 'https://spec.openapis.org/oas/3.0/schema/2021-09-28', 'specification'; is_deeply $schema->coerce, {booleans => 1, numbers => 1, strings => 1}, 'default coercion'; $schema = JSON::Validator->new->schema($cwd->child(qw(spec v3-petstore.json)))->schema; isa_ok $schema, 'JSON::Validator::Schema::OpenAPIv3'; @errors = @{JSON::Validator->new->schema({openapi => '3.0.0', paths => {}})->schema->errors}; is "@errors", '/info: Missing property.', 'invalid schema'; is_deeply( $schema->routes->to_array, [ {method => 'get', operation_id => 'showPetById', path => '/pets/{petId}'}, {method => 'get', operation_id => 'listPets', path => '/pets'}, {method => 'post', operation_id => 'createPets', path => '/pets'}, ], 'routes' ); }; subtest base_url => sub { is $schema->base_url, 'http://petstore.swagger.io/v1', 'get'; is $schema->base_url('https://api.example.com:8080/api'), $schema, 'set url'; is $schema->get('/servers/0/url'), 'https://api.example.com:8080/api', 'servers changed'; is $schema->base_url(Mojo::URL->new('//api2.example.com')), $schema, 'set without scheme'; is $schema->get('/servers/0/url'), 'https://api2.example.com', 'servers changed'; is $schema->base_url(Mojo::URL->new('/v1')), $schema, 'set path'; is $schema->base_url->to_string, 'https://api2.example.com/v1', 'get'; }; subtest 'parameters_for_request' => sub { is $schema->parameters_for_request([GET => '/pets/nope']), undef, 'no such path'; cmp_deeply $schema->parameters_for_request([GET => '/pets']), [superhashof({in => 'query', name => 'limit'})], 'parameters_for_request inside path'; cmp_deeply $schema->parameters_for_request([post => '/pets']), [ superhashof({in => 'cookie', name => 'debug'}), superhashof({in => 'body', name => 'body', accepts => [qw(application/json application/x-www-form-urlencoded)]}) ], 'parameters_for_request for body'; cmp_deeply $schema->parameters_for_request([get => '/pets/{petId}']), [superhashof({in => 'path', name => 'petId'}), superhashof({in => 'query', name => 'wantAge'})], 'parameters_for_request inside method'; }; subtest 'parameters_for_response' => sub { is $schema->parameters_for_response([GET => '/pets/nope']), undef, 'no such path'; cmp_deeply $schema->parameters_for_response([GET => '/pets']), [ superhashof({in => 'header', name => 'x-next'}), superhashof({in => 'body', name => 'body', accepts => [qw(application/json application/xml)]}), ], 'parameters_for_request inside path and default response code'; cmp_deeply $schema->parameters_for_response([GET => '/pets', 404]), [superhashof({in => 'body', name => 'body', accepts => [qw(application/json application/xml)]})], 'default response'; }; subtest 'validate_request' => sub { $p = Mojo::Parameters->new('limit=10&foo=42'); @errors = $schema->validate_request([get => '/pets'], {query => $p->to_hash}); is "@errors", '', 'limit ok, even as string'; @errors = $schema->validate_request([get => '/pets'], {query => {limit => 'foo'}}); is "@errors", '/limit: Expected integer - got string.', 'limit failed'; $body = {exists => 0}; @errors = $schema->validate_request([POST => '/pets'], {body => \&body}); is "@errors", '/body: Missing property.', 'default content type, but missing body'; is_deeply $body, {exists => 0, in => 'body', name => 'body', valid => 0}, 'input was mutated'; $body = {exists => 1, value => {name => 'kitty'}}; @errors = $schema->validate_request([POST => '/pets'], {body => \&body}); is "@errors", '/body/id: Missing property.', 'missing id in body'; $body = {exists => 1, value => {id => 42, name => 'kitty'}}; @errors = $schema->validate_request([POST => '/pets'], {body => \&body}); is "@errors", '', 'valid request body'; is_deeply $body, { content_type => 'application/json', exists => 1, in => 'body', name => 'body', valid => 1, value => $body->{value} }, 'input was mutated'; }; subtest 'validate_response' => sub { $body = {exists => 1, value => {id => 42, name => 'kitty'}}; @errors = $schema->validate_response([POST => '/pets', 201], {}); is "@errors", '', 'valid response body 201'; $body = {exists => 1, value => {code => 42}}; @errors = $schema->validate_response([post => '/pets', 200], {body => \&body}); is "@errors", '/body/message: Missing property.', 'valid response body default'; }; subtest 'validate_response - accept' => sub { $body = {accept => 'text/plain'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '/header/Accept: Expected application/json, application/xml - got text/plain.', 'invalid accept'; is_deeply $body, {accept => 'text/plain', content_type => '', in => 'body', name => 'body', valid => 0}, 'failed to negotiate content type'; $body = {accept => 'application/*'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '', 'valid accept'; is_deeply $body, {accept => 'application/*', content_type => 'application/json', in => 'body', name => 'body', valid => 1}, 'negotiated content type'; }; subtest 'validate_response - resusable response' => sub { $body = {accept => 'application/*'}; @errors = $schema->validate_response([get => '/pets', 201], {body => \&body}); is "@errors", '', 'valid accept'; is_deeply $body, {accept => 'application/*', content_type => 'application/json', in => 'body', name => 'body', valid => 1}, 'negotiated content type'; }; subtest 'validate_response - content_type' => sub { $body = {content_type => 'text/plain'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '/body: Expected application/json, application/xml - got text/plain.', 'invalid content_type'; is_deeply $body, {content_type => 'text/plain', in => 'body', name => 'body', valid => 0}, 'failed to negotiate content type'; $body = {content_type => 'application/json'}; @errors = $schema->validate_response([get => '/pets'], {body => \&body}); is "@errors", '', 'valid content_type'; is_deeply $body, {content_type => 'application/json', content_type => 'application/json', in => 'body', name => 'body', valid => 1}, 'negotiated content type'; }; subtest add_default_response => sub { my $schema = JSON::Validator->new->schema($cwd->child(qw(spec v3-petstore.json)))->schema; ok !$schema->get('/components/schemas/DefaultResponse'), 'default response missing'; ok !$schema->get([paths => '/petss', 'get', 'responses', '400']), 'default response missing for 400'; $schema->add_default_response; ok $schema->get('/components/schemas/DefaultResponse'), 'default response added'; for my $status (400, 401, 404, 500, 501) { ok $schema->get([paths => '/pets', 'get', 'responses', $status]), "default response for $status"; } delete $schema->{errors}; is_deeply $schema->errors, [], 'errors'; }; subtest 'add_default_response do not overwrite $ref' => sub { my $schema = JSON::Validator->new->schema($cwd->child(qw(spec v3-default-response-extra.yaml)))->schema; $schema->add_default_response; is $schema->get([qw(paths /item/{id} get summary)]), 'get a single item', 'summary'; is $schema->get([qw(paths /item/{id} get responses 200 content application/json schema type)]), 'object', 'responses 200'; is $schema->get([qw(paths /item/{id} get responses 404 description)]), 'Custom 404', 'responses 404'; is $schema->get([qw(paths /item/{id} get responses 500 description)]), 'Custom 500', 'responses 500'; }; subtest 'v3.1.x' => sub { my $schema = JSON::Validator->new->schema({openapi => '3.1.0', paths => {}})->schema; is $schema->specification, 'https://spec.openapis.org/oas/3.1/schema/2021-05-20', 'specification'; is join(', ', @{$schema->errors}), '/info: Missing property.', 'errors'; }; subtest 'coerce defaults' => sub { my $schema = JSON::Validator->new->coerce('defaults')->schema($cwd->child(qw(spec v3-petstore.json)))->schema; is_deeply $schema->errors, [], 'defaults turned off when validating the schema'; }; done_testing; sub body {$body} JSON-Validator-5.18/t/draft4.t0000644000076500000240000000216715210277065015661 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use JSON::Validator::Schema::Draft4; t::Helper->schema(JSON::Validator::Schema::Draft4->new); t::Helper->test(number => qw(basic maximum minimum)); t::Helper->test(array => qw(basic items additional_items min_max unique)); t::Helper->test(object => qw(basic properties)); t::Helper->test(object => qw(additional_properties pattern_properties min_max)); subtest 'exclusiveMaximum' => sub { schema_validate_ok 2.4, {exclusiveMaximum => true, maximum => 2.4}, E('/', '2.4 >= maximum(2.4)'); }; subtest 'exclusiveMinimum' => sub { schema_validate_ok 0, {exclusiveMaximum => true, maximum => 0}, E('/', '0 >= maximum(0)'); }; subtest 'bundle' => sub { my $bundle = JSON::Validator::Schema::Draft4->new('data://main/spec.json')->bundle; is $bundle->data->{properties}{name}{'$ref'}, '#/definitions/defs_json-name', 'bundle ref'; is $bundle->data->{'definitions'}{'defs_json-name'}{type}, 'string', 'bundled spec under definitions'; }; done_testing; __DATA__ @@ spec.json {"type":"object","properties":{"name":{"$ref":"data://main/defs.json#/name"}}} @@ defs.json {"name":{"type":"string"}} JSON-Validator-5.18/t/issue-22-duplicate-error-messages.t0000644000076500000240000000075415210277065022752 0ustar jhthorsenstaffuse 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-5.18/t/id-keyword-draft7.t0000644000076500000240000000260415210277065017734 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::Mojo; use Test::More; my ($base_url, $jv, $t, @e); use Mojolicious::Lite; get '/person' => [format => ['json']] => 'person'; get '/invalid-relative' => [format => ['json']] => 'invalid-relative'; $t = Test::Mojo->new; $jv = JSON::Validator->new(ua => $t->ua); eval { $t->get_ok('/person.json')->status_is(200); $base_url = $t->tx->req->url->to_abs->path('/'); $jv->load_and_validate_schema("${base_url}person.json", {schema => 'http://json-schema.org/draft-07/schema#'}); }; ok !$@, "${base_url}schema.json" or diag $@; isa_ok $jv->schema, 'JSON::Validator::Schema::Draft7'; is $jv->schema->id, 'http://example.com/person.json', 'schema id'; is $jv->schema->moniker, 'draft07', 'moniker'; is $jv->schema->specification, 'http://json-schema.org/draft-07/schema#', 'schema specification'; eval { $jv->load_and_validate_schema("${base_url}invalid-relative.json") }; like $@, qr{Relative URL not allowed}, 'Root id cannot be relative' or diag $@; done_testing; __DATA__ @@ invalid-relative.json.ep {"$id": "whatever", "$schema": "http://json-schema.org/draft-07/schema#"} @@ person.json.ep { "$id": "http://example.com/person.json", "definitions": { "Person": { "type": "object", "properties": { "firstName": { "type": "string" } } } } } JSON-Validator-5.18/t/validate-recursive.t0000644000076500000240000000232615210277065020270 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/' => sub { my $c = shift; my @errors = JSON::Validator->new->schema('data://main/spec.json')->validate($c->req->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-5.18/t/newline-warnings.t0000644000076500000240000000046415210277065017762 0ustar jhthorsenstaffuse Mojo::Base -strict; use Test::More; use JSON::Validator; my @warnings; $SIG{__WARN__} = sub { push @warnings, @_ }; JSON::Validator->new->schema(q!{ "type": "object" }!."\n"); ok(!@warnings, "no warning emitted when ->schema() method is passed a valid JSON schema ending in newline"); done_testing; JSON-Validator-5.18/t/recursive_data_protection.t0000644000076500000240000000214215210277065021734 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use JSON::Validator::Schema; use Mojo::Util 'monkey_patch'; use Scalar::Util qw(refaddr); use Test::More; my ($original_validate, %ref_counts) = (\&JSON::Validator::Schema::_validate); monkey_patch 'JSON::Validator::Schema', _validate => sub { my ($self, $data, $path, $schema) = @_; $ref_counts{refaddr($data)}++ if ref $data; goto &$original_validate; }; for ([1, 1], [0, 3]) { my ($enabled, $exp_ref_counts) = @$_; my $object = {level1 => {level2 => {level3 => 'Test'}}}; my $data = [$object, $object, $object]; %ref_counts = (); JSON::Validator->new->recursive_data_protection($enabled)->schema(schema())->validate($data); is $ref_counts{refaddr($object->{level1}{level2})}, $exp_ref_counts, "recursive_data_protection($enabled)"; } done_testing; sub schema { return { type => 'array', items => { type => 'object', properties => { level1 => { type => 'object', properties => {level2 => {type => 'object', properties => {level3 => {type => 'string'}}}} } } } }; } JSON-Validator-5.18/t/jv-anyof.t0000644000076500000240000000565115210277065016227 0ustar jhthorsenstaffuse lib '.'; use t::Helper; my $schema = {anyOf => [{type => "string", maxLength => 5}, {type => "number", minimum => 0}]}; validate_ok 'short', $schema; validate_ok 'too long', $schema, E('/', '/anyOf/0 String is too long: 8/5.'), E('/', '/anyOf/1 Expected number - got string.'); validate_ok 12, $schema; validate_ok int(-1), $schema, E('/', '/anyOf/0 Expected string - got number.'), E('/', '/anyOf/1 -1 < minimum(0)'); validate_ok {}, $schema, E('/', '/anyOf Expected string/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}]}} } ); validate_ok( {c => 'c present, a/b is missing'}, { type => 'object', properties => {a => {type => 'number'}, b => {type => 'string'}}, anyOf => [{required => ['a']}, {required => ['b']}], }, E('/a', '/anyOf/0 Missing property.'), E('/b', '/anyOf/1 Missing property.'), ); validate_ok 'hello', {type => ['integer', 'string'], enum => [123, 'HELLO']}, E('/', 'Not in enum list: 123, HELLO.'); validate_ok 'hello', {anyOf => [false, {type => ['integer', 'boolean']}]}, E('/', '/anyOf/0 Should not match.'), E('/', '/anyOf/1 Expected integer/boolean - got string.'); validate_ok 'hello', {type => ['integer', 'boolean']}, E('/', 'Expected integer/boolean - got string.'); validate_ok 'hello', {anyOf => [{type => ['integer', 'boolean']}]}, E('/', '/anyOf/0 Expected integer/boolean - got string.'); validate_ok 'hello', {anyOf => [{anyOf => [{type => 'boolean'}, {type => 'string', maxLength => 2}]}, {type => 'integer'}]}, E('/', '/anyOf/0/anyOf/0 Expected boolean - got string.'), E('/', '/anyOf/0/anyOf/1 String is too long: 5/2.'), E('/', '/anyOf/1 Expected integer - got string.'); validate_ok {foo => 'not an arrayref'}, {type => ['object', 'boolean'], properties => {foo => {type => 'array'}}}, E('/foo', 'Expected array - got string.'); validate_ok {foo => 'not an arrayref'}, {anyOf => [{type => 'object', properties => {foo => {type => 'array'}}}, {type => 'boolean'}]}, E('/foo', '/anyOf/0 Expected array - got string.'), E('/', '/anyOf/1 Expected boolean - got object.'); done_testing; JSON-Validator-5.18/t/load-yaml-pp.t0000644000076500000240000000052615210277065016766 0ustar jhthorsenstaffBEGIN { unshift @INC, sub { my $file = $_[1]; die "Skipping $file in this test" if $file =~ m!YAML\W+XS\.pm$!; }; } use Test::More; plan skip_all => 'YAML::PP not available' unless eval 'require JSON::Validator;1'; ok $INC{'YAML/PP.pm'}, 'YAML::PP was loaded'; ok !$INC{'YAML/XS.pm'}, 'YAML::XS was not loaded'; done_testing; JSON-Validator-5.18/t/issue-103-one-of.t0000644000076500000240000000351515210277065017305 0ustar jhthorsenstaffuse lib '.'; use t::Helper; validate_ok {who_id => 'WHO', expire => '2018-01-01', amount => 1000, desc => 'foo'}, 'data://main/example.json', E('/sym', '/oneOf/0/allOf/0/allOf/0 Missing property.'), E('/template', '/oneOf/0/allOf/2 Missing property.'), E('/sym', '/oneOf/1/allOf/0 Missing property.'), E('/', '/oneOf/2 Expected string - got object.'); validate_ok {sym => 'a', expire => 'b', amount => 1, desc => 'foo', who_id => 'c', template => 'd'}, 'data://main/example.json', E('/', 'oneOf rules 0, 1 match.'); done_testing; __DATA__ @@ example.json { "oneOf": [ {"$ref": "#/definitions/template_1"}, {"$ref": "#/definitions/bar_header"}, {"type": "string"} ], "definitions": { "hwho":{ "required": [ "who_id" ], "properties": { "who_id": { "type": "string" }, "sub_who_id": { "type": "string" } } }, "header": { "required": [ "sym", "expire" ], "properties": { "sym": { "type": "string" }, "expire": { "type": "string" } } }, "foo_header": { "allOf": [ { "$ref": "#/definitions/header" }, { "required": [ "amount", "desc" ], "properties": { "amount": { "type": "integer" }, "desc": { "enum": [ "foo" ] } } } ] }, "template_1": { "allOf": [ { "$ref": "#/definitions/foo_header" }, { "$ref": "#/definitions/hwho" }, { "required": [ "template" ], "properties": { "template": { "type": "string" } } } ] }, "bar_header" : { "allOf": [ { "$ref": "#/definitions/header" }, { "required": [ "amount", "desc" ], "properties": { "amount": { "type": "integer" }, "desc": { "enum": [ "foo" ] } } } ] } } } JSON-Validator-5.18/t/openapiv3-file.t0000644000076500000240000000730615211411261017303 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv3; use Mojo::File; use Mojo::Upload; use Test::Deep; use Test::More; my $cwd = Mojo::File->new(__FILE__)->dirname; my $schema = JSON::Validator::Schema::OpenAPIv3->new; my ($body, $p, @errors); subtest 'basic' => sub { is $schema->specification, 'https://spec.openapis.org/oas/3.0/schema/2021-09-28', 'specification'; is_deeply $schema->coerce, {booleans => 1, numbers => 1, strings => 1}, 'default coercion'; $schema = JSON::Validator->new->schema('data://main/spec.yaml')->schema; isa_ok $schema, 'JSON::Validator::Schema::OpenAPIv3'; @errors = @{JSON::Validator->new->schema({openapi => '3.0.0', paths => {}})->schema->errors}; is "@errors", '/info: Missing property.', 'invalid schema'; is_deeply( $schema->routes->to_array, [ {method => 'post', operation_id => 'submit', path => '/submit'}, {method => 'post', operation_id => 'uploadFile', path => '/upload'}, ], 'routes' ); }; subtest 'validate_request - file string' => sub { my $file = 'file contents\nmore content'; # Check required works $body = {exists => 1,value => {image => $file } }; @errors = $schema->validate_request([post => '/upload'], {body => \&body}); is "@errors", "/body/file: Missing property.", 'detects missing file'; $body = {exists => 1,value => {file => $file } }; @errors = $schema->validate_request([post => '/upload'], {body => \&body}); is "@errors", "", 'valid file'; is_deeply $body, { content_type => 'multipart/form-data', exists => 1, in => 'body', name => 'body', valid => 1, value => {file => $file}, }, 'valid file'; }; subtest 'validate_request - file placeholder' => sub { my $file = Mojo::Upload->new; # Check required works $body = {exists => 1,value => {image => $file } }; @errors = $schema->validate_request([post => '/upload'], {body => \&body}); is "@errors", "/body/file: Missing property.", 'detects missing file'; $body = {exists => 1,value => {file => $file } }; @errors = $schema->validate_request([post => '/upload'], {body => \&body}); is "@errors", "", 'valid file'; is_deeply $body, { content_type => 'multipart/form-data', exists => 1, in => 'body', name => 'body', valid => 1, value => {file => $file}, }, 'valid file'; }; subtest 'string in non-file string' => sub { $body = {exists => 1,value => {name => 'some string' } }; @errors = $schema->validate_request([post => '/submit'], {body => \&body}); is "@errors", "", 'valid file'; is_deeply $body, { content_type => 'multipart/form-data', exists => 1, value => {name => 'some string'}, in => 'body', name => 'body', valid => 1, }, 'valid file'; }; subtest 'file placehodler in non-file string' => sub { my $file = Mojo::Upload->new; $body = {exists => 1,value => {name => $file } }; @errors = $schema->validate_request([post => '/submit'], {body => \&body}); is "@errors", "/body/name: Expected string - got file.", 'valid file'; }; done_testing; sub body {$body} __DATA__ @@ spec.yaml openapi: 3.0.0 info: title: Test body version: 0.8 paths: /submit: post: operationId: submit requestBody: content: multipart/form-data: schema: type: object required: - name properties: name: type: string /upload: post: operationId: uploadFile requestBody: content: multipart/form-data: schema: type: object required: - file properties: file: type: string format: binary JSON-Validator-5.18/t/openapiv2-headers.t0000644000076500000240000000603515210277065020007 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::Headers; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my $headers = Mojo::Headers->new; my $body = sub { +{exists => 1, value => {}} }; my @errors; $headers->header('X-Number' => 'x')->header('X-String' => '123'); @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); is "@errors", '/X-Number: Expected number - got string.', 'request header not a number'; $headers->header('X-Number' => '42'); @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); is "@errors", '', 'request header is number'; $headers->header('X-Array' => '42'); @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash}); is ref $headers->to_hash->{'X-Array'}, '', 'request header is not an array'; is "@errors", '', 'request header is coerced into an array'; @errors = $schema->validate_response([get => '/test'], {body => $body, header => $headers->to_hash}); is "@errors", '', 'response header is coerced into an array'; $headers->add('X-Array' => '3.14'); @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); is ref $headers->to_hash(1)->{'X-Array'}, 'ARRAY', 'header is an array'; is "@errors", '', 'request header as array is valid'; $headers->header('X-Bool' => '42'); @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash(1)}); is "@errors", '/X-Bool: Expected boolean - got string.', 'request header not a boolean'; @errors = $schema->validate_response([get => '/test'], {body => $body, header => $headers->to_hash(1)}); is "@errors", '/X-Bool: Expected boolean - got string.', 'response header not a boolean'; for my $str (qw(true false 1 0)) { $headers->header('X-Bool' => $str); @errors = $schema->validate_request([get => '/test'], {header => $headers->to_hash}); is "@errors", '', q(request header as boolean "$str"); @errors = $schema->validate_response([get => '/test'], {body => $body, header => $headers->to_hash(1)}); is "@errors", '', q(response header as boolean "$str"); } done_testing; __DATA__ @@ spec.json { "swagger": "2.0", "info": {"version": "", "title": "Test headers"}, "basePath": "/api", "paths": { "/test": { "get": { "parameters": [ {"in": "header", "name": "X-Bool", "type": "boolean", "description": "desc..."}, {"in": "header", "name": "X-Number", "type": "number", "description": "desc..."}, {"in": "header", "name": "X-String", "type": "string", "description": "desc..."}, {"in": "header", "name": "X-Array", "items": {"type": "string"}, "type": "array", "description": "desc..."} ], "responses": { "200": { "description": "this is required", "headers": { "X-Array": {"type": "array", "items": {"type": "string"}, "minItems": 1}, "X-Bool": {"type": "boolean"} }, "schema": {"type": "object"} } } } } } } JSON-Validator-5.18/t/jv-boolean.t0000644000076500000240000000642115210277065016526 0ustar jhthorsenstaffuse lib '.'; use t::Helper; sub j { Mojo::JSON::decode_json(Mojo::JSON::encode_json($_[0])); } my $schema = {type => 'object', 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; validate_ok {v => true}, $schema; validate_ok {v => 1000}, $schema, E('/v', 'Expected boolean - got number.'); validate_ok {v => 0.5}, $schema, E('/v', 'Expected boolean - got number.'); validate_ok {v => 'active'}, $schema, E('/v', 'Expected boolean - got string.'); validate_ok {v => bless({}, 'BoolTestOk')}, $schema; validate_ok {v => bless({}, 'BoolTestFail')}, $schema, E('/v', '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.'); note 'boolean const'; my $bool_constant_false = {type => 'boolean', const => false}; my $bool_constant_true = {type => 'boolean', const => true}; validate_ok false, $bool_constant_false; validate_ok true, $bool_constant_false, E('/', q{Does not match const: false.}); validate_ok true, $bool_constant_true; validate_ok false, $bool_constant_true, E('/', q{Does not match const: true.}); note 'boolean objects'; my $data = jv->store->get(jv->store->load(\"---\nv: true\n")); isa_ok($data->{v}, 'JSON::PP::Boolean'); validate_ok $data, $schema; SKIP: { skip 'boolean not installed', 1 unless eval 'require boolean;1'; validate_ok {type => 'boolean'}, {type => 'object', properties => {type => {type => 'string'}}}; } note 'coerce check data'; jv->coerce('bool'); coerce_ok({v => !!1}, $schema); coerce_ok({v => !!0}, $schema); coerce_ok({v => 0}, $schema); coerce_ok({v => ''}, $schema); coerce_ok({v => 'false'}, $schema); coerce_ok({v => 'true'}, $schema); coerce_ok({v => 1}, $schema); coerce_ok({v => '1'}, $schema); note 'coerce fail'; jv->coerce('booleans'); validate_ok {v => 0.5}, $schema, E('/v', 'Expected boolean - got number.'); validate_ok {v => -1}, $schema, E('/v', 'Expected boolean - got number.'); validate_ok {v => 'yessir'}, $schema, E('/v', 'Expected boolean - got string.'); validate_ok {v => 'nope'}, $schema, E('/v', 'Expected boolean - got string.'); note 'coerce const'; validate_ok 0, $bool_constant_false; validate_ok 1, $bool_constant_false, E('/', q{Does not match const: false.}); validate_ok 1, $bool_constant_true; validate_ok 0, $bool_constant_true, E('/', q{Does not match const: true.}); done_testing; sub coerce_ok { my ($data, $schema) = @_; my $exp = {v => !$data->{v} || $data->{v} eq 'false' ? false : true}; validate_ok $data, $schema; is_deeply $data, $exp, 'data was coerced correctly'; } package BoolTestOk; use overload '""' => sub {1}; package BoolTestFail; use overload '""' => sub {2}; JSON-Validator-5.18/t/invalid-ref.t0000644000076500000240000000113315210277065016665 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::File 'path'; use Test::More; eval { JSON::Validator->new->schema('data://main/spec.json') }; like $@, qr{Unable to resolve .*/definitions/Pet"}, 'missing definition'; my $workdir = path(__FILE__)->dirname; eval { JSON::Validator->new->schema(path($workdir, 'spec', 'missing-ref.json')); }; ok $@, 'loading missing ref failed'; like $@, qr{Unable to load schema.*missing\.json}, 'error message' unless $^O eq 'MSWin32'; done_testing; __DATA__ @@ spec.json { "schema": { "type": "array", "items": { "$ref": "#/definitions/Pet" } } } JSON-Validator-5.18/t/issue-59-oneof-blessed-booleans.t0000644000076500000240000000120215210277065022370 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::JSON 'false'; use Test::More; my $jv = JSON::Validator->new->schema('data://main/spec.json'); my @errors = $jv->validate({prop1 => false, prop2 => false}); is "@errors", '', 'oneof blessed booleans'; 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-5.18/t/load-json.t0000644000076500000240000000212415210277065016354 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::File 'path'; use Test::More; my $file = path(path(__FILE__)->dirname, 'spec', 'person.json'); my $jv = JSON::Validator->new->schema($file); my @errors = $jv->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'; like $jv->schema->id, qr{^file:.*person\.json}, 'schema id'; is_deeply [sort keys %{$jv->store->schemas}], [$jv->schema->id], 'schemas in store' or diag join ', ', sort keys %{$jv->store->schemas}; my $spec = path($file)->slurp; $spec =~ s!"#!"person.json#! or die "Invalid spec: $spec"; path("$file.2")->spew($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"; note 'load from cache'; ok eval { JSON::Validator->new->schema('http://swagger.io/v2/schema.json') }, 'loaded from cache' or diag $@; done_testing; JSON-Validator-5.18/t/draft2019-09-acceptance.t0000644000076500000240000000312515210277065020416 0ustar jhthorsenstaffuse lib '.'; use t::Helper; $ENV{MOJO_LOG_LEVEL} //= 'fatal'; plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; my @todo_tests; push @todo_tests, ['', 'float and integers are equal up to 64-bit representation limits']; push @todo_tests, ['defs.json', 'validate definition against metaschema']; push @todo_tests, ['id.json', '$id inside an enum is not a real identifier']; push @todo_tests, ['ref.json', 'ref creates new scope when adjacent to keywords']; push @todo_tests, ['ref.json', 'refs with relative uris and defs']; push @todo_tests, ['ref.json', 'relative refs with absolute uris and defs']; push @todo_tests, ['ref.json', 'simple URN base URI with $ref via the URN']; push @todo_tests, ['anchor.json', '$anchor inside an enum is not a real identifier']; push @todo_tests, ['anchor.json', 'Location-independent identifier with base URI change in subschema']; push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; push @todo_tests, ['refRemote.json', 'remote ref with ref to defs']; push @todo_tests, ['recursiveRef.json']; push @todo_tests, ['unevaluatedItems.json']; push @todo_tests, ['unevaluatedProperties.json']; push @todo_tests, ['unknownKeyword.json', '$id inside an unknown keyword is not a real identifier']; push @todo_tests, ['vocabulary.json', 'schema that uses custom metaschema with with no validation vocabulary']; t::Helper->acceptance('JSON::Validator::Schema::Draft201909', todo_tests => \@todo_tests); done_testing; JSON-Validator-5.18/t/remotes/0000755000076500000240000000000015211411467015755 5ustar jhthorsenstaffJSON-Validator-5.18/t/remotes/folder/0000755000076500000240000000000015211411467017230 5ustar jhthorsenstaffJSON-Validator-5.18/t/remotes/folder/folderInteger.json0000644000076500000240000000003115210277065022711 0ustar jhthorsenstaff{ "type": "integer" }JSON-Validator-5.18/t/remotes/subSchemas.json0000644000076500000240000000015715210277065020753 0ustar jhthorsenstaff{ "integer": { "type": "integer" }, "refToInteger": { "$ref": "#/integer" } } JSON-Validator-5.18/t/remotes/integer.json0000644000076500000240000000003115210277065020302 0ustar jhthorsenstaff{ "type": "integer" }JSON-Validator-5.18/t/relative-ref.t0000644000076500000240000000071015210277065017052 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use Mojo::File 'path'; my $file = path(path(__FILE__)->dirname, 'spec', 'with-relative-ref.json'); my $jv = jv->cache_paths([]); validate_ok {age => -1}, $file, E('/age', '-1 < minimum(0)'); use Mojolicious::Lite; push @{app->static->paths}, path(__FILE__)->dirname; $jv->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-5.18/t/coerce.t0000644000076500000240000000257015210277065015733 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::JSON 'to_json'; use Test::More; my $jv = JSON::Validator->new; my %coerce = (booleans => 1); is_deeply($jv->coerce(%coerce)->coerce, {booleans => 1}, 'hash is accepted'); is_deeply($jv->coerce(\%coerce)->coerce, {booleans => 1}, 'hash reference is accepted'); note 'make sure input is coerced'; is_deeply($jv->coerce('booleans,numbers,strings')->coerce, {%coerce, numbers => 1, strings => 1}, '1 is accepted'); my @items = ([boolean => 'true'], [integer => '42'], [number => '4.2']); for my $i (@items) { for my $schema (schemas($i->[0])) { my $x = $i->[1]; $jv->schema($schema)->validate($x); is to_json($x), $i->[1], sprintf 'no quotes around %s %s', $i->[0], to_json($schema); $x = {v => $i->[1]}; $jv->schema({type => 'object', properties => {v => $schema}})->validate($x); is to_json($x->{v}), $i->[1], sprintf 'no quotes around %s %s', $i->[0], to_json($schema); $x = [$i->[1]]; $jv->schema({type => 'array', items => $schema})->validate($x); is to_json($x->[0]), $i->[1], sprintf 'no quotes around %s %s', $i->[0], to_json($schema); } } done_testing; sub schemas { my $base = {type => shift}; return ( $base, {type => ['array', $base->{type}]}, {allOf => [$base]}, {anyOf => [{type => 'array'}, $base]}, {oneOf => [$base, {type => 'array'}]}, ); } JSON-Validator-5.18/t/jv-object.t0000644000076500000240000001505315210277065016356 0ustar jhthorsenstaffuse lib '.'; use t::Helper; my $schema; subtest 'basic' => sub { $schema = {type => 'object'}; validate_ok {mynumber => 1}, $schema; validate_ok [1], $schema, E('/', 'Expected object - got array.'); }; subtest 'patternProperties' => sub { $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.'); }; subtest 'additionalProperties' => sub { local $schema->{additionalProperties} = 0; validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW', foo => 'nope'}, $schema, E('/', 'Properties not allowed: direction, foo.'); $schema->{additionalProperties} = {type => 'string'}; validate_ok {number => 1600, street_name => 'Pennsylvania', street_type => 'Avenue', direction => 'NW'}, $schema; }; subtest 'required' => sub { local $schema->{required} = ['number', 'street_name']; validate_ok {number => 1600, street_type => 'Avenue'}, $schema, E('/street_name', 'Missing property.'); }; subtest 'minProperties maxProperties' => sub { $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.'); }; subtest 'dependencies' => sub { $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'}, $schema; validate_ok {name => 'John Doe', billing_address => '123 Main St'}, $schema; validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, E('/billing_address', 'Missing property. Dependee: credit_card.'); $schema = { type => 'object', properties => {name => {type => 'string'}, credit_card => {type => 'number'}}, required => ['name'], dependencies => {credit_card => {properties => {billing_address => {type => 'string'}}, required => ['billing_address']}}, }; validate_ok {name => 'John Doe'}, $schema; validate_ok {name => 'John Doe', billing_address => '123 Main St'}, $schema; validate_ok {name => 'John Doe', credit_card => 5555555555555555}, $schema, E('/billing_address', 'Missing property.'); $schema = {dependencies => {bar => ['foo']}}; validate_ok {bar => 2}, $schema, E('/foo', 'Missing property. Dependee: bar.'); validate_ok {FOO => 1}, { type => 'object', propertyNames => {anyOf => [{type => 'string', enum => ['foo', 'bar', 'baz']}, {type => 'string', enum => ['hello']}]}, additionalProperties => {type => 'integer'}, }, E('/', '/propertyName/FOO /anyOf/0 Not in enum list: foo, bar, baz.'), E('/', '/propertyName/FOO /anyOf/1 Not in enum list: hello.'); }; subtest 'patternProperties' => sub { my $schema = {type => 'object', properties => {name => {type => 'string'}}}; validate_ok {}, $schema; # does not matter ok !$schema->{patternProperties}, 'patternProperties was not added issue#47'; }; subtest 'propertyNames' => sub { my $schema = {propertyNames => {minLength => 3, maxLength => 5}}; validate_ok {name => 'John', surname => 'Doe'}, $schema, E('/', '/propertyName/surname String is too long: 7/5.'); $schema->{propertyNames}{maxLength} = 7; validate_ok {name => 'John', surname => 'Doe'}, $schema; }; subtest 'TO_JSON' => sub { 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'); }; subtest 'const' => sub { my $object_constant = {type => 'object', const => {a => 1}}; validate_ok {a => 1}, $object_constant; validate_ok {b => 1}, $object_constant, E('/', q{Does not match const: {"a":1}.}); }; subtest 'boolean schemas' => sub { validate_ok {foo => 'bar'}, {type => 'object', required => ['foo'], %$_} for ({properties => {foo => {}}}, {additionalProperties => {}}, {patternProperties => {foo => {}}}); validate_ok {foo => 'bar'}, {definitions => {my_true_ref => {}}, type => 'object', required => ['foo'], %$_} for ( {properties => {foo => {'$ref' => '#/definitions/my_true_ref'}}}, {additionalProperties => {'$ref' => '#/definitions/my_true_ref'}}, {patternProperties => {foo => {'$ref' => '#/definitions/my_true_ref'}}}, ); validate_ok {foo => 'bar'}, {type => 'object', required => ['foo'], %$_} for ({properties => {foo => true}}, {additionalProperties => true}, {patternProperties => {foo => true}}); validate_ok {foo => 'bar'}, {definitions => {my_true_ref => true}, type => 'object', required => ['foo'], %$_} for ( {properties => {foo => {'$ref' => '#/definitions/my_true_ref'}}}, {additionalProperties => {'$ref' => '#/definitions/my_true_ref'}}, {patternProperties => {foo => {'$ref' => '#/definitions/my_true_ref'}}}, ); validate_ok {foo => 'bar'}, {type => 'object', required => ['foo'], %$_}, E('/foo', 'Should not match.') for ({properties => {foo => false}}, {patternProperties => {foo => false}}); validate_ok {foo => 'bar'}, {definitions => {my_false_ref => false}, type => 'object', required => ['foo'], %$_}, E('/foo', 'Should not match.') for ( {properties => {foo => {'$ref' => '#/definitions/my_false_ref'}}}, {additionalProperties => {'$ref' => '#/definitions/my_false_ref'}}, {patternProperties => {foo => {'$ref' => '#/definitions/my_false_ref'}}}, ); }; done_testing; sub TO_JSON { return {age => shift->{age}} } JSON-Validator-5.18/t/draft7-acceptance.t0000644000076500000240000000270015210277065017741 0ustar jhthorsenstaffuse lib '.'; use t::Helper; $ENV{MOJO_LOG_LEVEL} //= 'fatal'; plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; my @todo_tests; push @todo_tests, ['id.json', 'id inside an enum is not a real identifier']; push @todo_tests, ['const.json', 'float and integers are equal up to 64-bit representation limits']; push @todo_tests, ['maxItems.json', 'maxItems validation with a decimal']; push @todo_tests, ['maxLength.json', 'maxLength validation with a decimal']; push @todo_tests, ['maxProperties.json', 'maxProperties validation with a decimal']; push @todo_tests, ['minItems.json', 'minItems validation with a decimal']; push @todo_tests, ['minLength.json', 'minLength validation with a decimal']; push @todo_tests, ['minProperties.json', 'minProperties validation with a decimal']; push @todo_tests, ['ref.json', '$ref prevents a sibling $id from changing the base uri']; push @todo_tests, ['ref.json', 'simple URN base URI with $ref via the URN']; push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; push @todo_tests, ['refRemote.json', 'remote ref with ref to definitions']; push @todo_tests, ['unknownKeyword.json', '$id inside an unknown keyword is not a real identifier']; t::Helper->acceptance('JSON::Validator::Schema::Draft7', todo_tests => \@todo_tests); done_testing; JSON-Validator-5.18/t/openapiv2-default-values.t0000644000076500000240000000220515210277065021310 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my @errors; @errors = $schema->validate_request([get => '/pets/{id}'], {path => {id => 'a'}}); is "@errors", "/id: String is too short: 1/3.", 'invalid id'; @errors = $schema->validate_request([get => '/pets/{id}'], {path => {}}); is "@errors", "", 'default id'; my $id = {}; my %req = (path => sub {$id}); @errors = $schema->validate_request([get => '/pets/{id}'], \%req); is_deeply $id, {exists => 1, in => 'path', name => 'id', valid => 1, value => 'foo'}, 'input was mutated'; is "@errors", "", 'default id'; done_testing; __DATA__ @@ spec.json { "swagger": "2.0", "info": {"version": "", "title": "Test default values"}, "basePath": "/api", "paths": { "/pets/{id}": { "get": { "parameters": [ {"name": "id", "in": "path", "type": "string", "default": "foo", "required": true, "minLength": 3} ], "responses" : { "200": { "description": "pet response", "schema": {"type": "object"} } } } } } } JSON-Validator-5.18/t/jv-allof-and-not.t0000644000076500000240000000167015210277065017543 0ustar jhthorsenstaffuse lib '.'; use t::Helper; my $missing = E '/required', '/allOf/0 Missing property.'; my $schema = {type => 'object', allOf => [{required => ['required']}]}; my @tests = ( [{foo => 1, required => 2}, $schema], [{foo => 2, forbidden => 3}, $schema, $missing], [{foo => 3, forbidden => 3, required => 2}, $schema], [{foo => 4}, $schema, $missing] ); subtest 'property "required" must be present' => sub { validate_ok @$_ for @tests; }; subtest 'Property "forbidden" must not be present' => sub { $schema->{not} = {required => ['forbidden']}; splice @{$tests[1]}, 2, 0, E '/', 'Should not match.'; $tests[2][2] = E '/', 'Should not match.'; validate_ok @$_ for @tests; }; subtest 'Move "not" constraint to "allOf"' => sub { push @{$schema->{allOf}}, {not => delete $schema->{not}}; $tests[1][2] = $tests[2][2] = E '/', '/allOf/1 Should not match.'; $tests[1][3] = $missing; validate_ok @$_ for @tests; }; done_testing; JSON-Validator-5.18/t/load-yaml.t0000644000076500000240000000133015210277065016343 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $jv = JSON::Validator->new; my @errors = $jv->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-5.18/t/openapiv2-bundle.t0000644000076500000240000000240215210277065017637 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv2; use JSON::Validator::Util qw(str2data); use Mojo::Loader qw(data_section); use Test::Deep; use Test::More; my $cwd = Mojo::File->new(__FILE__)->dirname; my $schema = JSON::Validator::Schema::OpenAPIv2->new($cwd->child(qw(spec v2-bundle.yaml))); is_deeply $schema->errors, [], 'schema errors' or diag explain $schema->errors; my $bundle = $schema->bundle; is_deeply $bundle->errors, [], 'bundle errors' or diag explain $bundle->errors; my $from_data = JSON::Validator::Schema::OpenAPIv2->new($bundle->data); is_deeply $from_data->errors, [], 'from_data errors' or diag explain $from_data->errors; is_deeply $from_data->data, str2data(data_section(qw(main exp.yaml))), 'from_data schema' or diag explain $from_data->data; done_testing; __DATA__ @@ exp.yaml --- swagger: "2.0" info: title: Bundled version: "1.0" basePath: /api paths: /user: get: parameters: - $ref: "#/parameters/paths_yaml-parameters_id" responses: 200: description: A user schema: $ref: "#/x-def/User" x-def: User: properties: name: type: string parameters: paths_yaml-parameters_id: in: path name: id required: true type: string JSON-Validator-5.18/t/jv-allof.t0000644000076500000240000000412215210277065016200 0ustar jhthorsenstaffuse 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 Expected string - got number.'); $schema = {allOf => [{type => 'string', maxLength => 7}, {type => 'string', maxLength => 5}]}; validate_ok 'superlong', $schema, E('/', '/allOf/0 String is too long: 9/7.'), E('/', '/allOf/1 String is too long: 9/5.'); validate_ok 'toolong', $schema, E('/', '/allOf/1 String is too long: 7/5.'); $schema = { allOf => [{type => 'string', maxLength => 5}, {type => 'string', minLength => 3}], anyOf => [{pattern => '^[0-9]+$'}, {pattern => '^[a-z]+$'}], oneOf => [{pattern => '^[0-9]+$'}, {pattern => '^[a-z]+$', maxLength => 4}], }; validate_ok '123', $schema; validate_ok 'aaaa', $schema; validate_ok 'aaaaa', $schema, E('/', '/oneOf/0 String does not match ^[0-9]+$.'), E('/', '/oneOf/1 String is too long: 5/4.'); validate_ok 'he110th3re', $schema, E('/', '/allOf/0 String is too long: 10/5.'), E('/', '/anyOf/0 String does not match ^[0-9]+$.'), E('/', '/anyOf/1 String does not match ^[a-z]+$.'), E('/', '/oneOf/0 String does not match ^[0-9]+$.'), E('/', '/oneOf/1 String is too long: 10/4.'), E('/', '/oneOf/1 String does not match ^[a-z]+$.'); validate_ok 'hello', {type => ['integer', 'boolean']}, E('/', 'Expected integer/boolean - got string.'); validate_ok 'hello', {allOf => [{type => ['integer', 'boolean']}]}, E('/', '/allOf/0 Expected integer/boolean - got string.'); validate_ok 'hello', {allOf => [{allOf => [{type => 'boolean'}, {type => 'string', maxLength => 2}]}, {type => 'integer'}]}, E('/', '/allOf/0/allOf/0 Expected boolean - got string.'), E('/', '/allOf/0/allOf/1 String is too long: 5/2.'), E('/', '/allOf/1 Expected integer - got string.'); validate_ok {foo => 'not an arrayref'}, {allOf => [{type => 'object', properties => {foo => {type => 'array'}}}, {type => 'boolean'}]}, E('/foo', '/allOf/0 Expected array - got string.'), E('/', '/allOf/1 Expected boolean - got object.'); done_testing; JSON-Validator-5.18/t/joi.t0000644000076500000240000001245315210277065015255 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use JSON::Validator::Joi 'joi'; use Storable 'dclone'; isa_ok +joi->validator, 'JSON::Validator::Schema'; is_deeply +joi->validator->coerce, {booleans => 1, numbers => 1, strings => 1}, 'default coercion'; is_deeply( edj(joi->object->strict->props( age => joi->integer->min(0)->max(200), alphanum => joi->alphanum->length(12), color => joi->string->min(2)->max(12)->pattern('^\w+$'), date_time => joi->iso_date, email => joi->string->email->required, exists => joi->boolean, lc => joi->lowercase, name => joi->string->min(1), pos => joi->positive, token => joi->token, uc => joi->uppercase, uri => joi->uri, )), { type => 'object', required => ['email'], properties => { age => {type => 'integer', minimum => 0, maximum => 200}, alphanum => {type => 'string', minLength => 12, maxLength => 12, pattern => '^\w*$'}, color => {type => 'string', minLength => 2, maxLength => 12, pattern => '^\w+$'}, date_time => {type => 'string', format => 'date-time'}, email => {type => 'string', format => 'email'}, exists => {type => 'boolean'}, lc => {type => 'string', pattern => '^\p{Lowercase}*$'}, name => {type => 'string', minLength => 1}, pos => {type => 'number', minimum => 0}, token => {type => 'string', pattern => '^[a-zA-Z0-9_]+$'}, uc => {type => 'string', pattern => '^\p{Uppercase}*$'}, uri => {type => 'string', format => 'uri'}, }, additionalProperties => false }, 'generated correct object schema' ); is_deeply( edj(joi->array->min(0)->max(10)->strict->items(joi->integer->negative)), { additionalItems => false, type => 'array', minItems => 0, maxItems => 10, items => {type => 'integer', maximum => 0} }, 'generated correct array schema' ); is_deeply(edj(joi->string->enum([qw(1.0 2.0)])), {type => 'string', enum => [qw(1.0 2.0)]}, 'enum for string'); is_deeply(edj(joi->integer->enum([qw(1 2 4 8 16)])), {type => 'integer', enum => [qw(1 2 4 8 16)]}, 'enum for integer'); joi_ok( {age => 34, email => 'jhthorsen@cpan.org', name => 'Jan Henning Thorsen'}, joi->props( age => joi->integer->min(0)->max(200), email => joi->string->email->required, name => joi->string->min(1), ), ); joi_ok( {age => -1, name => 'Jan Henning Thorsen'}, joi->props( age => joi->integer->min(0)->max(200), email => joi->string->email->required, name => joi->string->min(1), ), E('/age', '-1 < minimum(0)'), E('/email', 'Missing property.'), ); note 'test that compile and not compile generates same strict result'; my $strict_obj = joi->object->strict->props({ns => joi->string->required}); joi_ok({ns => 'plop', toto => 'plouf'}, $strict_obj, E('/', 'Properties not allowed: toto.')); for my $item ($strict_obj->compile, $strict_obj) { joi_ok([{ns => 'plop', toto => 'plouf'}], joi->array->strict->items($item), E('/0', 'Properties not allowed: toto.'), ); } note "can omit non-required objects containing required properties"; joi_ok({}, joi->object->props(a => joi->object->props(b => joi->integer->required))); note "must include required objects containing required properties"; joi_ok( {}, joi->object->props(a => joi->object->required->props(b => joi->integer->required)), E('/a', 'Missing property.'), ); eval { joi->number->extend(joi->integer) }; like $@, qr{Cannot extend joi 'number' by 'integer'}, 'need to extend same type'; test_extend( joi->array->min(0)->max(10), joi->array->min(5), {type => 'array', minItems => 5, maxItems => 10}, 'extended array', ); test_extend( joi->array->items([joi->integer]), joi->array->items([joi->number]), {type => 'array', items => [{type => 'number'}]}, 'extended items in an array', ); test_extend( joi->integer->min(0)->max(10), joi->integer->min(5), {type => 'integer', minimum => 5, maximum => 10}, 'extended integer', 'extended integer', ); test_extend( joi->object->props(x => joi->integer, y => joi->integer), joi->object->props(x => joi->number), {type => 'object', properties => {x => {type => 'number'}, y => {type => 'integer'}}}, 'extended object', ); is_deeply( edj(joi->object->props(ip => joi->type([qw(string null)])->format('ip'), ns => joi->string)), {type => 'object', properties => {ip => {format => 'ip', type => [qw(string null)]}, ns => {type => 'string'}}}, 'null or string', ); test_extend( joi->object->props(a => joi->integer, b => joi->integer->required), joi->object->props(b => joi->integer->required, x => joi->string->required, y => joi->string->required), { type => 'object', required => bag(qw(b x y)), properties => {a => {type => 'integer'}, b => {type => 'integer'}, x => {type => 'string'}, y => {type => 'string'}}, }, 'extended object with required', ); done_testing; sub test_extend { my ($joi, $by, $expected, $description) = @_; my $joi_clone = dclone $joi; my $by_clone = dclone $by; cmp_deeply(edj($joi->extend($by)), $expected, $description); cmp_deeply $joi, $joi_clone, "$description did not mutate \$joi"; cmp_deeply $by, $by_clone, "$description did not mutate \$by"; } JSON-Validator-5.18/t/jv-enum.t0000644000076500000240000000342515210277065016054 0ustar jhthorsenstaffuse 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.') ); for my $v (undef, false, true) { validate_ok( {name => $v}, { type => 'object', required => ['name'], properties => {name => {type => [qw(boolean null)], enum => [undef, false, true]}}, }, ); } validate_ok( {name => undef}, { type => 'object', required => ['name'], properties => {name => {type => ['string'], enum => [qw(n yes true false)]}}, }, E('/name', 'Expected string - got null.'), ); validate_ok( {name => undef}, {type => 'object', required => ['name'], properties => {name => {enum => [qw(n yes true false)]}}}, E('/name', 'Not in enum list: n, yes, true, false.'), ); done_testing; JSON-Validator-5.18/t/Helper.pm0000644000076500000240000001254015210277065016061 0ustar jhthorsenstaffpackage t::Helper; use Mojo::Base -base; use JSON::Validator; use Mojo::File; use Mojo::JSON qw(decode_json encode_json); use Mojo::Util qw(monkey_patch); use Test::More; $ENV{TEST_VALIDATOR_CLASS} = 'JSON::Validator'; sub acceptance { my ($class, $schema_class, %acceptance_params) = @_; Test::More::plan(skip_all => 'cpanm Test::JSON::Schema::Acceptance') unless eval 'use Test::JSON::Schema::Acceptance 1.000 ();1'; Test::More::plan(skip_all => 'cpanm Test2::Tools::Compare') unless eval 'use Test2::Tools::Compare 0.0001 ();1'; Test::More::plan(skip_all => $@) unless eval "require $schema_class;1"; my $test = sub { +{file => $_[0], group_description => $_[1], test_description => $_[2]} }; my $ua = _acceptance_ua(); $acceptance_params{todo_tests} = [map { $test->(@$_) } @{$acceptance_params{todo_tests}}] if $acceptance_params{todo_tests}; my $specification = $schema_class =~ m!::(\w+)$! ? lc $1 : 'unknown'; $specification = 'draft2019-09' if $specification eq 'draft201909'; Test::JSON::Schema::Acceptance->new(specification => $specification)->acceptance( tests => $test->(split '/', $ENV{TEST_ACCEPTANCE} || ''), %acceptance_params, validate_data => sub { my ($schema_p, $data_p) = map { Mojo::JSON::Pointer->new(shift @_) } qw(schema data); my ($schema_d, $data_d) = map { decode_json(encode_json($_->data)) } $schema_p, $data_p; my $schema = $schema_class->new($schema_d, ua => $ua); return 0 if @{$schema->errors}; my @errors = $schema->validate($data_d); # Doing internal tests on mutation, since I think Test::JSON::Schema::Acceptance is a bit too strict Test2::Tools::Compare::is(encode_json($data_d), encode_json($data_p->data), 'data structure is the same'); Test2::Tools::Compare::is(encode_json($schema_d), encode_json($schema_p->data), 'schema structure is the same') unless _skip_schema_is($schema_p); return @errors ? 0 : 1; }, ); } sub edj { return Mojo::JSON::decode_json(Mojo::JSON::encode_json(@_)); } sub joi_ok { my ($data, $joi, @expected) = @_; my $description ||= @expected ? "errors: @expected" : "valid: " . encode_json($data); my @errors = JSON::Validator::Joi->new($joi)->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 jv { state $obj = $ENV{TEST_VALIDATOR_CLASS}->new } sub schema { state $schema; $schema = $_[1] if $_[1]; $schema } sub schema_validate_ok { my ($data, $schema, @expected) = @_; my $description = @expected ? "errors: @expected" : "valid: " . encode_json($data); my @errors = t::Helper->schema->resolve($schema)->validate($data); local $Test::Builder::Level = $Test::Builder::Level + 1; 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 test { my ($class, $category, @methods) = @_; my $test_class = "t::test::$category"; eval "require $test_class;1" or die $@; subtest "$category $_", sub { $test_class->$_ } for @methods; } sub validate_ok { my ($data, $schema, @expected) = @_; my $description = @expected ? "errors: @expected" : "valid: " . encode_json($data); my @errors = jv()->schema($schema)->validate($data); local $Test::Builder::Level = $Test::Builder::Level + 1; 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 import { my $class = shift; my $caller = caller; eval "package $caller; use Test::Deep; use Test::More; 1" or die $@; $_->import for qw(strict warnings); feature->import(':5.10'); monkey_patch $caller => E => \&JSON::Validator::E; monkey_patch $caller => done_testing => \&Test::More::done_testing; monkey_patch $caller => edj => \&edj; monkey_patch $caller => false => \&Mojo::JSON::false; monkey_patch $caller => joi_ok => \&joi_ok; monkey_patch $caller => jv => \&jv; monkey_patch $caller => schema_validate_ok => \&schema_validate_ok; monkey_patch $caller => true => \&Mojo::JSON::true; monkey_patch $caller => validate_ok => \&validate_ok; } sub _acceptance_ua { require Mojo::UserAgent; require Mojolicious; my $ua = Mojo::UserAgent->new; my $app = Mojolicious->new; $app->static->paths([Mojo::File->new(qw(t spec remotes))->to_string]); $ua->server->app($app); $ua->on( $_ => sub { my ($ua, $tx) = @_; my $url = $tx->req->url; $url->scheme(undef)->host(undef)->port(undef) if $url->host and $url->host eq 'localhost'; } ) for qw(prepare start); return $ua; } sub _skip_schema_is { my $p = shift; my @paths = ('', '/properties/foo'); # The URL has been changed by _acceptance_ua() return 1 if encode_json($p->data) =~ m!localhost:1234!; # JSON::Validator always normalizes $ref with multiple keys for my $path (@paths) { my $ref = $p->get($path); return 1 if ref $ref eq 'HASH' && $ref->{'$ref'} && 1 != keys %$ref; } return 0; } 1; JSON-Validator-5.18/t/draft6-acceptance.t0000644000076500000240000000270015210277065017740 0ustar jhthorsenstaffuse lib '.'; use t::Helper; $ENV{MOJO_LOG_LEVEL} //= 'fatal'; plan skip_all => 'TEST_ACCEPTANCE=1' unless $ENV{TEST_ACCEPTANCE}; delete $ENV{TEST_ACCEPTANCE} if $ENV{TEST_ACCEPTANCE} eq '1'; my @todo_tests; push @todo_tests, ['const.json', 'float and integers are equal up to 64-bit representation limits']; push @todo_tests, ['id.json', 'id inside an enum is not a real identifier']; push @todo_tests, ['maxItems.json', 'maxItems validation with a decimal']; push @todo_tests, ['maxLength.json', 'maxLength validation with a decimal']; push @todo_tests, ['maxProperties.json', 'maxProperties validation with a decimal']; push @todo_tests, ['minItems.json', 'minItems validation with a decimal']; push @todo_tests, ['minLength.json', 'minLength validation with a decimal']; push @todo_tests, ['minProperties.json', 'minProperties validation with a decimal']; push @todo_tests, ['ref.json', '$ref prevents a sibling $id from changing the base uri']; push @todo_tests, ['ref.json', 'simple URN base URI with $ref via the URN']; push @todo_tests, ['refRemote.json', 'remote ref with ref to definitions']; push @todo_tests, ['refRemote.json', 'Location-independent identifier in remote ref']; push @todo_tests, ['unknownKeyword.json', '$id inside an unknown keyword is not a real identifier']; t::Helper->acceptance('JSON::Validator::Schema::Draft6', todo_tests => \@todo_tests); done_testing; JSON-Validator-5.18/t/validate-id.t0000644000076500000240000000043715210277065016656 0ustar jhthorsenstaffuse lib '.'; use t::Helper; validate_ok {id => 1}, {type => 'object'}; validate_ok {id => 1, message => 'cannot exclude "id" #111'}, {type => 'object', additionalProperties => 0, properties => {message => {type => "string"}}}, E('/', 'Properties not allowed: id.'); done_testing; JSON-Validator-5.18/t/util.t0000644000076500000240000001011315210277065015440 0ustar jhthorsenstaffuse Mojo::Base -strict; use Mojo::JSON 'false'; use Mojo::Util 'md5_sum'; use JSON::Validator; use JSON::Validator::Util qw(E data_checksum data_type negotiate_content_type schema_type prefix_errors); use JSON::Validator::Util qw(is_bool is_num is_type); use Test::More; my $e = E '/path/x', 'some error'; is "$e", '/path/x: some error', 'E'; is data_type('string'), 'string', 'data_type string'; is data_type(4.2), 'number', 'data_type number'; is data_type(42, [{type => 'integer'}]), 'integer', 'data_type integer'; is data_type([]), 'array', 'data_type array'; is data_type(bless {}, 'other'), 'other', 'data_type other'; is data_type(false), 'boolean', 'data_type boolean'; is data_type(undef), 'null', 'data_type null'; is data_type($e), 'JSON::Validator::Error', 'data_type JSON::Validator::Error'; my $v = JSON::Validator->new; ok is_type($v, 'JSON::Validator'), 'is_type JSON::Validator'; ok is_type($v, 'Mojo::Base'), 'is_type Mojo::Base'; ok is_type($v, 'HASH'), 'is_type HASH'; ok is_type([], 'ARRAY'), 'is_type ARRAY'; ok is_type({}, 'HASH'), 'is_type HASH'; ok is_num(4.2), 'is_num 4.2'; ok is_num(42), 'is_num 42'; ok !is_num('2'), 'is_num 2'; ok is_bool(false), 'is_bool'; ok !is_bool(0), 'is_bool'; my $yikes = E {path => '/path/100/y', message => 'yikes'}; is_deeply( [map {"$_"} prefix_errors 'allOf', [2, $e], [5, $yikes]], ['/path/x: /allOf/2 some error', '/path/100/y: /allOf/5 yikes'], 'prefix_errors', ); is negotiate_content_type([]), '', 'accepts nothing'; is negotiate_content_type(['multipart/form-data'], 'multipart/form-data; boundary=mgkBX'), 'multipart/form-data', 'form-data boundary'; is negotiate_content_type(['application/json'], 'application/json;charset=UTF-8'), 'application/json', 'charset'; is negotiate_content_type(['application/json']), '', 'header missing'; is negotiate_content_type(['application/json', 'text/plain'], 'application/json'), 'application/json', 'exact match'; is negotiate_content_type(['application/json', 'text/*'], 'text/plain'), 'text/*', 'closest accept'; is negotiate_content_type( ['text/plain', 'application/xml'], 'text/html;text/plain;q=0.2,application/xml;q=0.9,*/*;q=0.8' ), 'application/xml', 'exact match with weight'; is negotiate_content_type(['application/xml'], 'application/json, text/plain, */*'), 'application/xml', 'star/star'; is schema_type({type => 'integer'}), 'integer', 'schema_type integer'; is schema_type({additionalProperties => {}}), 'object', 'schema_type object'; is schema_type({additionalProperties => {}}, {}), 'object', 'schema_type object'; is schema_type({additionalProperties => {}}, []), '', 'schema_type not object'; is schema_type({items => {}}), 'array', 'schema_type array'; is schema_type({items => {}}, {}), '', 'schema_type not array'; is schema_type({minLength => 4}), 'string', 'schema_type string'; is schema_type({multipleOf => 2}), 'number', 'schema_type number'; is schema_type({const => 42}), 'const', 'schema_type const'; is schema_type({cannot => 'guess'}), '', 'schema_type no idea'; subtest 'data_checksum with Sereal::Encoder' => sub { plan skip_all => 'Sereal::Encoder 4.00+ not installed' unless JSON::Validator::Util->SEREAL_SUPPORT; my $d_hash = {foo => {}, bar => {}}; my $d_hash2 = {bar => {}, foo => {}}; my $d_undef = {foo => undef}; my $d_obj = {foo => JSON::Validator::Error->new}; my $d_array = ['foo', 'bar']; my $d_array2 = ['bar', 'foo']; isnt data_checksum($d_array), data_checksum($d_array2), 'data_checksum array'; is data_checksum($d_hash), data_checksum($d_hash2), 'data_checksum hash field order'; isnt data_checksum($d_hash), data_checksum($d_undef), 'data_checksum hash not undef'; isnt data_checksum($d_hash), data_checksum($d_obj), 'data_checksum hash not object'; isnt data_checksum($d_obj), data_checksum($d_undef), 'data_checksum object not undef'; isnt data_checksum(3.14), md5_sum(3.15), 'data_checksum numeric'; is data_checksum(3.14), data_checksum('3.14'), 'data_checksum numeric like string'; }; done_testing; JSON-Validator-5.18/t/issue-158-draf7-coerce-defaults.t0000644000076500000240000000052115210277065022174 0ustar jhthorsenstaffuse lib '.'; use t::Helper; my $validator = JSON::Validator->new(coerce => 'defaults'); eval { $validator->load_and_validate_schema( {'$schema' => 'http://json-schema.org/draft-07/schema#'}, {schema => 'http://json-schema.org/draft-07/schema#'}, ); }; ok !$@, "load_and_validate_schema draft-07 \$@=$@"; done_testing; JSON-Validator-5.18/t/00-project.t0000644000076500000240000000401315210277065016350 0ustar jhthorsenstaffuse strict; use Test::More; use File::Find; plan skip_all => 'No such directory: .git' unless $ENV{TEST_ALL} or -d '.git'; plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/' if +($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/; for (qw( Test::CPAN::Changes::changes_file_ok+VERSION!4 Test::Pod::Coverage::pod_coverage_ok+VERSION!1 Test::Pod::pod_file_ok+VERSION!1 Test::Spelling::pod_file_spelling_ok+has_working_spellchecker!1 )) { my ($fqn, $module, $sub, $check, $skip_n) = /^((.*)::(\w+))\+(\w+)!(\d+)$/; next if eval "use $module;$module->$check"; no strict qw(refs); *$fqn = sub { SKIP: { skip "$sub(@_) ($module is required)", $skip_n } }; } my @files; find({wanted => sub { /\.pm$/ and push @files, $File::Find::name }, no_chdir => 1}, -e 'blib' ? 'blib' : 'lib'); plan tests => @files * 4 + 4; Test::Spelling::add_stopwords() if Test::Spelling->can('has_working_spellchecker') && Test::Spelling->has_working_spellchecker; 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 => [qr/^[A-Z_]+$/]}); Test::Spelling::pod_file_spelling_ok($file); } Test::CPAN::Changes::changes_file_ok(); __DATA__ Aleksandr Anwar Aymeric Barden Bernhard Berov Böhmer DT Dagfinn DefaultResponse Etheridge Fabrizio Gennari Goess Graf Hartmaier Henning Hradek IRI Ilmari Ishigaki JSONPatch Jemmeson Joi Karelas Kenichi Kirill Krasimir Lari Maijala Mannsåker Masse Mattias Matusov Morrott NID NSS OpenAPI Orlenko Päivärinta Petstore Rassadin Renvoize Riedel Schemas Schout Stallard Taskula Thorsen UUIDv Znet Zoffix additionalItems additionalProperties allOf alphanum anyOf basePath bc const fff formData iban ipv joi maxItems maxLength maxProperties minItems minLength minProperties multipleOf nid nss oneOf openapiv schemas str ua unevaluatedItems unevaluatedProperties uniqueItems validator validators JSON-Validator-5.18/t/draft2019-09.t0000644000076500000240000000447415210277065016342 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use JSON::Validator::Schema::Draft201909; my $schema = JSON::Validator::Schema::Draft201909->new; t::Helper->schema($schema); subtest 'formats' => sub { ok $schema->formats->{duration}, 'duration'; ok $schema->formats->{uuid}, 'uuid'; }; t::Helper->test(number => qw(basic maximum minimum)); t::Helper->test(array => qw(basic items additional_items contains min_max min_max_contains)); t::Helper->test(array => qw(unique unevaluated_items)); t::Helper->test(object => qw(basic properties)); t::Helper->test(object => qw(additional_properties pattern_properties min_max names)); t::Helper->test(object => qw(dependent_required dependent_schemas unevaluated_properties)); subtest 'anchor' => sub { $schema->resolve({'$ref' => '#foo', '$defs' => {'A' => {'$anchor' => 'foo', 'type' => 'integer'}}}); is $schema->get('/type'), 'integer', 'foo anchor type'; }; subtest 'recursiveRef, without recursiveAnchor' => sub { my $jv = JSON::Validator->new->schema('data://main/tree.json'); $jv->schema('data://main/recursiveRef.json'); isa_ok $jv->schema, 'JSON::Validator::Schema::Draft201909'; is $jv->schema->get('/type'), 'object', 'recursiveRef type'; is $jv->schema->get('/properties/data'), true, 'recursiveRef properties data'; is $jv->schema->get('/properties/children/items/type'), 'object', 'recursiveRef properties data items'; is $jv->schema->get('/properties/children/items/properties/children/items/type'), 'object', 'recursive'; is_deeply [sort keys %{$jv->store->schemas}], [qw(data://main/recursiveRef.json data://main/tree.json urn:x-test:recursiveRef urn:x-test:tree)], 'schemas in the store'; }; subtest 'test caching' => sub { no warnings 'redefine'; local *JSON::Validator::_load_from_data = sub { die 'not cached' }; ok eval { JSON::Validator->new->schema('data://main/tree.json') }, 'cached' or diag $@; }; done_testing; __DATA__ @@ tree.json { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "urn:x-test:tree", "type": "object", "properties": { "data": true, "children": { "type": "array", "items": {"$recursiveRef": "#"} } } } @@ recursiveRef.json { "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "urn:x-test:recursiveRef", "$ref": "urn:x-test:tree" } JSON-Validator-5.18/t/openapiv3-coerce-array.t0000644000076500000240000000444415210277065020753 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/openapi.yaml')->schema; my ($body, $query, @errors); subtest 'number to array' => sub { $body = {exists => 1, value => {id => 42}}; @errors = $schema->validate_request([post => '/test'], {body => \&body}); is "@errors", "", "valid"; }; subtest 'string to array' => sub { $body = {exists => 1, value => {id => '42'}}; @errors = $schema->validate_request([post => '/test'], {body => \&body}); is "@errors", "", "valid"; }; subtest 'already an array' => sub { $body = {exists => 1, value => {id => [42, '43']}}; @errors = $schema->validate_request([post => '/test'], {body => \&body}); is "@errors", "", "valid"; }; subtest 'array coercion with multipart/form-data boundary' => sub { $body = {exists => 1, value => {id => 42}, content_type => 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'}; @errors = $schema->validate_request([post => '/test'], {body => \&body}); is "@errors", "", "valid with boundary parameter"; }; subtest 'parameter array schema is $ref' => sub { $query = {exists => 1, value => [42, 43]}; @errors = $schema->validate_request([get => '/test'], {query => \&query}); is "@errors", "", "valid"; }; done_testing; sub body {$body} sub query {$query} __DATA__ @@ openapi.yaml --- openapi: 3.0.0 info: title: Upload test version: 1.0.0 servers: - url: http://example.com/api paths: /test: post: operationId: testPost requestBody: required: true content: application/x-www-form-urlencoded: schema: properties: id: type: array items: type: integer multipart/form-data: schema: properties: id: type: array items: type: integer responses: 200: description: OK get: parameters: - name: id in: query required: true schema: $ref: '#/components/schemas/IntArray' responses: 200: description: OK components: schemas: IntArray: type: array items: type: integer JSON-Validator-5.18/t/issue-42-cache-control.t0000644000076500000240000000207615210277065020566 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Mojo::File 'tempdir'; use Test::More; plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; $ENV{JSON_VALIDATOR_CACHE_PATH} = '/tmp/whatever'; my $jv = JSON::Validator->new; my @old_files = get_cached_files($jv); is $jv->cache_paths->[0], '/tmp/whatever', 'back compat env'; shift @{$jv->cache_paths}; my $spec_url = 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v2.0/schema.json'; $jv->schema($spec_url); my @new_files = get_cached_files($jv); ok @old_files == @new_files, 'remote file not cached in default cache dir'; my $tempdir = tempdir; $ENV{JSON_VALIDATOR_CACHE_PATH} = join ':', $tempdir->dirname, '/tmp/whatever'; $jv = JSON::Validator->new; is $jv->cache_paths->[0], $tempdir->dirname, 'env'; $jv->schema($spec_url); @new_files = get_cached_files($jv); 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 ($jv) = @_; return sort map { glob "$_/*" } @{$jv->cache_paths}; } JSON-Validator-5.18/t/more-bundle.t0000644000076500000240000001665115210277065016711 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::Deep; use Test::More; my $draft7_validator = JSON::Validator->new; subtest 'setup draft7_validator' => sub { $draft7_validator->schema('http://json-schema.org/draft-07/schema#'); isa_ok $draft7_validator->schema, 'JSON::Validator::Schema::Draft7'; is $draft7_validator->schema->id, 'http://json-schema.org/draft-07/schema#', 'draft7_validator schema id'; is $draft7_validator->schema->specification, $draft7_validator->schema->id, 'draft7_validator schema specification'; }; my $bundler_validator = JSON::Validator->new; subtest 'setup bundler_validator' => sub { $bundler_validator->load_and_validate_schema('t/spec/more-bundle.yaml'); isa_ok $bundler_validator->schema, 'JSON::Validator::Schema::Draft7'; like $bundler_validator->schema->id, qr{more-bundle\.yaml$}, 'bundler_validator schema id'; is $bundler_validator->schema->specification, 'http://json-schema.org/draft-07/schema#', 'bundler_validator schema specification'; }; bundle_test( 'find and resolve nested $refs; main schema is at the top level', 'i_have_nested_refs', { definitions => { ref1 => {type => 'array', items => {'$ref' => '#/definitions/ref2'}}, ref2 => {type => 'string', minLength => 1}, }, # begin i_have_nested_refs definition type => 'object', properties => {my_key1 => {'$ref' => '#/definitions/ref1'}, my_key2 => {'$ref' => '#/definitions/ref1'}}, }, ); bundle_test( 'find and resolve recursive $refs', 'i_have_a_recursive_ref', { definitions => { i_have_a_recursive_ref => { type => 'object', properties => { name => {type => 'string'}, children => {type => 'array', items => {'$ref' => '#/definitions/i_have_a_recursive_ref'}, default => []}, }, }, }, # begin i_have_a_recursive_ref definition # it is duplicated with the above, but there is no other way, # because $ref cannot be combined with other sibling keys type => 'object', properties => { name => {type => 'string'}, children => {type => 'array', items => {'$ref' => '#/definitions/i_have_a_recursive_ref'}, default => []}, }, }, ); bundle_test( 'find and resolve references to other local files', 'i_have_a_ref_to_another_file', { definitions => { 'more-bundle2_yaml-definitions_my_name' => {type => 'string', minLength => 2}, 'more-bundle2_yaml-definitions_my_address' => { type => 'object', properties => {street => {type => 'string'}, city => {'$ref' => '#/definitions/more-bundle2_yaml-definitions_my_name'}} }, ref1 => {type => 'array', items => {'$ref' => '#/definitions/ref2'}}, ref2 => {type => 'string', minLength => 1}, }, # begin i_have_a_ref_to_another_file definition type => 'object', properties => { # these ref targets are rewritten name => {'$ref' => '#/definitions/more-bundle2_yaml-definitions_my_name'}, address => {'$ref' => '#/definitions/more-bundle2_yaml-definitions_my_address'}, secrets => {'$ref' => '#/definitions/ref1'}, }, }, ); bundle_test( 'find and resolve references where the definition itself is a ref', 'i_am_a_ref', { definitions => {ref2 => {type => 'string', minLength => 1}}, # begin i_am_a_ref definition - which is actually ref1 type => 'array', items => {'$ref' => '#/definitions/ref2'}, }, ); bundle_test( 'find and resolve references where the definition itself is a ref, multiple times over', 'i_am_a_ref_level_1', { # begin i_am_a_ref definition - which is actually (eventually) ref3 type => 'integer', }, ); bundle_test( '$refs which are simply $refs themselves are traversed automatically during resolution', 'i_have_refs_with_the_same_name', { definitions => { i_am_a_ref_with_the_same_name => {'$ref' => '#/definitions/more-bundle2_yaml-definitions_i_am_a_ref_with_the_same_name'}, 'more-bundle2_yaml-definitions_i_am_a_ref_with_the_same_name' => {type => 'string'}, }, # begin i_have_a_ref_with_the_same_name definition type => 'object', properties => {me => {'$ref' => '#/definitions/i_am_a_ref_with_the_same_name'}}, }, ); bundle_test( '$refs which are simply $refs themselves are traversed automatically during resolution, at the top level too', 'i_am_a_ref_with_the_same_name', { # begin i_am_a_ref_with_the_same_name definition # - pulled from secondary file type => 'string', }, ); bundle_test( 'when encountering references that have the same root name, one is renamed', 'i_contain_refs_to_same_named_definitions', { definitions => code(sub { my $got = shift; return (0, 'expected hash with 2 keys') unless ref($got) eq 'HASH' and keys %$got == 2; return (0, 'missing "dupe_name" key') if not exists $got->{dupe_name}; # we don't know which ref will keep its name and which will be renamed my ($other_key) = grep $_ ne 'dupe_name', keys %$got; return 1 if (eq_deeply($got->{dupe_name}, {type => 'integer'}) and eq_deeply($got->{$other_key}, {type => 'string'}) and $other_key =~ qr/\bmore-bundle2_yaml-definitions_dupe_name$/); return (0, 'uh oh, got: ' . (Test::More::explain($got))[0]); }), # begin i_contain_refs_to_same_named_definitions definition type => 'object', properties => { foo => {'$ref' => '#/definitions/dupe_name'}, bar => {'$ref' => '#/definitions/more-bundle2_yaml-definitions_dupe_name'}, }, }, ); bundle_test( 'we can handle pulling in references that have the same root name as the top level name', 'i_have_a_ref_with_the_same_name', { definitions => {'more-bundle2_yaml-definitions_i_have_a_ref_with_the_same_name' => {type => 'string'}}, # begin i_have_a_ref_with_the_same_name definition type => 'object', properties => { name => {type => 'string'}, children => { type => 'array', items => {'$ref' => '#/definitions/more-bundle2_yaml-definitions_i_have_a_ref_with_the_same_name'}, default => [] }, }, }, ); bundle_test( 'find and resolve a reference that immediately leaps to another file', 'i_am_a_ref_to_another_file', { definitions => {'more-bundle_yaml-definitions_ref3' => {type => 'integer'}}, # begin i_am_a_ref_to_another_file definition - which is actually # i_have_a_ref_to_the_first_filename type => 'object', properties => {gotcha => {'$ref' => '#/definitions/more-bundle_yaml-definitions_ref3'}}, }, ); done_testing; sub bundle_test { my ($desc, $schema_name, $expected) = @_; subtest "$desc - $schema_name" => sub { my $source = $bundler_validator->schema->data->{definitions}{$schema_name}; my $got = $bundler_validator->bundle({schema => $source}); cmp_deeply($got, $expected, 'extracted schema for ' . $schema_name) or diag explain({expected => $expected, source => $source, got => $got}); my @errors = $draft7_validator->validate($got); ok !@errors, 'bundled schema conforms to the draft 7 spec'; my $fresh_draft7_validator = JSON::Validator->new; $fresh_draft7_validator->load_and_validate_schema($got, {schema => 'http://json-schema.org/draft-07/schema#'}); cmp_deeply( $fresh_draft7_validator->schema->data, $expected, 'our generated schema does not lose any data when parsed again by a new validator', ); }; } JSON-Validator-5.18/t/stack/0000755000076500000240000000000015211411467015404 5ustar jhthorsenstaffJSON-Validator-5.18/t/stack/Some.pm0000644000076500000240000000063215210277065016651 0ustar jhthorsenstaffpackage Some; use Mojo::Base -base; sub j { JSON::Validator->new } sub validate_age0 { shift->j->schema('data:///age0.json')->validate(shift) } sub validate_age1 { shift->j->schema('data:///age1.json')->validate(shift) } 1; __DATA__ @@ age0.json { "title": "Some module", "type": "object", "properties": { "age": { "type": "integer", "minimum": 0, "description": "Age in years" } } } JSON-Validator-5.18/t/stack/Some/0000755000076500000240000000000015211411467016307 5ustar jhthorsenstaffJSON-Validator-5.18/t/stack/Some/Module.pm0000644000076500000240000000060415210277065020075 0ustar jhthorsenstaffpackage Some::Module; use Mojo::Base 'Some'; sub validate_age0 { shift->j->schema('data:///age0.json')->validate(shift) } sub validate_age1 { shift->j->schema('data://Some::Module/age1.json')->validate(shift) } 1; __DATA__ @@ age1.json { "title": "Some module", "type": "object", "properties": { "age": { "type": "integer", "minimum": 1, "description": "Age in years" } } } JSON-Validator-5.18/t/validate-schema.t0000644000076500000240000000215315210277065017517 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; 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-5.18/t/openapiv3-default-values.t0000644000076500000240000000235215210277065021314 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my @errors; @errors = $schema->validate_request([get => '/pets/{id}'], {path => {id => 'a'}}); is "@errors", "/id: String is too short: 1/3.", 'invalid id'; @errors = $schema->validate_request([get => '/pets/{id}'], {path => {}}); is "@errors", "", 'default id'; my $id = {}; my %req = (path => sub {$id}); @errors = $schema->validate_request([get => '/pets/{id}'], \%req); is_deeply $id, {exists => 1, in => 'path', name => 'id', valid => 1, value => 'foo'}, 'input was mutated'; is "@errors", "", 'default id'; is $id->{value}, 'foo', 'coerced default value'; done_testing; __DATA__ @@ spec.json { "openapi": "3.0.0", "info": { "title": "Style And Explode", "version": "" }, "paths": { "/pets/{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": {"type": "string", "default": "foo", "minLength": 3} } ], "responses" : { "200": { "description": "pet response", "schema": {"type": "object"} } } } } } } JSON-Validator-5.18/t/openapiv3-nullable.t0000755000076500000240000001047015210277065020174 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my ($body, @errors); for my $path (qw(/nullable-data /nullable-ref)) { $body = {exists => 1, value => {id => 42}}; @errors = $schema->validate_response([get => $path], {body => \&body}); is "@errors", "/body/name: Missing property.", "$path - missing name"; $body = {exists => 1, value => {id => 42, name => undef}}; @errors = $schema->validate_response([get => $path], {body => \&body}); is "@errors", "", "$path - name is undef"; } for my $extra ({}, undef) { $body = {exists => 1, value => {extra => $extra, id => 42, name => undef}}; @errors = $schema->validate_response([get => '/nullable-data'], {body => \&body}); is "@errors", "", sprintf 'extra %s', $extra ? 'object' : 'null'; } for my $stuff ([], undef) { $body = {exists => 1, value => {stuff => $stuff, id => 42, name => undef}}; @errors = $schema->validate_response([get => '/nullable-data'], {body => \&body}); is "@errors", "", sprintf 'stuff %s', $stuff ? 'array' : 'null'; } $schema = JSON::Validator->new->schema('data://main/issue-241.json')->schema; $body = {exists => 1, value => {name => undef}}; @errors = $schema->validate_response([get => '/test'], {body => \&body}); is "@errors", "", "nullable inside oneOf"; $schema = JSON::Validator->new->schema('data://main/nullable-with-enum.json')->schema; $body = {exists => 1, value => {name => undef}}; @errors = $schema->validate_response([get => '/test'], {body => \&body}); is "@errors", "", "nullable string with enum"; done_testing; sub body {$body} __DATA__ @@ spec.json { "openapi": "3.0.0", "info": { "title": "Nullable", "version": "" }, "paths": { "/nullable-data": { "get": { "responses": { "200": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/WithNullable"} } } } } } }, "/nullable-ref": { "get": { "operationId": "withNullableRef", "responses": { "200": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/WithNullableRef"} } } } } } } }, "components": { "schemas": { "WithNullable": { "required": [ "id", "name" ], "properties": { "extra": { "type": "object", "nullable": true }, "id": { "type": "integer", "format": "int64" }, "name": { "type": "string", "nullable": true }, "stuff": { "type": "array", "nullable": true } } }, "WithNullableRef": { "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "$ref": "#/components/schemas/WithNullable/properties/name" } } } } } } @@ issue-241.json { "openapi": "3.0.0", "info": { "title": "Nullable", "version": "" }, "paths": { "/test": { "get": { "responses": { "200": { "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "oneOf": [ { "$ref": "#/components/schemas/name1" }, { "$ref": "#/components/schemas/name2" } ] } } } } } } } } } }, "components": { "schemas": { "name1": { "type": "string", "nullable": "true" }, "name2": { "type": "integer" } } } } @@ nullable-with-enum.json { "openapi": "3.0.0", "info": { "title": "Nullable", "version": "" }, "paths": { "/test": { "get": { "responses": { "200": { "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "$ref": "#/components/schemas/name" } } } } } } } } } }, "components": { "schemas": { "name": { "type": "string", "nullable": "true", "enum": ["foo", "bar"] } } } } JSON-Validator-5.18/t/openapiv2-collection-format.t0000644000076500000240000001002015210277065022002 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my @errors; @errors = $schema->validate_request([get => '/pets/{id}'], {path => {id => '42'}}); is "@errors", "", 'collectionFormat csv in path'; for ([csv => ","], [pipes => "|"], [ssv => " "], [tsv => "\t"]) { my ($name, $sep) = @$_; my $empty = $name =~ m!^pipes! ? '' : '0'; @errors = $schema->validate_request([get => '/pets'], {query => {"${name}0" => $empty, multir => ''}}); is "@errors", "", "collectionFormat ${name}0 empty string"; @errors = $schema->validate_request([get => '/pets'], {query => {"${name}0" => '42', multir => ''}}); is "@errors", "", "collectionFormat ${name}0 single item"; @errors = $schema->validate_request([get => '/pets'], {query => {"${name}0" => "4${sep}2", multir => ''}}); is "@errors", "", "collectionFormat ${name}0 two item"; @errors = $schema->validate_request([get => '/pets'], {query => {"${name}2" => '42', multir => ''}}); is "@errors", "/${name}2: Not enough items: 1/2.", "collectionFormat ${name}2 single item"; } done_testing; __DATA__ @@ spec.json { "swagger": "2.0", "info": {"version": "", "title": "Test collectionFormat"}, "basePath": "/api", "paths": { "/pets/{id}": { "get": { "parameters": [ {"name": "id", "in": "path", "type": "array", "collectionFormat": "csv", "items": {"type": "string"}, "minItems": 0, "required": true} ], "responses": { "200": { "description": "pet response", "schema": {"type": "object"} } } } }, "/pets": { "get": { "parameters": [ {"name": "csv", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "number"}, "minItems": 0, "default": []}, {"name": "csv0", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "number"}, "minItems": 0}, {"name": "csv2", "in": "query", "type": "array", "collectionFormat": "csv", "items": {"type": "number"}, "minItems": 2}, {"name": "multi", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "integer"}, "minItems": 0, "default": []}, {"name": "multi0", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "integer"}, "minItems": 0}, {"name": "multi2", "in": "query", "type": "array", "collectionFormat": "multi", "items": {"type": "integer"}, "minItems": 2}, {"name": "multir", "in": "query", "type": "array", "collectionFormat": "multi", "required": true, "items": {"type": "string"}, "minItems":1}, {"name": "pipes", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "string"}, "minItems": 0, "default": []}, {"name": "pipes0", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "string"}, "minItems": 0}, {"name": "pipes2", "in": "query", "type": "array", "collectionFormat": "pipes", "items": {"type": "integer"}, "minItems": 2}, {"name": "ssv", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "number"}, "minItems": 0, "default": []}, {"name": "ssv0", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "number"}, "minItems": 0}, {"name": "ssv2", "in": "query", "type": "array", "collectionFormat": "ssv", "items": {"type": "number"}, "minItems": 2}, {"name": "tsv", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "integer"}, "minItems": 0, "default": []}, {"name": "tsv0", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "integer"}, "minItems": 0}, {"name": "tsv2", "in": "query", "type": "array", "collectionFormat": "tsv", "items": {"type": "integer"}, "minItems": 2} ], "responses": { "200": { "description": "pet response", "schema": {"type": "object"} } } } } } } JSON-Validator-5.18/t/jv-string.t0000644000076500000240000000262615210277065016420 0ustar jhthorsenstaffuse lib '.'; use utf8; use t::Helper; 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('', jv->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; jv->coerce('str'); validate_ok {nick => 1000}, $schema; # https://github.com/mojolicious/json-validator/issues/134 validate_ok( {credit_card_number => '5252525252525252'}, { type => "object", required => ["credit_card_number"], properties => {credit_card_number => {type => "string", minLength => 15, maxLength => 16}} } ); my $string_constant = {type => 'string', const => 'foo'}; validate_ok 'foo', $string_constant; validate_ok 'bar', $string_constant, E('/', q{Does not match const: "foo".}); my $empty_string_constant = {type => 'string', const => ''}; validate_ok '', $empty_string_constant; validate_ok 'bar', $empty_string_constant, E('/', q{Does not match const: "".}); done_testing; JSON-Validator-5.18/t/benchmark.t0000644000076500000240000000375515210277065016433 0ustar jhthorsenstaffBEGIN { $ENV{JSON_VALIDATOR_WARN_MISSING_FORMAT} = 0 } use Mojo::Base -strict; use Benchmark qw(cmpthese timeit :hireswallclock); use JSON::Validator::Schema::Draft7; use List::Util qw(sum); use Test::More; use Time::HiRes qw(time); plan skip_all => 'TEST_BENCHMARK=500' unless my $n = $ENV{TEST_BENCHMARK}; diag sprintf "\n%s", scalar localtime; diag "n_times=$n"; my %bm; time_schema('defaults' => {}); time_schema('resolve_before' => {resolve_before => 1}); cmpthese \%bm if $ENV{HARNESS_IS_VERBOSE}; done_testing; sub time_schema { my ($desc, $attrs) = @_; my (@errors, @resolve_t, @validate_t, @total_t); my $resolve_before = delete $attrs->{resolve_before}; my $resolved_schema = $resolve_before && JSON::Validator::Schema::Draft7->new('http://json-schema.org/draft-07/schema#', %$attrs); $bm{$desc} = timeit 1 => sub { for (1 ... $n) { my $schema = $resolved_schema || JSON::Validator::Schema::Draft7->new(%$attrs); my $t0 = time; delete $schema->{errors}; $schema->resolve('http://json-schema.org/draft-07/schema#') unless $resolve_before; push @resolve_t, (my $t1 = time) - $t0; push @errors, @{$schema->errors}; push @validate_t, (my $t2 = time) - $t1; push @total_t, $t2 - $t0; } }; ok !@errors, 'valid schema' or diag "@errors"; my $rt = sprintf '%.3f', sum @resolve_t; ok $rt < 2, "$desc - resolve ${rt}s" unless $resolve_before; my $vt = sprintf '%.3f', sum @validate_t; ok $vt < 2, "$desc - validate ${vt}s"; my $tt = sprintf '%.3f', sum @total_t; ok $tt < 2, "$desc - total ${tt}s"; } __DATA__ # Tue Jul 20 07:25:46 2021 # n_times=200 ok 1 - valid schema ok 2 - defaults - resolve 0.560s ok 3 - defaults - validate 1.053s ok 4 - defaults - total 1.613s ok 5 - valid schema ok 6 - resolve_before - validate 1.030s ok 7 - resolve_before - total 1.030s s/iter defaults resolve_before defaults 1.61 -- -37% resolve_before 1.02 58% -- JSON-Validator-5.18/t/openapiv3-style-explode.t0000644000076500000240000002467315210277065021203 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv3; use Test::More; my $schema = JSON::Validator->new->schema('data://main/schema.json')->schema; test('get /array/label{id}' => [{path => {id => '.3,4,5'}}, {id => [3, 4, 5]}], [{path => {id => '5'}}, {id => [5]}]); test( 'get /array/label/explode{id}', [{path => {id => '.3.4.5'}}, {id => [3, 4, 5]}], [{path => {id => '.5'}}, {id => [5]}], ); test( 'get /array/matrix{id}', [{path => {id => ';id=3,4,5'}}, {id => [3, 4, 5]}], [{path => {id => ';id=5'}}, {id => [5]}], ); test( 'get /array/matrix/explode{id}', [{path => {id => ';id=3;id=4;id=5'}}, {id => [3, 4, 5]}], [{path => {id => ';id=5'}}, {id => [5]}], ); test( 'get /array/query', [{}, {}, '/ri: Missing property.'], [{query => {ri => '1.3'}}, {ri => 'ri'}, '/ri/0: Expected integer - got string.'], [{query => {ml => 5, ri => '0'}}, {ml => 'ml', ri => [0]}, '/ml: Not enough items: 1/2.'], [{query => {ml => ['3', 5], ri => '0'}}, {ml => [3, 5], ri => [0]}, ''], [{query => {pi => '1|2|3', ri => '0'}}, {pi => [1, 2, 3], ri => [0]}, ''], [{query => {ri => '0', sp => '2 3 4'}}, {ri => [0], sp => [2, 3, 4]}, ''] ); test( 'get /array/simple/{id}', [{}, {}, '/id: Missing property.'], [{path => {id => '10'}}, {id => [10]}], [{path => {id => '10,20'}}, {id => [10, 20]}] ); test('get /object/label{id}', [{path => {id => '.category.bird.name.birdy'}}, {id => {category => 'bird', name => 'birdy'}}]); test('get /object/label/explode{id}', [{path => {id => '.category=bird.name=birdy'}}, {id => {category => 'bird', name => 'birdy'}}]); test('get /object/matrix{id}', [{path => {id => ';id=category,bird,name,birdy'}}, {id => {category => 'bird', name => 'birdy'}}]); test('get /object/matrix/explode{id}', [{path => {id => ';category=bird;name=birdy'}}, {id => {category => 'bird', name => 'birdy'}}]); test( 'get /object/query', [{}, {}, ''], [{query => {ff => ''}}, {all => {ff => ['']}, ff => {}}], [{query => {pf => ''}}, {all => {pf => ''}, pf => {}}], [{query => {sf => ''}}, {all => {sf => ''}, sf => {}}], [{query => {ff => 'name,birdy,age,1'}}, {all => {ff => ['name,birdy,age,1']}, ff => {age => 1, name => 'birdy'}}], [{query => {pf => 'name|birdy|age|2'}}, {all => {pf => 'name|birdy|age|2'}, pf => {age => 2, name => 'birdy'}}], [{query => {sf => 'name birdy age 3'}}, {all => {sf => 'name birdy age 3'}, sf => {age => 3, name => 'birdy'}}], ); test( 'get /object/query', [ {query => {'do[name]' => 'birdy', 'do[birth-date][gte]' => '1970-01-01', 'do[numbers][0]' => '5'}}, { all => {'do[name]' => 'birdy', 'do[birth-date][gte]' => '1970-01-01', 'do[numbers][0]' => 5}, do => {name => 'birdy', 'birth-date' => {gte => '1970-01-01'}, numbers => [5]}, }, ], [ {query => {'do[0][1][0]' => 2, 'do[2][0]' => 4}}, {all => {'do[0][1][0]' => 2, 'do[2][0]' => 4}, do => {0 => [undef, [2]], 2 => [4]}}, ], [ {query => {'do[numbers][1]' => 2, 'do[numbers][0]' => '4'}}, {all => {'do[numbers][0]' => 4, 'do[numbers][1]' => 2}, do => {numbers => [4, 2]}}, ], [{query => {'do[numbers][]' => [3, '5']}}, {all => {'do[numbers][]' => [3, 5]}, do => {numbers => [3, 5]}}], [{query => {'do[numbers]' => [4, 6]}}, {all => {'do[numbers]' => [4, 6]}, do => {numbers => [4, 6]}}], ); test('get /object/simple/{id}', [{path => {id => 'category,bird,name,birdy'}}, {id => {category => 'bird', name => 'birdy'}}]); test('get /object/simple/explode/{id}', [{path => {id => 'category=bird,name=birdy'}}, {id => {category => 'bird', name => 'birdy'}}]); done_testing; sub test { my ($path, @tests) = @_; subtest "path $path" => sub { for (@tests) { my ($input, $exp, $err) = @$_; my (%mutated, %req); for my $in (keys %$input) { $req{$in} = sub { my ($name, $param) = @_; return $mutated{$param->{name}} = defined $name ? {exists => exists $input->{$in}{$name}, value => $input->{$in}{$name}} : {exists => 1, value => {map { ($_ => $input->{$in}{$_}) } keys %{$input->{$in}}}}; }; } my @errors = $schema->validate_request([split ' ', $path], \%req); is "@errors", $err || '', sprintf 'validate %s', Mojo::JSON::to_json($exp); delete $mutated{$_} for grep { !defined $mutated{$_}{valid} } keys %mutated; $mutated{$_}{value} = $mutated{$_}{name} for grep { !$mutated{$_}{valid} } keys %mutated; $mutated{$_} = $mutated{$_}{value} for keys %mutated; is_deeply \%mutated, $exp, sprintf 'mutated %s', Mojo::JSON::to_json($exp); } }; } __DATA__ @@ schema.json { "openapi": "3.0.0", "info": { "title": "Style And Explode", "version": "" }, "paths": { "/array/label{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": false, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ] } }, "/array/label/explode{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": true, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ] } }, "/array/matrix{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": false, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ] } }, "/array/matrix/explode{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": true, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ] } }, "/array/simple/{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "simple", "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ] } }, "/array/query": { "get": { "parameters": [ { "name": "ml", "in": "query", "style": "form", "explode": true, "schema": { "type": "array", "items": { "type": "string" }, "minItems": 2 } }, { "name": "ri", "in": "query", "required": true, "style": "form", "explode": true, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } }, { "name": "sp", "in": "query", "style": "spaceDelimited", "schema": { "type": "array", "items": { "type": "integer" } } }, { "name": "pi", "in": "query", "style": "pipeDelimited", "schema": { "type": "array", "items": { "type": "integer" } } } ] } }, "/object/label{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": false, "schema": { "type": "object" } } ] } }, "/object/label/explode{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": true, "schema": { "type": "object" } } ] } }, "/object/matrix{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": false, "schema": { "type": "object" } } ] } }, "/object/matrix/explode{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": true, "schema": { "type": "object" } } ] } }, "/object/query": { "get": { "parameters": [ { "name": "do", "in": "query", "style": "deepObject", "explode": true, "schema": { "type": "object" } }, { "name": "ff", "in": "query", "style": "form", "explode": false, "schema": { "type": "object" } }, { "name": "all", "in": "query", "style": "form", "explode": true, "schema": { "type": "object", "properties": { "ff": {"type": "array", "items": {"type": "string"}} } } }, { "name": "sf", "in": "query", "style": "spaceDelimited", "explode": false, "schema": { "type": "object" } }, { "name": "pf", "in": "query", "style": "pipeDelimited", "explode": false, "schema": { "type": "object" } } ] } }, "/object/simple/{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "simple", "explode": false, "schema": { "type": "object" } } ] } }, "/object/simple/explode/{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "style": "simple", "explode": true, "schema": { "type": "object" } } ] } } } } JSON-Validator-5.18/t/definitions/0000755000076500000240000000000015211411467016612 5ustar jhthorsenstaffJSON-Validator-5.18/t/definitions/weight.json0000644000076500000240000000023515210277065020777 0ustar jhthorsenstaff{ "type": "object", "description": "Weight with Units", "properties": { "mass": { "type": "integer" }, "unit": { "$ref": "./unit.json" } } } JSON-Validator-5.18/t/definitions/space age.json0000644000076500000240000000011115210277065021311 0ustar jhthorsenstaff{ "type": "integer", "minimum": 0, "description": "Age in years" } JSON-Validator-5.18/t/definitions/age.json0000644000076500000240000000011115210277065020235 0ustar jhthorsenstaff{ "type": "integer", "minimum": 0, "description": "Age in years" } JSON-Validator-5.18/t/definitions/unit.json0000644000076500000240000000012315210277065020463 0ustar jhthorsenstaff{ "type": "string", "description": "Unit of Mass", "pattern": "^kg|st|lb$" } JSON-Validator-5.18/t/get.t0000644000076500000240000000546315210277065015256 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::Draft201909; use Test::More; my $jv; subtest 'setup' => sub { $jv = JSON::Validator::Schema::Draft201909->new({ '$defs' => {z1 => {'$ref' => '#/$defs/z2', minLength => 1}, z2 => {type => 'string'}}, properties => { bar => {items => [{properties => {y => {'$ref' => '#/$defs/z1'}, x => {type => 'integer'}}}]}, foo => {items => [{properties => {y => {type => 'string'}}}]}, 'x/~y' => {type => 'boolean'}, }, }); ok !$jv->is_invalid, 'schema is valid' or diag explain $jv->errors; }; subtest 'get($string)' => sub { is $jv->get('/properties/foo/items/0/properties/y/type'), 'string', 'get /properties/foo/items/0/properties/y/type'; is $jv->get('/$defs/baz'), undef, 'get /$defs/baz'; is $jv->get('/properties/baz'), undef, 'get /properties/baz'; is $jv->get('/properties/baz'), undef, 'get /properties/baz'; is $jv->get('/properties/x~1~0y/type'), 'boolean', 'get /x~1y'; }; subtest 'get(\@array)' => sub { is $jv->get([qw(properties foo items 0 properties y type)]), 'string', 'get /properties/foo/items/0/properties/y/type'; is $jv->get([qw($defs baz)]), undef, 'get /$defs/baz'; is $jv->get([qw(properties baz)]), undef, 'get /properties/baz'; is $jv->get([qw(properties x/~y type)]), 'boolean', 'get /properties/x/~y type'; }; subtest '$ref' => sub { is_deeply $jv->get('/properties/bar/items/0/properties/y'), {minLength => 1, type => 'string'}, 'get /bar/items/0/properties/y'; is $jv->get('/properties/bar/items/0/properties/y/$ref'), '#/$defs/z1', 'get /bar/items/0/properties/y/$ref'; is_deeply $jv->get('/properties/bar/items/0/properties'), {y => {'$ref' => '#/$defs/z1'}, x => {type => 'integer'}}, 'get /bar/items/0/properties'; }; subtest 'callback' => sub { my @res; $jv->get(['properties', undef, 'items', '0', 'properties', undef, 'type'], sub { push @res, [@_] }); is @res, 3, 'callback called'; is_deeply \@res, [ ['integer', '/properties/bar/items/0/properties/x/type'], ['string', '/properties/bar/items/0/properties/y/type'], ['string', '/properties/foo/items/0/properties/y/type'], ], 'callback data'; }; subtest 'collection' => sub { note 'This is not officially supported. I think the callback version is the way to go.'; is_deeply $jv->get(['properties', 'bar', 'items', '0', 'properties', undef, 'type']), ['integer', 'string'], 'one level'; my $c = $jv->get(['properties', undef, 'items', '0', 'properties', undef, 'type']); is $c->first->first, 'integer', 'collections of collections'; is_deeply $c->flatten->to_array, ['integer', 'string', 'string', undef], 'flatten' or diag explain $c; }; done_testing; JSON-Validator-5.18/t/openapiv2-file.t0000644000076500000240000000174415210277065017315 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.json')->schema; my ($form, @errors); for my $image (undef, '') { $form = {image => '', id => 'i1'}; @errors = $schema->validate_request([post => '/pets'], {formData => $form}); is "@errors", '/image: Missing property.', 'missing image'; } $form = {image => '0', id => 'i1'}; @errors = $schema->validate_request([post => '/pets'], {formData => $form}); is "@errors", '', 'valid input'; done_testing; __DATA__ @@ spec.json { "swagger": "2.0", "info": {"version": "0.8", "title": "Test body"}, "basePath": "/api", "paths": { "/pets": { "post": { "parameters": [ {"name": "image", "in": "formData", "type": "file", "required": true}, {"name": "id", "in": "formData", "type": "string"} ], "responses": { "200": {"description": "ok", "schema": {"type": "object"}} } } } } } JSON-Validator-5.18/t/jv-integer.t0000644000076500000240000000227015210277065016542 0ustar jhthorsenstaffuse 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.'); my $int_constant = {type => 'integer', const => 2}; validate_ok 2, $int_constant; validate_ok 1, $int_constant, E('/', q{Does not match const: 2.}); jv->coerce('num'); 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; validate_ok '2', $int_constant; validate_ok '1', $int_constant, E('/', q{Does not match const: 2.}); done_testing; JSON-Validator-5.18/t/draft6.t0000644000076500000240000000146615210277065015664 0ustar jhthorsenstaffuse lib '.'; use t::Helper; use JSON::Validator::Schema::Draft6; t::Helper->schema(JSON::Validator::Schema::Draft6->new); t::Helper->test(number => qw(basic maximum minimum)); t::Helper->test(array => qw(basic items additional_items contains min_max unique)); t::Helper->test(object => qw(basic properties)); t::Helper->test(object => qw(additional_properties pattern_properties min_max names)); subtest 'exclusiveMaximum' => sub { schema_validate_ok 2.4, {exclusiveMaximum => 2.4}, E('/', '2.4 >= maximum(2.4)'); schema_validate_ok 0, {exclusiveMaximum => 0}, E('/', '0 >= maximum(0)'); }; subtest 'exclusiveMinimum' => sub { schema_validate_ok 4.2, {exclusiveMinimum => 4.2}, E('/', '4.2 <= minimum(4.2)'); schema_validate_ok 0, {exclusiveMinimum => 0}, E('/', '0 <= minimum(0)'); }; done_testing; JSON-Validator-5.18/t/jv-number.t0000644000076500000240000000301115210277065016367 0ustar jhthorsenstaffuse 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.'); my $numeric_constant = {type => 'number', const => 2.1}; validate_ok 2.1, $numeric_constant; validate_ok 1, $numeric_constant, E('/', q{Does not match const: 2.1.}); jv->coerce('numbers'); validate_ok {mynumber => '-0.5'}, $schema; validate_ok {mynumber => -0.6}, $schema, E('/mynumber', '-0.6 < minimum(-0.5)'); validate_ok {mynumber => '2.7'}, $schema; validate_ok {mynumber => '2.8'}, $schema, E('/mynumber', '2.8 > maximum(2.7)'); 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}}}; validate_ok '2.1', $numeric_constant; validate_ok '1', $numeric_constant, E('/', q{Does not match const: 2.1.}); for my $x ([-0.5, 2.7], [true, true]) { $schema->{properties}{mynumber}{exclusiveMaximum} = $x->[1]; $schema->{properties}{mynumber}{exclusiveMinimum} = $x->[0]; validate_ok {mynumber => 2.7}, $schema, E('/mynumber', '2.7 >= maximum(2.7)'); validate_ok {mynumber => -0.5}, $schema, E('/mynumber', '-0.5 <= minimum(-0.5)'); } done_testing; JSON-Validator-5.18/t/predictable-errors.t0000644000076500000240000000150015210277065020253 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $jv = JSON::Validator->new; my $broken_data = {ant => [qw(fire soldier termite)], bat => 'cricket', cat => 'lion', dog => 'good boy'}; my $num_errors; # The schema below gets turned into a perl hash inside JSON::Validator, # so looping around like this will execute the test with all kinds of # different internal ordering for (1 .. 20) { my $schema_text = '{"type":"object","properties":{"ant":{"type":"string"},"bat":{"type":"array"},"cat":{"type":"object"},"dog":{"type":"integer"}}}'; $jv->schema($schema_text); is_deeply [map { $_->path } $jv->validate($broken_data)], [qw(/ant /bat /cat /dog)], 'got errors in expected order'; is scalar $jv->validate($broken_data), 4, 'in scalar context got the right number of errors'; } done_testing; JSON-Validator-5.18/t/openapiv3-content-types.t0000644000076500000240000002010015210277065021176 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my $schema = JSON::Validator->new->schema('data://main/spec.yaml')->schema; my ($body, @errors); sub body {$body} subtest '*/* w/ required body' => sub { subtest 'content-type is missing' => sub { $body = {exists => 0}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is empty string' => sub { $body = {exists => 0, content_type => ''}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}, content_type => ''}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}, content_type => ''}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is application/json' => sub { $body = {exists => 0}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}, content_type => 'application/json'}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}, content_type => 'application/json'}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is application/json; charset=utf-8' => sub { $body = {exists => 0}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}, content_type => 'application/json; charset=utf-8'}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}, content_type => 'application/json; charset=utf-8'}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; }; subtest 'application/json w/ body' => sub { subtest 'content-type is missing' => sub { $body = {exists => 0}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is empty string' => sub { $body = {exists => 0, content_type => ''}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}, content_type => ''}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}, content_type => ''}; @errors = $schema->validate_request([post => '/pets_any'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is application/json' => sub { $body = {exists => 0}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body: Missing property.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}, content_type => 'application/json'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}, content_type => 'application/json'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is application/json; charset=utf-8' => sub { $body = {exists => 1, value => {name => 'kitty'}, content_type => 'application/json; charset=utf-8'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 1, value => {age => 42}, content_type => 'application/json; charset=utf-8'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body/name: Missing property.", 'invalid request body'; }; subtest 'content-type is application/xml' => sub { $body = {exists => 0, content_type => 'application/xml'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body: Expected application/json - got application/xml.", 'invalid request body'; $body = {exists => 1, value => {name => 'kitty'}, content_type => 'application/xml'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body: Expected application/json - got application/xml.", 'invalid request body'; $body = {exists => 1, value => {age => 42}, content_type => 'application/xml'}; @errors = $schema->validate_request([post => '/pets_json'], {body => \&body}); is "@errors", "/body: Expected application/json - got application/xml.", 'invalid request body'; }; }; subtest 'w/o body' => sub { $body = {exists => 0}; @errors = $schema->validate_request([post => '/pets/publish'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 0, content_type => ''}; @errors = $schema->validate_request([post => '/pets/publish'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 0, content_type => 'application/xml'}; @errors = $schema->validate_request([post => '/pets/publish'], {body => \&body}); is "@errors", "", 'valid request body'; $body = {exists => 0, content_type => 'application/json; charset=utf-8'}; @errors = $schema->validate_request([post => '/pets/publish'], {body => \&body}); is "@errors", "", 'valid request body'; }; done_testing; __DATA__ @@ spec.yaml openapi: 3.0.0 info: title: Style And Explode version: "" paths: /pets_any: post: requestBody: required: true content: "*/*": schema: type: object required: - name properties: name: type: string responses: "200": description: success schema: type: object /pets_json: post: requestBody: required: true content: "application/json": schema: type: object required: - name properties: name: type: string responses: "200": description: success schema: type: object /pets/publish: post: description: example of endpoint with no request body responses: "200": description: success schema: type: object JSON-Validator-5.18/t/to-json.t0000644000076500000240000000243315210277065016062 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use Test::More; my @errors = JSON::Validator->new->schema('data://main/error_object.json') ->validate(bless({path => '', message => 'yikes'}, 'JSON::Validator::Error')); ok !@errors, 'TO_JSON on objects' or diag join ', ', @errors; my $input = { errors => [JSON::Validator::Error->new('/', 'foo'), JSON::Validator::Error->new('/', 'bar')], valid => Mojo::JSON->false, }; @errors = JSON::Validator->new->schema('data://main/error_array.json')->validate($input); ok !@errors, 'TO_JSON on objects inside arrays' or diag join ', ', @errors; is_deeply $input, { errors => [JSON::Validator::Error->new('/', 'foo'), JSON::Validator::Error->new('/', 'bar')], valid => Mojo::JSON->false, }, 'input objects are not changed'; done_testing; __DATA__ @@ error_object.json { "type": "object", "properties": { "message": { "type": "string" } }, "required": ["message"] } @@ error_array.json { "type": "object", "required": [ "errors" ], "properties": { "valid": { "type": "boolean" }, "errors": { "type": "array", "items": { "type": "object", "required": [ "message" ], "properaties": { "message": { "type": "string" }, "path": { "type": "string" } } } } } } JSON-Validator-5.18/t/openapiv3-bundled-spec.t0000644000076500000240000000305115211411261020722 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator; use JSON::Validator::Schema::OpenAPIv3; use Test::More; # Issue #286: the default OpenAPI v3.0 specification must be bundled with the # distribution and resolvable offline. Any attempt to fetch it over the network # means the bundled spec is missing. subtest 'default specification is the bundled 2021-09-28 spec' => sub { my $schema = JSON::Validator::Schema::OpenAPIv3->new; is $schema->specification, 'https://spec.openapis.org/oas/3.0/schema/2021-09-28', 'specification'; }; subtest 'default specification loads from the bundle without network' => sub { my $jv = JSON::Validator->new; $jv->store->ua(FakeUA->new); # blow up if a network request is attempted my $url = 'https://spec.openapis.org/oas/3.0/schema/2021-09-28'; my $id = $jv->store->load($url); is $id, $url, 'loaded from bundle'; is $jv->store->get($id)->{id}, $url, 'bundled schema id matches url'; }; subtest 'openapi 3.0.x documents are validated against the bundled default' => sub { my $jv = JSON::Validator->new; $jv->store->ua(FakeUA->new); # blow up if a network request is attempted my $schema = $jv->schema({openapi => '3.0.3', info => {title => 't', version => '1'}, paths => {}})->schema; is $schema->specification, 'https://spec.openapis.org/oas/3.0/schema/2021-09-28', 'detected default'; is_deeply $schema->errors, [], 'valid minimal 3.0 document'; }; done_testing; package FakeUA; sub new { bless {}, shift } sub get { Test::More::BAIL_OUT('network access attempted - bundled OpenAPI v3.0 spec is missing') } JSON-Validator-5.18/t/openapiv2-routes.t0000644000076500000240000000440215210277065017711 0ustar jhthorsenstaffuse Mojo::Base -strict; use JSON::Validator::Schema::OpenAPIv2; use Test::Deep; use Test::More; my $schema = JSON::Validator::Schema::OpenAPIv2->new; $schema->data({ paths => { '/a1' => {get => {}}, '/a1/bbbbbbb2/{c3}' => {post => {}}, '/a1/bbbbbbbbbbbbbbbbbbbb2/{ccc3}' => {put => {}}, '/a1/xxxxxxxxx/{ccc3}' => {get => {}}, '/a1/{b2}/{ccc3}/{d4}' => {post => {}}, '/a1/{bb2}/{c3}/d' => {get => {}}, '/a1/{bb2}/{ccc3}/{dddd4}/{e5}' => {put => {}}, '/a1/{bbbb2}/{cc3}' => {get => {}}, '/aa1/bbb2/{c3}' => {post => {}}, '/aaa1/bb2' => {get => {}}, '/aaa2' => {put => {}}, '/{aaa1}/{bb2}/{ccc3}' => {get => {}}, '/{x}' => {delete => {}}, }, }); is_deeply( $schema->routes->to_array, [ {path => '/a1/{bb2}/{ccc3}/{dddd4}/{e5}', method => 'put', operation_id => undef}, {path => '/a1/{bb2}/{c3}/d', method => 'get', operation_id => undef}, {path => '/a1/{b2}/{ccc3}/{d4}', method => 'post', operation_id => undef}, {path => '/a1/bbbbbbb2/{c3}', method => 'post', operation_id => undef}, {path => '/a1/bbbbbbbbbbbbbbbbbbbb2/{ccc3}', method => 'put', operation_id => undef}, {path => '/a1/xxxxxxxxx/{ccc3}', method => 'get', operation_id => undef}, {path => '/aa1/bbb2/{c3}', method => 'post', operation_id => undef}, {path => '/a1/{bbbb2}/{cc3}', method => 'get', operation_id => undef}, {path => '/{aaa1}/{bb2}/{ccc3}', method => 'get', operation_id => undef}, {path => '/aaa1/bb2', method => 'get', operation_id => undef}, {path => '/a1', method => 'get', operation_id => undef}, {path => '/aaa2', method => 'put', operation_id => undef}, {path => '/{x}', method => 'delete', operation_id => undef}, ], 'sorted routes' ) or diag explain $schema->routes->map(sub { $_->{path} })->to_array; done_testing; JSON-Validator-5.18/CONTRIBUTING.md0000644000076500000240000000612415210277065016273 0ustar jhthorsenstaff# HOW TO CONTRIBUTE Thank you for considering contributing to this distribution. This file contains instructions that will help you work with the source code. ## Getting dependencies If you have App::cpanminus installed, you can use [cpanm](https://metacpan.org/pod/cpanm) to satisfy dependencies like this: cpanm --installdeps --with-develop . You can also run this command (or any other cpanm command) without installing App::cpanminus first, using the fatpacked `cpanm` script via curl or wget: curl -L https://cpanmin.us | perl - --installdeps --with-develop . wget -qO - https://cpanmin.us | perl - --installdeps --with-develop . Otherwise, look for either a `cpanfile` or `META.json` file for a list of dependencies to satisfy. There are also some optional modules which should be installe if you are contributing a code change: cpanm boolean cpanm Sereal::Encoder 4.00 cpanm Test::JSON::Schema::Acceptance 1.000 cpanm YAML::XS 0.67 cpanm Test::Pod cpanm Test::Pod::Coverage ## Running tests You can run tests directly using the `prove` tool: prove -l prove -lv t/some_test_file.t For most of my distributions, `prove` is entirely sufficient for you to test any patches you have. I use `prove` for 99% of my testing during development. ## Reporting bugs First of all, make sure you are using the latest version of JSON::Validator and its dependencies, it is quite likely that your bug has already been fixed. If that doesn't help, take a look at the list of currently open issues, perhaps it has already been reported by someone else and you can just add a comment confirming it. If it hasn't been reported yet, try to prepare a test case demonstrating the bug, you are not expected to fix it yourself, but you'll have to make sure the developers can replicate your problem. Sending in your whole application generally does more harm than good, the t directory of this distribution has many good examples for how to do it right. Writing a test is usually the hardest part of fixing a bug, so the better your test case the faster it can be fixed. And don't forget to add a descriptive title and text, when you create a new issue. If your issue does not contain enough information or is unintelligible, it might get closed pretty quickly. But don't be disheartened, if there's new activity it will get reopened just as quickly. ## Code style The code style is enforced with a `.perltidyrc` in the project root. Any pull request or patch should be run through [Perl::Tidy](https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy) before submitted. This can easily be enforced using a tool such as [githook-perltidy](https://metacpan.org/pod/githook-perltidy). ## Changes file Do not change the `Changes` file when working on a patch or "pull request". This file will be updated appropriately when a new release is made. # CREDITS This file was adapted from an initial `CONTRIBUTING.md` file from [Mojo::SQLite](https://github.com/Grinnz/Mojo-SQLite/blob/master/CONTRIBUTING.md), and paragraphs as heavily influenced by https://docs.mojolicious.org/Mojolicious/Guides/Contributing. JSON-Validator-5.18/META.yml0000644000076500000240000000521615211411470015303 0ustar jhthorsenstaff--- abstract: 'Validate data against a JSON schema' author: - 'Jan Henning Thorsen ' build_requires: ExtUtils::MakeMaker: '0' Test::Deep: '0' Test::More: '1.30' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.76, 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 - examples - t requires: Data::Validate::Domain: '0.11' Data::Validate::IP: '0.27' List::Util: '1.45' Mojolicious: '9.34' Net::IDN::Encode: '2.500' YAML::XS: '0.67' perl: '5.016' resources: IRC: url: irc://irc.libera.chat/#perl-openapi web: https://web.libera.chat/#perl-openapi bugtracker: https://github.com/jhthorsen/json-validator/issues homepage: https://github.com/jhthorsen/json-validator license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/jhthorsen/json-validator.git version: '5.18' x_contributors: - 'Aleksandr Orlenko ' - 'Alexander Hartmaier ' - 'Alexander Karelas ' - 'Bernhard Graf ' - 'Brad Barden ' - 'Dagfinn Ilmari Mannsåker ' - 'Daniel Böhmer ' - 'David Cantrell ' - 'Ed J ' - 'Ere Maijala ' - 'Fabrizio Gennari ' - 'Ilya Rassadin ' - 'Jan Henning Thorsen ' - 'Jason Cooper ' - 'Karen Etheridge ' - 'Kenichi Ishigaki ' - 'Kevin M. Goess ' - 'Kirill Matusov ' - 'Krasimir Berov ' - 'Lari Taskula ' - 'Lee Johnson ' - 'Martin Renvoize ' - 'Mattias Päivärinta ' - 'Michael Jemmeson ' - 'Michael Schout ' - 'Mohammad S Anwar ' - 'Nick Morrott ' - 'Pierre-Aymeric Masse ' - 'Roy Storey ' - 'Russell Jenkins ' - 'Sebastian Riedel ' - 'Stephan Hradek ' - 'Tim Stallard ' - 'Zoffix Znet ' x_serialization_backend: 'CPAN::Meta::YAML version 0.020' JSON-Validator-5.18/lib/0000755000076500000240000000000015211411467014602 5ustar jhthorsenstaffJSON-Validator-5.18/lib/JSON/0000755000076500000240000000000015211411467015353 5ustar jhthorsenstaffJSON-Validator-5.18/lib/JSON/Validator.pm0000644000076500000240000003421015211411456017634 0ustar jhthorsenstaffpackage JSON::Validator; use Mojo::Base -base; use Carp qw(confess); use JSON::Validator::Store; use JSON::Validator::Util qw(E data_checksum is_type); use Mojo::Util qw(sha1_sum); use Scalar::Util qw(blessed); our $VERSION = '5.18'; our %SCHEMAS = ( 'http://json-schema.org/draft-04/schema#' => '+Draft4', 'http://json-schema.org/draft-06/schema#' => '+Draft6', 'http://json-schema.org/draft-07/schema#' => '+Draft7', 'https://json-schema.org/draft/2019-09/schema' => '+Draft201909', 'http://swagger.io/v2/schema.json' => '+OpenAPIv2', 'https://spec.openapis.org/oas/3.0/schema/2021-09-28' => '+OpenAPIv3', 'https://spec.openapis.org/oas/3.1/schema/2021-05-20' => '+OpenAPIv3', ); has formats => sub { require JSON::Validator::Schema; JSON::Validator::Schema->_build_formats }; has recursive_data_protection => 1; has store => sub { my $self = shift; my %attrs; $attrs{$_} = delete $self->{$_} for grep { $self->{$_} } qw(cache_paths ua); return JSON::Validator::Store->new(%attrs); }; # store proxy attributes for my $method (qw(cache_paths ua)) { Mojo::Util::monkey_patch(__PACKAGE__, $method => sub { shift->store->$method(@_) }); } sub bundle { shift->schema->bundle(@_)->data } sub coerce { my $self = shift; return $self->{coerce} ||= {} unless defined(my $what = shift); state $short = {bool => 'booleans', def => 'defaults', num => 'numbers', str => 'strings'}; $what = {map { ($_ => 1) } split /,/, $what} unless ref $what; $self->{coerce} = {}; $self->{coerce}{($short->{$_} || $_)} = $what->{$_} for keys %$what; return $self; } sub get { shift->schema->get(@_) } sub load_and_validate_schema { my ($self, $schema, $args) = @_; delete $self->{schema}; my $schema_obj = $self->_new_schema($schema, %$args); confess join "\n", "Invalid JSON specification", (ref $schema eq 'HASH' ? Mojo::Util::dumper($schema) : $schema), map {"- $_"} @{$schema_obj->errors} if @{$schema_obj->errors}; $self->{schema} = $schema_obj; return $self; } sub new { my $self = shift->SUPER::new(@_); $self->coerce($self->{coerce}) if defined $self->{coerce}; return $self; } sub schema { my $self = shift; return $self->{schema} //= $self->_new_schema({}) unless @_; $self->{schema} = $self->_new_schema(shift); return $self; } sub validate { my ($self, $data, $schema) = @_; return +(defined $schema ? $self->_new_schema($schema) : $self->schema)->validate($_[1]); } sub _new_schema { my ($self, $source, %attrs) = @_; return $source if blessed $source and $source->can('specification'); # Compat with load_and_validate_schema() $attrs{specification} = delete $attrs{schema} if $attrs{schema}; my $loadable = (blessed $source && ($source->can('scheme') || $source->isa('Mojo::File'))) || ($source !~ /\n/ && -f $source) || (!ref $source && $source =~ /^\w/); my $store = $self->store; my $schema = $loadable ? $store->get($store->load($source)) : $source; $attrs{recursive_data_protection} //= $self->recursive_data_protection; $attrs{coerce} ||= $self->{coerce} if $self->{coerce}; $attrs{formats} ||= $self->{formats} if $self->{formats}; $attrs{specification} = $schema->{'$schema'} if !$attrs{specification} and is_type $schema, 'HASH' and $schema->{'$schema'}; $attrs{store} = $store; # Detect openapiv2 and v3 schemas by content, since no "$schema" is present my $spec = $attrs{specification} || $schema; if (ref $spec eq 'HASH' and $spec->{paths}) { if ($spec->{swagger} and $spec->{swagger} eq '2.0') { $spec = 'http://swagger.io/v2/schema.json'; } elsif ($spec->{openapi} and $spec->{openapi} =~ m!^3\.1\.\d+$!) { $spec = 'https://spec.openapis.org/oas/3.1/schema/2021-05-20'; $attrs{specification} ||= $spec; } elsif ($spec->{openapi} and $spec->{openapi} =~ m!^3\.0\.\d+$!) { $spec = 'https://spec.openapis.org/oas/3.0/schema/2021-09-28'; } } my $schema_class = $spec && $SCHEMAS{$spec} || 'JSON::Validator::Schema'; $schema_class =~ s!^\+(.+)$!JSON::Validator::Schema::$1!; confess "Could not load $schema_class: $@" unless $schema_class->can('new') or eval "require $schema_class;1"; return $schema_class->new($source, %attrs); } 1; =encoding utf8 =head1 NAME JSON::Validator - Validate data against a JSON schema =head1 SYNOPSIS =head2 Using a schema object L or any of the sub classes can be used instead of L. The only reason to use L directly is if you don't know the schema version up front. =head2 Basics use JSON::Validator; my $jv = JSON::Validator->new; # Define a schema - http://json-schema.org/learn/miscellaneous-examples.html # You can also load schema from disk or web $jv->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 my @errors = $jv->validate({firstName => "Jan Henning", lastName => "Thorsen", age => -42}); # Do something if any errors was found die "@errors" if @errors; =head2 Using joi # Use joi() to build the schema use JSON::Validator::Joi 'joi'; $jv->schema(joi->object->props({ firstName => joi->string->required, lastName => joi->string->required, age => joi->integer->min(0), })); # joi() can also validate directly my @errors = joi( {firstName => "Jan Henning", lastName => "Thorsen", age => -42}, joi->object->props({ firstName => joi->string->required, lastName => joi->string->required, age => joi->integer->min(0), }), ); =head1 DESCRIPTION L is a data structure validation library based around L. This module can be used directly with a JSON schema or you can use the elegant DSL schema-builder L to define the schema programmatically. =head2 Supported schema formats L can load JSON schemas in multiple formats: Plain perl data structured (as shown in L), JSON or YAML. The JSON parsing is done with L, while YAML files requires L or L. =head2 Resources Here are some resources that are related to JSON schemas and validation: =over 4 =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, 6, 7, 2019-09. Web page: L C<$ref>: L, L, 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 * OpenAPI specification, version 3 Web page: L C<$ref>: L This specification is still EXPERIMENTAL. =item * Swagger Petstore This is used for unit tests, and should not be relied on by external users. =back =head2 Optional modules =over 2 =item * Sereal::Encoder Installing L v4.00 (or later) will make L significantly faster. This function is used both when parsing schemas and validating data. =item * Format validators See the documentation in L for other optional modules to do validation of specific "format", such as "hostname", "ipv4" and others. =back =head1 ATTRIBUTES =head2 cache_paths Proxy attribute for L. =head2 formats This attribute will be used as default value for L. It is highly recommended to change this directly on the L instead: $jv->formats(...); # Legacy $jv->schema->formats(...); # Recommended way =head2 recursive_data_protection This attribute will be used as default value for L. It is highly recommended to change this directly on the L instead: $jv->recursive_data_protection(...); # Legacy $jv->schema->recursive_data_protection(...); # Recommended way =head2 store $store = $jv->store; Holds a L object that caches the retrieved schemas. This object will be shared amongst different L objects to prevent a schema from having to be downloaded again. =head2 ua Proxy attribute for L. =head1 METHODS =head2 bundle This method can be used to get a bundled version of L. It will however return a data-structure instead of a new object. See L for an alternative. # These two lines does the same $data = $jv->bundle; $data = $jv->schema->bundle->data; # Recommended way $schema = $jv->schema->bundle; =head2 coerce This attribute will be used as default value for L. It is highly recommended to change this directly on the L instead: $jv->coerce(...); # Legacy $jv->schema->coerce(...); # Recommended way =head2 get Proxy method for L. =head2 new $jv = JSON::Validator->new(%attributes); $jv = JSON::Validator->new(\%attributes); Creates a new L object. =head2 load_and_validate_schema This method will be deprecated in the future. See L and L instead. =head2 schema $jv = $jv->schema($json_or_yaml_string); $jv = $jv->schema($url); $jv = $jv->schema(\%schema); $jv = $jv->schema(JSON::Validator::Joi->new); $jv = $jv->schema(JSON::Validator::Schema->new); $schema = $jv->schema; Used to set a schema from either a data structure or a URL. C<$schema> will be an instance of L, L L, L, L, L or L. The C<$url> can take many forms, but needs to point to a text file in the JSON or YAML format. =over 4 =item * file://... A file on disk. Note that it is required to use the "file" scheme if you want to reference absolute paths on your file system. =item * http://... or https://... A web resource will be fetched using the L, stored in L. =item * data://Some::Module/spec.json Will load a given "spec.json" file from C using L. =item * data:///spec.json A "data" URL without a module name will use the current package and search up the call/inheritance tree. =item * Any other URL An URL (without a recognized scheme) will be treated as a path to a file on disk. If the file could not be found on disk and the path starts with "/", then the will be loaded from the app defined in L. Something like this: $jv->ua->server->app(MyMojoApp->new); $jv->ua->get('/any/other/url.json'); =back =head2 validate Proxy method for L. =head1 SEE ALSO =over 2 =item * L L contains utility functions for validating data types. Could be useful for validating data without loading a schema. =item * L L is the base class for L, L L, L, L or L. =item * L L contains many useful function when working with schemas. =item * L L is a plugin for L that utilize L and the L to build routes with input and output validation. =back =head1 COPYRIGHT AND LICENSE Copyright (C) 2014-2021, 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 AUTHORS =head2 Project Founder Jan Henning Thorsen - C =head2 Contributors =over 2 =item * Aleksandr Orlenko =item * Alexander Hartmaier =item * Alexander Karelas =item * Bernhard Graf =item * Brad Barden =item * Dagfinn Ilmari Mannsåker =item * Daniel Böhmer =item * David Cantrell =item * Ed J =item * Ere Maijala =item * Fabrizio Gennari =item * Ilya Rassadin =item * Jason Cooper =item * Karen Etheridge =item * Kenichi Ishigaki =item * Kevin M. Goess =item * Kirill Matusov =item * Krasimir Berov =item * Lari Taskula =item * Lee Johnson =item * Martin Renvoize =item * Mattias Päivärinta =item * Michael Jemmeson =item * Michael Schout =item * Mohammad S Anwar =item * Nick Morrott =item * Pierre-Aymeric Masse =item * Roy Storey =item * Russell Jenkins =item * Sebastian Riedel =item * Stephan Hradek =item * Tim Stallard =item * Zoffix Znet =back =cut JSON-Validator-5.18/lib/JSON/Validator/0000755000076500000240000000000015211411467017300 5ustar jhthorsenstaffJSON-Validator-5.18/lib/JSON/Validator/Store.pm0000644000076500000240000002272315211411456020736 0ustar jhthorsenstaffpackage JSON::Validator::Store; use Mojo::Base -base; use Mojo::Exception; use Mojo::File qw(path); use Mojo::JSON; use Mojo::JSON::Pointer; use Mojo::UserAgent; use Mojo::Util qw(url_unescape); use JSON::Validator::Schema; use JSON::Validator::URI qw(uri); use JSON::Validator::Util qw(data_section str2data); use Scalar::Util qw(blessed); use constant DEBUG => $ENV{JSON_VALIDATOR_DEBUG} && 1; use constant BUNDLED_PATH => path(path(__FILE__)->dirname, 'cache')->to_string; use constant CASE_TOLERANT => File::Spec->case_tolerant; die $@ unless eval q(package JSON::Validator::Exception; use Mojo::Base 'Mojo::Exception'; 1); has cache_paths => sub { [split(/:/, $ENV{JSON_VALIDATOR_CACHE_PATH} || ''), BUNDLED_PATH] }; has schemas => sub { +{} }; has ua => sub { my $ua = Mojo::UserAgent->new; $ua->proxy->detect; return $ua->max_redirects(3); }; sub add { my ($self, $id, $schema) = @_; $id =~ s!(.)#$!$1!; $self->schemas->{$id} = $schema; return $id; } sub exists { my ($self, $id) = @_; return undef unless defined $id; $id =~ s!(.)#$!$1!; return $self->schemas->{$id} && $id; } sub get { my ($self, $id) = @_; return undef unless defined $id; $id =~ s!(.)#$!$1!; return $self->schemas->{$id}; } sub load { return $_[0]->_load_from_url($_[1]) || $_[0]->_load_from_data($_[1]) || $_[0]->_load_from_text($_[1]) || $_[0]->_load_from_file($_[1]) || $_[0]->_load_from_app($_[1]) || $_[0]->get($_[1]) || _raise(qq(Unable to load schema "$_[1]".)); } sub resolve { my ($self, $ref, $curr) = @_; $curr //= {base_url => ''}; my ($base_url, $fragment) = split '#', $ref; my $abs_url = uri($base_url)->fragment($fragment); $abs_url = uri $abs_url, $curr->{base_url} if $curr->{base_url} and !$abs_url->is_abs; $fragment = '' unless defined $fragment; $base_url ||= $curr->{base_url} || ''; warn "[JSON::Validator] Resolve curr: ref=$ref,@{[map qq($_=$curr->{$_}), sort keys %$curr]}\n" if DEBUG; my $state = {base_url => $base_url, fragment => $fragment, source => 'unknown'}; if (defined(my $schema = $self->schemas->{$abs_url})) { @$state{qw(base_url id root schema source)} = ($abs_url, $abs_url, $schema, $schema, 'schema/abs_url'); } elsif (defined(my $root = $self->schemas->{$base_url})) { @$state{qw(base_url id root source)} = ($base_url, $base_url, $root, 'schema/base_url'); } elsif ($base_url) { $base_url = uri $base_url, $curr->{base_url} if $curr->{base_url}; my $id = $self->load($base_url); @$state{qw(base_url id root source)} = ($id, $id, $self->get($id), 'load'); $state->{root} = $self->get($id); } else { @$state{qw(id root source)} = ('', $curr->{root}, 'root'); } $fragment =~ s!%2f!~1!; # / $fragment =~ s!%7e!~0!; # ~ $fragment = url_unescape $fragment; $state->{schema} //= length $fragment ? Mojo::JSON::Pointer->new($state->{root})->get($fragment) : $state->{root}; _raise(qq[Unable to resolve "$ref" from "$state->{base_url}". ($state->{source})]) unless defined $state->{schema}; $state->{$_} //= $curr->{$_} for keys %$curr; # pass on original information warn "[JSON::Validator] Resolve state: @{[map qq($_=$state->{$_}), sort keys %$state]}\n" if DEBUG; return $state; } sub _add { my ($self, $id, $schema) = @_; $id = $self->add($id => $schema); if (ref $schema eq 'HASH') { return $schema->{'$id'} ? $self->add($schema->{'$id'} => $schema) : $schema->{id} ? $self->add($schema->{id} => $schema) : $id; } return $id; } sub _load_from_app { return undef unless $_[1] =~ m!^/!; my ($self, $url) = @_; my $id; return undef unless $self->ua->server->app; return undef if blessed $url and !$url->can('scheme'); return $id if $id = $self->exists($url); my $tx = $self->ua->get($url); my $err = $tx->error && $tx->error->{message}; _raise("GET $url: $err") if $err; warn "[JSON::Validator] Load from app $url\n" if DEBUG; return $self->_add($url => str2data $tx->res->body); } sub _load_from_data { return undef unless $_[1] =~ m!^data://([^/]*)/(.*)!; my ($self, $url) = @_; my $id; return $id if $id = $self->exists($url); my ($class, $file) = ($1, $2); # data://([^/]*)/(.*) my $text = data_section $class, $file, {encoding => 'UTF-8'}; _raise("Could not find $url") unless $text; warn "[JSON::Validator] Load from data $file in $class\n" if DEBUG; return $self->_add($url => str2data $text); } sub _load_from_file { my ($self, $file) = @_; $file =~ s!^file://!!; $file =~ s!#$!!; $file = path(split '/', url_unescape $file); return undef unless -e $file; $file = $file->realpath; my $id = uri()->new->scheme('file')->host('')->path(CASE_TOLERANT ? lc $file : "$file"); warn "[JSON::Validator] Load from file $file\n" if DEBUG; return $self->exists($id) || $self->_add($id => str2data $file->slurp); } sub _load_from_text { my ($self, $text) = @_; my $is_scalar_ref = ref $text eq 'SCALAR'; return undef unless $is_scalar_ref or $text =~ m!^\s*(?:---|\{)!s; my $id = uri->from_data($is_scalar_ref ? $$text : $text); warn "[JSON::Validator] Load from text $id\n" if DEBUG; return $self->exists($id) || $self->_add($id => str2data $is_scalar_ref ? $$text : $text); } sub _load_from_url { return undef unless $_[1] =~ m!^https?://!; my ($self, $url) = @_; my $id; return $id if $id = $self->exists($url); $url = uri($url)->fragment(undef); return $id if $id = $self->exists($url); my $cache_path = $self->cache_paths->[0]; my $cache_file = Mojo::Util::md5_sum("$url"); for (@{$self->cache_paths}) { my $path = path $_, $cache_file; warn "[JSON::Validator] Load from cache $path\n" if DEBUG and -r $path; return $self->_add($url => str2data $path->slurp) if -r $path; } my $tx = $self->ua->get($url); my $err = $tx->error && $tx->error->{message}; _raise("GET $url: $err") if $err; if ($cache_path and $cache_path ne BUNDLED_PATH and -w $cache_path) { $cache_file = path $cache_path, $cache_file; $cache_file->spurt($tx->res->body); } warn "[JSON::Validator] Load from URL $url\n" if DEBUG; return $self->_add($url => str2data $tx->res->body); } sub _raise { die JSON::Validator::Exception->new(@_)->trace } 1; =encoding utf8 =head1 NAME JSON::Validator::Store - Load and caching JSON schemas =head1 SYNOPSIS use JSON::Validator; my $jv = JSON::Validator->new; $jv->store->add("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f" => {...}); $jv->store->load("http://api.example.com/my/schema.json"); =head1 DESCRIPTION L is a class for loading and caching JSON-Schemas. =head1 ATTRIBUTES =head2 cache_paths my $store = $store->cache_paths(\@paths); my $array_ref = $store->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 schemas my $hash_ref = $store->schemas; my $store = $store->schemas({}); Hold the schemas as data structures. The keys are schema "id". =head2 ua my $ua = $store->ua; my $store = $store->ua(Mojo::UserAgent->new); Holds a L object, used by L to load a JSON schema from remote location. The default L will detect proxy settings and have L set to 3. =head1 METHODS =head2 add my $normalized_id = $store->add($id => \%schema); Used to add a schema data structure. Note that C<$id> might not be the same as C<$normalized_id>. =head2 exists my $normalized_id = $store->exists($id); Returns a C<$normalized_id> if it is present in the L. =head2 get my $schema = $store->get($normalized_id); Used to retrieve a C<$schema> added by L or L. =head2 load my $normalized_id = $store->load('https://...'); my $normalized_id = $store->load('data://main/foo.json'); my $normalized_id = $store->load('---\nid: yaml'); my $normalized_id = $store->load('{"id":"yaml"}'); my $normalized_id = $store->load(\$text); my $normalized_id = $store->load('/path/to/foo.json'); my $normalized_id = $store->load('file:///path/to/foo.json'); my $normalized_id = $store->load('/load/from/ua-server-app'); Can load a C<$schema> from many different sources. The input can be a string or a string-like object, and the L method will try to resolve it in the order listed in above. Loading schemas from C<$text> will generate an C<$normalized_id> in L looking like "urn:text:$text_checksum". This might change in the future! Loading files from disk will result in a C<$normalized_id> that always start with "file://". Loading can also be done with relative path, which will then load from: $store->ua->server->app; This method is EXPERIMENTAL, but unlikely to change significantly. =head2 resolve $hash_ref = $store->resolve($url, \%defaults); Takes a C<$url> (can also be a file, urn, ...) with or without a fragment and returns this structure about the schema: { base_url => $str, # the part before the fragment in the $url fragment => $str, # fragment part of the $url id => $str, # store ID root => ..., # the root schema schema => ..., # the schema inside "root" if fragment is present } This method is EXPERIMENTAL and can change without warning. =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Joi.pm0000644000076500000240000002472515211411456020367 0ustar jhthorsenstaffpackage JSON::Validator::Joi; use Mojo::Base -base; use Exporter 'import'; use List::Util qw(uniq); use Mojo::JSON qw(false true); use Mojo::Util; use Scalar::Util qw(blessed); use Storable qw(dclone); our @EXPORT_OK = qw(joi); # Avoid "Subroutine redefined" warnings require JSON::Validator::Schema::Draft7; has enum => sub { +[] }; has type => 'object'; has validator => sub { JSON::Validator::Schema::Draft7->new->coerce('booleans,numbers,strings') }; has [qw(format max min multiple_of regex)]; for my $attr (qw(required strict unique)) { Mojo::Util::monkey_patch(__PACKAGE__, $attr => sub { $_[0]->{$attr} = $_[1] // 1; $_[0]; }); } sub alphanum { shift->_type('string')->regex('^\w*$') } sub boolean { shift->type('boolean') } sub compile { my $self = shift; my $merged = {}; for (ref $self->type eq 'ARRAY' ? @{$self->type} : $self->type) { my $method = "_compile_$_"; my $compiled = $self->$method; @$merged{keys %$compiled} = values %$compiled; } return $merged; } sub date_time { shift->_type('string')->format('date-time') } sub email { shift->_type('string')->format('email') } sub extend { my ($self, $by) = @_; die "Cannot extend joi '@{[$self->type]}' by '@{[$by->type]}'" unless $self->type eq $by->type; my $clone = shift->new(dclone($self)); for my $key (keys %$by) { my $ref = ref $by->{$key}; $clone->{$key} = $by->{$key} unless $ref eq 'ARRAY' or $ref eq 'HASH'; } if ($self->type eq 'array') { $clone->{items} = dclone($by->{items}) if $by->{items}; } elsif ($self->type eq 'object') { $clone->{required_props} = [uniq @{$clone->{required_props}}, @{$by->{required_props}}] if defined $by->{required_props}; $clone->{properties}{$_} = dclone($by->{properties}{$_}) for keys %{$by->{properties} || {}}; } return $clone; } sub array { shift->type('array') } sub integer { shift->type('integer') } sub iso_date { shift->date_time } sub items { $_[0]->{items} = $_[1]; $_[0] } sub joi { __PACKAGE__->new(@_) } sub length { shift->min($_[0])->max($_[0]) } sub lowercase { shift->_type('string')->regex('^\p{Lowercase}*$') } sub negative { shift->_type('number')->max(0) } sub number { shift->type('number') } sub object { shift->type('object') } sub pattern { shift->regex(@_) } sub positive { shift->number->min(0) } sub props { my $self = shift->type('object'); my %properties = ref $_[0] ? %{$_[0]} : @_; while (my ($name, $property) = each %properties) { push @{$self->{required_props}}, $name if $property->{required}; $self->{properties}{$name} = $property->compile; } return $self; } sub string { shift->type('string') } sub token { shift->_type('string')->regex('^[a-zA-Z0-9_]+$') } sub uppercase { shift->_type('string')->regex('^\p{Uppercase}*$') } sub uri { shift->_type('string')->format('uri') } sub validate { my ($self, $data) = @_; return $self->validator->data($self->compile)->validate($data); } sub _compile_array { my $self = shift; my $json = {type => $self->type}; $json->{additionalItems} = false if $self->{strict}; $json->{maxItems} = $self->{max} if defined $self->{max}; $json->{minItems} = $self->{min} if defined $self->{min}; $json->{uniqueItems} = true if $self->{unique}; if ($self->{items}) { $json->{items} = $self->{items}; for my $item (ref $json->{items} eq 'ARRAY' ? @{$json->{items}} : $json->{items}) { $item = $item->compile if blessed $item and $item->can('compile'); } } return $json; } sub _compile_boolean { +{type => 'boolean'} } sub _compile_integer { shift->_compile_number } sub _compile_null { {type => shift->type} } sub _compile_number { my $self = shift; my $json = {type => $self->type}; $json->{enum} = $self->{enum} if defined $self->{enum} and @{$self->{enum}}; $json->{maximum} = $self->{max} if defined $self->{max}; $json->{minimum} = $self->{min} if defined $self->{min}; $json->{multipleOf} = $self->{multiple_of} if defined $self->{multiple_of}; return $json; } sub _compile_object { my $self = shift; my $json = {type => $self->type}; $json->{additionalProperties} = false if $self->{strict}; $json->{maxProperties} = $self->{max} if defined $self->{max}; $json->{minProperties} = $self->{min} if defined $self->{min}; $json->{patternProperties} = $self->{regex} if $self->{regex}; $json->{properties} = $self->{properties} if ref $self->{properties} eq 'HASH'; $json->{required} = $self->{required_props} if defined $self->{required_props}; return $json; } sub _compile_string { my $self = shift; my $json = {type => $self->type}; $json->{enum} = $self->{enum} if defined $self->{enum} and @{$self->{enum}}; $json->{format} = $self->{format} if defined $self->{format}; $json->{maxLength} = $self->{max} if defined $self->{max}; $json->{minLength} = $self->{min} if defined $self->{min}; $json->{pattern} = $self->{regex} if defined $self->{regex}; return $json; } sub _type { $_[0]->{type} = $_[1] unless $_[0]->{type}; return $_[0]; } sub TO_JSON { shift->compile } 1; =encoding utf8 =head1 NAME JSON::Validator::Joi - Joi validation sugar for JSON::Validator =head1 SYNOPSIS use JSON::Validator::Joi "joi"; my @errors = joi->object->props( age => joi->integer->min(0)->max(200), email => joi->regex(".@.")->required, name => joi->string->min(1), )->validate({ name => "Jan Henning", age => 34, email => "jhthorsen@cpan.org", }); die "@errors" if @errors; =head2 EXPORTED FUNCTIONS =head2 joi $joi = joi(%attrs); Same as: JSON::Validator::Joi->new(%attrs); =head1 DESCRIPTION L is an elegant DSL schema-builder. The main purpose is to build a L for L, but it can also validate data directly with sane defaults. =head1 ATTRIBUTES =head2 enum my $joi = $joi->enum(["foo", "bar"]); my $array_ref = $joi->enum; Defines a list of enum values for L, L and L. =head2 format my $joi = $joi->format("email"); my $str = $joi->format; Used to set the format of the L. See also L, L and L. =head2 max my $joi = $joi->max(10); my $int = $joi->max; =over 2 =item * array Defines the max number of items in the array. =item * integer, number Defined the max value. =item * object Defines the max number of items in the object. =item * string Defines how long the string can be. =back =head2 min my $joi = $joi->min(10); my $int = $joi->min; =over 2 =item * array Defines the minimum number of items in the array. =item * integer, number Defined the minimum value. =item * object Defines the minimum number of items in the object. =item * string Defines how short the string can be. =back =head2 multiple_of my $joi = $joi->multiple_of(3); my $int = $joi->multiple_of; Used by L and L to define what the number must be a multiple of. =head2 regex my $joi = $joi->regex("^\w+$"); my $str = $joi->regex; Defines a pattern that L will be validated against. =head2 type my $joi = $joi->type("string"); my $joi = $joi->type([qw(null integer)]); my $any = $joi->type; Sets the required type. This attribute is set by the convenience methods L, L, L and L, but can be set manually if you need to check against a list of type. =head2 validator my $joi = $joi->validator(JSON::Validator::Schema::Draft7->new); my $jv = $joi->validator; Defaults to a L object. This object is used by L. Note: This might change to L or a later schema in the future. =head1 METHODS =head2 TO_JSON Alias for L. =head2 alphanum my $joi = $joi->alphanum; Sets L to "^\w*$". =head2 array my $joi = $joi->array; Sets L to "array". =head2 boolean my $joi = $joi->boolean; Sets L to "boolean". =head2 compile my $hash_ref = $joi->compile; Will convert this object into a JSON-Schema data structure that L understands. =head2 date_time my $joi = $joi->date_time; Sets L to L. =head2 email my $joi = $joi->email; Sets L to L. =head2 extend my $new_joi = $joi->extend($other_joi_object); Will extend C<$joi> with the definitions in C<$other_joi_object> and return a new object. =head2 iso_date Alias for L. =head2 integer my $joi = $joi->integer; Sets L to "integer". =head2 items my $joi = $joi->items($joi); my $joi = $joi->items([$joi, ...]); Defines a list of items for the L type. =head2 length my $joi = $joi->length(10); Sets both L and L to the number provided. =head2 lowercase my $joi = $joi->lowercase; Will set L to only match lower case strings. =head2 negative my $joi = $joi->negative; Sets L to C<0>. =head2 number my $joi = $joi->number; Sets L to "number". =head2 object my $joi = $joi->object; Sets L to "object". =head2 pattern Alias for L. =head2 positive my $joi = $joi->positive; Sets L to C<0>. =head2 props my $joi = $joi->props(name => JSON::Validator::Joi->new->string, ...); Used to define properties for an L type. Each key is the name of the parameter and the values must be a L object. =head2 required my $joi = $joi->required; Marks the current property as required. =head2 strict my $joi = $joi->strict; Sets L and L to not allow any more items/keys than what is defined. =head2 string my $joi = $joi->string; Sets L to "string". =head2 token my $joi = $joi->token; Sets L to C<^[a-zA-Z0-9_]+$>. =head2 validate my @errors = $joi->validate($data); Used to validate C<$data> using L. Returns a list of L objects on invalid input. =head2 unique my $joi = $joi->unique; Used to force the L to only contain unique items. =head2 uppercase my $joi = $joi->uppercase; Will set L to only match upper case strings. =head2 uri my $joi = $joi->uri; Sets L to L. =head1 SEE ALSO L L. =cut JSON-Validator-5.18/lib/JSON/Validator/cache/0000755000076500000240000000000015211411467020343 5ustar jhthorsenstaffJSON-Validator-5.18/lib/JSON/Validator/cache/3d35aac549d951f4cf9182ff47bff0b40000644000076500000240000001053515210277065025027 0ustar jhthorsenstaff{ "$schema": "http://json-schema.org/draft-06/schema#", "$id": "http://json-schema.org/draft-06/schema#", "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] } }, "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri-reference" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "examples": { "type": "array", "items": {} }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "$ref": "#" }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "contains": { "$ref": "#" }, "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "$ref": "#" }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "propertyNames": { "$ref": "#" }, "const": {}, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "format": { "type": "string" }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "default": {} } JSON-Validator-5.18/lib/JSON/Validator/cache/36d1bd12eeed51e86c8695bd8876a9df0000644000076500000240000011647015210277065025052 0ustar jhthorsenstaff{ "title": "A JSON Schema for Swagger 2.0 API.", "id": "http://swagger.io/v2/schema.json#", "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "required": [ "swagger", "info", "paths" ], "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "swagger": { "type": "string", "enum": [ "2.0" ], "description": "The Swagger version of this document." }, "info": { "$ref": "#/definitions/info" }, "host": { "type": "string", "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", "description": "The host (name or ip) of the API. Example: 'swagger.io'" }, "basePath": { "type": "string", "pattern": "^/", "description": "The base path to the API. Example: '/api'." }, "schemes": { "$ref": "#/definitions/schemesList" }, "consumes": { "description": "A list of MIME types accepted by the API.", "allOf": [ { "$ref": "#/definitions/mediaTypeList" } ] }, "produces": { "description": "A list of MIME types the API can produce.", "allOf": [ { "$ref": "#/definitions/mediaTypeList" } ] }, "paths": { "$ref": "#/definitions/paths" }, "definitions": { "$ref": "#/definitions/definitions" }, "parameters": { "$ref": "#/definitions/parameterDefinitions" }, "responses": { "$ref": "#/definitions/responseDefinitions" }, "security": { "$ref": "#/definitions/security" }, "securityDefinitions": { "$ref": "#/definitions/securityDefinitions" }, "tags": { "type": "array", "items": { "$ref": "#/definitions/tag" }, "uniqueItems": true }, "externalDocs": { "$ref": "#/definitions/externalDocs" } }, "definitions": { "info": { "type": "object", "description": "General information about the API.", "required": [ "version", "title" ], "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "title": { "type": "string", "description": "A unique and precise title of the API." }, "version": { "type": "string", "description": "A semantic version number of the API." }, "description": { "type": "string", "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." }, "termsOfService": { "type": "string", "description": "The terms of service for the API." }, "contact": { "$ref": "#/definitions/contact" }, "license": { "$ref": "#/definitions/license" } } }, "contact": { "type": "object", "description": "Contact information for the owners of the API.", "additionalProperties": false, "properties": { "name": { "type": "string", "description": "The identifying name of the contact person/organization." }, "url": { "type": "string", "description": "The URL pointing to the contact information.", "format": "uri" }, "email": { "type": "string", "description": "The email address of the contact person/organization.", "format": "email" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "license": { "type": "object", "required": [ "name" ], "additionalProperties": false, "properties": { "name": { "type": "string", "description": "The name of the license type. It's encouraged to use an OSI compatible license." }, "url": { "type": "string", "description": "The URL pointing to the license.", "format": "uri" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "paths": { "type": "object", "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" }, "^/": { "$ref": "#/definitions/pathItem" } }, "additionalProperties": false }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#/definitions/schema" }, "description": "One or more JSON objects describing the schemas being consumed and produced by the API." }, "parameterDefinitions": { "type": "object", "additionalProperties": { "$ref": "#/definitions/parameter" }, "description": "One or more JSON representations for parameters" }, "responseDefinitions": { "type": "object", "additionalProperties": { "$ref": "#/definitions/response" }, "description": "One or more JSON representations for parameters" }, "externalDocs": { "type": "object", "additionalProperties": false, "description": "information about external documentation", "required": [ "url" ], "properties": { "description": { "type": "string" }, "url": { "type": "string", "format": "uri" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "examples": { "type": "object", "additionalProperties": true }, "mimeType": { "type": "string", "description": "The MIME type of the HTTP message." }, "operation": { "type": "object", "required": [ "responses" ], "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "tags": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, "summary": { "type": "string", "description": "A brief summary of the operation." }, "description": { "type": "string", "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." }, "externalDocs": { "$ref": "#/definitions/externalDocs" }, "operationId": { "type": "string", "description": "A unique identifier of the operation." }, "produces": { "description": "A list of MIME types the API can produce.", "allOf": [ { "$ref": "#/definitions/mediaTypeList" } ] }, "consumes": { "description": "A list of MIME types the API can consume.", "allOf": [ { "$ref": "#/definitions/mediaTypeList" } ] }, "parameters": { "$ref": "#/definitions/parametersList" }, "responses": { "$ref": "#/definitions/responses" }, "schemes": { "$ref": "#/definitions/schemesList" }, "deprecated": { "type": "boolean", "default": false }, "security": { "$ref": "#/definitions/security" } } }, "pathItem": { "type": "object", "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "$ref": { "type": "string" }, "get": { "$ref": "#/definitions/operation" }, "put": { "$ref": "#/definitions/operation" }, "post": { "$ref": "#/definitions/operation" }, "delete": { "$ref": "#/definitions/operation" }, "options": { "$ref": "#/definitions/operation" }, "head": { "$ref": "#/definitions/operation" }, "patch": { "$ref": "#/definitions/operation" }, "parameters": { "$ref": "#/definitions/parametersList" } } }, "responses": { "type": "object", "description": "Response objects names can either be any valid HTTP status code or 'default'.", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^([0-9]{3})$|^(default)$": { "$ref": "#/definitions/responseValue" }, "^x-": { "$ref": "#/definitions/vendorExtension" } }, "not": { "type": "object", "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } } }, "responseValue": { "oneOf": [ { "$ref": "#/definitions/response" }, { "$ref": "#/definitions/jsonReference" } ] }, "response": { "type": "object", "required": [ "description" ], "properties": { "description": { "type": "string" }, "schema": { "oneOf": [ { "$ref": "#/definitions/schema" }, { "$ref": "#/definitions/fileSchema" } ] }, "headers": { "$ref": "#/definitions/headers" }, "examples": { "$ref": "#/definitions/examples" } }, "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "headers": { "type": "object", "additionalProperties": { "$ref": "#/definitions/header" } }, "header": { "type": "object", "additionalProperties": false, "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "string", "number", "integer", "boolean", "array" ] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "vendorExtension": { "description": "Any property starting with x- is valid.", "additionalProperties": true, "additionalItems": true }, "bodyParameter": { "type": "object", "required": [ "name", "in", "schema" ], "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": [ "body" ] }, "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "schema": { "$ref": "#/definitions/schema" } }, "additionalProperties": false }, "headerParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": [ "header" ] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "type": { "type": "string", "enum": [ "string", "number", "boolean", "integer", "array" ] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "queryParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": [ "query" ] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "allowEmptyValue": { "type": "boolean", "default": false, "description": "allows sending a parameter by name only or with an empty value." }, "type": { "type": "string", "enum": [ "string", "number", "boolean", "integer", "array" ] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormatWithMulti" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "formDataParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "required": { "type": "boolean", "description": "Determines whether or not this parameter is required or optional.", "default": false }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": [ "formData" ] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "allowEmptyValue": { "type": "boolean", "default": false, "description": "allows sending a parameter by name only or with an empty value." }, "type": { "type": "string", "enum": [ "string", "number", "boolean", "integer", "array", "file" ] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormatWithMulti" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "pathParameterSubSchema": { "additionalProperties": false, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "required": [ "required" ], "properties": { "required": { "type": "boolean", "enum": [ true ], "description": "Determines whether or not this parameter is required or optional." }, "in": { "type": "string", "description": "Determines the location of the parameter.", "enum": [ "path" ] }, "description": { "type": "string", "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." }, "name": { "type": "string", "description": "The name of the parameter." }, "type": { "type": "string", "enum": [ "string", "number", "boolean", "integer", "array" ] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } } }, "nonBodyParameter": { "type": "object", "required": [ "name", "in", "type" ], "oneOf": [ { "$ref": "#/definitions/headerParameterSubSchema" }, { "$ref": "#/definitions/formDataParameterSubSchema" }, { "$ref": "#/definitions/queryParameterSubSchema" }, { "$ref": "#/definitions/pathParameterSubSchema" } ] }, "parameter": { "oneOf": [ { "$ref": "#/definitions/bodyParameter" }, { "$ref": "#/definitions/nonBodyParameter" } ] }, "schema": { "type": "object", "description": "A deterministic version of a JSON Schema object.", "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "properties": { "$ref": { "type": "string" }, "format": { "type": "string" }, "title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" }, "description": { "$ref": "http://json-schema.org/draft-04/schema#/properties/description" }, "default": { "$ref": "http://json-schema.org/draft-04/schema#/properties/default" }, "multipleOf": { "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" }, "maximum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" }, "exclusiveMaximum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" }, "minimum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" }, "exclusiveMinimum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" }, "maxLength": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" }, "minLength": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" }, "pattern": { "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" }, "maxItems": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" }, "minItems": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" }, "maxProperties": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" }, "minProperties": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" }, "enum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" }, "additionalProperties": { "anyOf": [ { "$ref": "#/definitions/schema" }, { "type": "boolean" } ], "default": {} }, "type": { "$ref": "http://json-schema.org/draft-04/schema#/properties/type" }, "items": { "anyOf": [ { "$ref": "#/definitions/schema" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/schema" } } ], "default": {} }, "allOf": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/schema" } }, "properties": { "type": "object", "additionalProperties": { "$ref": "#/definitions/schema" }, "default": {} }, "discriminator": { "type": "string" }, "readOnly": { "type": "boolean", "default": false }, "xml": { "$ref": "#/definitions/xml" }, "externalDocs": { "$ref": "#/definitions/externalDocs" }, "example": {} }, "additionalProperties": false }, "fileSchema": { "type": "object", "description": "A deterministic version of a JSON Schema object.", "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } }, "required": [ "type" ], "properties": { "format": { "type": "string" }, "title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" }, "description": { "$ref": "http://json-schema.org/draft-04/schema#/properties/description" }, "default": { "$ref": "http://json-schema.org/draft-04/schema#/properties/default" }, "required": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" }, "type": { "type": "string", "enum": [ "file" ] }, "readOnly": { "type": "boolean", "default": false }, "externalDocs": { "$ref": "#/definitions/externalDocs" }, "example": {} }, "additionalProperties": false }, "primitivesItems": { "type": "object", "additionalProperties": false, "properties": { "type": { "type": "string", "enum": [ "string", "number", "integer", "boolean", "array" ] }, "format": { "type": "string" }, "items": { "$ref": "#/definitions/primitivesItems" }, "collectionFormat": { "$ref": "#/definitions/collectionFormat" }, "default": { "$ref": "#/definitions/default" }, "maximum": { "$ref": "#/definitions/maximum" }, "exclusiveMaximum": { "$ref": "#/definitions/exclusiveMaximum" }, "minimum": { "$ref": "#/definitions/minimum" }, "exclusiveMinimum": { "$ref": "#/definitions/exclusiveMinimum" }, "maxLength": { "$ref": "#/definitions/maxLength" }, "minLength": { "$ref": "#/definitions/minLength" }, "pattern": { "$ref": "#/definitions/pattern" }, "maxItems": { "$ref": "#/definitions/maxItems" }, "minItems": { "$ref": "#/definitions/minItems" }, "uniqueItems": { "$ref": "#/definitions/uniqueItems" }, "enum": { "$ref": "#/definitions/enum" }, "multipleOf": { "$ref": "#/definitions/multipleOf" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "security": { "type": "array", "items": { "$ref": "#/definitions/securityRequirement" }, "uniqueItems": true }, "securityRequirement": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" }, "uniqueItems": true } }, "xml": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string" }, "namespace": { "type": "string" }, "prefix": { "type": "string" }, "attribute": { "type": "boolean", "default": false }, "wrapped": { "type": "boolean", "default": false } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "tag": { "type": "object", "additionalProperties": false, "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "externalDocs": { "$ref": "#/definitions/externalDocs" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "securityDefinitions": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/basicAuthenticationSecurity" }, { "$ref": "#/definitions/apiKeySecurity" }, { "$ref": "#/definitions/oauth2ImplicitSecurity" }, { "$ref": "#/definitions/oauth2PasswordSecurity" }, { "$ref": "#/definitions/oauth2ApplicationSecurity" }, { "$ref": "#/definitions/oauth2AccessCodeSecurity" } ] } }, "basicAuthenticationSecurity": { "type": "object", "additionalProperties": false, "required": [ "type" ], "properties": { "type": { "type": "string", "enum": [ "basic" ] }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "apiKeySecurity": { "type": "object", "additionalProperties": false, "required": [ "type", "name", "in" ], "properties": { "type": { "type": "string", "enum": [ "apiKey" ] }, "name": { "type": "string" }, "in": { "type": "string", "enum": [ "header", "query" ] }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2ImplicitSecurity": { "type": "object", "additionalProperties": false, "required": [ "type", "flow", "authorizationUrl" ], "properties": { "type": { "type": "string", "enum": [ "oauth2" ] }, "flow": { "type": "string", "enum": [ "implicit" ] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "authorizationUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2PasswordSecurity": { "type": "object", "additionalProperties": false, "required": [ "type", "flow", "tokenUrl" ], "properties": { "type": { "type": "string", "enum": [ "oauth2" ] }, "flow": { "type": "string", "enum": [ "password" ] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "tokenUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2ApplicationSecurity": { "type": "object", "additionalProperties": false, "required": [ "type", "flow", "tokenUrl" ], "properties": { "type": { "type": "string", "enum": [ "oauth2" ] }, "flow": { "type": "string", "enum": [ "application" ] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "tokenUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2AccessCodeSecurity": { "type": "object", "additionalProperties": false, "required": [ "type", "flow", "authorizationUrl", "tokenUrl" ], "properties": { "type": { "type": "string", "enum": [ "oauth2" ] }, "flow": { "type": "string", "enum": [ "accessCode" ] }, "scopes": { "$ref": "#/definitions/oauth2Scopes" }, "authorizationUrl": { "type": "string", "format": "uri" }, "tokenUrl": { "type": "string", "format": "uri" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { "$ref": "#/definitions/vendorExtension" } } }, "oauth2Scopes": { "type": "object", "additionalProperties": { "type": "string" } }, "mediaTypeList": { "type": "array", "items": { "$ref": "#/definitions/mimeType" }, "uniqueItems": true }, "parametersList": { "type": "array", "description": "The parameters needed to send a valid API call.", "additionalItems": false, "items": { "oneOf": [ { "$ref": "#/definitions/parameter" }, { "$ref": "#/definitions/jsonReference" } ] }, "uniqueItems": true }, "schemesList": { "type": "array", "description": "The transfer protocol of the API.", "items": { "type": "string", "enum": [ "http", "https", "ws", "wss" ] }, "uniqueItems": true }, "collectionFormat": { "type": "string", "enum": [ "csv", "ssv", "tsv", "pipes" ], "default": "csv" }, "collectionFormatWithMulti": { "type": "string", "enum": [ "csv", "ssv", "tsv", "pipes", "multi" ], "default": "csv" }, "title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" }, "description": { "$ref": "http://json-schema.org/draft-04/schema#/properties/description" }, "default": { "$ref": "http://json-schema.org/draft-04/schema#/properties/default" }, "multipleOf": { "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" }, "maximum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" }, "exclusiveMaximum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" }, "minimum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" }, "exclusiveMinimum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" }, "maxLength": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" }, "minLength": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" }, "pattern": { "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" }, "maxItems": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" }, "minItems": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" }, "enum": { "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" }, "jsonReference": { "type": "object", "required": [ "$ref" ], "additionalProperties": false, "properties": { "$ref": { "type": "string" } } } } }JSON-Validator-5.18/lib/JSON/Validator/cache/10a5eeb37fcd5d829449028f7ceb07740000644000076500000240000007642115210277065024674 0ustar jhthorsenstafftype: object required: - openapi - info - paths properties: openapi: type: string pattern: ^3\.0\.\d(-.+)?$ info: $ref: '#/definitions/Info' externalDocs: $ref: '#/definitions/ExternalDocumentation' servers: type: array items: $ref: '#/definitions/Server' security: type: array items: $ref: '#/definitions/SecurityRequirement' tags: type: array items: $ref: '#/definitions/Tag' paths: $ref: '#/definitions/Paths' components: $ref: '#/definitions/Components' patternProperties: '^x-': {} additionalProperties: false definitions: Reference: type: object required: - $ref patternProperties: '^\$ref$': type: string format: uri-reference Info: type: object required: - title - version properties: title: type: string description: type: string termsOfService: type: string format: uri-reference contact: $ref: '#/definitions/Contact' license: $ref: '#/definitions/License' version: type: string patternProperties: '^x-': {} additionalProperties: false Contact: type: object properties: name: type: string url: type: string format: uri-reference email: type: string format: email patternProperties: '^x-': {} additionalProperties: false License: type: object required: - name properties: name: type: string url: type: string format: uri-reference patternProperties: '^x-': {} additionalProperties: false Server: type: object required: - url properties: url: type: string description: type: string variables: type: object additionalProperties: $ref: '#/definitions/ServerVariable' patternProperties: '^x-': {} additionalProperties: false ServerVariable: type: object required: - default properties: enum: type: array items: type: string default: type: string description: type: string patternProperties: '^x-': {} additionalProperties: false Components: type: object properties: schemas: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Schema' responses: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Response' parameters: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Parameter' examples: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Example' requestBodies: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/RequestBody' headers: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Header' securitySchemes: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/SecurityScheme' links: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Link' callbacks: type: object patternProperties: '^[a-zA-Z0-9\.\-_]+$': oneOf: - $ref: '#/definitions/Reference' - $ref: '#/definitions/Callback' patternProperties: '^x-': {} additionalProperties: false Schema: type: object properties: title: type: string multipleOf: type: number minimum: 0 exclusiveMinimum: true maximum: type: number exclusiveMaximum: type: boolean default: false minimum: type: number exclusiveMinimum: type: boolean default: false maxLength: type: integer minimum: 0 minLength: type: integer minimum: 0 default: 0 pattern: type: string format: regex maxItems: type: integer minimum: 0 minItems: type: integer minimum: 0 default: 0 uniqueItems: type: boolean default: false maxProperties: type: integer minimum: 0 minProperties: type: integer minimum: 0 default: 0 required: type: array items: type: string minItems: 1 uniqueItems: true enum: type: array items: {} minItems: 1 uniqueItems: true type: type: string enum: - array - boolean - integer - number - object - string not: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' allOf: type: array items: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' oneOf: type: array items: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' anyOf: type: array items: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' items: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' properties: type: object additionalProperties: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' additionalProperties: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' - type: boolean default: true description: type: string format: type: string default: {} nullable: type: boolean default: false discriminator: $ref: '#/definitions/Discriminator' readOnly: type: boolean default: false writeOnly: type: boolean default: false example: {} externalDocs: $ref: '#/definitions/ExternalDocumentation' deprecated: type: boolean default: false xml: $ref: '#/definitions/XML' patternProperties: '^x-': {} additionalProperties: false Discriminator: type: object required: - propertyName properties: propertyName: type: string mapping: type: object additionalProperties: type: string XML: type: object properties: name: type: string namespace: type: string format: uri-reference prefix: type: string attribute: type: boolean default: false wrapped: type: boolean default: false patternProperties: '^x-': {} additionalProperties: false Response: type: object required: - description properties: description: type: string headers: additionalProperties: oneOf: - $ref: '#/definitions/Header' - $ref: '#/definitions/Reference' content: type: object additionalProperties: $ref: '#/definitions/MediaType' links: type: object additionalProperties: oneOf: - $ref: '#/definitions/Link' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false MediaType: oneOf: - $ref: '#/definitions/MediaTypeWithExample' - $ref: '#/definitions/MediaTypeWithExamples' MediaTypeWithExample: type: object properties: schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' example: {} encoding: type: object additionalProperties: $ref: '#/definitions/Encoding' patternProperties: '^x-': {} additionalProperties: false MediaTypeWithExamples: type: object required: - examples properties: schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' examples: type: object additionalProperties: oneOf: - $ref: '#/definitions/Example' - $ref: '#/definitions/Reference' encoding: type: object additionalProperties: $ref: '#/definitions/Encoding' patternProperties: '^x-': {} additionalProperties: false Example: type: object properties: summary: type: string description: type: string value: {} externalValue: type: string format: uri-reference patternProperties: '^x-': {} additionalProperties: false Header: oneOf: - $ref: '#/definitions/HeaderWithSchema' - $ref: '#/definitions/HeaderWithContent' HeaderWithSchema: oneOf: - $ref: '#/definitions/HeaderWithSchemaWithExample' - $ref: '#/definitions/HeaderWithSchemaWithExamples' HeaderWithSchemaWithExample: type: object required: - schema properties: description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - simple default: simple explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' example: {} patternProperties: '^x-': {} additionalProperties: false HeaderWithSchemaWithExamples: type: object required: - schema - examples properties: description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - simple default: simple explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' examples: type: object additionalProperties: oneOf: - $ref: '#/definitions/Example' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false HeaderWithContent: type: object required: - content properties: description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false content: type: object additionalProperties: $ref: '#/definitions/MediaType' minProperties: 1 maxProperties: 1 patternProperties: '^x-': {} additionalProperties: false Paths: type: object patternProperties: '^\/': $ref: '#/definitions/PathItem' '^x-': {} additionalProperties: false PathItem: type: object properties: $ref: type: string summary: type: string description: type: string get: $ref: '#/definitions/Operation' put: $ref: '#/definitions/Operation' post: $ref: '#/definitions/Operation' delete: $ref: '#/definitions/Operation' options: $ref: '#/definitions/Operation' head: $ref: '#/definitions/Operation' patch: $ref: '#/definitions/Operation' trace: $ref: '#/definitions/Operation' servers: type: array items: $ref: '#/definitions/Server' parameters: type: array items: oneOf: - $ref: '#/definitions/Parameter' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false Operation: type: object required: - responses properties: tags: type: array items: type: string summary: type: string description: type: string externalDocs: $ref: '#/definitions/ExternalDocumentation' operationId: type: string parameters: type: array items: oneOf: - $ref: '#/definitions/Parameter' - $ref: '#/definitions/Reference' requestBody: oneOf: - $ref: '#/definitions/RequestBody' - $ref: '#/definitions/Reference' responses: $ref: '#/definitions/Responses' callbacks: type: object additionalProperties: oneOf: - $ref: '#/definitions/Callback' - $ref: '#/definitions/Reference' deprecated: type: boolean default: false security: type: array items: $ref: '#/definitions/SecurityRequirement' servers: type: array items: $ref: '#/definitions/Server' patternProperties: '^x-': {} additionalProperties: false Responses: type: object properties: default: oneOf: - $ref: '#/definitions/Response' - $ref: '#/definitions/Reference' patternProperties: '[1-5](?:\d{2}|XX)': oneOf: - $ref: '#/definitions/Response' - $ref: '#/definitions/Reference' minProperties: 1 additionalProperties: false SecurityRequirement: type: object additionalProperties: type: array items: type: string Tag: type: object required: - name properties: name: type: string description: type: string externalDocs: $ref: '#/definitions/ExternalDocumentation' patternProperties: '^x-': {} additionalProperties: false ExternalDocumentation: type: object required: - url properties: description: type: string url: type: string format: uri-reference patternProperties: '^x-': {} additionalProperties: false Parameter: oneOf: - $ref: '#/definitions/ParameterWithSchema' - $ref: '#/definitions/ParameterWithContent' ParameterWithSchema: oneOf: - $ref: '#/definitions/ParameterWithSchemaWithExample' - $ref: '#/definitions/ParameterWithSchemaWithExamples' ParameterWithSchemaWithExample: oneOf: - $ref: '#/definitions/ParameterWithSchemaWithExampleInPath' - $ref: '#/definitions/ParameterWithSchemaWithExampleInQuery' - $ref: '#/definitions/ParameterWithSchemaWithExampleInHeader' - $ref: '#/definitions/ParameterWithSchemaWithExampleInCookie' ParameterWithSchemaWithExampleInPath: type: object required: - name - in - schema - required properties: name: type: string in: type: string enum: - path description: type: string required: type: boolean enum: - true deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - matrix - label - simple default: simple explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' example: {} patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExampleInQuery: type: object required: - name - in - schema properties: name: type: string in: type: string enum: - query description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - form - spaceDelimited - pipeDelimited - deepObject default: form explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' example: {} patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExampleInHeader: type: object required: - name - in - schema properties: name: type: string in: type: string enum: - header description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - simple default: simple explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' example: {} patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExampleInCookie: type: object required: - name - in - schema properties: name: type: string in: type: string enum: - cookie description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - form default: form explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' example: {} patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExamples: oneOf: - $ref: '#/definitions/ParameterWithSchemaWithExamplesInPath' - $ref: '#/definitions/ParameterWithSchemaWithExamplesInQuery' - $ref: '#/definitions/ParameterWithSchemaWithExamplesInHeader' - $ref: '#/definitions/ParameterWithSchemaWithExamplesInCookie' ParameterWithSchemaWithExamplesInPath: type: object required: - name - in - schema - required - examples properties: name: type: string in: type: string enum: - path description: type: string required: type: boolean enum: - true deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - matrix - label - simple default: simple explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' examples: type: object additionalProperties: oneOf: - $ref: '#/definitions/Example' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExamplesInQuery: type: object required: - name - in - schema - examples properties: name: type: string in: type: string enum: - query description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - form - spaceDelimited - pipeDelimited - deepObject default: form explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' examples: type: object additionalProperties: oneOf: - $ref: '#/definitions/Example' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExamplesInHeader: type: object required: - name - in - schema - examples properties: name: type: string in: type: string enum: - header description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - simple default: simple explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' examples: type: object additionalProperties: oneOf: - $ref: '#/definitions/Example' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false ParameterWithSchemaWithExamplesInCookie: type: object required: - name - in - schema - examples properties: name: type: string in: type: string enum: - cookie description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false style: type: string enum: - form default: form explode: type: boolean allowReserved: type: boolean default: false schema: oneOf: - $ref: '#/definitions/Schema' - $ref: '#/definitions/Reference' examples: type: object additionalProperties: oneOf: - $ref: '#/definitions/Example' - $ref: '#/definitions/Reference' patternProperties: '^x-': {} additionalProperties: false ParameterWithContent: oneOf: - $ref: '#/definitions/ParameterWithContentInPath' - $ref: '#/definitions/ParameterWithContentNotInPath' ParameterWithContentInPath: type: object required: - name - in - content properties: name: type: string in: type: string enum: - path description: type: string required: type: boolean enum: - true deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false content: type: object additionalProperties: $ref: '#/definitions/MediaType' minProperties: 1 maxProperties: 1 patternProperties: '^x-': {} additionalProperties: false ParameterWithContentNotInPath: type: object required: - name - in - content properties: name: type: string in: type: string enum: - query - header - cookie description: type: string required: type: boolean default: false deprecated: type: boolean default: false allowEmptyValue: type: boolean default: false content: type: object additionalProperties: $ref: '#/definitions/MediaType' minProperties: 1 maxProperties: 1 patternProperties: '^x-': {} additionalProperties: false RequestBody: type: object required: - content properties: description: type: string content: type: object additionalProperties: $ref: '#/definitions/MediaType' required: type: boolean default: false patternProperties: '^x-': {} additionalProperties: false SecurityScheme: oneOf: - $ref: '#/definitions/APIKeySecurityScheme' - $ref: '#/definitions/HTTPSecurityScheme' - $ref: '#/definitions/OAuth2SecurityScheme' - $ref: '#/definitions/OpenIdConnectSecurityScheme' APIKeySecurityScheme: type: object required: - type - name - in properties: type: type: string enum: - apiKey name: type: string in: type: string enum: - header - query - cookie description: type: string patternProperties: '^x-': {} additionalProperties: false HTTPSecurityScheme: oneOf: - $ref: '#/definitions/NonBearerHTTPSecurityScheme' - $ref: '#/definitions/BearerHTTPSecurityScheme' NonBearerHTTPSecurityScheme: type: object required: - scheme - type properties: scheme: type: string not: enum: - bearer description: type: string type: type: string enum: - http patternProperties: '^x-': {} additionalProperties: false BearerHTTPSecurityScheme: type: object required: - type - scheme properties: scheme: type: string enum: - bearer bearerFormat: type: string type: type: string enum: - http description: type: string patternProperties: '^x-': {} additionalProperties: false OAuth2SecurityScheme: type: object required: - type - flows properties: type: type: string enum: - oauth2 flows: $ref: '#/definitions/OAuthFlows' description: type: string patternProperties: '^x-': {} additionalProperties: false OpenIdConnectSecurityScheme: type: object required: - type - openIdConnectUrl properties: type: type: string enum: - openIdConnect openIdConnectUrl: type: string format: uri-reference description: type: string patternProperties: '^x-': {} additionalProperties: false OAuthFlows: type: object properties: implicit: $ref: '#/definitions/ImplicitOAuthFlow' password: $ref: '#/definitions/PasswordOAuthFlow' clientCredentials: $ref: '#/definitions/ClientCredentialsFlow' authorizationCode: $ref: '#/definitions/AuthorizationCodeOAuthFlow' patternProperties: '^x-': {} additionalProperties: false ImplicitOAuthFlow: type: object required: - authorizationUrl - scopes properties: authorizationUrl: type: string format: uri-reference refreshUrl: type: string format: uri-reference scopes: type: object additionalProperties: type: string patternProperties: '^x-': {} additionalProperties: false PasswordOAuthFlow: type: object required: - tokenUrl properties: tokenUrl: type: string format: uri-reference refreshUrl: type: string format: uri-reference scopes: type: object additionalProperties: type: string patternProperties: '^x-': {} additionalProperties: false ClientCredentialsFlow: type: object required: - tokenUrl properties: tokenUrl: type: string format: uri-reference refreshUrl: type: string format: uri-reference scopes: type: object additionalProperties: type: string patternProperties: '^x-': {} additionalProperties: false AuthorizationCodeOAuthFlow: type: object required: - authorizationUrl - tokenUrl properties: authorizationUrl: type: string format: uri-reference tokenUrl: type: string format: uri-reference refreshUrl: type: string format: uri-reference scopes: type: object additionalProperties: type: string patternProperties: '^x-': {} additionalProperties: false Link: oneOf: - $ref: '#/definitions/LinkWithOperationRef' - $ref: '#/definitions/LinkWithOperationId' LinkWithOperationRef: type: object properties: operationRef: type: string format: uri-reference parameters: type: object additionalProperties: {} requestBody: {} description: type: string server: $ref: '#/definitions/Server' patternProperties: '^x-': {} additionalProperties: false LinkWithOperationId: type: object properties: operationId: type: string parameters: type: object additionalProperties: {} requestBody: {} description: type: string server: $ref: '#/definitions/Server' patternProperties: '^x-': {} additionalProperties: false Callback: type: object additionalProperties: $ref: '#/definitions/PathItem' patternProperties: '^x-': {} Encoding: type: object properties: contentType: type: string headers: type: object additionalProperties: $ref: '#/definitions/Header' style: type: string enum: - form - spaceDelimited - pipeDelimited - deepObject explode: type: boolean allowReserved: type: boolean default: false additionalProperties: false JSON-Validator-5.18/lib/JSON/Validator/cache/d8cf7ae7a0fd14accadf5d18bc84d14f0000644000076500000240000000542215210277065025370 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/validation", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/validation": true }, "$recursiveAnchor": true, "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, "minContains": { "$ref": "#/$defs/nonNegativeInteger", "default": 1 }, "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, "required": { "$ref": "#/$defs/stringArray" }, "dependentRequired": { "type": "object", "additionalProperties": { "$ref": "#/$defs/stringArray" } }, "const": true, "enum": { "type": "array", "items": true }, "type": { "anyOf": [ { "$ref": "#/$defs/simpleTypes" }, { "type": "array", "items": { "$ref": "#/$defs/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] } }, "$defs": { "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] } } } JSON-Validator-5.18/lib/JSON/Validator/cache/a0f5b4b4e75ea17fc09e88ec0343d1480000644000076500000240000000652315210277065024737 0ustar jhthorsenstaff{ "swagger": "2.0", "info": { "version": "1.0.0", "title": "Swagger Petstore", "contact": { "name": "wordnik api team", "url": "http://developer.wordnik.com" }, "license": { "name": "Creative Commons 4.0 International", "url": "http://creativecommons.org/licenses/by/4.0/" } }, "host": "petstore.swagger.wordnik.com", "basePath": "/api", "schemes": [ "http" ], "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": { "x-mojo-controller": "t::Api", "tags": [ "pets" ], "summary": "finds pets in the system", "operationId": "listPets", "parameters": [ { "$ref": "#/parameters/limit" } ], "responses": { "200": { "description": "pet response", "schema": { "type": "array", "items": { "$ref": "#/definitions/Pet" } }, "headers": { "x-expires": { "type": "string" } } }, "default": { "description": "unexpected error", "schema": { "$ref": "#/definitions/Error" } } } }, "post": { "x-mojo-controller": "t::Api", "tags": [ "pets" ], "summary": "add pets to the system", "operationId": "addPet", "parameters": [ { "name": "data", "in": "body", "required": true, "schema": { "type": "object", "parameters": { "name": { "type": "string" }, "tag": { "type": "string" } } } } ], "responses": { "200": { "description": "pet response", "schema": { "$ref": "#/definitions/Pet" } }, "default": { "description": "unexpected error", "schema": { "$ref": "#/definitions/Error" } } } } }, "/pets/{petId}": { "post": { "x-mojo-controller": "t::Api", "tags": [ "pets" ], "summary": "Info for a specific pet", "operationId": "showPetById", "parameters": [ { "name": "petId", "in": "path", "required": true, "description": "The id of the pet to receive", "type": "integer" } ], "responses": { "200": { "description": "Expected response to a valid request", "schema": { "$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-5.18/lib/JSON/Validator/cache/630949337805585c8e52deea27d114190000644000076500000240000000051315210277065024305 0ustar jhthorsenstaff{ "type" : "object", "required": [ "errors" ], "properties": { "errors": { "type": "array", "items": { "type" : "object", "required": [ "message", "path" ], "properties": { "message": { "type": "string" }, "path": { "type": "string" } } } } } } JSON-Validator-5.18/lib/JSON/Validator/cache/eaa832720f36cff0abc20c05236a9cd90000644000076500000240000000051315210277065024767 0ustar jhthorsenstaff{ "type" : "object", "required": [ "errors" ], "properties": { "errors": { "type": "array", "items": { "type" : "object", "required": [ "message", "path" ], "properties": { "message": { "type": "string" }, "path": { "type": "string" } } } } } } JSON-Validator-5.18/lib/JSON/Validator/cache/089e74a6d17f64af17a9efd6d0fa0de60000644000076500000240000000100515210277065025100 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/content", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/content": true }, "$recursiveAnchor": true, "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "contentMediaType": { "type": "string" }, "contentEncoding": { "type": "string" }, "contentSchema": { "$recursiveRef": "#" } } } JSON-Validator-5.18/lib/JSON/Validator/cache/c6f188eb288cf986f23db49297b25e830000644000076500000240000000337115210277065024636 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/schema", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true, "https://json-schema.org/draft/2019-09/vocab/applicator": true, "https://json-schema.org/draft/2019-09/vocab/validation": true, "https://json-schema.org/draft/2019-09/vocab/meta-data": true, "https://json-schema.org/draft/2019-09/vocab/format": false, "https://json-schema.org/draft/2019-09/vocab/content": true }, "$recursiveAnchor": true, "title": "Core and Validation specifications meta-schema", "allOf": [ {"$ref": "meta/core"}, {"$ref": "meta/applicator"}, {"$ref": "meta/validation"}, {"$ref": "meta/meta-data"}, {"$ref": "meta/format"}, {"$ref": "meta/content"} ], "type": ["object", "boolean"], "properties": { "definitions": { "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", "type": "object", "additionalProperties": { "$recursiveRef": "#" }, "default": {} }, "dependencies": { "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", "type": "object", "additionalProperties": { "anyOf": [ { "$recursiveRef": "#" }, { "$ref": "meta/validation#/$defs/stringArray" } ] } } } } JSON-Validator-5.18/lib/JSON/Validator/cache/7fe97ed1a4c3fac607dd276b2b2982750000644000076500000240000000157415210277065024753 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/meta-data": true }, "$recursiveAnchor": true, "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "deprecated": { "type": "boolean", "default": false }, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true } } } JSON-Validator-5.18/lib/JSON/Validator/cache/4a31fe43be9e23ca9eb8d9e9faba88920000644000076500000240000001141715210277065025175 0ustar jhthorsenstaff{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://json-schema.org/draft-07/schema#", "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] } }, "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri-reference" }, "$comment": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "readOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "$ref": "#" }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": true }, "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "contains": { "$ref": "#" }, "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "$ref": "#" }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "propertyNames": { "$ref": "#" }, "const": true, "enum": { "type": "array", "items": true, "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "format": { "type": "string" }, "contentMediaType": { "type": "string" }, "contentEncoding": { "type": "string" }, "if": {"$ref": "#"}, "then": {"$ref": "#"}, "else": {"$ref": "#"}, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "default": true } JSON-Validator-5.18/lib/JSON/Validator/cache/4550dd8afbfee9e71377b45f5fea42ce0000644000076500000240000010565015211411261025162 0ustar jhthorsenstaff{ "id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28", "$schema": "http://json-schema.org/draft-04/schema#", "description": "The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3", "type": "object", "required": [ "openapi", "info", "paths" ], "properties": { "openapi": { "type": "string", "pattern": "^3\\.0\\.\\d(-.+)?$" }, "info": { "$ref": "#/definitions/Info" }, "externalDocs": { "$ref": "#/definitions/ExternalDocumentation" }, "servers": { "type": "array", "items": { "$ref": "#/definitions/Server" } }, "security": { "type": "array", "items": { "$ref": "#/definitions/SecurityRequirement" } }, "tags": { "type": "array", "items": { "$ref": "#/definitions/Tag" }, "uniqueItems": true }, "paths": { "$ref": "#/definitions/Paths" }, "components": { "$ref": "#/definitions/Components" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false, "definitions": { "Reference": { "type": "object", "required": [ "$ref" ], "patternProperties": { "^\\$ref$": { "type": "string", "format": "uri-reference" } } }, "Info": { "type": "object", "required": [ "title", "version" ], "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "termsOfService": { "type": "string", "format": "uri-reference" }, "contact": { "$ref": "#/definitions/Contact" }, "license": { "$ref": "#/definitions/License" }, "version": { "type": "string" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Contact": { "type": "object", "properties": { "name": { "type": "string" }, "url": { "type": "string", "format": "uri-reference" }, "email": { "type": "string", "format": "email" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "License": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string" }, "url": { "type": "string", "format": "uri-reference" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Server": { "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string" }, "description": { "type": "string" }, "variables": { "type": "object", "additionalProperties": { "$ref": "#/definitions/ServerVariable" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "ServerVariable": { "type": "object", "required": [ "default" ], "properties": { "enum": { "type": "array", "items": { "type": "string" } }, "default": { "type": "string" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Components": { "type": "object", "properties": { "schemas": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] } } }, "responses": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/Response" } ] } } }, "parameters": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/Parameter" } ] } } }, "examples": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/Example" } ] } } }, "requestBodies": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/RequestBody" } ] } } }, "headers": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/Header" } ] } } }, "securitySchemes": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/SecurityScheme" } ] } } }, "links": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/Link" } ] } } }, "callbacks": { "type": "object", "patternProperties": { "^[a-zA-Z0-9\\.\\-_]+$": { "oneOf": [ { "$ref": "#/definitions/Reference" }, { "$ref": "#/definitions/Callback" } ] } } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Schema": { "type": "object", "properties": { "title": { "type": "string" }, "multipleOf": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "maxLength": { "type": "integer", "minimum": 0 }, "minLength": { "type": "integer", "minimum": 0, "default": 0 }, "pattern": { "type": "string", "format": "regex" }, "maxItems": { "type": "integer", "minimum": 0 }, "minItems": { "type": "integer", "minimum": 0, "default": 0 }, "uniqueItems": { "type": "boolean", "default": false }, "maxProperties": { "type": "integer", "minimum": 0 }, "minProperties": { "type": "integer", "minimum": 0, "default": 0 }, "required": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true }, "enum": { "type": "array", "items": { }, "minItems": 1, "uniqueItems": false }, "type": { "type": "string", "enum": [ "array", "boolean", "integer", "number", "object", "string" ] }, "not": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] }, "allOf": { "type": "array", "items": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] } }, "oneOf": { "type": "array", "items": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] } }, "anyOf": { "type": "array", "items": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] } }, "items": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] }, "properties": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] } }, "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" }, { "type": "boolean" } ], "default": true }, "description": { "type": "string" }, "format": { "type": "string" }, "default": { }, "nullable": { "type": "boolean", "default": false }, "discriminator": { "$ref": "#/definitions/Discriminator" }, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "example": { }, "externalDocs": { "$ref": "#/definitions/ExternalDocumentation" }, "deprecated": { "type": "boolean", "default": false }, "xml": { "$ref": "#/definitions/XML" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Discriminator": { "type": "object", "required": [ "propertyName" ], "properties": { "propertyName": { "type": "string" }, "mapping": { "type": "object", "additionalProperties": { "type": "string" } } } }, "XML": { "type": "object", "properties": { "name": { "type": "string" }, "namespace": { "type": "string", "format": "uri" }, "prefix": { "type": "string" }, "attribute": { "type": "boolean", "default": false }, "wrapped": { "type": "boolean", "default": false } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Response": { "type": "object", "required": [ "description" ], "properties": { "description": { "type": "string" }, "headers": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Header" }, { "$ref": "#/definitions/Reference" } ] } }, "content": { "type": "object", "additionalProperties": { "$ref": "#/definitions/MediaType" } }, "links": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Link" }, { "$ref": "#/definitions/Reference" } ] } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "MediaType": { "type": "object", "properties": { "schema": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] }, "example": { }, "examples": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Example" }, { "$ref": "#/definitions/Reference" } ] } }, "encoding": { "type": "object", "additionalProperties": { "$ref": "#/definitions/Encoding" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false, "allOf": [ { "$ref": "#/definitions/ExampleXORExamples" } ] }, "Example": { "type": "object", "properties": { "summary": { "type": "string" }, "description": { "type": "string" }, "value": { }, "externalValue": { "type": "string", "format": "uri-reference" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Header": { "type": "object", "properties": { "description": { "type": "string" }, "required": { "type": "boolean", "default": false }, "deprecated": { "type": "boolean", "default": false }, "allowEmptyValue": { "type": "boolean", "default": false }, "style": { "type": "string", "enum": [ "simple" ], "default": "simple" }, "explode": { "type": "boolean" }, "allowReserved": { "type": "boolean", "default": false }, "schema": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] }, "content": { "type": "object", "additionalProperties": { "$ref": "#/definitions/MediaType" }, "minProperties": 1, "maxProperties": 1 }, "example": { }, "examples": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Example" }, { "$ref": "#/definitions/Reference" } ] } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false, "allOf": [ { "$ref": "#/definitions/ExampleXORExamples" }, { "$ref": "#/definitions/SchemaXORContent" } ] }, "Paths": { "type": "object", "patternProperties": { "^\\/": { "$ref": "#/definitions/PathItem" }, "^x-": { } }, "additionalProperties": false }, "PathItem": { "type": "object", "properties": { "$ref": { "type": "string" }, "summary": { "type": "string" }, "description": { "type": "string" }, "servers": { "type": "array", "items": { "$ref": "#/definitions/Server" } }, "parameters": { "type": "array", "items": { "oneOf": [ { "$ref": "#/definitions/Parameter" }, { "$ref": "#/definitions/Reference" } ] }, "uniqueItems": true } }, "patternProperties": { "^(get|put|post|delete|options|head|patch|trace)$": { "$ref": "#/definitions/Operation" }, "^x-": { } }, "additionalProperties": false }, "Operation": { "type": "object", "required": [ "responses" ], "properties": { "tags": { "type": "array", "items": { "type": "string" } }, "summary": { "type": "string" }, "description": { "type": "string" }, "externalDocs": { "$ref": "#/definitions/ExternalDocumentation" }, "operationId": { "type": "string" }, "parameters": { "type": "array", "items": { "oneOf": [ { "$ref": "#/definitions/Parameter" }, { "$ref": "#/definitions/Reference" } ] }, "uniqueItems": true }, "requestBody": { "oneOf": [ { "$ref": "#/definitions/RequestBody" }, { "$ref": "#/definitions/Reference" } ] }, "responses": { "$ref": "#/definitions/Responses" }, "callbacks": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Callback" }, { "$ref": "#/definitions/Reference" } ] } }, "deprecated": { "type": "boolean", "default": false }, "security": { "type": "array", "items": { "$ref": "#/definitions/SecurityRequirement" } }, "servers": { "type": "array", "items": { "$ref": "#/definitions/Server" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Responses": { "type": "object", "properties": { "default": { "oneOf": [ { "$ref": "#/definitions/Response" }, { "$ref": "#/definitions/Reference" } ] } }, "patternProperties": { "^[1-5](?:\\d{2}|XX)$": { "oneOf": [ { "$ref": "#/definitions/Response" }, { "$ref": "#/definitions/Reference" } ] }, "^x-": { } }, "minProperties": 1, "additionalProperties": false }, "SecurityRequirement": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } }, "Tag": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "externalDocs": { "$ref": "#/definitions/ExternalDocumentation" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "ExternalDocumentation": { "type": "object", "required": [ "url" ], "properties": { "description": { "type": "string" }, "url": { "type": "string", "format": "uri-reference" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "ExampleXORExamples": { "description": "Example and examples are mutually exclusive", "not": { "required": [ "example", "examples" ] } }, "SchemaXORContent": { "description": "Schema and content are mutually exclusive, at least one is required", "not": { "required": [ "schema", "content" ] }, "oneOf": [ { "required": [ "schema" ] }, { "required": [ "content" ], "description": "Some properties are not allowed if content is present", "allOf": [ { "not": { "required": [ "style" ] } }, { "not": { "required": [ "explode" ] } }, { "not": { "required": [ "allowReserved" ] } }, { "not": { "required": [ "example" ] } }, { "not": { "required": [ "examples" ] } } ] } ] }, "Parameter": { "type": "object", "properties": { "name": { "type": "string" }, "in": { "type": "string" }, "description": { "type": "string" }, "required": { "type": "boolean", "default": false }, "deprecated": { "type": "boolean", "default": false }, "allowEmptyValue": { "type": "boolean", "default": false }, "style": { "type": "string" }, "explode": { "type": "boolean" }, "allowReserved": { "type": "boolean", "default": false }, "schema": { "oneOf": [ { "$ref": "#/definitions/Schema" }, { "$ref": "#/definitions/Reference" } ] }, "content": { "type": "object", "additionalProperties": { "$ref": "#/definitions/MediaType" }, "minProperties": 1, "maxProperties": 1 }, "example": { }, "examples": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Example" }, { "$ref": "#/definitions/Reference" } ] } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false, "required": [ "name", "in" ], "allOf": [ { "$ref": "#/definitions/ExampleXORExamples" }, { "$ref": "#/definitions/SchemaXORContent" }, { "$ref": "#/definitions/ParameterLocation" } ] }, "ParameterLocation": { "description": "Parameter location", "oneOf": [ { "description": "Parameter in path", "required": [ "required" ], "properties": { "in": { "enum": [ "path" ] }, "style": { "enum": [ "matrix", "label", "simple" ], "default": "simple" }, "required": { "enum": [ true ] } } }, { "description": "Parameter in query", "properties": { "in": { "enum": [ "query" ] }, "style": { "enum": [ "form", "spaceDelimited", "pipeDelimited", "deepObject" ], "default": "form" } } }, { "description": "Parameter in header", "properties": { "in": { "enum": [ "header" ] }, "style": { "enum": [ "simple" ], "default": "simple" } } }, { "description": "Parameter in cookie", "properties": { "in": { "enum": [ "cookie" ] }, "style": { "enum": [ "form" ], "default": "form" } } } ] }, "RequestBody": { "type": "object", "required": [ "content" ], "properties": { "description": { "type": "string" }, "content": { "type": "object", "additionalProperties": { "$ref": "#/definitions/MediaType" } }, "required": { "type": "boolean", "default": false } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "SecurityScheme": { "oneOf": [ { "$ref": "#/definitions/APIKeySecurityScheme" }, { "$ref": "#/definitions/HTTPSecurityScheme" }, { "$ref": "#/definitions/OAuth2SecurityScheme" }, { "$ref": "#/definitions/OpenIdConnectSecurityScheme" } ] }, "APIKeySecurityScheme": { "type": "object", "required": [ "type", "name", "in" ], "properties": { "type": { "type": "string", "enum": [ "apiKey" ] }, "name": { "type": "string" }, "in": { "type": "string", "enum": [ "header", "query", "cookie" ] }, "description": { "type": "string" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "HTTPSecurityScheme": { "type": "object", "required": [ "scheme", "type" ], "properties": { "scheme": { "type": "string" }, "bearerFormat": { "type": "string" }, "description": { "type": "string" }, "type": { "type": "string", "enum": [ "http" ] } }, "patternProperties": { "^x-": { } }, "additionalProperties": false, "oneOf": [ { "description": "Bearer", "properties": { "scheme": { "type": "string", "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" } } }, { "description": "Non Bearer", "not": { "required": [ "bearerFormat" ] }, "properties": { "scheme": { "not": { "type": "string", "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" } } } } ] }, "OAuth2SecurityScheme": { "type": "object", "required": [ "type", "flows" ], "properties": { "type": { "type": "string", "enum": [ "oauth2" ] }, "flows": { "$ref": "#/definitions/OAuthFlows" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "OpenIdConnectSecurityScheme": { "type": "object", "required": [ "type", "openIdConnectUrl" ], "properties": { "type": { "type": "string", "enum": [ "openIdConnect" ] }, "openIdConnectUrl": { "type": "string", "format": "uri-reference" }, "description": { "type": "string" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "OAuthFlows": { "type": "object", "properties": { "implicit": { "$ref": "#/definitions/ImplicitOAuthFlow" }, "password": { "$ref": "#/definitions/PasswordOAuthFlow" }, "clientCredentials": { "$ref": "#/definitions/ClientCredentialsFlow" }, "authorizationCode": { "$ref": "#/definitions/AuthorizationCodeOAuthFlow" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "ImplicitOAuthFlow": { "type": "object", "required": [ "authorizationUrl", "scopes" ], "properties": { "authorizationUrl": { "type": "string", "format": "uri-reference" }, "refreshUrl": { "type": "string", "format": "uri-reference" }, "scopes": { "type": "object", "additionalProperties": { "type": "string" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "PasswordOAuthFlow": { "type": "object", "required": [ "tokenUrl", "scopes" ], "properties": { "tokenUrl": { "type": "string", "format": "uri-reference" }, "refreshUrl": { "type": "string", "format": "uri-reference" }, "scopes": { "type": "object", "additionalProperties": { "type": "string" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "ClientCredentialsFlow": { "type": "object", "required": [ "tokenUrl", "scopes" ], "properties": { "tokenUrl": { "type": "string", "format": "uri-reference" }, "refreshUrl": { "type": "string", "format": "uri-reference" }, "scopes": { "type": "object", "additionalProperties": { "type": "string" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "AuthorizationCodeOAuthFlow": { "type": "object", "required": [ "authorizationUrl", "tokenUrl", "scopes" ], "properties": { "authorizationUrl": { "type": "string", "format": "uri-reference" }, "tokenUrl": { "type": "string", "format": "uri-reference" }, "refreshUrl": { "type": "string", "format": "uri-reference" }, "scopes": { "type": "object", "additionalProperties": { "type": "string" } } }, "patternProperties": { "^x-": { } }, "additionalProperties": false }, "Link": { "type": "object", "properties": { "operationId": { "type": "string" }, "operationRef": { "type": "string", "format": "uri-reference" }, "parameters": { "type": "object", "additionalProperties": { } }, "requestBody": { }, "description": { "type": "string" }, "server": { "$ref": "#/definitions/Server" } }, "patternProperties": { "^x-": { } }, "additionalProperties": false, "not": { "description": "Operation Id and Operation Ref are mutually exclusive", "required": [ "operationId", "operationRef" ] } }, "Callback": { "type": "object", "additionalProperties": { "$ref": "#/definitions/PathItem" }, "patternProperties": { "^x-": { } } }, "Encoding": { "type": "object", "properties": { "contentType": { "type": "string" }, "headers": { "type": "object", "additionalProperties": { "oneOf": [ { "$ref": "#/definitions/Header" }, { "$ref": "#/definitions/Reference" } ] } }, "style": { "type": "string", "enum": [ "form", "spaceDelimited", "pipeDelimited", "deepObject" ] }, "explode": { "type": "boolean" }, "allowReserved": { "type": "boolean", "default": false } }, "additionalProperties": false } } } JSON-Validator-5.18/lib/JSON/Validator/cache/546acf85ddc442761c18517490215b900000644000076500000240000000350415210277065024354 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/applicator", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/applicator": true }, "$recursiveAnchor": true, "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "additionalItems": { "$recursiveRef": "#" }, "unevaluatedItems": { "$recursiveRef": "#" }, "items": { "anyOf": [ { "$recursiveRef": "#" }, { "$ref": "#/$defs/schemaArray" } ] }, "contains": { "$recursiveRef": "#" }, "additionalProperties": { "$recursiveRef": "#" }, "unevaluatedProperties": { "$recursiveRef": "#" }, "properties": { "type": "object", "additionalProperties": { "$recursiveRef": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$recursiveRef": "#" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependentSchemas": { "type": "object", "additionalProperties": { "$recursiveRef": "#" } }, "propertyNames": { "$recursiveRef": "#" }, "if": { "$recursiveRef": "#" }, "then": { "$recursiveRef": "#" }, "else": { "$recursiveRef": "#" }, "allOf": { "$ref": "#/$defs/schemaArray" }, "anyOf": { "$ref": "#/$defs/schemaArray" }, "oneOf": { "$ref": "#/$defs/schemaArray" }, "not": { "$recursiveRef": "#" } }, "$defs": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$recursiveRef": "#" } } } } JSON-Validator-5.18/lib/JSON/Validator/cache/3be3f46eb248daf48925640f8ef057e80000644000076500000240000000277315210277065024706 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/core", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true }, "$recursiveAnchor": true, "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$" }, "$schema": { "type": "string", "format": "uri" }, "$anchor": { "type": "string", "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" }, "$ref": { "type": "string", "format": "uri-reference" }, "$recursiveRef": { "type": "string", "format": "uri-reference" }, "$recursiveAnchor": { "type": "boolean", "default": false }, "$vocabulary": { "type": "object", "propertyNames": { "type": "string", "format": "uri" }, "additionalProperties": { "type": "boolean" } }, "$comment": { "type": "string" }, "$defs": { "type": "object", "additionalProperties": { "$recursiveRef": "#" }, "default": {} } } } JSON-Validator-5.18/lib/JSON/Validator/cache/d18065ce8fb1f748e766b2737bae52000000644000076500000240000000062315210277065024577 0ustar jhthorsenstaff{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/format", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/format": true }, "$recursiveAnchor": true, "title": "Format vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "format": { "type": "string" } } } JSON-Validator-5.18/lib/JSON/Validator/cache/ea34d47d4e060a1c3b12d2287aff89a70000644000076500000240000000247115210277065024725 0ustar jhthorsenstaff{ "title": "JSON schema for JSONPatch files", "$schema": "http://json-schema.org/draft-04/schema#", "type": "array", "items": { "$ref": "#/definitions/operation" }, "definitions": { "operation": { "type": "object", "required": [ "op", "path" ], "allOf": [ { "$ref": "#/definitions/path" } ], "oneOf": [ { "required": [ "value" ], "properties": { "op": { "description": "The operation to perform.", "type": "string", "enum": [ "add", "replace", "test" ] }, "value": { "description": "The value to add, replace or test." } } }, { "properties": { "op": { "description": "The operation to perform.", "type": "string", "enum": [ "remove" ] } } }, { "required": [ "from" ], "properties": { "op": { "description": "The operation to perform.", "type": "string", "enum": [ "move", "copy" ] }, "from": { "description": "A JSON Pointer path pointing to the location to move/copy from.", "type": "string" } } } ] }, "path": { "properties": { "path": { "description": "A JSON Pointer path.", "type": "string" } } } } } JSON-Validator-5.18/lib/JSON/Validator/cache/33912dbbde6e1d936140f1c82b283d010000644000076500000240000007325215210277065024563 0ustar jhthorsenstaff{ "$id": "https://spec.openapis.org/oas/3.1/schema/2021-05-20", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "openapi": { "type": "string", "pattern": "^3\\.1\\.\\d+(-.+)?$" }, "info": { "$ref": "#/$defs/info" }, "jsonSchemaDialect": { "type": "string", "format": "uri", "default": "https://spec.openapis.org/oas/3.1/dialect/base" }, "servers": { "type": "array", "items": { "$ref": "#/$defs/server" } }, "paths": { "$ref": "#/$defs/paths" }, "webhooks": { "type": "object", "additionalProperties": { "$ref": "#/$defs/path-item-or-reference" } }, "components": { "$ref": "#/$defs/components" }, "security": { "type": "array", "items": { "$ref": "#/$defs/security-requirement" } }, "tags": { "type": "array", "items": { "$ref": "#/$defs/tag" } }, "externalDocs": { "$ref": "#/$defs/external-documentation" } }, "required": [ "openapi", "info" ], "anyOf": [ { "required": [ "paths" ] }, { "required": [ "components" ] }, { "required": [ "webhooks" ] } ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false, "$defs": { "info": { "type": "object", "properties": { "title": { "type": "string" }, "summary": { "type": "string" }, "description": { "type": "string" }, "termsOfService": { "type": "string" }, "contact": { "$ref": "#/$defs/contact" }, "license": { "$ref": "#/$defs/license" }, "version": { "type": "string" } }, "required": [ "title", "version" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "contact": { "type": "object", "properties": { "name": { "type": "string" }, "url": { "type": "string" }, "email": { "type": "string" } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "license": { "type": "object", "properties": { "name": { "type": "string" }, "identifier": { "type": "string" }, "url": { "type": "string", "format": "uri" } }, "required": [ "name" ], "oneOf": [ { "required": [ "identifier" ] }, { "required": [ "url" ] } ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "server": { "type": "object", "properties": { "url": { "type": "string", "format": "uri-reference" }, "description": { "type": "string" }, "variables": { "type": "object", "additionalProperties": { "$ref": "#/$defs/server-variable" } } }, "required": [ "url" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "server-variable": { "type": "object", "properties": { "enum": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "default": { "type": "string" }, "descriptions": { "type": "string" } }, "required": [ "default" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "components": { "type": "object", "properties": { "schemas": { "type": "object", "additionalProperties": { "$dynamicRef": "#meta" } }, "responses": { "type": "object", "additionalProperties": { "$ref": "#/$defs/response-or-reference" } }, "parameters": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameter-or-reference" } }, "examples": { "type": "object", "additionalProperties": { "$ref": "#/$defs/example-or-reference" } }, "requestBodies": { "type": "object", "additionalProperties": { "$ref": "#/$defs/request-body-or-reference" } }, "headers": { "type": "object", "additionalProperties": { "$ref": "#/$defs/header-or-reference" } }, "securitySchemes": { "type": "object", "additionalProperties": { "$ref": "#/$defs/security-scheme-or-reference" } }, "links": { "type": "object", "additionalProperties": { "$ref": "#/$defs/link-or-reference" } }, "callbacks": { "type": "object", "additionalProperties": { "$ref": "#/$defs/callbacks-or-reference" } }, "pathItems": { "type": "object", "additionalProperties": { "$ref": "#/$defs/path-item-or-reference" } } }, "patternProperties": { "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": { "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", "propertyNames": { "pattern": "^[a-zA-Z0-9._-]+$" } } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "paths": { "type": "object", "patternProperties": { "^/": { "$ref": "#/$defs/path-item" } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "path-item": { "type": "object", "properties": { "summary": { "type": "string" }, "description": { "type": "string" }, "servers": { "type": "array", "items": { "$ref": "#/$defs/server" } }, "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter-or-reference" } } }, "patternProperties": { "^(get|put|post|delete|options|head|patch|trace)$": { "$ref": "#/$defs/operation" } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "path-item-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/path-item" } }, "operation": { "type": "object", "properties": { "tags": { "type": "array", "items": { "type": "string" } }, "summary": { "type": "string" }, "description": { "type": "string" }, "externalDocs": { "$ref": "#/$defs/external-documentation" }, "operationId": { "type": "string" }, "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter-or-reference" } }, "requestBody": { "$ref": "#/$defs/request-body-or-reference" }, "responses": { "$ref": "#/$defs/responses" }, "callbacks": { "type": "object", "additionalProperties": { "$ref": "#/$defs/callbacks-or-reference" } }, "deprecated": { "default": false, "type": "boolean" }, "security": { "type": "array", "items": { "$ref": "#/$defs/security-requirement" } }, "servers": { "type": "array", "items": { "$ref": "#/$defs/server" } } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "external-documentation": { "type": "object", "properties": { "description": { "type": "string" }, "url": { "type": "string", "format": "uri" } }, "required": [ "url" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "parameter": { "type": "object", "properties": { "name": { "type": "string" }, "in": { "enum": [ "query", "header", "path", "cookie" ] }, "description": { "type": "string" }, "required": { "default": false, "type": "boolean" }, "deprecated": { "default": false, "type": "boolean" }, "allowEmptyValue": { "default": false, "type": "boolean" }, "schema": { "$dynamicRef": "#meta" }, "content": { "$ref": "#/$defs/content" } }, "required": [ "in" ], "oneOf": [ { "required": [ "schema" ] }, { "required": [ "content" ] } ], "dependentSchemas": { "schema": { "properties": { "style": { "type": "string" }, "explode": { "type": "boolean" }, "allowReserved": { "default": false, "type": "boolean" } }, "allOf": [ { "$ref": "#/$defs/examples" }, { "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path" }, { "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header" }, { "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query" }, { "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie" }, { "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form" } ], "$defs": { "styles-for-path": { "if": { "properties": { "in": { "const": "path" } }, "required": [ "in" ] }, "then": { "properties": { "style": { "default": "simple", "enum": [ "matrix", "label", "simple" ] }, "required": { "const": true } }, "required": [ "required" ] } }, "styles-for-header": { "if": { "properties": { "in": { "const": "header" } }, "required": [ "in" ] }, "then": { "properties": { "style": { "default": "simple", "enum": [ "simple" ] } } } }, "styles-for-query": { "if": { "properties": { "in": { "const": "query" } }, "required": [ "in" ] }, "then": { "properties": { "style": { "default": "form", "enum": [ "form", "spaceDelimited", "pipeDelimited", "deepObject" ] } } } }, "styles-for-cookie": { "if": { "properties": { "in": { "const": "cookie" } }, "required": [ "in" ] }, "then": { "properties": { "style": { "default": "form", "enum": [ "form" ] } } } }, "styles-for-form": { "if": { "properties": { "style": { "const": "form" } }, "required": [ "style" ] }, "then": { "properties": { "explode": { "default": true } } }, "else": { "properties": { "explode": { "default": false } } } } } } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "parameter-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/parameter" } }, "request-body": { "type": "object", "properties": { "description": { "type": "string" }, "content": { "$ref": "#/$defs/content" }, "required": { "default": false, "type": "boolean" } }, "required": [ "content" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "request-body-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/request-body" } }, "content": { "type": "object", "additionalProperties": { "$ref": "#/$defs/media-type" }, "propertyNames": { "format": "media-range" } }, "media-type": { "type": "object", "properties": { "schema": { "$dynamicRef": "#meta" }, "encoding": { "type": "object", "additionalProperties": { "$ref": "#/$defs/encoding" } } }, "allOf": [ { "$ref": "#/$defs/specification-extensions" }, { "$ref": "#/$defs/examples" } ], "unevaluatedProperties": false }, "encoding": { "type": "object", "properties": { "contentType": { "type": "string", "format": "media-range" }, "headers": { "type": "object", "additionalProperties": { "$ref": "#/$defs/header-or-reference" } }, "style": { "default": "form", "enum": [ "form", "spaceDelimited", "pipeDelimited", "deepObject" ] }, "explode": { "type": "boolean" }, "allowReserved": { "default": false, "type": "boolean" } }, "allOf": [ { "$ref": "#/$defs/specification-extensions" }, { "$ref": "#/$defs/encoding/$defs/explode-default" } ], "unevaluatedProperties": false, "$defs": { "explode-default": { "if": { "properties": { "style": { "const": "form" } }, "required": [ "style" ] }, "then": { "properties": { "explode": { "default": true } } }, "else": { "properties": { "explode": { "default": false } } } } } }, "responses": { "type": "object", "properties": { "default": { "$ref": "#/$defs/response-or-reference" } }, "patternProperties": { "^[1-5][0-9X]{2}$": { "$ref": "#/$defs/response-or-reference" } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "response": { "type": "object", "properties": { "description": { "type": "string" }, "headers": { "type": "object", "additionalProperties": { "$ref": "#/$defs/header-or-reference" } }, "content": { "$ref": "#/$defs/content" }, "links": { "type": "object", "additionalProperties": { "$ref": "#/$defs/link-or-reference" } } }, "required": [ "description" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "response-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/response" } }, "callbacks": { "type": "object", "$ref": "#/$defs/specification-extensions", "additionalProperties": { "$ref": "#/$defs/path-item-or-reference" } }, "callbacks-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/callbacks" } }, "example": { "type": "object", "properties": { "summary": { "type": "string" }, "description": { "type": "string" }, "value": true, "externalValue": { "type": "string", "format": "uri" } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "example-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/example" } }, "link": { "type": "object", "properties": { "operationRef": { "type": "string", "format": "uri-reference" }, "operationId": true, "parameters": { "$ref": "#/$defs/map-of-strings" }, "requestBody": true, "description": { "type": "string" }, "body": { "$ref": "#/$defs/server" } }, "oneOf": [ { "required": [ "operationRef" ] }, { "required": [ "operationId" ] } ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "link-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/link" } }, "header": { "type": "object", "properties": { "description": { "type": "string" }, "required": { "default": false, "type": "boolean" }, "deprecated": { "default": false, "type": "boolean" }, "allowEmptyValue": { "default": false, "type": "boolean" } }, "dependentSchemas": { "schema": { "properties": { "style": { "default": "simple", "enum": [ "simple" ] }, "explode": { "default": false, "type": "boolean" }, "allowReserved": { "default": false, "type": "boolean" }, "schema": { "$dynamicRef": "#meta" } }, "$ref": "#/$defs/examples" }, "content": { "properties": { "content": { "$ref": "#/$defs/content" } } } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "header-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/header" } }, "tag": { "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "externalDocs": { "$ref": "#/$defs/external-documentation" } }, "required": [ "name" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "reference": { "type": "object", "properties": { "$ref": { "type": "string", "format": "uri-reference" }, "summary": { "type": "string" }, "description": { "type": "string" } }, "unevaluatedProperties": false }, "schema": { "$dynamicAnchor": "meta", "type": [ "object", "boolean" ] }, "security-scheme": { "type": "object", "properties": { "type": { "enum": [ "apiKey", "http", "mutualTLS", "oauth2", "openIdConnect" ] }, "description": { "type": "string" } }, "required": [ "type" ], "allOf": [ { "$ref": "#/$defs/specification-extensions" }, { "$ref": "#/$defs/security-scheme/$defs/type-apikey" }, { "$ref": "#/$defs/security-scheme/$defs/type-http" }, { "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" }, { "$ref": "#/$defs/security-scheme/$defs/type-oauth2" }, { "$ref": "#/$defs/security-scheme/$defs/type-oidc" } ], "unevaluatedProperties": false, "$defs": { "type-apikey": { "if": { "properties": { "type": { "const": "apiKey" } }, "required": [ "type" ] }, "then": { "properties": { "name": { "type": "string" }, "in": { "enum": [ "query", "header", "cookie" ] } }, "required": [ "name", "in" ] } }, "type-http": { "if": { "properties": { "type": { "const": "http" } }, "required": [ "type" ] }, "then": { "properties": { "scheme": { "type": "string" } }, "required": [ "scheme" ] } }, "type-http-bearer": { "if": { "properties": { "type": { "const": "http" }, "scheme": { "const": "bearer" } }, "required": [ "type", "scheme" ] }, "then": { "properties": { "bearerFormat": { "type": "string" } }, "required": [ "scheme" ] } }, "type-oauth2": { "if": { "properties": { "type": { "const": "oauth2" } }, "required": [ "type" ] }, "then": { "properties": { "flows": { "$ref": "#/$defs/oauth-flows" } }, "required": [ "flows" ] } }, "type-oidc": { "if": { "properties": { "type": { "const": "openIdConnect" } }, "required": [ "type" ] }, "then": { "properties": { "openIdConnectUrl": { "type": "string", "format": "uri" } }, "required": [ "openIdConnectUrl" ] } } } }, "security-scheme-or-reference": { "if": { "required": [ "$ref" ] }, "then": { "$ref": "#/$defs/reference" }, "else": { "$ref": "#/$defs/security-scheme" } }, "oauth-flows": { "type": "object", "properties": { "implicit": { "$ref": "#/$defs/oauth-flows/$defs/implicit" }, "password": { "$ref": "#/$defs/oauth-flows/$defs/password" }, "clientCredentials": { "$ref": "#/$defs/oauth-flows/$defs/client-credentials" }, "authorizationCode": { "$ref": "#/$defs/oauth-flows/$defs/authorization-code" } }, "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false, "$defs": { "implicit": { "type": "object", "properties": { "authorizationUrl": { "type": "string" }, "refreshUrl": { "type": "string" }, "scopes": { "$ref": "#/$defs/map-of-strings" } }, "required": [ "authorizationUrl", "scopes" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "password": { "type": "object", "properties": { "tokenUrl": { "type": "string" }, "refreshUrl": { "type": "string" }, "scopes": { "$ref": "#/$defs/map-of-strings" } }, "required": [ "tokenUrl", "scopes" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "client-credentials": { "type": "object", "properties": { "tokenUrl": { "type": "string" }, "refreshUrl": { "type": "string" }, "scopes": { "$ref": "#/$defs/map-of-strings" } }, "required": [ "tokenUrl", "scopes" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false }, "authorization-code": { "type": "object", "properties": { "authorizationUrl": { "type": "string" }, "tokenUrl": { "type": "string" }, "refreshUrl": { "type": "string" }, "scopes": { "$ref": "#/$defs/map-of-strings" } }, "required": [ "authorizationUrl", "tokenUrl", "scopes" ], "$ref": "#/$defs/specification-extensions", "unevaluatedProperties": false } } }, "security-requirement": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } }, "specification-extensions": { "patternProperties": { "^x-": true } }, "examples": { "properties": { "example": true, "examples": { "type": "object", "additionalProperties": { "$ref": "#/$defs/example-or-reference" } } } }, "map-of-strings": { "type": "object", "additionalProperties": { "type": "string" } } } } JSON-Validator-5.18/lib/JSON/Validator/cache/49c95b866e40f788892a7fb3c816b0e80000644000076500000240000001042715210277065024555 0ustar jhthorsenstaff{ "id": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#", "description": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "positiveInteger": { "type": "integer", "minimum": 0 }, "positiveIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "type": "object", "properties": { "id": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "multipleOf": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "maxLength": { "$ref": "#/definitions/positiveInteger" }, "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/positiveInteger" }, "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxProperties": { "$ref": "#/definitions/positiveInteger" }, "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "dependencies": { "exclusiveMaximum": [ "maximum" ], "exclusiveMinimum": [ "minimum" ] }, "default": {} } JSON-Validator-5.18/lib/JSON/Validator/Error.pm0000644000076500000240000001303015211411456020722 0ustar jhthorsenstaffpackage JSON::Validator::Error; use Mojo::Base -base; use overload q("") => \&to_string, bool => sub {1}, fallback => 1; our $MESSAGES = { allOf => {type => '/allOf Expected %3 - got %4.'}, anyOf => {type => '/anyOf Expected %3 - got %4.'}, array => { additionalItems => 'Invalid number of items: %3/%4.', maxContains => 'Contains too many items: %3/%4.', maxItems => 'Too many items: %3/%4.', minContains => 'Contains not enough items: %3/%4.', minItems => 'Not enough items: %3/%4.', uniqueItems => 'Unique items required.', contains => 'No items contained.', }, const => {const => 'Does not match const: %3.'}, enum => {enum => 'Not in enum list: %3.'}, integer => { ex_maximum => '%3 >= maximum(%4)', ex_minimum => '%3 <= minimum(%4)', maximum => '%3 > maximum(%4)', minimum => '%3 < minimum(%4)', multipleOf => 'Not multiple of %3.', }, not => {not => 'Should not match.'}, null => {type => 'Not null.'}, number => { ex_maximum => '%3 >= maximum(%4)', ex_minimum => '%3 <= minimum(%4)', maximum => '%3 > maximum(%4)', minimum => '%3 < minimum(%4)', multipleOf => 'Not multiple of %3.', }, object => { additionalProperties => 'Properties not allowed: %3.', maxProperties => 'Too many properties: %3/%4.', minProperties => 'Not enough properties: %3/%4.', required => 'Missing property.', dependencies => 'Missing property. Dependee: %3.', }, oneOf => { all_rules_match => 'All of the oneOf rules match.', n_rules_match => 'oneOf rules %3 match.', type => '/oneOf Expected %3 - got %4.', }, string => { pattern => 'String does not match %3.', maxLength => 'String is too long: %3/%4.', minLength => 'String is too short: %3/%4.', } }; has details => sub { [qw(generic generic)] }; has message => sub { my $self = shift; my $details = $self->details; my $message; if (($details->[0] || '') eq 'format') { $message = '%3'; } elsif (($details->[1] || '') eq 'type' and @$details == 3) { $message = 'Expected %1 - got %3.'; } elsif (my $group = $MESSAGES->{$details->[0]}) { $message = $group->{$details->[1] || 'default'}; } return join ' ', Failed => @$details unless defined $message; $message =~ s!\%(\d)\b!{$details->[$1 - 1] // ''}!ge; return $message; }; has path => '/'; sub new { my $class = shift; return $class->SUPER::new unless @_; # Constructed with attributes return $class->SUPER::new($_[0]) if ref $_[0] eq 'HASH'; # Constructed with ($path, ...) my $self = $class->SUPER::new; my $path = ref $_[0] ? join '/', '', map { s!~!~0!g; s!/!~1!g; $_ } @{shift(@_)} : shift || '/'; $self->{path} = $path || '/'; # Constructed with ($path, $message) or ($path, \@details) return !@_ ? $self : ref $_[0] ? $self->details(shift) : $self->message(shift); } sub to_string { sprintf '%s: %s', $_[0]->path, $_[0]->message } 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 details my $error = $error->details(["generic", "generic"]); my $error = $error->details([qw(array type object)]); my $error = $error->details([qw(format date-time Invalid)]); my $array_ref = $error->details; Details about the error: =over 2 =item 1. Often the category of tests that was run. Example values: allOf, anyOf, array, const, enum, format, integer, not, null, number, object, oneOf and string. =item 2. Often the test that failed. Example values: additionalItems, additionalProperties, const, enum, maxItems, maxLength, maxProperties, maximum, minItems, minLength. minProperties, minimum, multipleOf, not, null, pattern, required, type and uniqueItems, =item 3. The rest of the list contains parameters for the test that failed. It can be a plain human-readable string or numbers indicating things such as max/min values. =back =head2 message my $str = $error->message; A human readable description of the error. Defaults to being being constructed from L. See the C<$MESSAGES> variable in the source code for more details. As an EXPERIMENTAL hack you can localize C<$JSON::Validator::Error::MESSAGES> to get i18n support. Example: sub validate_i18n { local $JSON::Validator::Error::MESSAGES = { allOf => {type => '/allOf Forventet %3 - fikk %4.'}, }; my @error_norwegian = $jv->validate({age => 42}); } Note that the error messages might contain a mix of English and the local language. Run some tests to see how it looks. =head2 path my $str = $error->path; A JSON pointer to where the error occurred. Defaults to "/". =head1 METHODS =head2 new my $error = JSON::Validator::Error->new(\%attributes); my $error = JSON::Validator::Error->new($path, \@details); my $error = JSON::Validator::Error->new($path, \@details); Object constructor. =head2 to_string my $str = $error->to_string; Returns the "path" and "message" part as a string: "$path: $message". =head1 OPERATORS L overloads the following operators: =head2 bool my $bool = !!$error; Always true. =head2 stringify my $str = "$error"; Alias for L. =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Util.pm0000644000076500000240000002172015211411456020553 0ustar jhthorsenstaffpackage JSON::Validator::Util; use Mojo::Base -strict; use B; use Carp (); use Exporter 'import'; use JSON::Validator::Error; use List::Util; use Mojo::Collection; use Mojo::JSON; use Mojo::Loader; use Mojo::Util; use Scalar::Util 'blessed'; use constant SEREAL_SUPPORT => !$ENV{JSON_VALIDATOR_NO_SEREAL} && eval 'use Sereal::Encoder 4.00;1'; use constant CORE_BOOL => defined &builtin::is_bool; our @EXPORT_OK = ( qw(E data_checksum data_section data_type is_bool is_num is_type), qw(negotiate_content_type prefix_errors schema_type str2data), ); sub E { JSON::Validator::Error->new(@_) } my $serializer = SEREAL_SUPPORT ? \&_sereal_encode : \&_yaml_dump; sub data_checksum { return Mojo::Util::md5_sum(ref $_[0] ? $serializer->($_[0]) : defined $_[0] ? do { utf8::encode(my $x = shift); qq('$x') } : 'undef'); } sub data_section { my ($class, $file, $params) = @_; state $skip_re = qr{(^JSON::Validator|^Mojo::Base$|^Mojolicious$|\w+::_Dynamic)}; my @classes = $class ? ([$class]) : (); unless (@classes) { my $i = 0; while ($class = caller($i++)) { push @classes, [$class] unless $class =~ $skip_re; } } for my $group (@classes) { push @$group, grep { !/$skip_re/ } do { no strict 'refs'; @{"$group->[0]\::ISA"} }; for my $class (@$group) { next unless my $text = Mojo::Loader::data_section($class, $file); return Mojo::Util::encode($params->{encoding}, $text) if $params->{encoding}; return $text; } } return undef unless $params->{confess}; my $err = Mojo::JSON::encode_json([map { @$_ == 1 ? $_->[0] : $_ } @classes]); Carp::confess(qq(Could not find "$file" in __DATA__ section of $err.)); } sub data_type { my $ref = ref $_[0]; my $blessed = blessed $_[0]; return 'object' if $ref eq 'HASH'; return 'file' if $ref eq 'Mojo::Upload'; return lc $ref if $ref and !$blessed; return 'null' if !defined $_[0]; return 'boolean' if $blessed and ("$_[0]" eq "1" or !"$_[0]"); if (is_num($_[0])) { return 'integer' if grep { ($_->{type} // '') eq 'integer' } @{$_[1] || []}; return 'number'; } return $blessed || 'string'; } sub is_bool { if (CORE_BOOL) { BEGIN { warnings->unimport('experimental::builtin') if CORE_BOOL } return !!1 if builtin::is_bool $_[0]; } blessed $_[0] && ($_[0]->isa('JSON::PP::Boolean') || "$_[0]" eq "1" || !$_[0]); } sub is_num { B::svref_2object(\$_[0])->FLAGS & (B::SVp_IOK | B::SVp_NOK) && 0 + $_[0] eq $_[0] && $_[0] * 0 == 0 } sub is_type { blessed $_[0] ? $_[0]->isa($_[1]) : ref $_[0] eq $_[1] } sub negotiate_content_type { my ($accepts, $header) = @_; return '' unless $header; my %header_map = map { /^\s*([^,; ]+)(?:\s*\;\s*q\s*=\s*(\d+(?:\.\d+)?))?\s*$/i ? (lc $1, $2 // -3) : /^\s*([^,; ]+)(?:\s*\;\s*\w+\s*=\S+)?\s*$/i ? (lc $1, -1) : (lc $_, -2); } split /,/, $header; my @headers = sort { $header_map{$b} <=> $header_map{$a} } sort keys %header_map; # Check for exact match for my $ct (@$accepts) { return $ct if exists $header_map{$ct}; } # Check for closest match for my $re (map { my $re = "$_"; $re =~ s!\*!.*!g; $re = qr{$re}; [$_, $re] } grep {/\*/} @$accepts) { for my $ct (@headers) { return $re->[0] if $ct =~ $re->[1]; } } for my $re (map { local $_ = "$_"; s!\*!.*!g; qr{$_} } grep {/\*/} @headers) { for my $ct (@$accepts) { return $ct if $ct =~ $re; } } # Could not find any valid content type return ''; } sub prefix_errors { my ($type, @errors_with_index) = @_; my @errors; for my $e (@errors_with_index) { my $index = shift @$e; push @errors, map { my $msg = sprintf '/%s/%s %s', $type, $index, $_->message; $msg =~ s!(\d+)\s/!$1/!g; E +{%$_, message => $msg}; # preserve 'details', for later introspection } @$e; } return @errors; } sub schema_type { return '' if ref $_[0] ne 'HASH'; return $_[0]->{type} if $_[0]->{type}; return _guessed_right(object => $_[1]) if $_[0]->{additionalProperties}; return _guessed_right(object => $_[1]) if $_[0]->{patternProperties}; return _guessed_right(object => $_[1]) if $_[0]->{properties}; return _guessed_right(object => $_[1]) if exists $_[0]->{propertyNames}; return _guessed_right(object => $_[1]) if $_[0]->{required}; return _guessed_right(object => $_[1]) if $_[0]->{dependencies} or $_[0]->{dependentSchemas} or $_[0]->{dependentRequired}; return _guessed_right(object => $_[1]) if defined $_[0]->{maxProperties} or defined $_[0]->{minProperties}; # additionalItems is intentionally omitted - it requires 'items' to take effect return _guessed_right(array => $_[1]) if exists $_[0]->{items}; return _guessed_right(array => $_[1]) if $_[0]->{uniqueItems}; return _guessed_right(array => $_[1]) if exists $_[0]->{contains}; return _guessed_right(array => $_[1]) if exists $_[0]->{maxItems} or exists $_[0]->{minItems}; return _guessed_right(string => $_[1]) if $_[0]->{pattern}; return _guessed_right(string => $_[1]) if exists $_[0]->{maxLength} or defined $_[0]->{minLength}; return _guessed_right(number => $_[1]) if $_[0]->{multipleOf}; return _guessed_right(number => $_[1]) if defined $_[0]->{maximum} or defined $_[0]->{minimum} or defined $_[0]->{exclusiveMaximum} or defined $_[0]->{exclusiveMinimum}; return 'const' if exists $_[0]->{const}; return ''; } sub str2data { my $data; eval { $data = $_[0] =~ m!^\s*\{!s ? Mojo::JSON::decode_json($_[0]) : _yaml_load($_[0]); 1 } // Carp::confess($@); return $data; } # _guessed_right($type, $data); sub _guessed_right { return $_[0] if !defined $_[1]; return $_[0] if $_[0] eq data_type $_[1], [{type => $_[0]}]; return ''; } sub _sereal_encode { state $s = Sereal::Encoder->new({canonical => 1}); return $s->encode($_[0]); } BEGIN { if (eval 'use YAML::XS 0.67;1') { *_yaml_dump = sub { local $YAML::XS::Boolean = 'JSON::PP'; YAML::XS::Dump(@_) }; *_yaml_load = sub { local $YAML::XS::Boolean = 'JSON::PP'; YAML::XS::Load(@_) }; } else { require YAML::PP; my $pp = YAML::PP->new(boolean => 'JSON::PP'); *_yaml_dump = sub { $pp->dump_string(@_) }; *_yaml_load = sub { $pp->load_string(@_) }; } } 1; =encoding utf8 =head1 NAME JSON::Validator::Util - Utility functions for JSON::Validator =head1 DESCRIPTION L is a package containing utility functions for L. Each of the L can be imported. =head1 FUNCTIONS =head2 data_checksum $str = data_checksum $any; Will create a checksum for any data structure stored in C<$any>. =head2 data_section $str = data_section "Some::Module", "file.json"; $str = data_section "Some::Module", "file.json", {encode => 'UTF-8'}; Same as L, but will also look up the file in any inherited class. =head2 data_type $str = data_type $any; $str = data_type $any, [@schemas]; $str = data_type $any, [{type => "integer", ...}]; Returns the JSON type for C<$any>. C<$str> can be array, boolean, integer, null, number object or string. Note that a list of schemas need to be provided to differentiate between "integer" and "number". =head2 is_bool $bool = is_bool $any; Checks if C<$any> looks like a boolean. =head2 is_num $bool = is_num $any; Checks if C<$any> looks like a number. =head2 is_type $bool = is_type $any, $class; $bool = is_type $any, $type; Checks if C<$any> is a, or inherits from, C<$class> or C<$type>. =head2 negotiate_content_type $content_type = negotiate_content_type($header, \@content_types); This method can take a "Content-Type" or "Accept" header and find the closest matching content type in C<@content_types>. C<@content_types> can contain wildcards, meaning "*/*" will match anything. =head2 prefix_errors @errors = prefix_errors $prefix, @errors; Consider this internal for now. =head2 schema_type $str = schema_type $hash_ref; $str = schema_type $hash_ref, $any; Looks at C<$hash_ref> and tries to figure out what kind of type the schema represents. C<$str> can be "array", "const", "number", "object", "string", or fallback to empty string if the correct type could not be figured out. C<$any> can be provided to double check the type, so if C<$hash_ref> describes an "object", but C<$any> is an array-ref, then C<$str> will become an empty string. Example: # $str = ""; $str = schema {additionalProperties => false}, []; # $str = "object" $str = schema {additionalProperties => false}; $str = schema {additionalProperties => false}, {}; Note that this process is relatively slow, so it will make your validation faster if you specify "type". Both of the two below is valid, but the one with "type" will be faster. {"type": "object", "properties": {}} # Faster {"properties": {}} # Slower =head2 str2data $any = str2data $str; Will try to parse C<$str> as JSON or YAML, and return a data structure. =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Schema/0000755000076500000240000000000015211411467020500 5ustar jhthorsenstaffJSON-Validator-5.18/lib/JSON/Validator/Schema/Draft4.pm0000644000076500000240000001641315211411456022165 0ustar jhthorsenstaffpackage JSON::Validator::Schema::Draft4; use Mojo::Base 'JSON::Validator::Schema'; use JSON::Validator::Util qw(E data_checksum data_type is_type); use List::Util 'uniq'; has id => sub { my $data = shift->data; return is_type($data, 'HASH') ? $data->{id} || '' : ''; }; has specification => 'http://json-schema.org/draft-04/schema#'; sub _build_formats { return { 'date-time' => JSON::Validator::Formats->can('check_date_time'), 'email' => JSON::Validator::Formats->can('check_email'), 'hostname' => JSON::Validator::Formats->can('check_hostname'), 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 'regex' => JSON::Validator::Formats->can('check_regex'), 'uri' => JSON::Validator::Formats->can('check_uri'), }; } sub _validate_number_max { my ($self, $value, $state, $expected) = @_; return unless defined(my $cmp_with = $state->{schema}{maximum}); my $key = $state->{schema}{exclusiveMaximum} ? 'ex_maximum' : 'maximum'; return if $key eq 'maximum' ? $value <= $cmp_with : $value < $cmp_with; return E $state->{path}, [$expected => $key => $value, $cmp_with]; } sub _validate_number_min { my ($self, $value, $state, $expected) = @_; return unless defined(my $cmp_with = $state->{schema}{minimum}); my $key = $state->{schema}{exclusiveMinimum} ? 'ex_minimum' : 'minimum'; return if $key eq 'minimum' ? $value >= $cmp_with : $value > $cmp_with; return E $state->{path}, [$expected => $key => $value, $cmp_with]; } sub _validate_type_array { my ($self, $data, $state) = @_; return E $state->{path}, [array => type => data_type $data] if ref $data ne 'ARRAY'; return ( $self->_validate_type_array_min_max($_[1], $state), $self->_validate_type_array_unique($_[1], $state), $self->_validate_type_array_items($_[1], $state), ); } sub _validate_type_array_items { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; my @errors; if (ref $schema->{items} eq 'ARRAY') { my $additional_items = $schema->{additionalItems} // {}; my @rules = @{$schema->{items}}; if ($additional_items) { push @rules, $additional_items while @rules < @$data; } if (@rules >= @$data) { for my $i (0 .. @$data - 1) { push @errors, $self->_validate($data->[$i], $self->_state($state, path => [@$path, $i], schema => $rules[$i])); } } elsif (!$additional_items) { push @errors, E $path, [array => additionalItems => int(@$data), int(@rules)]; } } elsif (exists $schema->{items}) { for my $i (0 .. @$data - 1) { push @errors, $self->_validate($data->[$i], $self->_state($state, path => [@$path, $i], schema => $schema->{items})); } } return @errors; } sub _validate_type_array_min_max { my ($self, $data, $state) = @_; my @errors; if (defined $state->{schema}{minItems} and $state->{schema}{minItems} > @$data) { push @errors, E $state->{path}, [array => minItems => int(@$data), $state->{schema}{minItems}]; } if (defined $state->{schema}{maxItems} and $state->{schema}{maxItems} < @$data) { push @errors, E $state->{path}, [array => maxItems => int(@$data), $state->{schema}{maxItems}]; } return @errors; } sub _validate_type_array_unique { my ($self, $data, $state) = @_; return unless $state->{schema}{uniqueItems}; my (@errors, %uniq); for (@$data) { next if !$uniq{data_checksum($_)}++; push @errors, E $state->{path}, [array => 'uniqueItems']; last; } return @errors; } sub _validate_type_object { my ($self, $data, $state) = @_; return E $state->{path}, [object => type => data_type $data] if ref $data ne 'HASH'; return ( $self->_validate_type_object_min_max($_[1], $state), $self->_validate_type_object_dependencies($_[1], $state), $self->_validate_type_object_properties($_[1], $state), ); } sub _validate_type_object_min_max { my ($self, $data, $state) = @_; my @errors; my @dkeys = keys %$data; if (defined $state->{schema}{maxProperties} and $state->{schema}{maxProperties} < @dkeys) { push @errors, E $state->{path}, [object => maxProperties => int(@dkeys), $state->{schema}{maxProperties}]; } if (defined $state->{schema}{minProperties} and $state->{schema}{minProperties} > @dkeys) { push @errors, E $state->{path}, [object => minProperties => int(@dkeys), $state->{schema}{minProperties}]; } return @errors; } sub _validate_type_object_dependencies { my ($self, $data, $state) = @_; my $dependencies = $state->{schema}{dependencies} || {}; my @errors; for my $k (keys %$dependencies) { next if not exists $data->{$k}; if (ref $dependencies->{$k} eq 'ARRAY') { push @errors, map { E [@{$state->{path}}, $_], [object => dependencies => $k] } grep { !exists $data->{$_} } @{$dependencies->{$k}}; } else { push @errors, $self->_validate($data, $self->_state($state, schema => $dependencies->{$k})); } } return @errors; } sub _validate_type_object_properties { my ($self, $data, $state) = @_; my ($path, $schema, @errors, %rules) = @$state{qw(path schema)}; my $defaults = $self->{coerce}{defaults}; my @dkeys = keys %$data; if (my $properties = $schema->{properties}) { if ($defaults) { push @{$rules{$_}}, $properties->{$_} for keys %$properties; } else { defined $properties->{$_} && push @{$rules{$_}}, $properties->{$_} for @dkeys; } } for my $p (keys %{$schema->{patternProperties} || {}}) { my $r = $schema->{patternProperties}{$p}; push @{$rules{$_}}, $r for grep /$p/, @dkeys; } my $additional = exists $schema->{additionalProperties} ? $schema->{additionalProperties} : {}; if ($additional) { $additional = {} unless is_type $additional, 'HASH'; $rules{$_} ||= [$additional] for @dkeys; } elsif (my @k = grep { !$rules{$_} } @dkeys) { local $" = ', '; return E $path, [object => additionalProperties => join ', ', sort @k]; } for my $k (uniq @{$schema->{required} || []}) { next if exists $data->{$k}; push @errors, E [@$path, $k], [object => 'required']; delete $rules{$k}; } for my $k (keys %rules) { for my $r (@{$rules{$k}}) { next if !exists $data->{$k} and !$defaults; my $s2 = $self->_state($state, path => [@$path, $k], schema => $r); if ($defaults and ref $s2->{schema} eq 'HASH' and exists $s2->{schema}{default} and !exists $data->{$k}) { $data->{$k} = $s2->{schema}{default}; } next if !exists $data->{$k}; my @e = $self->_validate($data->{$k}, $s2); push @errors, @e; next if @e or !is_type $r, 'HASH'; push @errors, $self->_validate_type_enum($data->{$k}, $s2) if $r->{enum}; push @errors, $self->_validate_type_const($data->{$k}, $s2) if $r->{const}; } } return @errors; } 1; =encoding utf8 =head1 NAME JSON::Validator::Schema::Draft4 - JSON-Schema Draft 4 =head1 SYNOPSIS See L. =head1 DESCRIPTION This class represents L. =head1 ATTRIBUTES =head2 specification my $str = $schema->specification; my $schema = $schema->specification($str); Defaults to "L". =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Schema/Draft201909.pm0000644000076500000240000001503215211411456022562 0ustar jhthorsenstaffpackage JSON::Validator::Schema::Draft201909; use Mojo::Base 'JSON::Validator::Schema'; use JSON::Validator::Schema::Draft4; use JSON::Validator::Schema::Draft6; use JSON::Validator::Schema::Draft7; use JSON::Validator::URI qw(uri); use JSON::Validator::Util qw(E is_bool is_type); has moniker => 'draft2019'; has specification => 'https://json-schema.org/draft/2019-09/schema'; has _ref_keys => sub { [qw($ref $recursiveRef)] }; sub _build_formats { my $formats = shift->JSON::Validator::Schema::Draft7::_build_formats; $formats->{duration} = JSON::Validator::Formats->can('check_duration'); $formats->{uuid} = JSON::Validator::Formats->can('check_uuid'); return $formats; } sub _bundle_ref_path_expand { local $_ = $_[1]; s!^\$defs/!!; return '$defs', $_; } sub _extract_ref_from_schema { $_[1]->{'$recursiveRef'} // $_[1]->{'$ref'} } sub _resolve_object { my ($self, $state, $schema, $refs, $found) = @_; if ($schema->{'$id'} and !ref $schema->{'$id'}) { my $id = uri $schema->{'$id'}, $state->{base_url}; $self->store->add($id => $schema); $state = {%$state}; # make sure we don't mutate $state ref $state->{base_url} = $id->clone->fragment(undef); } if ($schema->{'$anchor'} && !ref $schema->{'$anchor'}) { my $id = uri(uri()->new->fragment($schema->{'$anchor'}), $state->{base_url}); $self->store->add($id => $schema); $state = {%$state, base_url => $id->fragment(undef)->to_string}; } if ($found->{'$recursiveRef'} = $schema->{'$recursiveRef'} && !ref $schema->{'$recursiveRef'}) { push @$refs, [$schema, $state]; } if ($found->{'$ref'} = $schema->{'$ref'} && !ref $schema->{'$ref'}) { push @$refs, [$schema, $state]; } return $state; } sub _state { my ($self, $curr, %override) = @_; my $schema = $override{schema}; my (%alongside, %seen); while (ref $schema eq 'HASH') { last unless my $ref = $schema->{'$ref'} || $schema->{'$recursiveRef'}; last if ref $ref; last if $seen{$schema}++; %alongside = (%alongside, %$schema); $schema = $self->_refs->{$schema}{schema} // Carp::confess(qq(You have to call resolve() before validate() to lookup "$ref".)); } return {%$curr, %override, schema => $schema} unless ref $schema eq 'HASH'; delete $alongside{$_} for qw($anchor $id $recursiveAnchor $recursiveRef $ref); return {%$curr, %override, schema => {%alongside, %$schema}}; } sub _validate_type_array_contains { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; return unless exists $schema->{contains}; return if defined $schema->{minContains} and $schema->{minContains} == 0 and !$schema->{maxContains}; return if defined $schema->{minContains} and $schema->{minContains} == 0 and !@$data; my ($n_valid, @e, @errors) = (0); for my $i (0 .. @$data - 1) { my @tmp = $self->_validate($data->[$i], $self->_state($state, path => [@$path, $i], schema => $schema->{contains})); @tmp ? push @e, \@tmp : $n_valid++; } push @errors, map {@$_} @e if @e >= @$data; push @errors, E $path, [array => 'maxContains', int @$data, $schema->{maxContains}] if exists $schema->{maxContains} and $n_valid > $schema->{maxContains}; push @errors, E $path, [array => 'minContains', int @$data, $schema->{minContains}] if $schema->{minContains} and $n_valid < $schema->{minContains}; push @errors, E $path, [array => 'contains'] if not @$data; return @errors; } sub _validate_type_object_dependencies { my ($self, $data, $state) = @_; my $dependencies = $state->{schema}{dependentSchemas} || {}; my @errors; for my $k (keys %$dependencies) { next if not exists $data->{$k}; if (ref $dependencies->{$k} eq 'ARRAY') { push @errors, map { E [@{$state->{path}}, $_], [object => dependencies => $k] } grep { !exists $data->{$_} } @{$dependencies->{$k}}; } else { push @errors, $self->_validate($data, $self->_state($state, schema => $dependencies->{$k})); } } $dependencies = $state->{schema}{dependentRequired} || {}; for my $k (keys %$dependencies) { next if not exists $data->{$k}; push @errors, map { E [@{$state->{path}}, $_], [object => dependencies => $k] } grep { !exists $data->{$_} } @{$dependencies->{$k}}; } return @errors; } *_validate_number_max = \&JSON::Validator::Schema::Draft6::_validate_number_max; *_validate_number_min = \&JSON::Validator::Schema::Draft6::_validate_number_min; *_validate_type_array = \&JSON::Validator::Schema::Draft6::_validate_type_array; *_validate_type_array_items = \&JSON::Validator::Schema::Draft4::_validate_type_array_items; *_validate_type_array_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_array_min_max; *_validate_type_array_unique = \&JSON::Validator::Schema::Draft4::_validate_type_array_unique; *_validate_type_object = \&JSON::Validator::Schema::Draft6::_validate_type_object; *_validate_type_object_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_object_min_max; *_validate_type_object_names = \&JSON::Validator::Schema::Draft6::_validate_type_object_names; *_validate_type_object_properties = \&JSON::Validator::Schema::Draft4::_validate_type_object_properties; 1; =encoding utf8 =head1 NAME JSON::Validator::Schema::Draft201909 - JSON-Schema Draft 2019-09 =head1 SYNOPSIS See L. =head1 DESCRIPTION This class represents L. Support for parsing the draft is not yet complete. Look at L for the most recent overview of what is not yet supported. Currently less than 1% of the official test suite gets skipped. Here is a list of known limitations: =over 2 =item * Float and integers are equal up to 64-bit representation limits This module is unable to say that the 64-bit number "9007199254740992.0" is the same as "9007199254740992". =item * unevaluatedItems See L =item * unevaluatedProperties L and L needs to track what has been validated or not using annotations. This is not yet supported. =item * $recursiveAnchor Basic support for C<$recursiveRef> is supported, but using it together with C<$recursiveAnchor> is not. =back =head1 ATTRIBUTES =head2 specification my $str = $schema->specification; Defaults to "L". =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Schema/Draft7.pm0000644000076500000240000000670015211411456022166 0ustar jhthorsenstaffpackage JSON::Validator::Schema::Draft7; use Mojo::Base 'JSON::Validator::Schema'; use JSON::Validator::Schema::Draft4; use JSON::Validator::Schema::Draft6; use JSON::Validator::Util qw(E is_type); has id => sub { my $data = shift->data; return is_type($data, 'HASH') ? $data->{'$id'} || '' : ''; }; has specification => 'http://json-schema.org/draft-07/schema#'; sub _build_formats { return { 'date' => JSON::Validator::Formats->can('check_date'), 'date-time' => JSON::Validator::Formats->can('check_date_time'), 'email' => JSON::Validator::Formats->can('check_email'), 'hostname' => JSON::Validator::Formats->can('check_hostname'), 'idn-email' => JSON::Validator::Formats->can('check_idn_email'), 'idn-hostname' => JSON::Validator::Formats->can('check_idn_hostname'), 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 'iri' => JSON::Validator::Formats->can('check_iri'), 'iri-reference' => JSON::Validator::Formats->can('check_iri_reference'), 'json-pointer' => JSON::Validator::Formats->can('check_json_pointer'), 'regex' => JSON::Validator::Formats->can('check_regex'), 'relative-json-pointer' => JSON::Validator::Formats->can('check_relative_json_pointer'), 'time' => JSON::Validator::Formats->can('check_time'), 'uri' => JSON::Validator::Formats->can('check_uri'), 'uri-reference' => JSON::Validator::Formats->can('check_uri_reference'), 'uri-template' => JSON::Validator::Formats->can('check_uri_template'), }; } *_resolve_object = \&JSON::Validator::Schema::Draft6::_resolve_object; *_validate_number_max = \&JSON::Validator::Schema::Draft6::_validate_number_max; *_validate_number_min = \&JSON::Validator::Schema::Draft6::_validate_number_min; *_validate_type_array = \&JSON::Validator::Schema::Draft6::_validate_type_array; *_validate_type_array_contains = \&JSON::Validator::Schema::Draft6::_validate_type_array_contains; *_validate_type_array_items = \&JSON::Validator::Schema::Draft4::_validate_type_array_items; *_validate_type_array_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_array_min_max; *_validate_type_array_unique = \&JSON::Validator::Schema::Draft4::_validate_type_array_unique; *_validate_type_object = \&JSON::Validator::Schema::Draft6::_validate_type_object; *_validate_type_object_dependencies = \&JSON::Validator::Schema::Draft4::_validate_type_object_dependencies; *_validate_type_object_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_object_min_max; *_validate_type_object_names = \&JSON::Validator::Schema::Draft6::_validate_type_object_names; *_validate_type_object_properties = \&JSON::Validator::Schema::Draft4::_validate_type_object_properties; 1; =encoding utf8 =head1 NAME JSON::Validator::Schema::Draft7 - JSON-Schema Draft 7 =head1 SYNOPSIS See L. =head1 DESCRIPTION This class represents L. =head1 ATTRIBUTES =head2 specification my $str = $schema->specification; Defaults to "L". =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Schema/OpenAPIv2.pm0000644000076500000240000005140315211411456022542 0ustar jhthorsenstaffpackage JSON::Validator::Schema::OpenAPIv2; use Mojo::Base 'JSON::Validator::Schema::Draft4'; use JSON::Validator::Util qw(E data_type negotiate_content_type schema_type); use Mojo::Collection; my $X_RE = qr{^x-}; # Some of the keywords are OpenAPIv3 keywords my %SKIP_KEYWORDS_IN_PATH = map { ($_, 1) } qw(description parameters servers summary); has errors => sub { my $self = shift; my $validator = $self->new(store => $self->store, _refs => {})->coerce('numbers,strings'); return [$validator->resolve($self->specification)->validate($self->resolve->data)]; }; has moniker => 'openapiv2'; has specification => 'http://swagger.io/v2/schema.json'; sub add_default_response { my ($self, $params) = ($_[0], shift->_params_for_add_default_response(@_)); my $definitions = $self->data->{definitions} ||= {}; $definitions->{$params->{name}} ||= $params->{schema}; my $ref = {'$ref' => sprintf '%s#/definitions/%s', $self->id, $params->{name}}; $self->_register_ref($ref, schema => $definitions->{$params->{name}}); for my $route ($self->routes->each) { my $op = $self->get([paths => @$route{qw(path method)}]); for my $status (@{$params->{status}}) { $op->{responses}{$status} ||= {description => $params->{description}, schema => $ref}; } } return $self; } sub base_url { my ($self, $url) = @_; my $spec = $self->data; # Get unless ($url) { $url = Mojo::URL->new; my @host_port = split ':', ($spec->{host} // ''); $url->host($host_port[0]) if $host_port[0]; $url->port($host_port[1]) if $host_port[1]; $url->path($spec->{basePath} || '/'); $url->scheme($spec->{schemes} && $spec->{schemes}[0] || undef); $url->host('localhost') if $url->scheme and !$url->host; return $url; } # Set $url = Mojo::URL->new($url)->to_abs($self->base_url); $spec->{host} = $url->host_port if $url->host_port; $spec->{schemes}[0] = $url->scheme if $url->scheme; $spec->{basePath} = $url->path->to_string || '/'; return $self; } sub coerce { my $self = shift; return $self->SUPER::coerce(@_) if @_; $self->{coerce} ||= {booleans => 1, numbers => 1, strings => 1}; return $self->{coerce}; } sub new { my $self = shift->SUPER::new(@_); $self->coerce; # make sure this attribute is built $self; } sub parameters_for_request { my $self = shift; my ($method, $path) = (lc $_[0][0], $_[0][1]); my $cache_key = "parameters_for_request:$method:$path"; return $self->{cache}{$cache_key} if $self->{cache}{$cache_key}; return undef unless $self->get([paths => $path, $method]); my @accepts = map {@$_} $self->_find_all_nodes([paths => $path, $method], 'consumes'); my @parameters; for my $param (map {@$_} $self->_find_all_nodes([paths => $path, $method], 'parameters')) { push @parameters, {%$param}; $parameters[-1]{type} ||= schema_type($param->{schema} || $param); $parameters[-1]{accepts} = \@accepts if $param->{in} eq 'body'; } return $self->{cache}{$cache_key} = \@parameters; } sub parameters_for_response { my $self = shift; my ($method, $path, $status) = (lc $_[0][0], $_[0][1], $_[0][2] || 200); $status ||= 200; my $cache_key = "parameters_for_response:$method:$path:$status"; return $self->{cache}{$cache_key} if $self->{cache}{$cache_key}; my $responses = $self->get([paths => $path, $method, 'responses']); my $response = $responses->{$status} || $responses->{default}; return undef unless $response; my @parameters; if (my $headers = $response->{headers}) { push @parameters, map { +{%{$headers->{$_}}, in => 'header', name => $_} } sort keys %$headers; } my @accepts = $self->_find_all_nodes([paths => $path, $method], 'produces'); if (exists $response->{schema}) { push @parameters, {%$response, in => 'body', name => 'body', accepts => pop @accepts || []}; } return $self->{cache}{$cache_key} = \@parameters; } sub routes { my $self = shift; my @paths_with_weight; for my $path (keys %{$self->get('/paths') || {}}) { next if $path =~ $X_RE; my $weight = '1'; $weight .= /^\{/ ? 0 : 1 for split '/', $path; push @paths_with_weight, [int $weight, $path]; } my @operations; for (sort { $b->[0] <=> $a->[0] || $a->[1] cmp $b->[1] } @paths_with_weight) { my $path = $_->[1]; next unless my $path_item = $self->get([paths => $path]); $path_item = $self->get($path_item->{'$ref'} =~ s!^#!!r) if $path_item->{'$ref'}; for my $method (sort keys %$path_item) { next if $method =~ $X_RE or $SKIP_KEYWORDS_IN_PATH{$method}; push @operations, {method => $method, operation_id => $path_item->{$method}{operationId}, path => $path}; } } return Mojo::Collection->new(@operations); } sub validate_request { my ($self, $method_path, $req) = @_; my $parameters = $self->parameters_for_request($method_path); my %get; for my $in (qw(body cookie formData header path query)) { $get{$in} = ref $req->{$in} eq 'CODE' ? $req->{$in} : sub { my ($name, $param) = @_; return {exists => exists $req->{$in}, value => $req->{$in}} unless defined $name; return {exists => exists $req->{$in}{$name}, value => $req->{$in}{$name}}; }; } return $self->_validate_request_or_response(request => $parameters, \%get); } sub validate_response { my ($self, $method_path_status, $res) = @_; my $parameters = $self->parameters_for_response($method_path_status); my %get; for my $in (qw(body cookie header)) { $get{$in} = ref $res->{$in} eq 'CODE' ? $res->{$in} : sub { my ($name, $param) = @_; return {exists => exists $res->{$in}{$name}, value => $res->{$in}{$name}}; }; } return $self->_validate_request_or_response(response => $parameters, \%get); } sub _build_formats { my $self = shift; return { 'binary' => sub {undef}, 'byte' => JSON::Validator::Formats->can('check_byte'), 'date' => JSON::Validator::Formats->can('check_date'), 'date-time' => JSON::Validator::Formats->can('check_date_time'), 'double' => JSON::Validator::Formats->can('check_double'), 'email' => JSON::Validator::Formats->can('check_email'), 'float' => JSON::Validator::Formats->can('check_float'), 'hostname' => JSON::Validator::Formats->can('check_hostname'), 'int32' => JSON::Validator::Formats->can('check_int32'), 'int64' => JSON::Validator::Formats->can('check_int64'), 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 'password' => sub {undef}, 'regex' => JSON::Validator::Formats->can('check_regex'), 'uri' => JSON::Validator::Formats->can('check_uri'), }; } sub _bundle_ref { my ($self, $state, $source, $target) = @_; # Recurse into the inner $ref to avoid invalid schema my $source_state = $self->store->resolve($source->{'$ref'}, $state); return $self->_bundle_ref($source_state, $source_state->{schema}, $target) if $source_state->{schema}{'$ref'}; # Replace "paths" inline if (@{$state->{schema_path}} == 2 and $state->{schema_path}[0] eq 'paths') { delete $state->{seen_schema}{$target}; # path definitions should not recurse return [$source_state, $source_state->{schema}, $target, $state->{schema_path}]; } return $self->SUPER::_bundle_ref($state, $source, $target); } sub _bundle_ref_path_expand { my ($self, $state, $ref) = @_; return (parameters => $ref) if $state->{schema}{in}; return ($1 => $2) if $ref =~ m!\b(definitions|parameters|responses)\b\.*(?:json|yaml)?(.+)$!; return ($1, $2 ? ($2) : ()) if $ref =~ m!\b(x-[^/]+)/?\b(.*)!; return (definitions => $ref); } sub _coerce_arrays { my ($self, $val, $param) = @_; my $data_type = data_type $val->{value}; my $schema = $param->{schema} || $param; # $param->{schema} is for OpenAPIv3 $schema = $self->get($schema->{'$ref'} =~ s!^#!!r) if $schema->{'$ref'}; my $schema_type = schema_type $schema; return $val->{value} = [$val->{value}] if $schema_type eq 'array' and $data_type ne 'array'; return $val->{value} = @{$val->{value}} ? $val->{value}[-1] : undef if $schema_type ne 'array' and $data_type eq 'array'; } sub _coerce_default_value { my ($self, $val, $param) = @_; if ($param->{schema} and exists $param->{schema}{default}) { @$val{qw(exists value)} = (1, $param->{schema}{default}); } elsif (exists $param->{default}) { @$val{qw(exists value)} = (1, $param->{default}); } } sub _coerce_parameter_format { my ($self, $val, $param) = @_; return unless $val->{exists}; return unless my $format = $param->{collectionFormat} || ($param->{type} eq 'array' ? 'csv' : ''); return $val->{value} = ref $val->{value} eq 'ARRAY' ? $val->{value} : [$val->{value}] if $format eq 'multi'; $val->{value} = $val->{value}[0] // '' if ref $val->{value} eq 'ARRAY'; return $val->{value} = [split /\|/, $val->{value}] if $format eq 'pipes'; return $val->{value} = [split /[ ]/, $val->{value}] if $format eq 'ssv'; return $val->{value} = [split /\t/, $val->{value}] if $format eq 'tsv'; return $val->{value} = [split /,/, $val->{value}]; } sub _default_response_schema { return { type => 'object', required => ['errors'], properties => { errors => { type => 'array', items => { type => 'object', required => ['message'], properties => {message => {type => 'string'}, path => {type => 'string'}}, }, }, }, }; } sub _extract_ref_from_schema { $_[1]->{'$ref'} =~ /^\w+$/ ? "#/definitions/$_[1]->{'$ref'}" : $_[1]->{'$ref'} } sub _find_all_nodes { my ($self, $pointer, $leaf) = @_; my @found; push @found, $self->data->{$leaf} if ref $self->data->{$leaf} eq 'ARRAY'; my @path; for my $p (@$pointer) { push @path, $p; my $node = $self->get([@path]); push @found, $node->{$leaf} if ref $node->{$leaf} eq 'ARRAY'; } # Resolve all $ref before returning the data for my $items (@found) { for my $schema (@$items) { my %seen; while (ref $schema eq 'HASH' and $schema->{'$ref'} and !ref $schema->{'$ref'}) { last if $seen{$schema}++; $schema = $self->_refs->{$schema}{schema} // Carp::confess(qq(resolve() must be called before validate() to lookup "$schema->{'$ref'}".)); } } } return @found; } sub _get_parameter_value { my ($self, $param, $get) = @_; my $val = $get->{$param->{in}}->($param->{name}, $param); @$val{qw(in name)} = (@$param{qw(in name)}); return $val; } sub _params_for_add_default_response { my $self = shift; my $params = shift || {}; return { description => $params->{description} || 'Default response.', name => $params->{name} || 'DefaultResponse', schema => $params->{schema} || _default_response_schema(), status => $params->{status} || [400, 401, 404, 500, 501], }; } sub _prefix_error_path { return join '', "/$_[0]", $_[1] =~ /\w/ ? ($_[1]) : (); } sub _validate_body { my ($self, $direction, $val, $param) = @_; if ($val->{accept}) { $val->{content_type} = negotiate_content_type($param->{accepts}, $val->{accept}); $val->{valid} = $val->{content_type} ? 1 : 0; return E "/header/Accept", [join(', ', @{$param->{accepts}}), type => $val->{accept}] unless $val->{valid}; } if (@{$param->{accepts}} and $val->{content_type}) { my $negotiated = negotiate_content_type($param->{accepts}, $val->{content_type}); $val->{valid} = $negotiated ? 1 : 0; return E "/$param->{name}", [join(', ', @{$param->{accepts}}) => type => $val->{content_type}] unless $negotiated; } if ($param->{required} and !$val->{exists}) { $val->{valid} = 0; return E "/$param->{name}", [qw(object required)]; } if ($val->{exists}) { local $self->{"validate_$direction"} = 1; $val->{content_type} //= $param->{accepts}[0]; my @errors = map { $_->path(_prefix_error_path($param->{name}, $_->path)); $_ } $self->validate($val->{value}, $param->{schema}); $val->{valid} = @errors ? 0 : 1; return @errors; } return; } sub _validate_id { } sub _validate_request_or_response { my ($self, $direction, $parameters, $get) = @_; my @errors; for my $param (@$parameters) { my $val = $self->_get_parameter_value($param, $get); $self->_coerce_parameter_format($val, $param) if $direction eq 'request' and $param->{in} ne 'body'; $self->_coerce_default_value($val, $param) unless $val->{exists}; if ($param->{in} eq 'body') { push @errors, $self->_validate_body($direction, $val, $param); next; } if ($val->{exists}) { $self->_coerce_arrays($val, $param); local $self->{"validate_$direction"} = 1; my @e = map { $_->path(_prefix_error_path($param->{name}, $_->path)); $_ } $self->validate($val->{value}, $param->{schema} || $param); push @errors, @e; $val->{valid} = @e ? 0 : 1; } elsif ($param->{required}) { push @errors, E "/$param->{name}", [qw(object required)]; $val->{valid} = 0; } } return @errors; } sub _validate_type_file { my ($self, $data, $state) = @_; return unless $state->{schema}{required} and (not defined $data or not length $data); return E $state->{path}, 'Missing property.'; } sub _validate_type_object { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; return E $path, [object => type => data_type $data] if ref $data ne 'HASH'; return shift->SUPER::_validate_type_object(@_) unless $self->{validate_request}; my (@errors, %ro); for my $name (keys %{$schema->{properties} || {}}) { next unless $self->_get(['readOnly'], {path => [], schema => $schema->{properties}{$name}}); push @errors, E [@$path, $name], "Read-only." if exists $data->{$name}; $ro{$name} = 1; } local $schema->{required} = [grep { !$ro{$_} } @{$schema->{required} || []}]; my $discriminator = $schema->{discriminator}; if ($discriminator and !$self->{inside_discriminator}) { return E $path, "Discriminator $discriminator has no value." unless my $name = $data->{$discriminator}; return E $path, "No definition for discriminator $name." unless my $ds = $self->get("/definitions/$name"); local $self->{inside_discriminator} = 1; return $self->_validate($data, $self->_state($state, schema => $ds)); } return ( @errors, $self->_validate_type_object_min_max($_[1], $state), $self->_validate_type_object_dependencies($_[1], $state), $self->_validate_type_object_properties($_[1], $state), ); } 1; =encoding utf8 =head1 NAME JSON::Validator::Schema::OpenAPIv2 - OpenAPI version 2 / Swagger =head1 SYNOPSIS use JSON::Validator; my $schema = JSON::Validator->new->schema("...")->schema; # Check for specification errors my $errors = $schema->errors; # Returns a list of zero or more JSON::Validator::Error objects my @request_errors = $schema->validate_request( [get => "/path"], {body => sub { return {exists => 1, value => {}} }}, ); # Returns a list of zero or more JSON::Validator::Error objects my @response_errors = $schema->validate_response( [get => "/path", 200], {body => sub { return {exists => 1, value => {}} }}, ); =head1 DESCRIPTION This class represents L. =head1 ATTRIBUTES =head2 errors my $array_ref = $schema->errors; See L. =head2 moniker $str = $schema->moniker; $schema = $schema->moniker("openapiv2"); Used to get/set the moniker for the given schema. Default value is "openapiv2". =head2 specification my $str = $schema->specification; my $schema = $schema->specification($str); Defaults to "L". =head1 METHODS =head2 add_default_response $schema = $schema->add_default_response(\%params); Used to add a default response schema for operations that does not already have one. C<%params> can be: =over 2 =item * description The human readable description added to the operation. Defaults: "Default response." =item * name The name used in the specification under "/components/schemas/". Defaults: "DefaultResponse" =item * schema The schema to add. The default schema below might change, but the basics will stay the same: { type: "object", required: ["errors"], properties: { errors: { type: "array", items: { type: "object", required: ["message"], properties: { message: {type: "string"}, path: {type: "string"} } } } } } =item * status A list of status codes to apply the default schema to. Default: C<[400, 401, 404, 500, 501]>. =back =head2 base_url $url = $schema->base_url; $schema = $schema->base_url($url); Can get or set the default URL for this schema. C<$url> can be either a L object or a plain string. This method will read or write "basePath", "host" and/or "schemas" in L. =head2 coerce my $schema = $schema->coerce({booleans => 1, numbers => 1, strings => 1}); my $hash_ref = $schema->coerce; Coercion is enabled by default, since headers, path parts, query parameters, ... are in most cases strings. See also L. =head2 new $schema = JSON::Validator::Schema::OpenAPIv2->new(\%attrs); $schema = JSON::Validator::Schema::OpenAPIv2->new; Same as L, but will also build L/coerce>. =head2 parameters_for_request $parameters = $schema->parameters_for_request([$method, $path]); Finds all the request parameters defined in the schema, including inherited parameters. Returns C if the C<$path> and C<$method> cannot be found. Example return value: [ {in => "query", name => "q"}, {in => "body", name => "body", accepts => ["application/json"]}, ] The return value MUST not be mutated. =head2 parameters_for_response $array_ref = $schema->parameters_for_response([$method, $path, $status]); Finds the response parameters defined in the schema. Returns C if the C<$path>, C<$method> and C<$status> cannot be found. Will default to the "default" response definition if C<$status> could not be found and "default" exists. Example return value: [ {in => "header", name => "X-Foo"}, {in => "body", name => "body", accepts => ["application/json"]}, ] The return value MUST not be mutated. =head2 routes $collection = $schema->routes; Used to gather all available routes in the schema and return them sorted. The result is a L object, where each item has a hash looking like this: { method => 'get', path => '/user/{id}', operation_id => 'getUser', # Might be undef() } =head2 validate_request @errors = $schema->validate_request([$method, $path], \%req); This method can be used to validate a HTTP request. C<%req> should contain key/value pairs representing the request parameters. Example: %req = ( body => sub { my ($name, $param) = shift; # $param = {name => $name, in => ..., schema => ..., ...} return {exists => 1, value => \%all_params} unless defined $name; return {exists => 1, value => "..."}; }, formData => {email => "..."}, header => {"X-Request-Base" => "..."}, path => {id => "..."}, query => {limit => 42}, ); "formData", "header", "path" and "query" can be either a hash-ref, a hash-like object or a code ref, while "body" MUST be a code ref. The return value from the code ref will get mutated, making it possible to check if an individual parameter was validated or not. # Before: "exists" and "value" must be present my @evaluated; $req{query} = sub { push @evaluated, {exists => 1, value => 42}, return $evaluated[-1] }; # Validate $schema->validate_request(get => "/user"], \%req); # After: "in", "name" and "valid" are added $evaluated[-1] ==> {exists => 1, value => 42, in => "query", name => "foo", valid => 1}; A plain hash-ref will I get mutated. The body hash-ref can also have a "content_type" key. This will be checked against the list of valid request or response content types in the spec. =head2 validate_response @errors = $schema->validate_response([$method, $path, $status], \%res); This method can be used to validate a HTTP response. C<%res> should contain key/value pairs representing the response parameters. Example: %res = ( body => sub { my ($name, $param) = shift; # $param = {name => $name, in => ..., ...} return {exists => 1, value => \%all_params} unless defined $name; return {accept => "application/json", exists => 1, value => "..."}; }, header => {"Location" => "..."}, ); C<%res> follows the same rules as C<%req> in L, but also supports "accept", instead of specifying "content_type". "accept" should have the same format as an "Accept" HTTP header. =head1 SEE ALSO L, L, L =cut JSON-Validator-5.18/lib/JSON/Validator/Schema/OpenAPIv3.pm0000644000076500000240000004606415211411456022552 0ustar jhthorsenstaffpackage JSON::Validator::Schema::OpenAPIv3; use Mojo::Base 'JSON::Validator::Schema::Draft201909'; use JSON::Validator::Util qw(E data_type negotiate_content_type schema_type); use Mojo::JSON qw(false true); use Mojo::Path; has moniker => 'openapiv3'; has specification => 'https://spec.openapis.org/oas/3.0/schema/2021-09-28'; require JSON::Validator::Schema::OpenAPIv2; *coerce = \&JSON::Validator::Schema::OpenAPIv2::coerce; *errors = \&JSON::Validator::Schema::OpenAPIv2::errors; *routes = \&JSON::Validator::Schema::OpenAPIv2::routes; *validate_request = \&JSON::Validator::Schema::OpenAPIv2::validate_request; *validate_response = \&JSON::Validator::Schema::OpenAPIv2::validate_response; *_coerce_arrays = \&JSON::Validator::Schema::OpenAPIv2::_coerce_arrays; *_coerce_default_value = \&JSON::Validator::Schema::OpenAPIv2::_coerce_default_value; *_find_all_nodes = \&JSON::Validator::Schema::OpenAPIv2::_find_all_nodes; *_params_for_add_default_response = \&JSON::Validator::Schema::OpenAPIv2::_params_for_add_default_response; *_prefix_error_path = \&JSON::Validator::Schema::OpenAPIv2::_prefix_error_path; *_validate_request_or_response = \&JSON::Validator::Schema::OpenAPIv2::_validate_request_or_response; sub add_default_response { my ($self, $params) = ($_[0], shift->_params_for_add_default_response(@_)); my $schemas = $self->data->{components}{schemas} ||= {}; $schemas->{$params->{name}} ||= $params->{schema}; my $ref = {'$ref' => "#/components/schemas/$params->{name}"}; $self->_register_ref($ref, schema => $schemas->{$params->{name}}); for my $route ($self->routes->each) { my $op = $self->get([paths => @$route{qw(path method)}]); for my $status (@{$params->{status}}) { next if $self->get(['paths', @$route{qw(path method)}, 'responses', $status]); $op->{responses}{$status}{content}{'application/json'} //= {schema => $ref}; $op->{responses}{$status}{description} //= $params->{description}; } } return $self; } sub base_url { my ($self, $url) = @_; # Get return Mojo::URL->new($self->get('/servers/0/url') || '') unless $url; # Set $url = Mojo::URL->new($url)->to_abs($self->base_url); $self->data->{servers}[0]{url} = $url->to_string; return $self; } sub new { my $self = shift->SUPER::new(@_); $self->coerce; # make sure this attribute is built $self; } sub parameters_for_request { my $self = shift; my ($method, $path) = (lc $_[0][0], $_[0][1]); my $cache_key = "parameters_for_request:$method:$path"; return $self->{cache}{$cache_key} if $self->{cache}{$cache_key}; return undef unless $self->get([paths => $path, $method]); my @parameters = map {@$_} $self->_find_all_nodes([paths => $path, $method], 'parameters'); if (my $request_body = $self->get([paths => $path, $method, 'requestBody'])) { my @accepts = sort keys %{$request_body->{content} || {}}; push @parameters, { accepts => \@accepts, content => $request_body->{content}, in => 'body', name => 'body', required => $request_body->{required}, }; } return $self->{cache}{$cache_key} = \@parameters; } sub parameters_for_response { my $self = shift; my ($method, $path, $status) = (lc $_[0][0], $_[0][1], $_[0][2] || 200); $status ||= 200; my $cache_key = "parameters_for_response:$method:$path:$status"; return $self->{cache}{$cache_key} if $self->{cache}{$cache_key}; my $response = $self->get([paths => $path, $method, 'responses', $status]) || $self->get([paths => $path, $method, 'responses', 'default']); return undef unless $response; my @parameters; if (my $headers = $response->{headers}) { push @parameters, map { +{%{$headers->{$_}}, in => 'header', name => $_} } sort keys %$headers; } if (my @accepts = sort keys %{$response->{content} || {}}) { push @parameters, {accepts => \@accepts, content => $response->{content}, in => 'body', name => 'body'}; } return $self->{cache}{$cache_key} = \@parameters; } sub _build_formats { # TODO: Figure out if this is the correct list return { 'binary' => sub {undef}, 'byte' => JSON::Validator::Formats->can('check_byte'), 'date' => JSON::Validator::Formats->can('check_date'), 'date-time' => JSON::Validator::Formats->can('check_date_time'), 'double' => JSON::Validator::Formats->can('check_double'), 'duration' => JSON::Validator::Formats->can('check_duration'), 'email' => JSON::Validator::Formats->can('check_email'), 'float' => JSON::Validator::Formats->can('check_float'), 'hostname' => JSON::Validator::Formats->can('check_hostname'), 'idn-email' => JSON::Validator::Formats->can('check_idn_email'), 'idn-hostname' => JSON::Validator::Formats->can('check_idn_hostname'), 'int32' => JSON::Validator::Formats->can('check_int32'), 'int64' => JSON::Validator::Formats->can('check_int64'), 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 'iri' => JSON::Validator::Formats->can('check_iri'), 'iri-reference' => JSON::Validator::Formats->can('check_iri_reference'), 'json-pointer' => JSON::Validator::Formats->can('check_json_pointer'), 'password' => sub {undef}, 'regex' => JSON::Validator::Formats->can('check_regex'), 'relative-json-pointer' => JSON::Validator::Formats->can('check_relative_json_pointer'), 'time' => JSON::Validator::Formats->can('check_time'), 'uri' => JSON::Validator::Formats->can('check_uri'), 'uri-reference' => JSON::Validator::Formats->can('check_uri_reference'), 'uri-template' => JSON::Validator::Formats->can('check_uri_template'), 'uuid' => JSON::Validator::Formats->can('check_uuid'), }; } sub _bundle_ref_path_expand { my ($self, $schema, $ref) = @_; return $ref =~ m!\bcomponents/([^/]+)/(.+)! ? ('components', $1, $2) : ('components', 'schemas', $ref); } sub _coerce_parameter_format { my ($self, $val, $param) = @_; return unless $val->{exists}; state $in_style = {cookie => 'form', header => 'simple', path => 'simple', query => 'form'}; $param->{style} = $in_style->{$param->{in}} unless $param->{style}; return $self->_coerce_parameter_style_object_deep($val, $param) if +($param->{style} // '') eq 'deepObject'; my $schema_type = schema_type $param->{schema}; return $self->_coerce_parameter_style_array($val, $param) if $schema_type eq 'array'; return $self->_coerce_parameter_style_object($val, $param) if $schema_type eq 'object'; } sub _coerce_parameter_style_array { my ($self, $val, $param) = @_; my $style = $param->{style}; my $explode = $param->{explode} // $param->{style} eq 'form' ? true : false; my $re; if ($style =~ m!^(form|pipeDelimited|spaceDelimited|simple)$!) { return $val->{value} = ref $val->{value} eq 'ARRAY' ? $val->{value} : [$val->{value}] if $explode; $re = $style eq 'pipeDelimited' ? qr{\|} : $style eq 'spaceDelimited' ? $re = qr{[ ]} : qr{,}; } elsif ($style eq 'label') { $re = qr{\.}; $re = qr{,} if $val->{value} =~ s/^$re// and !$explode; } elsif ($style eq 'matrix') { $re = qr{;\Q$param->{name}\E=}; $re = qr{,} if $val->{value} =~ s/^$re// and !$explode; } return $val->{value} = [_split($re, $val->{value})]; } sub _coerce_parameter_style_object { my ($self, $val, $param) = @_; my $style = $param->{style}; my $explode = $param->{explode} // (grep { $style eq $_ } qw(cookie query)) ? 1 : 0; if ($explode) { return $self->_coerce_parameter_style_object_form($val, $param) if $style eq 'form'; state $style_re = {label => qr{\.}, matrix => qr{;}, simple => qr{,}}; return unless my $re = $style_re->{$style}; return if $style eq 'matrix' && $val->{value} !~ s/^;//; return if $style eq 'label' && $val->{value} !~ s/^\.//; my $params = Mojo::Parameters->new; $params->append(Mojo::Parameters->new($_)) for _split($re, $val->{value}); return $val->{value} = $params->to_hash; } else { state $style_re = { form => qr{,}, label => qr{\.}, matrix => qr{,}, pipeDelimited => qr{\|}, simple => qr{,}, spaceDelimited => qr{[ ]}, }; return unless my $re = $style_re->{$style}; return if $style eq 'matrix' && $val->{value} !~ s/^;\Q$param->{name}\E=//; return if $style eq 'label' && $val->{value} !~ s/^\.//; return $val->{value} = Mojo::Parameters->new->pairs([_split($re, $val->{value})])->to_hash; } } sub _coerce_parameter_style_object_deep { my ($self, $val, $param) = @_; my %res; for my $k (keys %{$val->{value}}) { next unless $k =~ /^\Q$param->{name}\E\[(.*)\]/; my @path = $k =~ m!\[([^]]*)\]!g; my $values = ref $val->{value}{$k} eq 'ARRAY' ? $val->{value}{$k} : [$val->{value}{$k}]; my $node = \%res; while (defined(my $p = shift @path)) { if (@path) { my $next = $path[0] =~ m!^(|\d+)$! ? [] : {}; $node = ref $node eq 'ARRAY' ? ($node->[$p] ||= $next) : ($node->{$p} ||= $next); } elsif ($p eq '') { @$node = @$values; } elsif ($p =~ /^\d+$/) { $node->[$p] = $values->[0]; } else { $node->{$p} = @$values > 1 ? $values : $values->[0]; } } } return $val->{value} = \%res if %res; return $val->{exists} = 0; } sub _coerce_parameter_style_object_form { my ($self, $val, $param) = @_; return unless my $properties = $param->{schema} && $param->{schema}{properties}; for my $k (keys %{$val->{value}}) { next unless my $type = $properties->{$k} && $properties->{$k}{type}; $val->{value}{$k} = [$val->{value}{$k}] if $type eq 'array' and ref $val->{value}{$k} ne 'ARRAY'; } } sub _get_parameter_value { my ($self, $param, $get) = @_; my $schema_type = schema_type $param->{schema}; my $name = $param->{name}; $name = undef if $schema_type eq 'object' && $param->{explode} && ($param->{style} || '') =~ m!^(form|deepObject)$!; my $val = $get->{$param->{in}}->($name, $param); @$val{qw(in name)} = (@$param{qw(in name)}); return $val; } sub _split { my ($re, $val) = @_; $val = @$val ? $val->[-1] : '' if ref $val; return split /$re/, $val; } sub _to_list { ref $_[0] eq 'ARRAY' ? @{$_[0]} : $_[0] ? ($_[0]) : () } sub _validate_body { my ($self, $direction, $val, $param) = @_; if ($val->{accept}) { $val->{content_type} = negotiate_content_type($param->{accepts}, $val->{accept}); $val->{valid} = $val->{content_type} ? 1 : 0; return E "/header/Accept", [join(', ', @{$param->{accepts}}), type => $val->{accept}] unless $val->{valid}; } my $negotiated_content_type; if (@{$param->{accepts}} and $val->{content_type}) { $negotiated_content_type = negotiate_content_type($param->{accepts}, $val->{content_type}); $val->{valid} = $negotiated_content_type ? 1 : 0; return E "/$param->{name}", [join(', ', @{$param->{accepts}}) => type => $val->{content_type}] unless $negotiated_content_type; } if ($param->{required} and !$val->{exists}) { $val->{valid} = 0; return E "/$param->{name}", [qw(object required)]; } if ($val->{exists}) { # Ensures we have a valid content-type so we can select a schema # This can happen if the negotiation fails (e.g. content-type is empty) $negotiated_content_type //= $param->{accepts}[0]; # Mutate request content-type if one was not set $val->{content_type} //= $negotiated_content_type; local $self->{coerce}{arrays} = 1 if $val->{content_type} =~ m!^(application/x-www-form-urlencoded|multipart/form-data)\s*(;|$)!; local $self->{"validate_$direction"} = 1; my $body_params = $param->{content}{$negotiated_content_type}; my @errors = map { $_->path(_prefix_error_path($param->{name}, $_->path)); $_ } $self->validate($val->{value}, $body_params->{schema}); $val->{valid} = @errors ? 0 : 1; return @errors; } return; } sub _validate_id { } sub _validate_type_array { my $self = shift; $_[0] = [$_[0]] if ref $_[0] ne 'ARRAY' and $self->{coerce}{arrays}; return $_[1]->{schema}{nullable} && !defined $_[0] ? () : $self->SUPER::_validate_type_array(@_); } sub _validate_type_boolean { my $self = shift; return $_[1]->{schema}{nullable} && !defined $_[0] ? () : $self->SUPER::_validate_type_boolean(@_); } sub _validate_type_enum { my $self = shift; return $_[1]->{schema}{nullable} && !defined $_[0] ? () : $self->SUPER::_validate_type_enum(@_); } sub _validate_type_integer { my $self = shift; return $_[1]->{schema}{nullable} && !defined $_[0] ? () : $self->SUPER::_validate_type_integer(@_); } sub _validate_type_number { my $self = shift; return $_[1]->{schema}{nullable} && !defined $_[0] ? () : $self->SUPER::_validate_type_number(@_); } sub _validate_type_object { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; return if $schema->{nullable} && !defined $data; return E $path, [object => type => data_type $data] if ref $data ne 'HASH'; return shift->SUPER::_validate_type_object(@_) unless $self->{validate_request} or $self->{validate_response}; # TODO: Support external URLs in "mapping" my $discriminator = $schema->{discriminator}; if (ref $discriminator eq 'HASH' and $discriminator->{propertyName} and !$self->{inside_discriminator}) { my ($name, $mapping) = @$discriminator{qw(propertyName mapping)}; return E $path, "Discriminator $name has no value." unless my $map_name = $data->{$name}; return E $path, "No definition for discriminator $map_name." unless my $url = $mapping->{$map_name}; return E $path, "TODO: Not yet supported: $url" unless $url =~ s!^#!!; local $self->{inside_discriminator} = 1; return $self->_validate($data, $self->_state($state, schema => $self->get($url))); } return $self->{validate_request} ? $self->_validate_type_object_request($_[1], $state) : $self->_validate_type_object_response($_[1], $state); } sub _validate_type_object_request { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; my (@errors, %ro); for my $name (keys %{$schema->{properties} || {}}) { next unless $self->_get(['readOnly'], {path => [], schema => $schema->{properties}{$name}}); push @errors, E [@$path, $name], "Read-only." if exists $data->{$name}; $ro{$name} = 1; } local $schema->{required} = [grep { !$ro{$_} } @{$schema->{required} || []}]; return ( @errors, $self->_validate_type_object_min_max($_[1], $state), $self->_validate_type_object_dependencies($_[1], $state), $self->_validate_type_object_properties($_[1], $state), ); } sub _validate_type_object_response { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; my (@errors, %rw); for my $name (keys %{$schema->{properties} || {}}) { next unless $self->_get(['writeOnly'], {path => [], schema => $schema->{properties}{$name}}); push @errors, E [@$path, $name], "Write-only." if exists $data->{$name}; $rw{$name} = 1; } local $schema->{required} = [grep { !$rw{$_} } @{$schema->{required} || []}]; return ( @errors, $self->_validate_type_object_min_max($_[1], $state), $self->_validate_type_object_dependencies($_[1], $state), $self->_validate_type_object_properties($_[1], $state), ); } sub _validate_type_string { my ($self, $val, $state) = @_; return () if $state->{schema}{nullable} && !defined $val; if (ref $val eq 'Mojo::Upload') { return () if defined $state->{schema}{format} && $state->{schema}{format} eq 'binary'; return E $state->{path}, [string => type => data_type $val]; } return $self->SUPER::_validate_type_string($val, $state); } 1; =encoding utf8 =head1 NAME JSON::Validator::Schema::OpenAPIv3 - OpenAPI version 3 =head1 SYNOPSIS See L. =head1 DESCRIPTION This class represents L. =head1 ATTRIBUTES =head2 errors my $array_ref = $schema->errors; See L. =head2 moniker $str = $schema->moniker; $schema = $schema->moniker("openapiv3"); Used to get/set the moniker for the given schema. Default value is "openapiv3". =head2 specification my $str = $schema->specification; my $schema = $schema->specification($str); Defaults to "L". =head1 METHODS =head2 add_default_response $schema = $schema->add_default_response(\%params); See L for details. =head2 base_url $url = $schema->base_url; $schema = $schema->base_url($url); Can get or set the default URL for this schema. C<$url> can be either a L object or a plain string. This method will read or write "/servers/0/url" in L. =head2 coerce my $schema = $schema->coerce({booleans => 1, numbers => 1, strings => 1}); my $hash_ref = $schema->coerce; Coercion is enabled by default, since headers, path parts, query parameters, ... are in most cases strings. =head2 new $schema = JSON::Validator::Schema::OpenAPIv2->new(\%attrs); $schema = JSON::Validator::Schema::OpenAPIv2->new; Same as L, but will also build L/coerce>. =head2 parameters_for_request $parameters = $schema->parameters_for_request([$method, $path]); Finds all the request parameters defined in the schema, including inherited parameters. Returns C if the C<$path> and C<$method> cannot be found. Example return value: [ {in => "query", name => "q"}, {in => "body", name => "body", accepts => ["application/json"]}, ] The return value MUST not be mutated. =head2 parameters_for_response $array_ref = $schema->parameters_for_response([$method, $path, $status]); Finds the response parameters defined in the schema. Returns C if the C<$path>, C<$method> and C<$status> cannot be found. Will default to the "default" response definition if C<$status> could not be found and "default" exists. Example return value: [ {in => "header", name => "X-Foo"}, {in => "body", name => "body", accepts => ["application/json"]}, ] The return value MUST not be mutated. =head2 routes $collection = $schema->routes; Shares the same interface as L. =head2 validate_request @errors = $schema->validate_request([$method, $path], \%req); Shares the same interface as L. =head2 validate_response @errors = $schema->validate_response([$method, $path], \%req); Shares the same interface as L. =head1 SEE ALSO L, L and and L. =cut JSON-Validator-5.18/lib/JSON/Validator/Schema/Draft6.pm0000644000076500000240000001230315211411456022161 0ustar jhthorsenstaffpackage JSON::Validator::Schema::Draft6; use Mojo::Base 'JSON::Validator::Schema'; use JSON::Validator::Schema::Draft4; use JSON::Validator::URI qw(uri); use JSON::Validator::Util qw(E data_type is_type prefix_errors); has id => sub { my $data = shift->data; return is_type($data, 'HASH') ? $data->{'$id'} || '' : ''; }; has specification => 'http://json-schema.org/draft-06/schema#'; sub _build_formats { return { 'date-time' => JSON::Validator::Formats->can('check_date_time'), 'email' => JSON::Validator::Formats->can('check_email'), 'hostname' => JSON::Validator::Formats->can('check_hostname'), 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 'json-pointer' => JSON::Validator::Formats->can('check_json_pointer'), 'regex' => JSON::Validator::Formats->can('check_regex'), 'relative-json-pointer' => JSON::Validator::Formats->can('check_relative_json_pointer'), 'uri' => JSON::Validator::Formats->can('check_uri'), 'uri-reference' => JSON::Validator::Formats->can('check_uri_reference'), 'uri-template' => JSON::Validator::Formats->can('check_uri_template'), }; } sub _resolve_object { my ($self, $state, $schema, $refs, $found) = @_; if ($schema->{'$id'} and !ref $schema->{'$id'}) { my $id = uri $schema->{'$id'}, $state->{base_url}; $self->store->add($id => $schema); $state = {%$state, base_url => $id->fragment(undef)->to_string}; } if ($found->{'$ref'} = $schema->{'$ref'} && !ref $schema->{'$ref'}) { push @$refs, [$schema, $state]; } return $state; } sub _validate_number_max { my ($self, $value, $state, $expected) = @_; my $cmp_with = $state->{schema}{maximum}; return E $state->{path}, [$expected => maximum => $value, $cmp_with] if defined $cmp_with and $value > $cmp_with; $cmp_with = $state->{schema}{exclusiveMaximum}; return E $state->{path}, [$expected => ex_maximum => $value, $cmp_with] if defined $cmp_with and $value >= $cmp_with; return; } sub _validate_number_min { my ($self, $value, $state, $expected) = @_; my $cmp_with = $state->{schema}{minimum}; return E $state->{path}, [$expected => minimum => $value, $cmp_with] if defined $cmp_with and $value < $cmp_with; $cmp_with = $state->{schema}{exclusiveMinimum}; return E $state->{path}, [$expected => ex_minimum => $value, $cmp_with] if defined $cmp_with and $value <= $cmp_with; return; } sub _validate_type_array { my ($self, $data, $state) = @_; return E $state->{path}, [array => type => data_type $data] if ref $data ne 'ARRAY'; return ( $self->_validate_type_array_min_max($_[1], $state), $self->_validate_type_array_unique($_[1], $state), $self->_validate_type_array_contains($_[1], $state), $self->_validate_type_array_items($_[1], $state), ); } sub _validate_type_array_contains { my ($self, $data, $state) = @_; return unless exists $state->{schema}{contains}; my (@e, @errors); for my $i (0 .. @$data - 1) { my @tmp = $self->_validate($data->[$i], $self->_state($state, path => [@{$state->{path}}, $i], schema => $state->{schema}{contains})); push @e, \@tmp if @tmp; } push @errors, map {@$_} @e if @e >= @$data; push @errors, E $state->{path}, [array => 'contains'] if not @$data; return @errors; } sub _validate_type_object { my ($self, $data, $state) = @_; return E $state->{path}, [object => type => data_type $data] if ref $data ne 'HASH'; return ( $self->_validate_type_object_min_max($_[1], $state), $self->_validate_type_object_names($_[1], $state), $self->_validate_type_object_properties($_[1], $state), $self->_validate_type_object_dependencies($_[1], $state), ); } sub _validate_type_object_names { my ($self, $data, $state) = @_; return unless exists $state->{schema}{propertyNames}; my @errors; for my $name (keys %$data) { next unless my @e = $self->_validate($name, $self->_state($state, schema => $state->{schema}{propertyNames})); push @errors, prefix_errors propertyName => map [$name, $_], @e; } return @errors; } *_validate_type_array_items = \&JSON::Validator::Schema::Draft4::_validate_type_array_items; *_validate_type_array_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_array_min_max; *_validate_type_array_unique = \&JSON::Validator::Schema::Draft4::_validate_type_array_unique; *_validate_type_object_dependencies = \&JSON::Validator::Schema::Draft4::_validate_type_object_dependencies; *_validate_type_object_min_max = \&JSON::Validator::Schema::Draft4::_validate_type_object_min_max; *_validate_type_object_properties = \&JSON::Validator::Schema::Draft4::_validate_type_object_properties; 1; =encoding utf8 =head1 NAME JSON::Validator::Schema::Draft6 - JSON-Schema Draft 6 =head1 SYNOPSIS See L. =head1 DESCRIPTION This class represents L. =head1 ATTRIBUTES =head2 specification my $str = $schema->specification; Defaults to "L". =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Formats.pm0000644000076500000240000003025115211411456021250 0ustar jhthorsenstaffpackage JSON::Validator::Formats; use Mojo::Base -strict; use Scalar::Util 'looks_like_number'; require Time::Local; use constant DATA_VALIDATE_DOMAIN => eval 'require Data::Validate::Domain;1'; use constant DATA_VALIDATE_IP => eval 'require Data::Validate::IP;1'; use constant IV_SIZE => eval 'require Config;$Config::Config{ivsize}'; use constant NET_IDN_ENCODE => eval 'require Net::IDN::Encode;1'; use constant WARN_MISSING_MODULE => $ENV{JSON_VALIDATOR_WARN} // 1; our $IRI_TEST_NAME = 'iri-reference'; sub check_byte { $_[0] =~ /^[A-Za-z0-9\+\/\=]+$/ ? undef : 'Does not match byte format.'; } sub check_date { my @date = $_[0] =~ m!^(\d{4})-(\d\d)-(\d\d)$!io; return 'Does not match date format.' unless @date; @date = map { s/^0+//; $_ || 0 } reverse @date; $date[1] -= 1; # month are zero based local $@; return undef if eval { Time::Local::timegm(0, 0, 0, @date); 1 }; my $err = (split / at /, $@)[0]; $err =~ s!('-?\d+'\s|\s[\d\.]+)!!g; $err .= '.'; return $err; } sub check_date_time { my @dt = $_[0] =~ m!^(\d{4})-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d(?:\.\d+)?)(?:Z|([+-])(\d\d):(\d\d))?$!io; return 'Does not match date-time format.' unless @dt; return 'Time offset hour out of range.' if defined $dt[7] and $dt[7] > 23; return 'Time offset minute out of range.' if defined $dt[8] and $dt[8] > 59; @dt = map { s/^0//; $_ } reverse @dt[0 .. 5]; $dt[4] -= 1; # month are zero based local $@; return undef if eval { Time::Local::timegm(@dt); 1 }; my $err = (split / at /, $@)[0]; $err =~ s!('-?\d+'\s|\s[\d\.]+)!!g; $err .= '.'; return $err; } sub check_double { _match_number(double => $_[0], '') } sub check_duration { state $rfc3339_duration_re = do { my $num = qr{\d+(?:[,.]\d+)?}; my $sec = qr/${num}S/; my $min = qr/${num}M(?:$sec)?/; my $hour = qr/${num}H(?:$min)?/; my $day = qr/${num}D(?:$hour)?/; my $mon = qr/${num}M(?:$day)?/; my $year = qr/${num}Y(?:$mon)?/; my $week = qr/${num}W/; my $time = qr/T($hour|$min|$sec)/; my $date = qr/(?:$day|$mon|$year)(?:$time)?/; qr{^P(?:$date|$time|$week)$}; }; return $_[0] =~ $rfc3339_duration_re ? undef : 'Does not match duration format.'; } sub check_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 ? undef : 'Does not match email format.'; } sub check_float { _match_number(float => $_[0], '') } sub check_hostname { return _module_missing(hostname => 'Data::Validate::Domain') unless DATA_VALIDATE_DOMAIN; return undef if Data::Validate::Domain::is_hostname($_[0]); return 'Does not match hostname format.'; } sub check_idn_email { return _module_missing('idn-email' => 'Net::IDN::Encode') unless NET_IDN_ENCODE; local $@; my $err = eval { my ($local_part, $domain) = split /@/, $_[0], 2; $local_part = join '.', map { Net::IDN::Encode::to_ascii($_) } split( /\./, $local_part // ''); check_email( join '@', $local_part, Net::IDN::Encode::domain_to_ascii($domain // ''), ); }; return $err || $@ ? 'Does not match idn-email format.' : undef; } sub check_idn_hostname { return _module_missing('idn-hostname' => 'Net::IDN::Encode') unless NET_IDN_ENCODE; local $@; my $err = eval { check_hostname(Net::IDN::Encode::domain_to_ascii($_[0])) }; return $err ? 'Does not match idn-hostname format.' : $@ || undef; } sub check_int32 { _match_number(int32 => $_[0], 'l') } sub check_int64 { _match_number(int64 => $_[0], IV_SIZE >= 8 ? 'q' : '') } sub check_iri { local $IRI_TEST_NAME = 'iri'; return 'Scheme missing.' unless $_[0] =~ m!^\w+:!; return check_iri_reference($_[0]); } sub check_iri_reference { return "Does not match $IRI_TEST_NAME format." unless $_[0] =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!; my ($scheme, $auth_host, $path, $query, $has_fragment, $fragment) = map { $_ // '' } ($2, $4, $5, $7, $8, $9); return 'Scheme missing.' if length $auth_host and !length $scheme; return 'Scheme, path or fragment are required.' unless length($scheme) + length($path) + length($has_fragment); return 'Scheme must begin with a letter.' if length $scheme and lc($scheme) !~ m!^[a-z][a-z0-9\+\-\.]*$!; return 'Invalid hex escape.' if $_[0] =~ /%[^0-9a-f]/i; return 'Hex escapes are not complete.' if $_[0] =~ /%[0-9a-f](:?[^0-9a-f]|$)/i; if (defined $auth_host and length $auth_host) { return 'Path cannot be empty and must begin with a /' unless !length $path or $path =~ m!^/!; } elsif ($path =~ m!^//!) { return 'Path cannot not start with //.'; } return undef; } sub check_json_pointer { return !length $_[0] || $_[0] =~ m!^/! ? undef : 'Does not match json-pointer format.'; } sub check_ipv4 { return undef if DATA_VALIDATE_IP and Data::Validate::IP::is_ipv4($_[0]); my (@octets) = $_[0] =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; return undef if 4 == grep { $_ >= 0 && $_ <= 255 && $_ !~ /^0\d{1,2}$/ } @octets; return 'Does not match ipv4 format.'; } sub check_ipv6 { return _module_missing(ipv6 => 'Data::Validate::IP') unless DATA_VALIDATE_IP; return undef if Data::Validate::IP::is_ipv6($_[0]); return 'Does not match ipv6 format.'; } sub check_relative_json_pointer { return 'Relative JSON Pointer must start with a non-negative-integer.' unless $_[0] =~ m!^\d+!; return undef if $_[0] =~ m!^(\d+)#?$!; return 'Relative JSON Pointer must have "#" or a JSON Pointer.' unless $_[0] =~ m!^\d+(.+)!; return 'Does not match relative-json-pointer format.' if check_json_pointer($1); return undef; } sub check_regex { eval {qr{$_[0]}} ? undef : 'Does not match regex format.'; } sub check_time { my @time = $_[0] =~ m!^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(?:Z|([+-])(\d+):(\d+))?$!io; return 'Does not match time format.' unless @time; @time = map { s/^0//; $_ } reverse @time[0 .. 2]; local $@; return undef if eval { Time::Local::timegm(@time, 31, 11, 1947); 1 }; my $err = (split / at /, $@)[0]; $err =~ s!('-?\d+'\s|\s[\d\.]+)!!g; $err .= '.'; return $err; } sub check_uri { return 'An URI can only only contain ASCII characters.' if $_[0] =~ m!\P{ASCII}!; local $IRI_TEST_NAME = 'uri'; return check_iri_reference($_[0]); } sub check_uri_reference { local $IRI_TEST_NAME = 'uri-reference'; return check_iri_reference($_[0]); } sub check_uri_template { return check_iri($_[0]); } sub check_uuid { state $uuid_re = do { my $x = qr{[0-9A-Fa-f]}; qr{^$x$x$x$x$x$x$x$x-$x$x$x$x-[0-9]$x$x$x-$x$x$x$x-$x$x$x$x$x$x$x$x$x$x$x$x$}; }; return $_[0] =~ $uuid_re ? undef : 'Does not match uuid format.'; } sub _match_number { my ($name, $val, $format) = @_; return 'Does not look like an integer' if $name =~ m!^int! and $val !~ /^-?\d+(\.\d+)?$/; return 'Does not look like a number.' unless looks_like_number $val; return undef unless $format; return undef if $val eq unpack $format, pack $format, $val; return "Does not match $name format."; } sub _module_missing { warn "[JSON::Validator] Cannot validate $_[0] format: $_[1] is missing" if WARN_MISSING_MODULE; return undef; } 1; =encoding utf8 =head1 NAME JSON::Validator::Formats - Functions for validating JSON schema formats =head1 SYNOPSIS use JSON::Validator::Formats; my $error = JSON::Validator::Formats::check_uri($str); die $error if $error; my $jv = JSON::Validator->new; $jv->formats({ "date-time" => JSON::Validator::Formats->can("check_date_time"), "email" => JSON::Validator::Formats->can("check_email"), "hostname" => JSON::Validator::Formats->can("check_hostname"), "ipv4" => JSON::Validator::Formats->can("check_ipv4"), "ipv6" => JSON::Validator::Formats->can("check_ipv6"), "regex" => JSON::Validator::Formats->can("check_regex"), "uri" => JSON::Validator::Formats->can("check_uri"), "uri-reference" => JSON::Validator::Formats->can("check_uri_reference"), }); =head1 DESCRIPTION L is a module with utility functions used by L to match JSON Schema formats. All functions return C for success or an error message for failure. =head1 FUNCTIONS =head2 check_byte my $str_or_undef = check_byte $str; Checks that the string matches byte format. =head2 check_date my $str_or_undef = check_date $str; Validates the date part of a RFC3339 string. =head2 check_date_time my $str_or_undef = check_date_time $str; Validated against RFC3339 timestamp in UTC time. This is formatted as "YYYY-MM-DDThh:mm:ss.fffZ". The milliseconds portion (".fff") is optional =head2 check_duration my $str_or_undef = check_duration $str; Validate a RFC3339 duration string, such as "P3Y6M4DT12H30M5S". =head2 check_double my $str_or_undef = check_double $number; Tries to check if the number is a double. Note that this check is not very accurate. =head2 check_email my $str_or_undef = check_email $str; Validated against the RFC5322 spec. =head2 check_float my $str_or_undef = check_float $number; Tries to check if the number is a float. Note that this check is not very accurate. =head2 check_hostname my $str_or_undef = check_hostname $str; Will be validated using L, if installed. =head2 check_idn_email my $str_or_undef = check_idn_email $str; Will validate an email with non-ASCII characters using L if installed. =head2 check_idn_hostname my $str_or_undef = check_idn_hostname $str; Will validate a hostname with non-ASCII characters using L if installed. =head2 check_int32 my $str_or_undef = check_int32 $number; Tries to check if the number is a int32. Note that this check is not very accurate. =head2 check_int64 my $str_or_undef = check_int64 $number; Tries to check if the number is a int64. Note that this check is not very accurate. =head2 check_ipv4 my $str_or_undef = check_ipv4 $str; Will be validated using L, if installed or fall back to a plain IPv4 IP regex. =head2 check_ipv6 my $str_or_undef = check_ipv6 $str; Will be validated using L, if installed. =head2 check_iri my $str_or_undef = check_iri $str; Validate either an absolute IRI containing ASCII or non-ASCII characters, against the RFC3986 spec. =head2 check_iri_reference my $str_or_undef = check_iri_reference $str; Validate either a relative or absolute IRI containing ASCII or non-ASCII characters, against the RFC3986 spec. =head2 check_json_pointer my $str_or_undef = check_json_pointer $str; Validates a JSON pointer, such as "/foo/bar/42". =head2 check_regex my $str_or_undef = check_regex $str; Will check if the string is a regex, using C. =head2 check_relative_json_pointer my $str_or_undef = check_relative_json_pointer $str; Validates a relative JSON pointer, such as "0/foo" or "3#". =head2 check_time my $str_or_undef = check_time $str; Validates the time and optionally the offset part of a RFC3339 string. =head2 check_uri my $str_or_undef = check_uri $str; Validate either a relative or absolute URI containing just ASCII characters, against the RFC3986 spec. Note that this might change in the future to only check absolute URI. =head2 check_uri_reference my $str_or_undef = check_uri_reference $str; Validate either a relative or absolute URI containing just ASCII characters, against the RFC3986 spec. =head2 check_uri_template my $str_or_undef = check_uri_reference $str; Validate an absolute URI with template characters. =head2 check_uuid my $str_or_undef = check_uuid $str; Will check if C<$str> looks like an UUID. Example UUID: "5782165B-6BB6-472F-B3DD-369D707D6C72". =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/URI.pm0000644000076500000240000001067315211411456020302 0ustar jhthorsenstaffpackage JSON::Validator::URI; use Mojo::Base 'Mojo::URL'; use Exporter qw(import); use Digest::SHA (); use Mojo::JSON; use Scalar::Util qw(blessed); use constant UUID_NAMESPACE => do { my $uuid = '1bab225d-1ca6-4cc5-9c53-a37cc7527848'; # UUIDv4 $uuid =~ tr/-//d; pack 'H*', $uuid; }; our @EXPORT_OK = qw(uri); has nid => undef; has nss => undef; sub from_data { my $self = shift->scheme('urn')->nid('uuid'); state $d = Digest::SHA->new(1); $d->reset->add(UUID_NAMESPACE)->add(Mojo::JSON::encode_json(shift)); my $uuid = substr $d->digest, 0, 16; substr $uuid, 6, 1, chr(ord(substr $uuid, 6, 1) & 0x0f | 0x50); # set version 5 substr $uuid, 8, 1, chr(ord(substr $uuid, 8, 1) & 0x3f | 0x80); # set variant 2 return $self->nss(sprintf '%s-%s-%s-%s-%s', map { unpack 'H*', $_ } map { substr $uuid, 0, $_, '' } 4, 2, 2, 2, 6); } sub parse { my ($self, $url) = @_; # URL return $self->SUPER::parse($url) unless $url =~ m!^urn:(.*)$!i; # URN $self->scheme('urn'); # TODO This regex is not 100% correct according to the 1997 changes regarding "?" return $self unless $1 =~ m/^([a-z0-9][a-z0-9-]{0,31}):([^#]+)(#(.*))?/; $self->fragment($4) if defined $3; return $self->nid($1)->nss($2); } sub to_abs { my $self = shift; my $abs = $self->clone; return $abs if $abs->is_abs; # Scheme my $base = shift || $abs->base; $abs->base($base)->scheme($base->scheme); my $scheme = lc($base->scheme // $abs->scheme // ''); if ($scheme eq 'urn') { return $abs->scheme('urn') if $abs->nid and $abs->nss; $abs->nid($base->nid)->nss($base->nss); } else { return $abs if $abs->host; $abs->host($base->host)->port($base->port); } $abs->fragment($base->fragment) unless $abs->fragment; $abs->userinfo($base->userinfo) unless $abs->userinfo; # Absolute path my $path = $abs->path; return $abs if $path->leading_slash; # Inherit path if (!@{$path->parts}) { $abs->path($base->path->clone->canonicalize); # Query $abs->query($base->query->clone) unless length $abs->query->to_string; } # Merge paths else { $abs->path($base->path->clone->merge($path)->canonicalize) } return $abs; } sub to_string { my $self = shift; # URL return $self->SUPER::to_string unless 'urn' eq ($self->scheme // ''); # URN my $urn = sprintf 'urn:%s:%s', $self->nid, $self->nss; my $p = $self->path_query; $urn .= $p =~ m!^/! ? $p : "/$p" if length $p; $urn .= "#$p" if defined($p = $self->fragment); return $urn; } sub to_unsafe_string { my $self = shift; return 'urn' eq ($self->scheme // '') ? $self->to_string : $self->SUPER::to_unsafe_string; } sub uri { my ($uri, $base) = @_; return __PACKAGE__->new unless @_; $uri = __PACKAGE__->new($uri) unless blessed $uri; $base = __PACKAGE__->new($base) if $base and !blessed $base; return $base ? $uri->to_abs($base) : $uri->clone; } 1; =encoding utf8 =head1 NAME JSON::Validator::URI - Uniform Resource Identifier =head1 SYNOPSIS use JSON::Validator::URI; my $urn = JSON::Validator::URI->new('urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f'); my $url = JSON::Validator::URI->new('/foo'); my $url = JSON::Validator::URI->new('https://mojolicious.org'); =head1 DESCRIPTION L is a class for presenting both URL and URN. This class is currently EXPERIMENTAL. =head1 EXPORTED FUNCTIONS =head2 uri $uri = uri; $uri = uri $orig, $base; Returns a new L object from C<$orig> and C<$base>. Both variables can be either a string or a L object. =head1 ATTRIBUTES L inherits all attributes from L and implements the following ones. =head2 nid $str = $uri->nid; Returns the NID part of a URN. Example "uuid" or "iban". =head2 nss $str = $uri->nss; Returns the NSS part of a URN. Example "6e8bc430-9c3a-11d9-9669-0800200c9a66". =head1 METHODS L inherits all methods from L and implements the following ones. =head2 from_data $str = $uri->from_data($data); This method will generate a URN for C<$data>. C<$data> will be serialized using L before being used to generate an UUIDv5. This method is EXPERIMENTAL and subject to change! =head2 parse See L. =head2 to_abs See L. =head2 to_string See L. =head2 to_unsafe_string See L. =head1 SEE ALSO L. =cut JSON-Validator-5.18/lib/JSON/Validator/Schema.pm0000644000076500000240000011245115211411456021040 0ustar jhthorsenstaffpackage JSON::Validator::Schema; use Mojo::Base 'JSON::Validator'; # TODO: Change this to "use Mojo::Base -base" use Carp qw(carp); use JSON::Validator::Formats; use JSON::Validator::URI qw(uri); use JSON::Validator::Util qw(E data_checksum data_type is_bool is_num is_type prefix_errors schema_type str2data); use List::Util qw(first uniq); use Mojo::JSON qw(false true); use Mojo::JSON::Pointer; use Mojo::Util qw(deprecated); use Scalar::Util qw(blessed); has errors => sub { my $self = shift; my $specification = $self->specification || 'http://json-schema.org/draft-04/schema#'; my $validator = $self->new(store => $self->store, _refs => {})->coerce('numbers,strings')->resolve($specification); return [$self->_validate_id($self->id), $validator->validate($self->data)]; }; has formats => sub { shift->_build_formats }; has id => sub { my $data = shift->data; return is_type($data, 'HASH') ? $data->{'$id'} || $data->{id} || '' : ''; }; has moniker => sub { my $self = shift; return "draft$1" if $self->specification =~ m!draft-(\d+)!; return ''; }; has recursive_data_protection => 1; has specification => sub { my $data = shift->data; is_type($data, 'HASH') ? $data->{'$schema'} || $data->{schema} || '' : ''; }; has _ref_keys => sub { [qw($ref)] }; has _refs => sub { +{} }; sub bundle { my ($self, $args) = @_; my $data = $args->{schema} // $self->data; # Avoid $ref on top level while (ref $data eq 'HASH' and $data->{'$ref'} and !ref $data->{'$ref'}) { $data = $self->_refs->{$data}{schema}; } # Check if we have a complex schema or not return $self->new(%$self) unless ref $data eq 'HASH'; my $bundle = $self->new(%$self, data => {}, _refs => {}); local $bundle->{seen_bundle_ref} = {}; local $bundle->{seen_schema} = {}; $bundle->_bundle_from({base_url => $self->id, root => $self->data, schema => $data}); my $id = JSON::Validator::URI->new->from_data($bundle->data); $bundle->id($bundle->store->exists($id) ? $id : $self->store->add($id, $bundle->data)); return $bundle; } sub contains { deprecated 'contains() is deprecated'; state $p = Mojo::JSON::Pointer->new; return $p->data(shift->{data})->contains(@_); } sub data { my $self = shift; return $self->{data} //= {} unless @_; $self->{data} = shift; delete $self->{errors}; return $self; } sub get { my ($self, $pointer, $cb) = @_; my %state = (path => [], root => $self->data, schema => $self->data); return $self->_get([@$pointer], \%state, $cb) if is_type $pointer, 'ARRAY'; return $self->_get([split '/', $pointer], \%state, $cb) if $pointer =~ s!^/!!; return length $pointer ? undef : $self->data; } sub is_invalid { !!@{shift->errors} } sub load_and_validate_schema { Carp::confess('load_and_validate_schema(...) is unsupported.') } sub new { my $class = shift; return $class->SUPER::new(@_) unless @_ % 2; my $data = shift; $data = str2data $data if !ref $data and $data =~ m!^\{|^---|\n!s; return $class->SUPER::new(@_)->resolve($data); } sub resolve { my $self = shift; my $data = shift // $self->data; # $self->data will get deprecated my $state = !ref $data ? $self->store->resolve($data) : (blessed $data && $data->can('to_abs')) ? $self->store->resolve($data->to_abs) : {root => $data, schema => $data}; $self->_refs({}); $self->data($state->{schema}); $self->id($state->{id} || JSON::Validator::URI->new->from_data($state->{schema})) unless $self->id; $state->{id} ||= $self->store->exists($self->id) || $self->store->add($self->id, $self->data); $state->{base_url} ||= $self->id; my (@topics, @refs, %seen) = ([$state, $state->{schema}]); # Search the whole document for id/$id/$ref/$recursiveRef/... TOPIC: while (@topics) { my ($state, $schema) = @{shift @topics}; if (is_type $schema, 'ARRAY') { push @topics, map { [$state, $_] } @$schema; } elsif (is_type $schema, 'HASH') { next TOPIC if $seen{$schema}++; $state = $self->_resolve_object($state, $schema, \@refs, \my %found); ref $schema->{$_} and !$found{$_} and push @topics, [$state, $schema->{$_}] for keys %$schema; } } # Need to resolve the $ref/$recursiveRef/... after id/$id/$anchor/... is found above @topics = (); while (my $r = shift @refs) { my ($schema, $state) = @$r; my $resolved = $self->store->resolve($self->_extract_ref_from_schema($schema), $state); $self->_refs->{$schema} = $resolved; push @topics, [$resolved, $resolved->{schema}]; } # Traverse the newly discovered sub documents, if any goto TOPIC if @topics; return $self; } sub validate { my ($self, $data, $schema) = @_; local $self->{seen} = {}; my %state = (base_url => $self->id, path => [], root => $self->data); my @errors = sort { $a->path cmp $b->path } $self->_validate($_[1], $self->_state(\%state, schema => $schema // $self->data)); return @errors; } sub schema { deprecated 'schema() is deprecated'; return $_[0]->can('data') ? $_[0] : $_[0]->SUPER::schema; } sub _build_formats { return { 'byte' => JSON::Validator::Formats->can('check_byte'), 'date' => JSON::Validator::Formats->can('check_date'), 'date-time' => JSON::Validator::Formats->can('check_date_time'), 'duration' => JSON::Validator::Formats->can('check_duration'), 'double' => JSON::Validator::Formats->can('check_double'), 'email' => JSON::Validator::Formats->can('check_email'), 'float' => JSON::Validator::Formats->can('check_float'), 'hostname' => JSON::Validator::Formats->can('check_hostname'), 'idn-email' => JSON::Validator::Formats->can('check_idn_email'), 'idn-hostname' => JSON::Validator::Formats->can('check_idn_hostname'), 'int32' => JSON::Validator::Formats->can('check_int32'), 'int64' => JSON::Validator::Formats->can('check_int64'), 'ipv4' => JSON::Validator::Formats->can('check_ipv4'), 'ipv6' => JSON::Validator::Formats->can('check_ipv6'), 'iri' => JSON::Validator::Formats->can('check_iri'), 'iri-reference' => JSON::Validator::Formats->can('check_iri_reference'), 'json-pointer' => JSON::Validator::Formats->can('check_json_pointer'), 'regex' => JSON::Validator::Formats->can('check_regex'), 'relative-json-pointer' => JSON::Validator::Formats->can('check_relative_json_pointer'), 'string' => sub { undef }, 'time' => JSON::Validator::Formats->can('check_time'), 'uri' => JSON::Validator::Formats->can('check_uri'), 'uri-reference' => JSON::Validator::Formats->can('check_uri_reference'), 'uri-reference' => JSON::Validator::Formats->can('check_uri_reference'), 'uri-template' => JSON::Validator::Formats->can('check_uri_template'), 'uuid' => JSON::Validator::Formats->can('check_uuid'), }; } # $self inside _bundle() is the target bundle schema object and not the source sub _bundle_from { my ($self, $root_state) = @_; my @topics = ([$root_state, $root_state->{schema}, $self->data, []]); while (my $topic = shift @topics) { my ($state, $source, $target, $path) = @$topic; next if $state->{seen_schema}{$target}++; # Avoid recursion if (ref $source eq 'HASH') { for my $k (keys %$source) { if ($k eq '$ref') { local @$state{qw(root_state schema_path)} = ($root_state, $path); push @topics, $self->_bundle_ref($state, $source, $target); } else { my $type = ref $source->{$k}; $target->{$k} //= $type eq 'HASH' ? {} : $type eq 'ARRAY' ? [] : $source->{$k}; push @topics, [$state, $source->{$k}, $target->{$k}, [@$path, $k]] if $type eq 'HASH' or $type eq 'ARRAY'; } } } elsif (ref $source eq 'ARRAY') { for my $i (0 .. @$source - 1) { my $type = ref $source->[$i]; $target->[$i] //= $type eq 'HASH' ? {} : $type eq 'ARRAY' ? [] : $source->[$i]; push @topics, [$state, $source->[$i], $target->[$i], [@$path, $i]] if $type eq 'HASH' or $type eq 'ARRAY'; } } else { warn "This should not happen! _bundle_from << $source => $target"; } } } sub _bundle_ref { my ($self, $state, $source, $target) = @_; my $uri = $state->{base_url} eq $state->{root_state}{base_url} ? uri($source->{'$ref'}) : uri($source->{'$ref'}, $state->{base_url}); my $source_state = $self->store->resolve($source->{'$ref'}, $state); my $prefix = join '-', map { s!^/+!!; $_ } grep { length $_ } pop @{$uri->path}, $uri->fragment; my ($i, $suffix, @path) = (-1); while (++$i <= 3) { $suffix ||= data_checksum $source_state->{schema} if $i; @path = $self->_bundle_ref_path_expand($source_state, $i ? join '-', $prefix, substr $suffix, 0, $i * 4 : $prefix); $path[-1] =~ s!^\W+!!; $path[-1] =~ s![^\w-]!_!g; # Make a pretty path last unless $self->{seen_bundle_ref}{@path}; } my $def_target = $self->data; $target->{'$ref'} = join '/', '#', @path; $def_target = $def_target->{$_} //= {} for @path; $self->_refs->{$target} = $source_state; return [$source_state, $source_state->{schema}, $def_target, $state->{schema_path}]; } sub _bundle_ref_path_expand { local $_ = $_[2]; s!^definitions/!!; return 'definitions', $_; } sub _extract_ref_from_schema { $_[1]->{'$ref'} } sub _get { my ($self, $pointer, $state, $cb) = @_; my $path = $state->{path}; my $schema = $state->{schema}; my $follow_ref = sub { return if $pointer->[0] and $pointer->[0] eq '$ref'; my $ref_keys = $self->_ref_keys; # ($ref, $recursiveRef ...) my $schema_lookup = $schema; while (ref $schema eq 'HASH') { last unless my $ref_key = first { $schema->{$_} && !ref $schema->{$_} } @$ref_keys; $state = $self->_refs->{$schema_lookup} // Carp::confess(qq(resolve() must be called before validate() to lookup "$schema_lookup->{$ref_key}".)); if (is_type $state->{schema}, 'HASH') { $schema_lookup = $state->{schema}; $schema = {%{$state->{schema}}, %$schema}; $state->{schema}{'$ref'} ? ($schema->{'$ref'} = $state->{schema}{'$ref'}) : delete $schema->{'$ref'}; $state->{schema}{'$recursiveRef'} ? ($schema->{'$recursiveRef'} = $state->{schema}{'$recursiveRef'}) : delete $schema->{'$recursiveRef'}; } else { $schema = $schema_lookup = $state->{schema}; } $state = {%$state, path => $path, schema => $schema}; } }; $follow_ref->(); while (@$pointer) { my $p = shift @$pointer; unless (defined $p) { my $i = 0; return Mojo::Collection->new( map { $self->_get([@$pointer], {%$state, path => [@$path, $_->[1]], schema => $_->[0]}, $cb) } ref $schema eq 'ARRAY' ? (map { [$_, $i++] } @$schema) : ref $schema eq 'HASH' ? (map { [$schema->{$_}, $_] } sort keys %$schema) : ([$schema, ''])); } $p =~ s!~1!/!g; $p =~ s/~0/~/g; push @$path, $p; if (ref $schema eq 'HASH') { return undef unless exists $schema->{$p}; $schema = $schema->{$p}; } elsif (ref $schema eq 'ARRAY') { return undef unless $p =~ /^\d+$/ and @$schema > $p; $schema = $schema->[$p]; } else { return undef; } $follow_ref->(); } return $cb->($schema, E($path)->path) if $cb; return $schema; } sub _register_ref { my ($self, $ref, %state) = @_; $state{base_url} //= $self->id; $state{id} //= $self->id; $state{root} //= $self->data; $state{source} //= '_register_ref'; $self->_refs->{$ref} = \%state; } sub _resolve_object { my ($self, $state, $schema, $refs, $found) = @_; if ($schema->{id} and !ref $schema->{id}) { my $id = uri $schema->{id}, $state->{base_url}; $self->store->add($id => $schema); $state = {%$state, base_url => $id->fragment(undef), id => $id}; } if ($found->{'$ref'} = $schema->{'$ref'} && !ref $schema->{'$ref'}) { push @$refs, [$schema, $state]; } return $state; } sub _state { my ($self, $curr, %override) = @_; my $schema = $override{schema}; my %seen; while (ref $schema eq 'HASH' and $schema->{'$ref'} and !ref $schema->{'$ref'}) { last if $seen{$schema}++; $schema = $self->_refs->{$schema}{schema} // Carp::confess(qq(resolve() must be called before validate() to lookup "$schema->{'$ref'}".)); } return {%$curr, %override, schema => $schema}; } sub _validate { my ($self, $data, $state) = @_; my $schema = $state->{schema}; return $schema ? () : E $state->{path}, [not => 'not'] if is_bool $schema; my @errors; if ($self->recursive_data_protection and 2 == grep { ref $_ && !is_bool($_) } $data, $schema) { my $seen_addr = "$schema:$data"; my $seen = $self->{seen}{$seen_addr}; return @{$seen->[0]} if $seen; # Avoid recursion # Keep $schema and $data referenced for the rest of this validation run, so # their memory addresses cannot be reused by transient (merged) schemas built # later while following $ref/$dynamicRef. Otherwise this address-based cache # key collides with a freed entry and returns stale errors (GH #272). $self->{seen}{$seen_addr} = [\@errors, $schema, $data]; } local $_[1] = $data->TO_JSON if blessed $data and $data->can('TO_JSON'); if ($schema->{not}) { local $self->{seen} = {}; my @e = $self->_validate($_[1], $self->_state($state, schema => $schema->{not})); push @errors, E $state->{path}, [not => 'not'] unless @e; } if (my $rules = $schema->{allOf}) { push @errors, $self->_validate_all_of($_[1], {%$state, schema => $rules}); } if (my $rules = $schema->{anyOf}) { push @errors, $self->_validate_any_of($_[1], {%$state, schema => $rules}); } if (my $rules = $schema->{oneOf}) { push @errors, $self->_validate_one_of($_[1], {%$state, schema => $rules}); } if (exists $schema->{if}) { local $self->{seen} = {}; my $rules = !$schema->{if} || $self->_validate($_[1], $self->_state($state, schema => $schema->{if})) ? $schema->{else} : $schema->{then}; push @errors, $self->_validate($_[1], $self->_state($state, schema => $rules // {})); } my $type = $schema->{type} || schema_type $schema, $_[1]; if (ref $type eq 'ARRAY') { push @errors, $self->_validate_any_of_types($_[1], {%$state, schema => [map { +{%$schema, type => $_} } @$type]}); } elsif ($type) { my $method = sprintf '_validate_type_%s', $type; push @errors, $self->$method($_[1], $state); } return @errors if @errors; if (exists $schema->{const}) { push @errors, $self->_validate_type_const($_[1], $state); } if ($schema->{enum}) { push @errors, $self->_validate_type_enum($_[1], $state); } return @errors; } sub _validate_all_of { my ($self, $data, $state) = @_; my (@errors, @errors_with_prefix); my $i = 0; for my $rule (@{$state->{schema}}) { local $self->{seen} = {}; next unless my @e = $self->_validate($_[1], $self->_state($state, schema => $rule)); push @errors, @e; push @errors_with_prefix, [$i, @e]; } continue { $i++; } return if not @errors; my $e = E $state->{path}; return prefix_errors(allOf => @errors_with_prefix) if @errors == 1 or (grep { $_->details->[1] ne 'type' or $_->path ne $e->path } @errors); # combine all 'type' errors at the base path together my @details = map $_->details, @errors; my $want_types = join '/', uniq map $_->[0], @details; return $e->details([allOf => type => $want_types, $details[-1][2]]); } sub _validate_any_of_types { my ($self, $data, $state) = @_; my @errors; for my $rule (@{$state->{schema}}) { local $self->{seen} = {}; return unless my @e = $self->_validate($_[1], $self->_state($state, schema => $rule)); push @errors, @e; } # favor a non-type error from one of the rules my $e = E $state->{path}; if (my @e = grep { $_->details->[1] ne 'type' or $_->path ne $e->path } @errors) { return @e; } # the type didn't match any of the rules: combine the errors together my @details = map $_->details, @errors; my $want_types = join '/', uniq map $_->[0], @details; return $e->details([$want_types => 'type', $details[-1][2]]); } sub _validate_any_of { my ($self, $data, $state) = @_; my (@errors, @errors_with_prefix); my $i = 0; for my $rule (@{$state->{schema}}) { local $self->{seen} = {}; return unless my @e = $self->_validate($_[1], $self->_state($state, schema => $rule)); push @errors, @e; push @errors_with_prefix, [$i, @e]; } continue { $i++; } my $e = E $state->{path}; return prefix_errors(anyOf => @errors_with_prefix) if @errors == 1 or (grep { $_->details->[1] ne 'type' or $_->path ne $e->path } @errors); # combine all 'type' errors at the base path together my @details = map $_->details, @errors; my $want_types = join '/', uniq map $_->[0], @details; return $e->details([anyOf => type => $want_types, $details[-1][2]]); } sub _validate_id { my ($self, $id) = @_; return unless length $id; return E '/id', 'Fragment not allowed.' if $id =~ /\#./; return E '/id', 'Relative URL not allowed.' unless $id =~ /^\w+:/ or -e $id or $id =~ m!^/!; return; } sub _validate_one_of { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; my (@errors, @errors_with_prefix); my ($i, @passed) = (0); for my $rule (@{$state->{schema}}) { local $self->{seen} = {}; my @e = $self->_validate($_[1], $self->_state($state, schema => $rule)); push @passed, $i and next unless @e; push @errors_with_prefix, [$i, @e]; push @errors, @e; } continue { $i++; } return if @passed == 1; return E $path, [oneOf => 'all_rules_match'] unless @errors; return E $path, [oneOf => 'n_rules_match', join(', ', @passed)] if @passed; my $e = E $state->{path}; return prefix_errors(oneOf => @errors_with_prefix) if @errors == 1 or (grep { $_->details->[1] ne 'type' or $_->path ne $e->path } @errors); # the type didn't match any of the rules: combine the errors together my @details = map $_->details, @errors; my $want_types = join '/', uniq map $_->[0], @details; return $e->details([oneOf => type => $want_types, $details[-1][2]]); } sub _validate_number_max { my ($self, $value, $state, $expected) = @_; my ($path, $schema) = @$state{qw(path schema)}; my @errors; my $cmp_with = $schema->{exclusiveMaximum} // ''; if (is_bool $cmp_with) { push @errors, E $path, [$expected => ex_maximum => $value, $schema->{maximum}] unless $value < $schema->{maximum}; } elsif (is_num $cmp_with) { push @errors, E $path, [$expected => ex_maximum => $value, $cmp_with] unless $value < $cmp_with; } if (exists $schema->{maximum}) { my $cmp_with = $schema->{maximum}; push @errors, E $path, [$expected => maximum => $value, $cmp_with] unless $value <= $cmp_with; } return @errors; } sub _validate_number_min { my ($self, $value, $state, $expected) = @_; my ($path, $schema) = @$state{qw(path schema)}; my @errors; my $cmp_with = $schema->{exclusiveMinimum} // ''; if (is_bool $cmp_with) { push @errors, E $path, [$expected => ex_minimum => $value, $schema->{minimum}] unless $value > $schema->{minimum}; } elsif (is_num $cmp_with) { push @errors, E $path, [$expected => ex_minimum => $value, $cmp_with] unless $value > $cmp_with; } if (exists $schema->{minimum}) { my $cmp_with = $schema->{minimum}; push @errors, E $path, [$expected => minimum => $value, $cmp_with] unless $value >= $cmp_with; } return @errors; } sub _validate_type_enum { my ($self, $data, $state) = @_; my $enum = $state->{schema}{enum}; my $m = data_checksum $data; for my $i (@$enum) { return if $m eq data_checksum $i; } $enum = join ', ', map { (!defined or ref) ? Mojo::JSON::encode_json($_) : $_ } @$enum; return E $state->{path}, [enum => enum => $enum]; } sub _validate_type_const { my ($self, $data, $state) = @_; my $const = $state->{schema}{const}; return if data_checksum($data) eq data_checksum($const); return E $state->{path}, [const => const => Mojo::JSON::encode_json($const)]; } sub _validate_format { my ($self, $value, $state) = @_; my $format = $state->{schema}{format}; my $code = $self->formats->{$format}; return do { warn "Format rule for '$format' is missing"; return } unless $code; return unless my $err = $code->($value); return E $state->{path}, [format => $format, $err]; } sub _validate_type_any { } sub _validate_type_array { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; my @errors; if (ref $data ne 'ARRAY') { return E $path, [array => type => data_type $data]; } if (defined $schema->{minItems} and $schema->{minItems} > @$data) { push @errors, E $path, [array => minItems => int(@$data), $schema->{minItems}]; } if (defined $schema->{maxItems} and $schema->{maxItems} < @$data) { push @errors, E $path, [array => maxItems => int(@$data), $schema->{maxItems}]; } if ($schema->{uniqueItems}) { my %uniq; for (@$data) { next if !$uniq{data_checksum($_)}++; push @errors, E $path, [array => 'uniqueItems']; last; } } if (exists $schema->{contains}) { my ($contains, @e) = ($self->_state($state, schema => $schema->{contains})); for my $i (0 .. @$data - 1) { my @tmp = $self->_validate($data->[$i], {%$contains, path => [@$path, $i]}); push @e, \@tmp if @tmp; } push @errors, map {@$_} @e if @e >= @$data; push @errors, E $path, [array => 'contains'] if not @$data; } if (ref $schema->{items} eq 'ARRAY') { my $additional_items = $schema->{additionalItems} // {}; my @rules = @{$schema->{items}}; if ($additional_items) { push @rules, $additional_items while @rules < @$data; } if (@rules >= @$data) { for my $i (0 .. @$data - 1) { push @errors, $self->_validate($data->[$i], $self->_state($state, path => [@$path, $i], schema => $rules[$i])); } } elsif (!$additional_items) { push @errors, E $path, [array => additionalItems => int(@$data), int(@rules)]; } } elsif (exists $schema->{items}) { my $items = $self->_state($state, schema => $schema->{items}); for my $i (0 .. @$data - 1) { push @errors, $self->_validate($data->[$i], {%$items, path => [@$path, $i]}); } } return @errors; } sub _validate_type_boolean { my ($self, $value, $state) = @_; # String that looks like a boolean if (defined $value and $self->{coerce}{booleans}) { $_[1] = false if $value =~ m!^(0|false|)$!; $_[1] = true if $value =~ m!^(1|true)$!; } return if is_bool $_[1]; return E $state->{path}, [boolean => type => data_type $value]; } sub _validate_type_integer { my ($self, $value, $state) = @_; my @errors = $self->_validate_type_number($_[1], $state, 'integer'); return @errors if @errors; return if $value =~ /^-?\d+$/; return E $state->{path}, [integer => type => data_type $value]; } sub _validate_type_null { my ($self, $value, $state) = @_; return unless defined $value; return E $state->{path}, [null => type => data_type $value]; } sub _validate_type_number { my ($self, $value, $state, $expected) = @_; my @errors; $expected ||= 'number'; if (!defined $value or ref $value) { return E $state->{path}, [$expected => type => data_type $value]; } unless (is_num $value) { return E $state->{path}, [$expected => type => data_type $value] if !$self->{coerce}{numbers} or $value !~ /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/; $_[1] = 0 + $value; # coerce input value } push @errors, $self->_validate_format($value, $state) if $state->{schema}{format}; push @errors, $self->_validate_number_max($value, $state, $expected); push @errors, $self->_validate_number_min($value, $state, $expected); my $d = $state->{schema}{multipleOf}; push @errors, E $state->{path}, [$expected => multipleOf => $d] if $d and ($value / $d) =~ /\.[^0]+$/; return @errors; } sub _validate_type_object { my ($self, $data, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; return E $path, [object => type => data_type $data] unless ref $data eq 'HASH'; my @errors; my @dkeys = keys %$data; if (defined $schema->{maxProperties} and $schema->{maxProperties} < @dkeys) { push @errors, E $path, [object => maxProperties => int(@dkeys), $schema->{maxProperties}]; } if (defined $schema->{minProperties} and $schema->{minProperties} > @dkeys) { push @errors, E $path, [object => minProperties => int(@dkeys), $schema->{minProperties}]; } if (exists $schema->{propertyNames}) { my $property_names = $self->_state($state, schema => $schema->{propertyNames}); for my $name (keys %$data) { next unless my @e = $self->_validate($name, $property_names); push @errors, prefix_errors propertyName => map [$name, $_], @e; } } my %rules; for my $k (keys %{$schema->{properties} || {}}) { my $r = $schema->{properties}{$k}; push @{$rules{$k}}, $r; if ($self->{coerce}{defaults} and ref $r eq 'HASH' and exists $r->{default} and !exists $data->{$k}) { $data->{$k} = $r->{default}; } } for my $p (keys %{$schema->{patternProperties} || {}}) { my $r = $schema->{patternProperties}{$p}; push @{$rules{$_}}, $r for grep { $_ =~ /$p/ } @dkeys; } my $additional = exists $schema->{additionalProperties} ? $schema->{additionalProperties} : {}; if ($additional) { $additional = {} unless is_type $additional, 'HASH'; $rules{$_} ||= [$additional] for @dkeys; } elsif (my @k = grep { !$rules{$_} } @dkeys) { local $" = ', '; return E $path, [object => additionalProperties => join ', ', sort @k]; } for my $k (uniq @{$schema->{required} || []}) { next if exists $data->{$k}; push @errors, E [@$path, $k], [object => 'required']; delete $rules{$k}; } my $dependencies = $schema->{dependencies} || {}; for my $k (keys %$dependencies) { next if not exists $data->{$k}; if (ref $dependencies->{$k} eq 'ARRAY') { push @errors, map { E [@$path, $_], [object => dependencies => $k] } grep { !exists $data->{$_} } @{$dependencies->{$k}}; } elsif (ref $dependencies->{$k} eq 'HASH') { push @errors, $self->_validate_type_object($data, $self->_state($state, schema => $schema->{dependencies}{$k})); } } for my $k (keys %rules) { for my $r (@{$rules{$k}}) { next unless exists $data->{$k}; my $s2 = $self->_state($state, path => [@$path, $k], schema => $r); my @e = $self->_validate($data->{$k}, $s2); push @errors, @e; next if @e or !is_type $r, 'HASH'; push @errors, $self->_validate_type_enum($data->{$k}, $s2) if $r->{enum}; push @errors, $self->_validate_type_const($data->{$k}, $s2) if $r->{const}; } } return @errors; } sub _validate_type_string { my ($self, $value, $state) = @_; my ($path, $schema) = @$state{qw(path schema)}; my @errors; if (!$schema->{type} and !defined $value) { return; } if (!defined $value or ref $value) { return E $path, [string => type => data_type $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, [string => type => data_type $value] unless $self->{coerce}{strings}; $_[1] = "$value"; # coerce input value } if ($schema->{format}) { push @errors, $self->_validate_format($value, $state); } if (defined $schema->{maxLength}) { if (length($value) > $schema->{maxLength}) { push @errors, E $path, [string => maxLength => length($value), $schema->{maxLength}]; } } if (defined $schema->{minLength}) { if (length($value) < $schema->{minLength}) { push @errors, E $path, [string => minLength => length($value), $schema->{minLength}]; } } if (defined $schema->{pattern}) { my $p = $schema->{pattern}; push @errors, E $path, [string => pattern => $p] unless $value =~ /$p/; } return @errors; } 1; =encoding utf8 =head1 NAME JSON::Validator::Schema - Base class for JSON::Validator schemas =head1 SYNOPSIS =head2 Basics # Create a new schema from a file on disk # It is also possible to create the object from JSON::Validator::Schema, # but you most likely want to use one of the subclasses. my $schema = JSON::Validator::Schema::Draft7->new('file:///cool/beans.yaml'); # Validate the schema die $schema->errors->[0] if $schema->is_invalid; # Validate data my @errors = $schema->validate({some => 'data'}); die $errors[0] if @errors; =head2 Shared store my $store = JSON::Validator::Store->new; my $schema = JSON::Validator::Schema::Draft7->new(store => $store); # Will not fetch the file from web, if the $store has already retrieved # the schema $schema->resolve('https://api.example.com/cool/beans.json'); =head2 Make a new validation class package JSON::Validator::Schema::SomeSchema; use Mojo::Base 'JSON::Validator::Schema'; has specification => 'https://api.example.com/my/spec.json#'; 1; =head1 DESCRIPTION L is the base class for L, L, L, L, L and L. Any of the classes above can be used instead of L if you know which draft/version you are working with up front. =head2 Validation L can both validate user input and the schema itself. =over 2 =item * A L represents a set of validation rules stored in L. The rules stored in the L attribute will be used when calling the L method. =item * The input to C could be some data from a web request or some other user input. C returns a list of L objects, if the user input (input to C) contains invalid data. =item * The L and L attributes has nothing to do with user input, meaning it is I relevant for L. These two accessors are used to check if the rules/schema stored in L is correct. The validation is performed against the L. This is pretty much the same as: my $jv = JSON::Validator->new; my $draft7 = $jv->schema('http://json-schema.org/draft-07/schema#')->schema; my $schema = $jv->schema({name => {type => 'string'}})->schema; my @errors = $draft7->validate($schema->data); =back =head1 ATTRIBUTES =head2 errors $array_ref = $schema->errors; Holds the errors after checking L against L. C<$array_ref> containing no elements means L is valid. Each element in the array-ref is a L object. This attribute is I changed by L. It only reflects if the C<$schema> is valid. =head2 formats $hash_ref = $schema->formats; $schema = $schema->formats(\%hash); Holds a hash-ref, where the keys are supported JSON type "formats", and the associated values hold code blocks which can validate the given format. A code block should return C on success and an error string on error: sub { return defined $_[0] && $_[0] eq "42" ? undef : "Not the answer." }; See L for a list of supported formats. =head2 recursive_data_protection The value of this attribute will be copied into the created L. See L for more details. =head2 id $str = $schema->id; $schema = $schema->id($str); Holds the ID for this schema. Usually extracted from C<"$id"> or C<"id"> in L. =head2 moniker $str = $schema->moniker; $schema = $schema->moniker("some_name"); Used to get/set the moniker for the given schema. Will be "draft04" if L points to a JSON Schema draft URL, and fallback to empty string if unable to guess a moniker name. This attribute will (probably) detect more monikers from a given L or C in the future. =head2 recursive_data_protection $schema = $schema->recursive_data_protection($bool); $bool = $schema->recursive_data_protection; Recursive data protection is active by default, however it can be deactivated by assigning a false value to the L attribute. Recursive data protection can have a noticeable impact on memory usage when validating large data structures. If you are encountering issues with memory and you can guarantee that you do not have any loops in your data structure then deactivating the recursive data protection may help. This attribute is EXPERIMENTAL and may change in a future release. B =head2 specification $str = $schema->specification; $schema = $schema->specification($str); The URL to the specification used when checking for L. Usually extracted from C<"$schema"> or C<"schema"> in L. =head2 store $store = $schema->store; Holds a L object that caches the retrieved schemas. This object can be shared amongst different schema objects to prevent a schema from having to be downloaded again. =head1 METHODS =head2 bundle $bundled = $schema->bundle; C<$bundled> is a new L object where none of the "$ref" will point to external resources. This can be useful, if you want to have a bunch of files locally, but hand over a single file to a client. Mojo::File->new("client.json") ->spurt(Mojo::JSON::to_json($schema->bundle->data)); =head2 coerce $schema = $schema->coerce('bool,def,num,str'); $schema = $schema->coerce('booleans,defaults,numbers,strings'); $hash_ref = $schema->coerce; Set the given type to coerce. Before enabling coercion this module is very strict when it comes to validating types. Example: The string C<"1"> is not the same as the number C<1>, unless you have "numbers" coercion enabled. =over 2 =item * booleans Will convert what could be interpreted as a boolean (that is, an actual numeric C<1> or C<0>, and the strings "true" and "false") to a L object. Note that "foo" is not considered a true value and will fail the validation. =item * defaults Will copy the default value defined in the schema, into the input structure, if the input value is non-existing. Note that support for "default" is currently EXPERIMENTAL, and enabling this might be changed in future versions. =item * numbers Will convert strings that look like numbers, into true numbers. This works for both the "integer" and "number" types. =item * strings Will convert a number into a string. This works for the "string" type. =back =head2 contains This method will be removed in a future release. =head2 data my $hash_ref = $schema->data; my $schema = $schema->data($bool); my $schema = $schema->data($hash_ref); Will set a structure representing the schema. In most cases you want to use L instead of L. =head2 get my $data = $schema->get([@json_pointer]); my $data = $schema->get($json_pointer); my $data = $schema->get($json_pointer, sub { my ($data, $json_pointer) = @_; }); This method will extract data from L, using a C<$json_pointer> - L. It can however be used in a more complex way by passing in an array-ref: The array-ref can contain C values, will result in extracting any element on that point, regardless of value. In that case a L will be returned. A callback can also be given. This callback will be called each time the C<$json_pointer> matches some data, and will pass in the C<$json_pointer> at that place. In addition if this method "sees" a JSON-Schema C<$ref> on the way, the "$ref" will be followed into any given sub-schema. =head2 is_invalid my $bool = $schema->is_invalid; Returns true if the schema in L is invalid. Internally this method calls L which will validate L against L. =head2 load_and_validate_schema This method is unsupported. Use L or L instead. =head2 new my $schema = JSON::Validator::Schema->new($data); my $schema = JSON::Validator::Schema->new($data, %attributes); my $schema = JSON::Validator::Schema->new(%attributes); Construct a new L object. Passing C<$data> as the first argument will cause L to be called, meaning the constructor might throw an exception if the schema could not be successfully resolved. =head2 resolve $schema = $schema->resolve($uri); $schema = $schema->resolve($data); Used to resolve L<$uri> or L<$data> and store the resolved schema in L. If C<$data> or C<$uri> contain any "$ref", then these schemas will be downloaded and resolved as well. If L does not contain an "id" or "$id", then L will be assigned a autogenerated "urn". This "urn" might be changed in future releases, but should always be the same for the same L. =head2 schema This method will be removed in a future release. =head2 validate @errors = $schema->validate($any); Will validate C<$any> against the schema defined in L. Each element in C<@errors> is an L object. =head1 SEE ALSO L. =cut JSON-Validator-5.18/Makefile.PL0000644000076500000240000000663515210277065016023 0ustar jhthorsenstaffuse 5.016; use strict; use warnings; use utf8; use ExtUtils::MakeMaker; my $GITHUB_URL = 'https://github.com/jhthorsen/json-validator'; my @PREREQ_YAML = !$ENV{JSON_VALIDATOR_PREFER_YAML_PP} || eval 'use YAML::XS 0.67;1' ? ('YAML::XS' => '0.67') : ('YAML::PP' => '0.020'); my %WriteMakefileArgs = ( NAME => 'JSON::Validator', ABSTRACT_FROM => 'lib/JSON/Validator.pm', AUTHOR => 'Jan Henning Thorsen ', LICENSE => 'artistic_2', VERSION_FROM => 'lib/JSON/Validator.pm', PREREQ_PM => { 'perl' => 'v5.16.0', 'Data::Validate::Domain' => '0.11', 'Data::Validate::IP' => '0.27', 'List::Util' => '1.45', 'Mojolicious' => '9.34', 'Net::IDN::Encode' => '2.500', @PREREQ_YAML }, TEST_REQUIRES => {'Test::More' => '1.30', 'Test::Deep' => '0'}, META_MERGE => { 'dynamic_config' => 0, 'meta-spec' => {version => 2}, 'no_index' => {directory => [qw(examples t)]}, 'prereqs' => {runtime => {requires => {perl => '5.016'}}}, 'resources' => { bugtracker => {web => "$GITHUB_URL/issues"}, homepage => $GITHUB_URL, license => ['http://www.opensource.org/licenses/artistic-license-2.0'], repository => {type => 'git', url => "$GITHUB_URL.git", web => $GITHUB_URL}, x_IRC => {url => 'irc://irc.libera.chat/#perl-openapi', web => 'https://web.libera.chat/#perl-openapi'}, }, 'x_contributors' => [ 'Aleksandr Orlenko ', 'Alexander Hartmaier ', 'Alexander Karelas ', 'Bernhard Graf ', 'Brad Barden ', 'Dagfinn Ilmari Mannsåker ', 'Daniel Böhmer ', 'David Cantrell ', 'Ed J ', 'Ere Maijala ', 'Fabrizio Gennari ', 'Ilya Rassadin ', 'Jan Henning Thorsen ', 'Jason Cooper ', 'Karen Etheridge ', 'Kenichi Ishigaki ', 'Kevin M. Goess ', 'Kirill Matusov ', 'Krasimir Berov ', 'Lari Taskula ', 'Lee Johnson ', 'Martin Renvoize ', 'Mattias Päivärinta ', 'Michael Jemmeson ', 'Michael Schout ', 'Mohammad S Anwar ', 'Nick Morrott ', 'Pierre-Aymeric Masse ', 'Roy Storey ', 'Russell Jenkins ', 'Sebastian Riedel ', 'Stephan Hradek ', 'Tim Stallard ', 'Zoffix Znet ', ], }, test => {TESTS => (-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-5.18/META.json0000644000076500000240000000705515211411470015456 0ustar jhthorsenstaff{ "abstract" : "Validate data against a JSON schema", "author" : [ "Jan Henning Thorsen " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.76, 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", "examples", "t" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Data::Validate::Domain" : "0.11", "Data::Validate::IP" : "0.27", "List::Util" : "1.45", "Mojolicious" : "9.34", "Net::IDN::Encode" : "2.500", "YAML::XS" : "0.67", "perl" : "5.016" } }, "test" : { "requires" : { "Test::Deep" : "0", "Test::More" : "1.30" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/jhthorsen/json-validator/issues" }, "homepage" : "https://github.com/jhthorsen/json-validator", "license" : [ "http://www.opensource.org/licenses/artistic-license-2.0" ], "repository" : { "type" : "git", "url" : "https://github.com/jhthorsen/json-validator.git", "web" : "https://github.com/jhthorsen/json-validator" }, "x_IRC" : { "url" : "irc://irc.libera.chat/#perl-openapi", "web" : "https://web.libera.chat/#perl-openapi" } }, "version" : "5.18", "x_contributors" : [ "Aleksandr Orlenko ", "Alexander Hartmaier ", "Alexander Karelas ", "Bernhard Graf ", "Brad Barden ", "Dagfinn Ilmari Mannsåker ", "Daniel Böhmer ", "David Cantrell ", "Ed J ", "Ere Maijala ", "Fabrizio Gennari ", "Ilya Rassadin ", "Jan Henning Thorsen ", "Jason Cooper ", "Karen Etheridge ", "Kenichi Ishigaki ", "Kevin M. Goess ", "Kirill Matusov ", "Krasimir Berov ", "Lari Taskula ", "Lee Johnson ", "Martin Renvoize ", "Mattias Päivärinta ", "Michael Jemmeson ", "Michael Schout ", "Mohammad S Anwar ", "Nick Morrott ", "Pierre-Aymeric Masse ", "Roy Storey ", "Russell Jenkins ", "Sebastian Riedel ", "Stephan Hradek ", "Tim Stallard ", "Zoffix Znet " ], "x_serialization_backend" : "JSON::PP version 4.16" }