From dd987001702d609087263abdf53f1524c9e96f6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:11:09 +0000 Subject: [PATCH 1/2] build(deps): bump justinrainbow/json-schema from 6.6.4 to 6.10.0 Bumps [justinrainbow/json-schema](https://github.com/jsonrainbow/json-schema) from 6.6.4 to 6.10.0. - [Release notes](https://github.com/jsonrainbow/json-schema/releases) - [Changelog](https://github.com/jsonrainbow/json-schema/blob/main/CHANGELOG.md) - [Commits](https://github.com/jsonrainbow/json-schema/compare/6.6.4...6.10.0) --- updated-dependencies: - dependency-name: justinrainbow/json-schema dependency-version: 6.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 3677d10f3..d92c5fde9 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "icewind/searchdav": "^3.2.0", "icewind/smb": "^3.8.1", "icewind/streams": "^0.7.8", - "justinrainbow/json-schema": "^6.6", + "justinrainbow/json-schema": "^6.10", "kornrunner/blurhash": "^1.2", "laravel/serializable-closure": "^2.0.4", "mexitek/phpcolors": "^1.0", diff --git a/composer.lock b/composer.lock index 87981eb61..c382652df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "61e5e26a724caa259b18258e2cb006e8", + "content-hash": "eba98ef52bbf1ab1f9b60b4e76d92abc", "packages": [ { "name": "aws/aws-crt-php", @@ -1655,16 +1655,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.4", + "version": "6.10.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" + "reference": "8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33", + "reference": "8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33", "shasum": "" }, "require": { @@ -1674,7 +1674,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "^23.2", + "json-schema/json-schema-test-suite": "dev-main", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", @@ -1724,9 +1724,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.10.0" }, - "time": "2025-12-19T15:01:32+00:00" + "time": "2026-06-16T20:50:26+00:00" }, { "name": "kornrunner/blurhash", From b735369e55819b9f922bc86e2ccf0bffe2fc4197 Mon Sep 17 00:00:00 2001 From: nextcloud-command Date: Fri, 7 Aug 2026 13:40:22 +0000 Subject: [PATCH 2/2] chore(autoloader): Dump autoloader Signed-off-by: nextcloud-command --- composer/autoload_classmap.php | 71 ++++ composer/autoload_static.php | 71 ++++ composer/installed.json | 16 +- composer/installed.php | 6 +- .../dist/schema/json-schema-draft-07.json | 245 ++++++++++++ .../schema/json-schema-draft-2019-09.json | 152 ++++++++ .../src/JsonSchema/ConstraintError.php | 10 +- .../src/JsonSchema/Constraints/Constraint.php | 1 + .../AdditionalPropertiesConstraint.php | 2 +- .../Drafts/Draft06/FormatConstraint.php | 4 +- .../Draft06/PropertiesNamesConstraint.php | 15 + .../Draft07/AdditionalItemsConstraint.php | 61 +++ .../AdditionalPropertiesConstraint.php | 93 +++++ .../Drafts/Draft07/AllOfConstraint.php | 42 +++ .../Drafts/Draft07/AnyOfConstraint.php | 51 +++ .../Drafts/Draft07/ConstConstraint.php | 35 ++ .../Drafts/Draft07/ContainsConstraint.php | 47 +++ .../Drafts/Draft07/ContentConstraint.php | 55 +++ .../Drafts/Draft07/DependenciesConstraint.php | 64 ++++ .../Drafts/Draft07/Draft07Constraint.php | 83 ++++ .../Drafts/Draft07/EnumConstraint.php | 41 ++ .../Draft07/ExclusiveMaximumConstraint.php | 38 ++ .../Draft07/ExclusiveMinimumConstraint.php | 38 ++ .../Constraints/Drafts/Draft07/Factory.php | 48 +++ .../Drafts/Draft07/FormatConstraint.php | 354 ++++++++++++++++++ .../Drafts/Draft07/IfThenElseConstraint.php | 67 ++++ .../Drafts/Draft07/ItemsConstraint.php | 52 +++ .../Drafts/Draft07/MaxItemsConstraint.php | 39 ++ .../Drafts/Draft07/MaxLengthConstraint.php | 39 ++ .../Draft07/MaxPropertiesConstraint.php | 39 ++ .../Drafts/Draft07/MaximumConstraint.php | 38 ++ .../Drafts/Draft07/MinItemsConstraint.php | 39 ++ .../Drafts/Draft07/MinLengthConstraint.php | 39 ++ .../Draft07/MinPropertiesConstraint.php | 39 ++ .../Drafts/Draft07/MinimumConstraint.php | 38 ++ .../Drafts/Draft07/MultipleOfConstraint.php | 54 +++ .../Drafts/Draft07/NotConstraint.php | 40 ++ .../Drafts/Draft07/OneOfConstraint.php | 50 +++ .../Drafts/Draft07/PatternConstraint.php | 63 ++++ .../Draft07/PatternPropertiesConstraint.php | 72 ++++ .../Drafts/Draft07/PropertiesConstraint.php | 48 +++ .../Draft07/PropertiesNamesConstraint.php | 80 ++++ .../Drafts/Draft07/RefConstraint.php | 45 +++ .../Drafts/Draft07/RequiredConstraint.php | 58 +++ .../Drafts/Draft07/TypeConstraint.php | 50 +++ .../Drafts/Draft07/UniqueItemsConstraint.php | 48 +++ .../Draft2019/AdditionalItemsConstraint.php | 64 ++++ .../AdditionalPropertiesConstraint.php | 93 +++++ .../Drafts/Draft2019/AllOfConstraint.php | 42 +++ .../Drafts/Draft2019/AnyOfConstraint.php | 51 +++ .../Drafts/Draft2019/ConstConstraint.php | 35 ++ .../Drafts/Draft2019/ContainsConstraint.php | 54 +++ .../Drafts/Draft2019/ContentConstraint.php | 25 ++ .../Draft2019/DependentRequiredConstraint.php | 50 +++ .../Draft2019/DependentSchemasConstraint.php | 58 +++ .../Drafts/Draft2019/Draft2019Constraint.php | 84 +++++ .../Drafts/Draft2019/EnumConstraint.php | 41 ++ .../Draft2019/ExclusiveMaximumConstraint.php | 38 ++ .../Draft2019/ExclusiveMinimumConstraint.php | 38 ++ .../Constraints/Drafts/Draft2019/Factory.php | 49 +++ .../Drafts/Draft2019/FormatConstraint.php | 337 +++++++++++++++++ .../Drafts/Draft2019/IfThenElseConstraint.php | 67 ++++ .../Drafts/Draft2019/ItemsConstraint.php | 54 +++ .../Drafts/Draft2019/MaxItemsConstraint.php | 39 ++ .../Drafts/Draft2019/MaxLengthConstraint.php | 39 ++ .../Draft2019/MaxPropertiesConstraint.php | 39 ++ .../Drafts/Draft2019/MaximumConstraint.php | 38 ++ .../Drafts/Draft2019/MinItemsConstraint.php | 39 ++ .../Drafts/Draft2019/MinLengthConstraint.php | 39 ++ .../Draft2019/MinPropertiesConstraint.php | 39 ++ .../Drafts/Draft2019/MinimumConstraint.php | 38 ++ .../Drafts/Draft2019/MultipleOfConstraint.php | 54 +++ .../Drafts/Draft2019/NotConstraint.php | 40 ++ .../Drafts/Draft2019/OneOfConstraint.php | 50 +++ .../Drafts/Draft2019/PatternConstraint.php | 63 ++++ .../Draft2019/PatternPropertiesConstraint.php | 75 ++++ .../Drafts/Draft2019/PropertiesConstraint.php | 51 +++ .../Draft2019/PropertiesNamesConstraint.php | 80 ++++ .../Drafts/Draft2019/RefConstraint.php | 45 +++ .../Drafts/Draft2019/RequiredConstraint.php | 58 +++ .../Drafts/Draft2019/TypeConstraint.php | 50 +++ .../Draft2019/UniqueItemsConstraint.php | 48 +++ .../src/JsonSchema/Constraints/Factory.php | 2 + .../Constraints/FormatConstraint.php | 3 +- .../Constraints/ObjectConstraint.php | 2 +- .../src/JsonSchema/DraftIdentifiers.php | 45 ++- .../src/JsonSchema/Entity/JsonPointer.php | 2 +- .../src/JsonSchema/SchemaStorage.php | 5 + .../Validator/RelativeReferenceValidator.php | 8 - .../src/JsonSchema/Uri/UriRetriever.php | 14 +- 90 files changed, 4821 insertions(+), 44 deletions(-) create mode 100644 justinrainbow/json-schema/dist/schema/json-schema-draft-07.json create mode 100644 justinrainbow/json-schema/dist/schema/json-schema-draft-2019-09.json create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentRequiredConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentSchemasConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/EnumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMaximumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMinimumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/IfThenElseConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxLengthConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaximumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinItemsConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinLengthConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinimumConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MultipleOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/NotConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/OneOfConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesNamesConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RequiredConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/TypeConstraint.php create mode 100644 justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/UniqueItemsConstraint.php diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 2d0b3124c..fbf7ba482 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -1236,6 +1236,77 @@ 'JsonSchema\\Constraints\\Drafts\\Draft06\\RequiredConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php', 'JsonSchema\\Constraints\\Drafts\\Draft06\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php', 'JsonSchema\\Constraints\\Drafts\\Draft06\\UniqueItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AllOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AnyOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ConstConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ContainsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ContentConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\DependenciesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\Draft07Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\IfThenElseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MultipleOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\NotConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\OneOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesNamesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\RefConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\RequiredConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\UniqueItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AdditionalItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AdditionalPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AllOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AnyOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ConstConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ContainsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ContentConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\DependentRequiredConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentRequiredConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\DependentSchemasConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentSchemasConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\Draft2019Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/EnumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ExclusiveMaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ExclusiveMinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\IfThenElseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/IfThenElseConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaxItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaxLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaxPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MultipleOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MultipleOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\NotConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/NotConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\OneOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/OneOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PatternConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PatternPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PropertiesNamesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesNamesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\RefConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\RequiredConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RequiredConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/TypeConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\UniqueItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/UniqueItemsConstraint.php', 'JsonSchema\\Constraints\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php', 'JsonSchema\\Constraints\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php', 'JsonSchema\\Constraints\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php', diff --git a/composer/autoload_static.php b/composer/autoload_static.php index a75415bdf..2f2c871c6 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -1791,6 +1791,77 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'JsonSchema\\Constraints\\Drafts\\Draft06\\RequiredConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php', 'JsonSchema\\Constraints\\Drafts\\Draft06\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php', 'JsonSchema\\Constraints\\Drafts\\Draft06\\UniqueItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AllOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\AnyOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ConstConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ContainsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ContentConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\DependenciesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\Draft07Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\IfThenElseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\ItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\MultipleOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\NotConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\OneOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesNamesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\RefConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\RequiredConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft07\\UniqueItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AdditionalItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AdditionalPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AllOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\AnyOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ConstConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ContainsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ContentConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\DependentRequiredConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentRequiredConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\DependentSchemasConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentSchemasConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\Draft2019Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/EnumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ExclusiveMaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ExclusiveMinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\IfThenElseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/IfThenElseConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\ItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaxItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaxLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaxPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaximumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinItemsConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinLengthConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinimumConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\MultipleOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MultipleOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\NotConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/NotConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\OneOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/OneOfConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PatternConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PatternPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\PropertiesNamesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesNamesConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\RefConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\RequiredConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RequiredConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/TypeConstraint.php', + 'JsonSchema\\Constraints\\Drafts\\Draft2019\\UniqueItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/UniqueItemsConstraint.php', 'JsonSchema\\Constraints\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php', 'JsonSchema\\Constraints\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php', 'JsonSchema\\Constraints\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php', diff --git a/composer/installed.json b/composer/installed.json index b27c78441..4ffd9f084 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -1718,17 +1718,17 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.4", - "version_normalized": "6.6.4.0", + "version": "6.10.0", + "version_normalized": "6.10.0.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" + "reference": "8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33", + "reference": "8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33", "shasum": "" }, "require": { @@ -1738,13 +1738,13 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "^23.2", + "json-schema/json-schema-test-suite": "dev-main", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^8.5" }, - "time": "2025-12-19T15:01:32+00:00", + "time": "2026-06-16T20:50:26+00:00", "bin": [ "bin/validate-json" ], @@ -1790,7 +1790,7 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.10.0" }, "install-path": "../justinrainbow/json-schema" }, diff --git a/composer/installed.php b/composer/installed.php index 4f9cd04f3..d7cce4ca9 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -218,9 +218,9 @@ 'dev_requirement' => false, ), 'justinrainbow/json-schema' => array( - 'pretty_version' => '6.6.4', - 'version' => '6.6.4.0', - 'reference' => '2eeb75d21cf73211335888e7f5e6fd7440723ec7', + 'pretty_version' => '6.10.0', + 'version' => '6.10.0.0', + 'reference' => '8b1308a9d7bdbdb20ce87ef920f82b4564bb2d33', 'type' => 'library', 'install_path' => __DIR__ . '/../justinrainbow/json-schema', 'aliases' => array(), diff --git a/justinrainbow/json-schema/dist/schema/json-schema-draft-07.json b/justinrainbow/json-schema/dist/schema/json-schema-draft-07.json new file mode 100644 index 000000000..c173cb868 --- /dev/null +++ b/justinrainbow/json-schema/dist/schema/json-schema-draft-07.json @@ -0,0 +1,245 @@ +{ + "$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 + }, + "writeOnly": { + "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 +} diff --git a/justinrainbow/json-schema/dist/schema/json-schema-draft-2019-09.json b/justinrainbow/json-schema/dist/schema/json-schema-draft-2019-09.json new file mode 100644 index 000000000..bc67d49b4 --- /dev/null +++ b/justinrainbow/json-schema/dist/schema/json-schema-draft-2019-09.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "title": "Core and Validation specifications meta-schema", + "type": ["object", "boolean"], + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/$defs/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "properties": { + "$id": { "type": "string", "format": "uri-reference" }, + "$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": { "$ref": "#" }, + "default": {} + }, + "definitions": { + "$comment": "Retained for backwards compatibility, replaced by $defs.", + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "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 }, + "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" }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/$defs/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { "type": "boolean", "default": false }, + "contains": { "$ref": "#" }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "unevaluatedItems": { "$ref": "#" }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "unevaluatedProperties": { "$ref": "#" }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { "$ref": "#" } + }, + "dependentRequired": { + "type": "object", + "additionalProperties": { "$ref": "#/$defs/stringArray" } + }, + "dependencies": { + "$comment": "Retained for backwards compatibility, replaced by dependentSchemas/dependentRequired.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/$defs/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentSchema": { "$ref": "#" }, + "if": { "$ref": "#" }, + "then": { "$ref": "#" }, + "else": { "$ref": "#" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true +} \ No newline at end of file diff --git a/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php b/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php index ed6cf03e0..081e38ad7 100644 --- a/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php +++ b/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php @@ -39,7 +39,9 @@ class ConstraintError extends Enum public const INVALID_SCHEMA = 'invalidSchema'; public const LENGTH_MAX = 'maxLength'; public const LENGTH_MIN = 'minLength'; + public const MAX_CONTAINS = 'maxContains'; public const MAXIMUM = 'maximum'; + public const MIN_CONTAINS = 'minContains'; public const MIN_ITEMS = 'minItems'; public const MINIMUM = 'minimum'; public const MISSING_ERROR = 'missingError'; @@ -58,6 +60,8 @@ class ConstraintError extends Enum public const PROPERTY_NAMES = 'propertyNames'; public const TYPE = 'type'; public const UNIQUE_ITEMS = 'uniqueItems'; + public const CONTENT_MEDIA_TYPE = 'contentMediaType'; + public const CONTENT_ENCODING = 'contentEncoding'; /** * @return string @@ -97,8 +101,10 @@ public function getMessage() self::LENGTH_MAX => 'Must be at most %d characters long', self::INVALID_SCHEMA => 'Schema is not valid', self::LENGTH_MIN => 'Must be at least %d characters long', + self::MAX_CONTAINS => 'There must be a maximum of %d valid items in the array, %d found', self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found', self::MAXIMUM => 'Must have a maximum value less than or equal to %d', + self::MIN_CONTAINS => 'There must be a minimum of %d valid items in the array, %d found', self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found', self::MINIMUM => 'Must have a minimum value greater than or equal to %d', self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum', @@ -115,7 +121,9 @@ public function getMessage() self::PROPERTIES_MAX => 'Must contain no more than %d properties', self::PROPERTY_NAMES => 'Property name %s is invalid', self::TYPE => '%s value found, but %s is required', - self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array' + self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array', + self::CONTENT_MEDIA_TYPE => 'Value is not valid with content media type', + self::CONTENT_ENCODING => 'Value is not valid with content encoding', ]; if (!isset($messages[$name])) { diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php index 3c7e824b1..7538ad673 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php @@ -11,6 +11,7 @@ abstract class Constraint extends BaseConstraint implements ConstraintInterface /** @var string */ protected $inlineSchemaProperty = '$schema'; + /** @deprecated CHECK_MODE_NONE is unused and will be removed in the next major release (7.0.0) */ public const CHECK_MODE_NONE = 0x00000000; public const CHECK_MODE_NORMAL = 0x00000001; public const CHECK_MODE_TYPE_CAST = 0x00000002; diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php index 5f2448961..5d6c55cb5 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php @@ -66,7 +66,7 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n } foreach ($additionalProperties as $key => $additionalPropertiesValue) { - $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]); + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $key]); } } diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php index 578a27c37..ec61b73b6 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php @@ -155,7 +155,7 @@ private function validateColor(string $color): bool return true; } - return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false; + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) === 1; } private function validateStyle(string $style): bool @@ -168,7 +168,7 @@ private function validateStyle(string $style): bool private function validatePhone(string $phone): bool { - return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false; + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) === 1; } private function validateHostname(string $host): bool diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php index d621ecf26..96312cef8 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php @@ -61,5 +61,20 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n } } } + + if (property_exists($schema->propertyNames, 'const')) { + foreach ($propertyNames as $propertyName => $_) { + if ($propertyName !== $schema->propertyNames->const) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'enum')) { + $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum); + foreach ($diff as $propertyName) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]); + } + } } } diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php new file mode 100644 index 000000000..8444d0535 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php @@ -0,0 +1,61 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalItems')) { + return; + } + + if ($schema->additionalItems === true) { + return; + } + if ($schema->additionalItems === false && !property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + if (!property_exists($schema, 'items')) { + return; + } + if (property_exists($schema, 'items') && is_object($schema->items)) { + return; + } + + $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []); + + foreach ($additionalItems as $propertyName => $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php new file mode 100644 index 000000000..7162c11be --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php @@ -0,0 +1,93 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalProperties')) { + return; + } + + if ($schema->additionalProperties === true) { + return; + } + + if (!is_object($value)) { + return; + } + + $additionalProperties = get_object_vars($value); + + if (isset($schema->properties)) { + $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties); + } + + if (isset($schema->patternProperties)) { + $patterns = array_keys(get_object_vars($schema->patternProperties)); + + foreach ($additionalProperties as $key => $_) { + foreach ($patterns as $pattern) { + if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) { + unset($additionalProperties[$key]); + break; + } + } + } + } + + if (is_object($schema->additionalProperties)) { + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path + if ($schemaConstraint->isValid()) { + unset($additionalProperties[$key]); + } + } + } + + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $key]); + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', +// '\d' => '[0-9]', + '\p{digit}' => '\p{Nd}', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php new file mode 100644 index 000000000..6aa9fdf2e --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php @@ -0,0 +1,42 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'allOf')) { + return; + } + + foreach ($schema->allOf as $allOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $allOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + $this->addError(ConstraintError::ALL_OF(), $path); + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php new file mode 100644 index 000000000..85242962a --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php @@ -0,0 +1,51 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'anyOf')) { + return; + } + + foreach ($schema->anyOf as $anyOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + try { + $schemaConstraint->check($value, $anyOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $this->errorBag()->reset(); + + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } catch (ValidationException $e) { + } + } + + $this->addError(ConstraintError::ANY_OF(), $path); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php new file mode 100644 index 000000000..e0ff337b1 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php @@ -0,0 +1,35 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'const')) { + return; + } + + if (DeepComparer::isEqual($value, $schema->const)) { + return; + } + + $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php new file mode 100644 index 000000000..e2e48ffe2 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php @@ -0,0 +1,47 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'contains')) { + return; + } + + $properties = []; + if (!is_array($value)) { + return; + } + + foreach ($value as $propertyName => $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + $schemaConstraint->check($propertyValue, $schema->contains, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + } + + $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php new file mode 100644 index 000000000..4e5e3f010 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php @@ -0,0 +1,55 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'contentMediaType') && !property_exists($schema, 'contentEncoding')) { + return; + } + if (!is_string($value)) { + return; + } + + $decodedValue = $value; + + if (property_exists($schema, 'contentEncoding')) { + if ($schema->contentEncoding === 'base64') { + if (!preg_match('/^[A-Za-z0-9+\/=]+$/', $decodedValue)) { + $this->addError(ConstraintError::CONTENT_ENCODING(), $path, ['contentEncoding' => $schema->contentEncoding]); + + return; + } + $decodedValue = base64_decode($decodedValue); + } + } + + if (property_exists($schema, 'contentMediaType')) { + if ($schema->contentMediaType === 'application/json') { + json_decode($decodedValue, false); + if (json_last_error() === JSON_ERROR_NONE) { + return; + } + } + + $this->addError(ConstraintError::CONTENT_MEDIA_TYPE(), $path, ['contentMediaType' => $schema->contentMediaType]); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php new file mode 100644 index 000000000..f4b03ff8c --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php @@ -0,0 +1,64 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'dependencies')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->dependencies as $dependant => $dependencies) { + if (!property_exists($value, $dependant)) { + continue; + } + if ($dependencies === true) { + continue; + } + if ($dependencies === false) { + $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]); + continue; + } + + if (is_array($dependencies)) { + foreach ($dependencies as $dependency) { + if (property_exists($value, $dependant) && !property_exists($value, $dependency)) { + $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]); + } + } + } + + if (is_object($dependencies)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $dependencies, $path, $i); + if (!$schemaConstraint->isValid()) { + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php new file mode 100644 index 000000000..084ce96cc --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php @@ -0,0 +1,83 @@ +getSchemaStorage() : new SchemaStorage(), + $factory ? $factory->getUriRetriever() : new UriRetriever(), + $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL + )); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (is_bool($schema)) { + if ($schema === false) { + $this->addError(ConstraintError::FALSE(), $path, []); + } + + return; + } + + // Apply defaults + $this->checkForKeyword('ref', $value, $schema, $path, $i); + $this->checkForKeyword('required', $value, $schema, $path, $i); + $this->checkForKeyword('contains', $value, $schema, $path, $i); + $this->checkForKeyword('properties', $value, $schema, $path, $i); + $this->checkForKeyword('propertyNames', $value, $schema, $path, $i); + $this->checkForKeyword('patternProperties', $value, $schema, $path, $i); + $this->checkForKeyword('type', $value, $schema, $path, $i); + $this->checkForKeyword('not', $value, $schema, $path, $i); + $this->checkForKeyword('dependencies', $value, $schema, $path, $i); + $this->checkForKeyword('allOf', $value, $schema, $path, $i); + $this->checkForKeyword('anyOf', $value, $schema, $path, $i); + $this->checkForKeyword('oneOf', $value, $schema, $path, $i); + $this->checkForKeyword('ifThenElse', $value, $schema, $path, $i); + + $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); + $this->checkForKeyword('items', $value, $schema, $path, $i); + $this->checkForKeyword('additionalItems', $value, $schema, $path, $i); + $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i); + $this->checkForKeyword('minItems', $value, $schema, $path, $i); + $this->checkForKeyword('minProperties', $value, $schema, $path, $i); + $this->checkForKeyword('maxProperties', $value, $schema, $path, $i); + $this->checkForKeyword('minimum', $value, $schema, $path, $i); + $this->checkForKeyword('maximum', $value, $schema, $path, $i); + $this->checkForKeyword('minLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i); + $this->checkForKeyword('maxItems', $value, $schema, $path, $i); + $this->checkForKeyword('maxLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i); + $this->checkForKeyword('enum', $value, $schema, $path, $i); + $this->checkForKeyword('const', $value, $schema, $path, $i); + $this->checkForKeyword('multipleOf', $value, $schema, $path, $i); + $this->checkForKeyword('format', $value, $schema, $path, $i); + $this->checkForKeyword('pattern', $value, $schema, $path, $i); + $this->checkForKeyword('content', $value, $schema, $path, $i); + } + + /** + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor($keyword); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php new file mode 100644 index 000000000..6a81401d7 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php @@ -0,0 +1,41 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'enum')) { + return; + } + + foreach ($schema->enum as $enumCase) { + if (DeepComparer::isEqual($value, $enumCase)) { + return; + } + + if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) { + return; + } + } + + $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php new file mode 100644 index 000000000..484768f8a --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMaximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value < $schema->exclusiveMaximum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php new file mode 100644 index 000000000..c97c8c535 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMinimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value > $schema->exclusiveMinimum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php new file mode 100644 index 000000000..cb5599525 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php @@ -0,0 +1,48 @@ + + */ + protected $constraintMap = [ + 'schema' => Draft07Constraint::class, + 'additionalProperties' => AdditionalPropertiesConstraint::class, + 'additionalItems' => AdditionalItemsConstraint::class, + 'dependencies' => DependenciesConstraint::class, + 'type' => TypeConstraint::class, + 'const' => ConstConstraint::class, + 'enum' => EnumConstraint::class, + 'uniqueItems' => UniqueItemsConstraint::class, + 'minItems' => MinItemsConstraint::class, + 'minProperties' => MinPropertiesConstraint::class, + 'maxProperties' => MaxPropertiesConstraint::class, + 'minimum' => MinimumConstraint::class, + 'maximum' => MaximumConstraint::class, + 'exclusiveMinimum' => ExclusiveMinimumConstraint::class, + 'minLength' => MinLengthConstraint::class, + 'maxLength' => MaxLengthConstraint::class, + 'maxItems' => MaxItemsConstraint::class, + 'exclusiveMaximum' => ExclusiveMaximumConstraint::class, + 'multipleOf' => MultipleOfConstraint::class, + 'required' => RequiredConstraint::class, + 'format' => FormatConstraint::class, + 'anyOf' => AnyOfConstraint::class, + 'allOf' => AllOfConstraint::class, + 'oneOf' => OneOfConstraint::class, + 'not' => NotConstraint::class, + 'ifThenElse' => IfThenElseConstraint::class, + 'contains' => ContainsConstraint::class, + 'propertyNames' => PropertiesNamesConstraint::class, + 'patternProperties' => PatternPropertiesConstraint::class, + 'pattern' => PatternConstraint::class, + 'properties' => PropertiesConstraint::class, + 'items' => ItemsConstraint::class, + 'ref' => RefConstraint::class, + 'content' => ContentConstraint::class, + ]; +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php new file mode 100644 index 000000000..8ddc879dd --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php @@ -0,0 +1,354 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'format')) { + return; + } + + if (!is_string($value)) { + return; + } + + switch ($schema->format) { + case 'date': + if (!$this->validateDateTime($value, 'Y-m-d')) { + $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]); + } + break; + case 'time': + if (!$this->validateDateTime($value, 'H:i:sP') + && !$this->validateDateTime($value, 'H:i:sp') + && !$this->validateDateTime($value, 'H:i:s.up') + && !$this->validateDateTime($value, 'H:i:s.uP') + ) { + $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]); + } + break; + case 'date-time': + if (!$this->validateRfc3339DateTime($value)) { + $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]); + } + break; + case 'utc-millisec': + if (!$this->validateDateTime($value, 'U')) { + $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'regex': + if (!$this->validateRegex($value)) { + $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'ip-address': + case 'ipv4': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'ipv6': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'color': + if (!$this->validateColor($value)) { + $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); + } + break; + case 'style': + if (!$this->validateStyle($value)) { + $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); + } + break; + case 'phone': + if (!$this->validatePhone($value)) { + $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); + } + break; + case 'uri': + if (!UriValidator::isValid($value)) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + + case 'uriref': + case 'uri-reference': + if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + case 'uri-template': + if (!$this->validateUriTemplate($value)) { + $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]); + } + break; + + case 'email': + if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) { + $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); + } + break; + case 'host-name': + case 'hostname': + if (!$this->validateHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'idn-hostname': + if (!$this->validateInternationalizedHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'json-pointer': + if (!$this->validateJsonPointer($value)) { + $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]); + } + break; + default: + break; + } + } + + private function validateDateTime(string $datetime, string $format): bool + { + $datetime = strtoupper($datetime); // Cleanup for lowercase z + $isPhpLt80WithZulu = PHP_VERSION_ID < 80000 && substr($datetime, -1) === 'Z'; + $isLeap = substr($datetime, 6, 2) === '60'; + $input = $datetime; + + // Correct for Zulu in PHP < 8.0 + if ($isPhpLt80WithZulu) { + $input = sprintf('%s+00:00', substr($input, 0, -1)); + } + // Correct for leap second + if ($isLeap) { + $input = sprintf('%s59%s', substr($datetime, 0, 6), substr($datetime, 8)); + } + + $dt = \DateTimeImmutable::createFromFormat($format, $input); + if (!$dt) { + return false; + } + + // Handle invalid timezone offsets + $timezoneOffset = $dt->getTimezone()->getOffset($dt); + if ($timezoneOffset >= 86400 || $timezoneOffset <= -86400) { + return false; + } + + $expected = $dt->format($format); + // Correct for trailing zeros on microseconds + if ($format === 'H:i:s.up' || $format === 'H:i:s.uP') { + $expected = sprintf( + '%s%s', + rtrim($dt->format('H:i:s.u'), '0'), + $dt->format(substr($format, -1)) + ); + } + // Correct back for leap seconds + if ($isLeap) { + // Only when 23:59:59 in UTC + $utcDT = $dt->setTimezone(new DateTimeZone('UTC')); + if ($utcDT->format('H:i:s') !== '23:59:59') { + return false; + } + + $expected = sprintf('%s60%s', substr($expected, 0, 6), substr($expected, 8)); + } + // Correct back for PHP > 8.0 and Zulu + if ($isPhpLt80WithZulu) { + $expected = sprintf('%sZ', substr($expected, 0, -6)); + } + + return $datetime === $expected; + } + + private function validateRegex(string $regex): bool + { + return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false; + } + + /** + * Transform a JSON pattern into a PCRE regex + */ + private static function jsonPatternToPhpRegex(string $pattern): string + { + return '~' . str_replace('~', '\\~', $pattern) . '~u'; + } + + private function validateColor(string $color): bool + { + if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', + 'red', 'silver', 'teal', 'white', 'yellow'])) { + return true; + } + + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) === 1; + } + + private function validateStyle(string $style): bool + { + $properties = explode(';', rtrim($style, ';')); + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); + + return empty($invalidEntries); + } + + private function validatePhone(string $phone): bool + { + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) === 1; + } + + private function validateHostname(string $host): bool + { + $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; + + return preg_match($hostnameRegex, $host) === 1; + } + + private function validateInternationalizedHostname(string $host): bool + { + if ($host === '') { + return false; + } + $host = rtrim($host, '.'); + $labels = explode('.', $host); + $asciiLabels = []; + + if ($labels === false) { + return false; + } + + foreach ($labels as $label) { + if ($label === '') { + return false; + } + + // CONTEXTJ / CONTEXTO checks + if ( + // Greek KERAIA U+0375 + preg_match('/\x{0375}/u', $label) && + !preg_match('/\x{0375}[\x{0370}-\x{03FF}]/u', $label) + ) { + return false; + } + + // Hebrew GERESH / GERSHAYIM U+05F3 / U+05F4 + if (preg_match('/[\x{05F3}\x{05F4}]/u', $label) && + !preg_match('/[\x{0590}-\x{05FF}][\x{05F3}\x{05F4}]/u', $label) + ) { + return false; + } + + // Katakana middle dot U+30FB + if (str_contains($label, "\u{30FB}") && + !preg_match('/[\x{30A0}-\x{30FF}]/u', $label) + ) { + return false; + } + + // Arabic digit mixing + $hasArabicIndic = preg_match('/[\x{0660}-\x{0669}]/u', $label); + $hasExtArabicIndic = preg_match('/[\x{06F0}-\x{06F9}]/u', $label); + if ($hasArabicIndic && $hasExtArabicIndic) { + return false; + } + + // Devanagari danda U+0964 / U+0965 + if (preg_match('/[\x{0964}\x{0965}]/u', $label) && + !preg_match('/[\x{0900}-\x{097F}]/u', $label) + ) { + return false; + } + + // ZWNJ / ZWJ U+200C / U+200D + if (preg_match('/[\x{200C}\x{200D}]/u', $label)) { + return false; + } + + $ascii = idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46); + if ($ascii === false) { + return false; + } + // DNS label length + if (strlen($ascii) > 63) { + return false; + } + // LDH rule (after IDNA) + if (!preg_match('/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i', $ascii)) { + return false; + } + $asciiLabels[] = $ascii; + } + + // Total hostname length (ASCII) + $asciiHost = implode('.', $asciiLabels); + + return strlen($asciiHost) <= 253; + } + + private function validateJsonPointer(string $value): bool + { + // Must be empty or start with a forward slash + if ($value !== '' && $value[0] !== '/') { + return false; + } + + // Split into reference tokens and check for invalid escape sequences + $tokens = explode('/', $value); + array_shift($tokens); // remove leading empty part due to leading slash + + foreach ($tokens as $token) { + // "~" must only be followed by "0" or "1" + if (preg_match('/~(?![01])/', $token)) { + return false; + } + } + + return true; + } + + private function validateRfc3339DateTime(string $value): bool + { + $dateTime = Rfc3339::createFromString($value); + if (is_null($dateTime)) { + return false; + } + + // Compare value and date result to be equal + return true; + } + + private function validateUriTemplate(string $value): bool + { + return preg_match( + '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/', + $value + ) === 1; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php new file mode 100644 index 000000000..d3fe82c7e --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php @@ -0,0 +1,67 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'if')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $ifSchema = $schema->if; + + if (!is_bool($ifSchema)) { + $schemaConstraint->check($value, $ifSchema, $path, $i); + $meetsIfConditions = $schemaConstraint->isValid(); + $schemaConstraint->reset(); + } else { + $meetsIfConditions = $ifSchema; + } + + if ($meetsIfConditions) { + if (!property_exists($schema, 'then')) { + return; + } + + $schemaConstraint->check($value, $schema->then, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + + return; + } + + if (!property_exists($schema, 'else')) { + return; + } + + $schemaConstraint->check($value, $schema->else, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php new file mode 100644 index 000000000..16abae328 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php @@ -0,0 +1,52 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + + foreach ($value as $propertyName => $propertyValue) { + $itemSchema = $schema->items; + if (is_array($itemSchema)) { + if (!array_key_exists($propertyName, $itemSchema)) { + continue; + } + + $itemSchema = $itemSchema[$propertyName]; + } + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $itemSchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php new file mode 100644 index 000000000..101a0cf98 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count <= $schema->maxItems) { + return; + } + + $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php new file mode 100644 index 000000000..9043cf92d --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length <= $schema->maxLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php new file mode 100644 index 000000000..106e98b02 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count <= $schema->maxProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php new file mode 100644 index 000000000..dd9e1b982 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value <= $schema->maximum) { + return; + } + + $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php new file mode 100644 index 000000000..026170392 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count >= $schema->minItems) { + return; + } + + $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php new file mode 100644 index 000000000..5c7243a82 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length >= $schema->minLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php new file mode 100644 index 000000000..07014fb6a --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count >= $schema->minProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php new file mode 100644 index 000000000..5f1b480c7 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value >= $schema->minimum) { + return; + } + + $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php new file mode 100644 index 000000000..134cfda2d --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php @@ -0,0 +1,54 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'multipleOf')) { + return; + } + + if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) { + return; + } + + if (!is_int($value) && !is_float($value)) { + return; + } + + if ($this->isMultipleOf($value, $schema->multipleOf)) { + return; + } + + $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]); + } + + /** + * @param int|float $number1 + * @param int|float $number2 + */ + private function isMultipleOf($number1, $number2): bool + { + $modulus = ($number1 - round($number1 / $number2) * $number2); + $precision = 0.0000000001; + + return -$precision < $modulus && $modulus < $precision; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php new file mode 100644 index 000000000..fdba81bfe --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php @@ -0,0 +1,40 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'not')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $schema->not, $path, $i); + + if (!$schemaConstraint->isValid()) { + return; + } + + $this->addError(ConstraintError::NOT(), $path); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php new file mode 100644 index 000000000..4523f73cb --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php @@ -0,0 +1,50 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'oneOf')) { + return; + } + + $matchedSchema = 0; + foreach ($schema->oneOf as $oneOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $oneOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $matchedSchema++; + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + + if ($matchedSchema !== 1) { + $this->addError(ConstraintError::ONE_OF(), $path); + } else { + $this->errorBag()->reset(); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php new file mode 100644 index 000000000..062b61d3f --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php @@ -0,0 +1,63 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'pattern')) { + return; + } + + if (!is_string($value)) { + return; + } + + $matchPattern = $this->createPregMatchPattern($schema->pattern); + if (preg_match($matchPattern, $value) === 1) { + return; + } + + $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]); + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ + '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', + '\w' => '[A-Za-z0-9_]', + '\W' => '[^A-Za-z0-9_]', + '\s' => '[\s\x{200B}]', // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php new file mode 100644 index 000000000..d821abf18 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php @@ -0,0 +1,72 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'patternProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $properties = get_object_vars($value); + + foreach ($properties as $propertyName => $propertyValue) { + foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) { + $matchPattern = $this->createPregMatchPattern($patternPropertyRegex); + if (preg_match($matchPattern, (string) $propertyName)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php new file mode 100644 index 000000000..25c55e666 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php @@ -0,0 +1,48 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'properties')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->properties as $propertyName => $propertySchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + if (!property_exists($value, $propertyName)) { + continue; + } + + $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php new file mode 100644 index 000000000..e3838990e --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php @@ -0,0 +1,80 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'propertyNames')) { + return; + } + + if (!is_object($value)) { + return; + } + if ($schema->propertyNames === true) { + return; + } + + $propertyNames = get_object_vars($value); + + if ($schema->propertyNames === false) { + foreach ($propertyNames as $propertyName => $_) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]); + } + + return; + } + + if (property_exists($schema->propertyNames, 'maxLength')) { + foreach ($propertyNames as $propertyName => $_) { + $length = mb_strlen($propertyName); + if ($length > $schema->propertyNames->maxLength) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'pattern')) { + foreach ($propertyNames as $propertyName => $_) { + if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'const')) { + foreach ($propertyNames as $propertyName => $_) { + if ($propertyName !== $schema->propertyNames->const) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'enum')) { + $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum); + foreach ($diff as $propertyName) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]); + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php new file mode 100644 index 000000000..9ec6efdac --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php @@ -0,0 +1,45 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, '$ref')) { + return; + } + + try { + $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); + } catch (\Exception $e) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $refSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php new file mode 100644 index 000000000..99a5b55af --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php @@ -0,0 +1,58 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'required')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->required as $required) { + if (property_exists($value, $required)) { + continue; + } + + $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]); + } + } + + /** + * @todo refactor as this was only copied from UndefinedConstraint + * Bubble down the path + * + * @param JsonPointer|null $path Current path + * @param mixed $i What to append to the path + */ + protected function incrementPath(?JsonPointer $path, $i): JsonPointer + { + $path = $path ?? new JsonPointer(''); + + if ($i === null || $i === '') { + return $path; + } + + return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i])); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php new file mode 100644 index 000000000..9601954f1 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php @@ -0,0 +1,50 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'type')) { + return; + } + + $schemaTypes = (array) $schema->type; + $valueType = strtolower(gettype($value)); + // All specific number types are a number + $valueIsNumber = $valueType === 'double' || $valueType === 'integer'; + // A float with zero fractional part is an integer + $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0; + + foreach ($schemaTypes as $type) { + if ($valueType === $type) { + return; + } + + if ($type === 'number' && $valueIsNumber) { + return; + } + if ($type === 'integer' && $isInteger) { + return; + } + } + + $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php new file mode 100644 index 000000000..5363b5c58 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php @@ -0,0 +1,48 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'uniqueItems')) { + return; + } + if (!is_array($value)) { + return; + } + + if ($schema->uniqueItems !== true) { + // If unique items not is true duplicates are allowed. + return; + } + + $count = count($value); + for ($x = 0; $x < $count - 1; $x++) { + for ($y = $x + 1; $y < $count; $y++) { + if (DeepComparer::isEqual($value[$x], $value[$y])) { + $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); + + return; + } + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalItemsConstraint.php new file mode 100644 index 000000000..79079c379 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalItemsConstraint.php @@ -0,0 +1,64 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalItems')) { + return; + } + + if ($schema->additionalItems === true) { + return; + } + if ($schema->additionalItems === false && !property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + if (!property_exists($schema, 'items')) { + return; + } + if (is_object($schema->items)) { + return; + } + + $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []); + $basePath = $path ?? new JsonPointer(''); + + foreach ($additionalItems as $propertyName => $propertyValue) { + $incrementedPath = $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $schema->additionalItems, $incrementedPath, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $incrementedPath, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php new file mode 100644 index 000000000..9406c4770 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php @@ -0,0 +1,93 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'additionalProperties')) { + return; + } + + if ($schema->additionalProperties === true) { + return; + } + + if (!is_object($value)) { + return; + } + + $additionalProperties = get_object_vars($value); + + if (isset($schema->properties)) { + $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties); + } + + if (isset($schema->patternProperties)) { + $patterns = array_keys(get_object_vars($schema->patternProperties)); + + foreach ($additionalProperties as $key => $_) { + foreach ($patterns as $pattern) { + if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) { + unset($additionalProperties[$key]); + break; + } + } + } + } + + if (is_object($schema->additionalProperties)) { + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path + if ($schemaConstraint->isValid()) { + unset($additionalProperties[$key]); + } + } + } + + foreach ($additionalProperties as $key => $additionalPropertiesValue) { + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $key]); + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', +// '\d' => '[0-9]', + '\p{digit}' => '\p{Nd}', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php new file mode 100644 index 000000000..42d29d395 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php @@ -0,0 +1,42 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'allOf')) { + return; + } + + foreach ($schema->allOf as $allOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $allOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + continue; + } + $this->addError(ConstraintError::ALL_OF(), $path); + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php new file mode 100644 index 000000000..f0b66049a --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php @@ -0,0 +1,51 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'anyOf')) { + return; + } + + foreach ($schema->anyOf as $anyOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + try { + $schemaConstraint->check($value, $anyOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $this->errorBag()->reset(); + + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } catch (ValidationException $e) { + } + } + + $this->addError(ConstraintError::ANY_OF(), $path); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php new file mode 100644 index 000000000..9cfacf87a --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php @@ -0,0 +1,35 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'const')) { + return; + } + + if (DeepComparer::isEqual($value, $schema->const)) { + return; + } + + $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php new file mode 100644 index 000000000..3e0e5a1e6 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php @@ -0,0 +1,54 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'contains')) { + return; + } + + if (!is_array($value)) { + return; + } + + $validElementCount = 0; + foreach ($value as $propertyValue) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + + $schemaConstraint->check($propertyValue, $schema->contains, $path, $i); + if ($schemaConstraint->isValid()) { + $validElementCount++; + } + } + + if (property_exists($schema, 'maxContains') && $validElementCount > $schema->maxContains) { + $this->addError(ConstraintError::MAX_CONTAINS(), $path, ['maxContains' => $schema->maxContains, 'count' => $validElementCount]); + } + + $minContains = property_exists($schema, 'minContains') ? $schema->minContains : 1; + if ($validElementCount < $minContains) { + $this->addError(ConstraintError::MIN_CONTAINS(), $path, ['minContains' => $minContains, 'count' => $validElementCount]); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php new file mode 100644 index 000000000..ed3c49039 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php @@ -0,0 +1,25 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + // See https://json-schema.org/draft/2019-09/json-schema-validation#rfc.section.8 + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentRequiredConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentRequiredConstraint.php new file mode 100644 index 000000000..a1cf42ff9 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentRequiredConstraint.php @@ -0,0 +1,50 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'dependentRequired')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->dependentRequired as $dependant => $dependencies) { + if (!property_exists($value, $dependant)) { + continue; + } + + foreach ($dependencies as $dependency) { + if (!property_exists($value, $dependency)) { + $this->addError(ConstraintError::DEPENDENCIES(), $path, [ + 'dependant' => $dependant, + 'dependency' => $dependency, + ]); + } + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentSchemasConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentSchemasConstraint.php new file mode 100644 index 000000000..0b469c65b --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/DependentSchemasConstraint.php @@ -0,0 +1,58 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'dependentSchemas')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->dependentSchemas as $dependant => $dependentSchema) { + if (!property_exists($value, $dependant)) { + continue; + } + + if ($dependentSchema === true) { + continue; + } + + if ($dependentSchema === false) { + $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]); + continue; + } + + if (is_object($dependentSchema)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $dependentSchema, $path, $i); + if (!$schemaConstraint->isValid()) { + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php new file mode 100644 index 000000000..0b003b546 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php @@ -0,0 +1,84 @@ +getSchemaStorage() : new SchemaStorage(), + $factory ? $factory->getUriRetriever() : new UriRetriever(), + $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL + )); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (is_bool($schema)) { + if ($schema === false) { + $this->addError(ConstraintError::FALSE(), $path, []); + } + + return; + } + + // Apply defaults + $this->checkForKeyword('ref', $value, $schema, $path, $i); + $this->checkForKeyword('required', $value, $schema, $path, $i); + $this->checkForKeyword('contains', $value, $schema, $path, $i); + $this->checkForKeyword('properties', $value, $schema, $path, $i); + $this->checkForKeyword('propertyNames', $value, $schema, $path, $i); + $this->checkForKeyword('patternProperties', $value, $schema, $path, $i); + $this->checkForKeyword('type', $value, $schema, $path, $i); + $this->checkForKeyword('not', $value, $schema, $path, $i); + $this->checkForKeyword('dependentSchemas', $value, $schema, $path, $i); + $this->checkForKeyword('dependentRequired', $value, $schema, $path, $i); + $this->checkForKeyword('allOf', $value, $schema, $path, $i); + $this->checkForKeyword('anyOf', $value, $schema, $path, $i); + $this->checkForKeyword('oneOf', $value, $schema, $path, $i); + $this->checkForKeyword('ifThenElse', $value, $schema, $path, $i); + + $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); + $this->checkForKeyword('items', $value, $schema, $path, $i); + $this->checkForKeyword('additionalItems', $value, $schema, $path, $i); + $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i); + $this->checkForKeyword('minItems', $value, $schema, $path, $i); + $this->checkForKeyword('minProperties', $value, $schema, $path, $i); + $this->checkForKeyword('maxProperties', $value, $schema, $path, $i); + $this->checkForKeyword('minimum', $value, $schema, $path, $i); + $this->checkForKeyword('maximum', $value, $schema, $path, $i); + $this->checkForKeyword('minLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i); + $this->checkForKeyword('maxItems', $value, $schema, $path, $i); + $this->checkForKeyword('maxLength', $value, $schema, $path, $i); + $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i); + $this->checkForKeyword('enum', $value, $schema, $path, $i); + $this->checkForKeyword('const', $value, $schema, $path, $i); + $this->checkForKeyword('multipleOf', $value, $schema, $path, $i); + $this->checkForKeyword('format', $value, $schema, $path, $i); + $this->checkForKeyword('pattern', $value, $schema, $path, $i); + $this->checkForKeyword('content', $value, $schema, $path, $i); + } + + /** + * @param mixed $value + * @param mixed $schema + * @param mixed $i + */ + protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + $validator = $this->factory->createInstanceFor($keyword); + $validator->check($value, $schema, $path, $i); + + $this->addErrors($validator->getErrors()); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/EnumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/EnumConstraint.php new file mode 100644 index 000000000..aba263711 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/EnumConstraint.php @@ -0,0 +1,41 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'enum')) { + return; + } + + foreach ($schema->enum as $enumCase) { + if (DeepComparer::isEqual($value, $enumCase)) { + return; + } + + if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) { + return; + } + } + + $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMaximumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMaximumConstraint.php new file mode 100644 index 000000000..0241197b1 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMaximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value < $schema->exclusiveMaximum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMinimumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMinimumConstraint.php new file mode 100644 index 000000000..9c9e7a323 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ExclusiveMinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'exclusiveMinimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value > $schema->exclusiveMinimum) { + return; + } + + $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php new file mode 100644 index 000000000..cc490225e --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php @@ -0,0 +1,49 @@ + + */ + protected $constraintMap = [ + 'schema' => Draft2019Constraint::class, + 'additionalProperties' => AdditionalPropertiesConstraint::class, + 'additionalItems' => AdditionalItemsConstraint::class, + 'dependentSchemas' => DependentSchemasConstraint::class, + 'dependentRequired' => DependentRequiredConstraint::class, + 'type' => TypeConstraint::class, + 'const' => ConstConstraint::class, + 'enum' => EnumConstraint::class, + 'uniqueItems' => UniqueItemsConstraint::class, + 'minItems' => MinItemsConstraint::class, + 'minProperties' => MinPropertiesConstraint::class, + 'maxProperties' => MaxPropertiesConstraint::class, + 'minimum' => MinimumConstraint::class, + 'maximum' => MaximumConstraint::class, + 'exclusiveMinimum' => ExclusiveMinimumConstraint::class, + 'minLength' => MinLengthConstraint::class, + 'maxLength' => MaxLengthConstraint::class, + 'maxItems' => MaxItemsConstraint::class, + 'exclusiveMaximum' => ExclusiveMaximumConstraint::class, + 'multipleOf' => MultipleOfConstraint::class, + 'required' => RequiredConstraint::class, + 'format' => FormatConstraint::class, + 'anyOf' => AnyOfConstraint::class, + 'allOf' => AllOfConstraint::class, + 'oneOf' => OneOfConstraint::class, + 'not' => NotConstraint::class, + 'ifThenElse' => IfThenElseConstraint::class, + 'contains' => ContainsConstraint::class, + 'propertyNames' => PropertiesNamesConstraint::class, + 'patternProperties' => PatternPropertiesConstraint::class, + 'pattern' => PatternConstraint::class, + 'properties' => PropertiesConstraint::class, + 'items' => ItemsConstraint::class, + 'ref' => RefConstraint::class, + 'content' => ContentConstraint::class, + ]; +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php new file mode 100644 index 000000000..5fb47c875 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php @@ -0,0 +1,337 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'format')) { + return; + } + + if (!is_string($value)) { + return; + } + + switch ($schema->format) { + case 'date': + if (!$this->validateDateTime($value, 'Y-m-d')) { + $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]); + } + break; + case 'time': + if (!$this->validateDateTime($value, 'H:i:sp') && !$this->validateDateTime($value, 'H:i:s.up')) { + $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]); + } + break; + case 'date-time': + if (!$this->validateRfc3339DateTime($value)) { + $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]); + } + break; + case 'utc-millisec': + if (!$this->validateDateTime($value, 'U')) { + $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'regex': + if (!$this->validateRegex($value)) { + $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]); + } + break; + case 'ip-address': + case 'ipv4': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'ipv6': + if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) { + $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); + } + break; + case 'color': + if (!$this->validateColor($value)) { + $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); + } + break; + case 'style': + if (!$this->validateStyle($value)) { + $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); + } + break; + case 'phone': + if (!$this->validatePhone($value)) { + $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); + } + break; + case 'uri': + if (!UriValidator::isValid($value)) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + + case 'uriref': + case 'uri-reference': + if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) { + $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); + } + break; + case 'uri-template': + if (!$this->validateUriTemplate($value)) { + $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]); + } + break; + + case 'email': + if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) { + $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); + } + break; + case 'host-name': + case 'hostname': + if (!$this->validateHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'idn-hostname': + if (!$this->validateInternationalizedHostname($value)) { + $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); + } + break; + case 'json-pointer': + if (!$this->validateJsonPointer($value)) { + $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]); + } + break; + default: + break; + } + } + + private function validateDateTime(string $datetime, string $format): bool + { + $datetime = strtoupper($datetime); // Cleanup for lowercase z + $isLeap = substr($datetime, 6, 2) === '60'; + $input = $datetime; + + // Correct for leap second + if ($isLeap) { + $input = sprintf('%s59%s', substr($datetime, 0, 6), substr($datetime, 8)); + } + + $dt = \DateTimeImmutable::createFromFormat($format, $input); + if (!$dt) { + return false; + } + + // Handle invalid timezone offsets + $timezoneOffset = $dt->getTimezone()->getOffset($dt); + if ($timezoneOffset >= 86400 || $timezoneOffset <= -86400) { + return false; + } + + $expected = $dt->format($format); + // Correct for trailing zeros on microseconds + if ($format === 'H:i:s.up') { + $expected = sprintf( + '%s%s', + rtrim($dt->format('H:i:s.u'), '0'), + $dt->format('p') + ); + } + // Correct back for leap seconds + if ($isLeap) { + // Only when 23:59:59 in UTC + $utcDT = $dt->setTimezone(new DateTimeZone('UTC')); + if ($utcDT->format('H:i:s') !== '23:59:59') { + return false; + } + + $expected = sprintf('%s60%s', substr($expected, 0, 6), substr($expected, 8)); + } + + return $datetime === $expected; + } + + private function validateRegex(string $regex): bool + { + return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false; + } + + /** + * Transform a JSON pattern into a PCRE regex + */ + private static function jsonPatternToPhpRegex(string $pattern): string + { + return '~' . str_replace('~', '\\~', $pattern) . '~u'; + } + + private function validateColor(string $color): bool + { + if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', + 'red', 'silver', 'teal', 'white', 'yellow'])) { + return true; + } + + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) === 1; + } + + private function validateStyle(string $style): bool + { + $properties = explode(';', rtrim($style, ';')); + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); + + return empty($invalidEntries); + } + + private function validatePhone(string $phone): bool + { + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) === 1; + } + + private function validateHostname(string $host): bool + { + $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; + + return preg_match($hostnameRegex, $host) === 1; + } + + private function validateInternationalizedHostname(string $host): bool + { + if ($host === '') { + return false; + } + $host = rtrim($host, '.'); + $labels = explode('.', $host); + $asciiLabels = []; + + foreach ($labels as $label) { + if ($label === '') { + return false; + } + + // CONTEXTJ / CONTEXTO checks + if ( + // Greek KERAIA U+0375 + preg_match('/\x{0375}/u', $label) && + !preg_match('/\x{0375}[\x{0370}-\x{03FF}]/u', $label) + ) { + return false; + } + + // Hebrew GERESH / GERSHAYIM U+05F3 / U+05F4 + if (preg_match('/[\x{05F3}\x{05F4}]/u', $label) && + !preg_match('/[\x{0590}-\x{05FF}][\x{05F3}\x{05F4}]/u', $label) + ) { + return false; + } + + // Katakana middle dot U+30FB + if (str_contains($label, "\u{30FB}") && + !preg_match('/[\x{30A0}-\x{30FF}]/u', $label) + ) { + return false; + } + + // Arabic digit mixing + $hasArabicIndic = preg_match('/[\x{0660}-\x{0669}]/u', $label); + $hasExtArabicIndic = preg_match('/[\x{06F0}-\x{06F9}]/u', $label); + if ($hasArabicIndic && $hasExtArabicIndic) { + return false; + } + + // Devanagari danda U+0964 / U+0965 + if (preg_match('/[\x{0964}\x{0965}]/u', $label) && + !preg_match('/[\x{0900}-\x{097F}]/u', $label) + ) { + return false; + } + + // ZWNJ / ZWJ U+200C / U+200D + if (preg_match('/[\x{200C}\x{200D}]/u', $label)) { + return false; + } + + $ascii = idn_to_ascii($label); + if ($ascii === false) { + return false; + } + // DNS label length + if (strlen($ascii) > 63) { + return false; + } + // LDH rule (after IDNA) + if (!preg_match('/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i', $ascii)) { + return false; + } + $asciiLabels[] = $ascii; + } + + // Total hostname length (ASCII) + $asciiHost = implode('.', $asciiLabels); + + return strlen($asciiHost) <= 253; + } + + private function validateJsonPointer(string $value): bool + { + // Must be empty or start with a forward slash + if ($value !== '' && $value[0] !== '/') { + return false; + } + + // Split into reference tokens and check for invalid escape sequences + $tokens = explode('/', $value); + array_shift($tokens); // remove leading empty part due to leading slash + + foreach ($tokens as $token) { + // "~" must only be followed by "0" or "1" + if (preg_match('/~(?![01])/', $token)) { + return false; + } + } + + return true; + } + + private function validateRfc3339DateTime(string $value): bool + { + $dateTime = Rfc3339::createFromString($value); + if (is_null($dateTime)) { + return false; + } + + // Compare value and date result to be equal + return true; + } + + private function validateUriTemplate(string $value): bool + { + return preg_match( + '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/', + $value + ) === 1; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/IfThenElseConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/IfThenElseConstraint.php new file mode 100644 index 000000000..a94514208 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/IfThenElseConstraint.php @@ -0,0 +1,67 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'if')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $ifSchema = $schema->if; + + if (!is_bool($ifSchema)) { + $schemaConstraint->check($value, $ifSchema, $path, $i); + $meetsIfConditions = $schemaConstraint->isValid(); + $schemaConstraint->reset(); + } else { + $meetsIfConditions = $ifSchema; + } + + if ($meetsIfConditions) { + if (!property_exists($schema, 'then')) { + return; + } + + $schemaConstraint->check($value, $schema->then, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + + return; + } + + if (!property_exists($schema, 'else')) { + return; + } + + $schemaConstraint->check($value, $schema->else, $path, $i); + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ItemsConstraint.php new file mode 100644 index 000000000..38abb3df3 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/ItemsConstraint.php @@ -0,0 +1,54 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'items')) { + return; + } + + if (!is_array($value)) { + return; + } + + $basePath = $path ?? new JsonPointer(''); + foreach ($value as $propertyName => $propertyValue) { + $itemSchema = $schema->items; + if (is_array($itemSchema)) { + if (!array_key_exists($propertyName, $itemSchema)) { + continue; + } + + $itemSchema = $itemSchema[$propertyName]; + } + $incrementedPath = $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $itemSchema, $incrementedPath, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxItemsConstraint.php new file mode 100644 index 000000000..fd5c45be8 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count <= $schema->maxItems) { + return; + } + + $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxLengthConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxLengthConstraint.php new file mode 100644 index 000000000..17eee392a --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length <= $schema->maxLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxPropertiesConstraint.php new file mode 100644 index 000000000..8bae235e7 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaxPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maxProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count <= $schema->maxProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaximumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaximumConstraint.php new file mode 100644 index 000000000..3e10ee524 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MaximumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'maximum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value <= $schema->maximum) { + return; + } + + $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinItemsConstraint.php new file mode 100644 index 000000000..f26082c06 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinItemsConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minItems')) { + return; + } + + if (!is_array($value)) { + return; + } + + $count = count($value); + if ($count >= $schema->minItems) { + return; + } + + $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinLengthConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinLengthConstraint.php new file mode 100644 index 000000000..9784c39ff --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinLengthConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minLength')) { + return; + } + + if (!is_string($value)) { + return; + } + + $length = mb_strlen($value); + if ($length >= $schema->minLength) { + return; + } + + $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinPropertiesConstraint.php new file mode 100644 index 000000000..ea4dd5d7f --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinPropertiesConstraint.php @@ -0,0 +1,39 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $count = count(get_object_vars($value)); + if ($count >= $schema->minProperties) { + return; + } + + $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinimumConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinimumConstraint.php new file mode 100644 index 000000000..b0a8ab8e6 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MinimumConstraint.php @@ -0,0 +1,38 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'minimum')) { + return; + } + + if (!is_numeric($value)) { + return; + } + + if ($value >= $schema->minimum) { + return; + } + + $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MultipleOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MultipleOfConstraint.php new file mode 100644 index 000000000..0b33601dd --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/MultipleOfConstraint.php @@ -0,0 +1,54 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'multipleOf')) { + return; + } + + if ((!is_int($schema->multipleOf) && !is_float($schema->multipleOf)) || $schema->multipleOf <= 0.0) { + return; + } + + if (!is_int($value) && !is_float($value)) { + return; + } + + if ($this->isMultipleOf($value, $schema->multipleOf)) { + return; + } + + $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]); + } + + /** + * @param int|float $number1 + * @param int|float $number2 + */ + private function isMultipleOf($number1, $number2): bool + { + $modulus = ($number1 - round($number1 / $number2) * $number2); + $precision = 0.0000000001; + + return -$precision < $modulus && $modulus < $precision; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/NotConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/NotConstraint.php new file mode 100644 index 000000000..8b9b40bd2 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/NotConstraint.php @@ -0,0 +1,40 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'not')) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $schema->not, $path, $i); + + if (!$schemaConstraint->isValid()) { + return; + } + + $this->addError(ConstraintError::NOT(), $path); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/OneOfConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/OneOfConstraint.php new file mode 100644 index 000000000..fd93cefe4 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/OneOfConstraint.php @@ -0,0 +1,50 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'oneOf')) { + return; + } + + $matchedSchema = 0; + foreach ($schema->oneOf as $oneOfSchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $oneOfSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + $matchedSchema++; + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + + if ($matchedSchema !== 1) { + $this->addError(ConstraintError::ONE_OF(), $path); + } else { + $this->errorBag()->reset(); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php new file mode 100644 index 000000000..b36ec026b --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php @@ -0,0 +1,63 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'pattern')) { + return; + } + + if (!is_string($value)) { + return; + } + + $matchPattern = $this->createPregMatchPattern($schema->pattern); + if (preg_match($matchPattern, $value) === 1) { + return; + } + + $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]); + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ + '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', + '\w' => '[A-Za-z0-9_]', + '\W' => '[^A-Za-z0-9_]', + '\s' => '[\s\x{200B}]', // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php new file mode 100644 index 000000000..441561540 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php @@ -0,0 +1,75 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'patternProperties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $properties = get_object_vars($value); + + $basePath = $path ?? new JsonPointer(''); + foreach ($properties as $propertyName => $propertyValue) { + $incrementedPath = $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); + + foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) { + $matchPattern = $this->createPregMatchPattern($patternPropertyRegex); + if (preg_match($matchPattern, (string) $propertyName)) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($propertyValue, $patternPropertySchema, $incrementedPath, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } + } + } + + private function createPregMatchPattern(string $pattern): string + { + $replacements = [ +// '\D' => '[^0-9]', + '\d' => '[0-9]', + '\p{digit}' => '[0-9]', +// '\w' => '[A-Za-z0-9_]', +// '\W' => '[^A-Za-z0-9_]', +// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space + '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + ]; + + $pattern = str_replace( + array_keys($replacements), + array_values($replacements), + $pattern + ); + + return '/' . str_replace('/', '\/', $pattern) . '/u'; + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesConstraint.php new file mode 100644 index 000000000..4052b1b9e --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesConstraint.php @@ -0,0 +1,51 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'properties')) { + return; + } + + if (!is_object($value)) { + return; + } + + $basePath = $path ?? new JsonPointer(''); + foreach ($schema->properties as $propertyName => $propertySchema) { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + if (!property_exists($value, $propertyName)) { + continue; + } + + $incrementedPath = $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); + + $schemaConstraint->check($value->{$propertyName}, $propertySchema, $incrementedPath, $i); + if ($schemaConstraint->isValid()) { + continue; + } + + $this->addErrors($schemaConstraint->getErrors()); + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesNamesConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesNamesConstraint.php new file mode 100644 index 000000000..1ce7f0c4f --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/PropertiesNamesConstraint.php @@ -0,0 +1,80 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'propertyNames')) { + return; + } + + if (!is_object($value)) { + return; + } + if ($schema->propertyNames === true) { + return; + } + + $propertyNames = get_object_vars($value); + + if ($schema->propertyNames === false) { + foreach ($propertyNames as $propertyName => $_) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]); + } + + return; + } + + if (property_exists($schema->propertyNames, 'maxLength')) { + foreach ($propertyNames as $propertyName => $_) { + $length = mb_strlen($propertyName); + if ($length > $schema->propertyNames->maxLength) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'pattern')) { + foreach ($propertyNames as $propertyName => $_) { + if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'const')) { + foreach ($propertyNames as $propertyName => $_) { + if ($propertyName !== $schema->propertyNames->const) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]); + } + } + } + + if (property_exists($schema->propertyNames, 'enum')) { + $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum); + foreach ($diff as $propertyName) { + $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]); + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php new file mode 100644 index 000000000..989b1f039 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php @@ -0,0 +1,45 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, '$ref')) { + return; + } + + try { + $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); + } catch (\Exception $e) { + return; + } + + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $refSchema, $path, $i); + + if ($schemaConstraint->isValid()) { + return; + } + + $this->addErrors($schemaConstraint->getErrors()); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RequiredConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RequiredConstraint.php new file mode 100644 index 000000000..a5c4c93ad --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/RequiredConstraint.php @@ -0,0 +1,58 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'required')) { + return; + } + + if (!is_object($value)) { + return; + } + + foreach ($schema->required as $required) { + if (property_exists($value, $required)) { + continue; + } + + $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]); + } + } + + /** + * @todo refactor as this was only copied from UndefinedConstraint + * Bubble down the path + * + * @param JsonPointer|null $path Current path + * @param mixed $i What to append to the path + */ + protected function incrementPath(?JsonPointer $path, $i): JsonPointer + { + $path = $path ?? new JsonPointer(''); + + if ($i === null || $i === '') { + return $path; + } + + return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i])); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/TypeConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/TypeConstraint.php new file mode 100644 index 000000000..e9ba31ddb --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/TypeConstraint.php @@ -0,0 +1,50 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'type')) { + return; + } + + $schemaTypes = (array) $schema->type; + $valueType = strtolower(gettype($value)); + // All specific number types are a number + $valueIsNumber = $valueType === 'double' || $valueType === 'integer'; + // A float with zero fractional part is an integer + $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0; + + foreach ($schemaTypes as $type) { + if ($valueType === $type) { + return; + } + + if ($type === 'number' && $valueIsNumber) { + return; + } + if ($type === 'integer' && $isInteger) { + return; + } + } + + $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]); + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/UniqueItemsConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/UniqueItemsConstraint.php new file mode 100644 index 000000000..9406a2720 --- /dev/null +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft2019/UniqueItemsConstraint.php @@ -0,0 +1,48 @@ +initialiseErrorBag($factory ?: new Factory()); + } + + public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!property_exists($schema, 'uniqueItems')) { + return; + } + if (!is_array($value)) { + return; + } + + if ($schema->uniqueItems !== true) { + // If unique items not is true duplicates are allowed. + return; + } + + $count = count($value); + for ($x = 0; $x < $count - 1; $x++) { + for ($y = $x + 1; $y < $count; $y++) { + if (DeepComparer::isEqual($value[$x], $value[$y])) { + $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); + + return; + } + } + } + } +} diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php index 5d7e67d08..6cbefcef7 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php @@ -75,6 +75,8 @@ class Factory 'schema' => 'JsonSchema\Constraints\SchemaConstraint', 'validator' => 'JsonSchema\Validator', 'draft06' => Drafts\Draft06\Draft06Constraint::class, + 'draft07' => Drafts\Draft07\Draft07Constraint::class, + 'draft2019-09' => Drafts\Draft2019\Draft2019Constraint::class, ]; /** diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php index 9ed4df9dc..c38488dd1 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php @@ -210,7 +210,8 @@ protected function validateHostname($host) return true; } - $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; + // RFC 1035: labels are max 63 chars (1 start + 0-61 middle + 1 end) + $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; return preg_match($hostnameRegex, $host); } diff --git a/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php b/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php index c2c614a90..0e1252e30 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php @@ -61,7 +61,7 @@ public function validatePatternProperties($element, ?JsonPointer $path, $pattern continue; } foreach ($element as $i => $value) { - if (preg_match($fullRegex, $i)) { + if (preg_match($fullRegex, (string) $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); } diff --git a/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php b/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php index d0e05e787..e062b6a8a 100644 --- a/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php +++ b/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php @@ -21,24 +21,39 @@ class DraftIdentifiers extends Enum public const DRAFT_2019_09 = 'https://json-schema.org/draft/2019-09/schema'; public const DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema'; + /** @var array */ + private const MAPPING = [ + self::DRAFT_3 => 'draft03', + self::DRAFT_4 => 'draft04', + self::DRAFT_6 => 'draft06', + self::DRAFT_7 => 'draft07', + self::DRAFT_2019_09 => 'draft2019-09', + self::DRAFT_2020_12 => 'draft2020-12', + ]; + + private const FALLBACK_MAPPING = [ + 'draft3' => self::DRAFT_3, + 'draft4' => self::DRAFT_4, + 'draft6' => self::DRAFT_6, + 'draft7' => self::DRAFT_7, + ]; + public function toConstraintName(): string { - switch ($this->getValue()) { - case self::DRAFT_3: - return 'draft03'; - case self::DRAFT_4: - return 'draft04'; - case self::DRAFT_6: - return 'draft06'; - case self::DRAFT_7: - return 'draft07'; - case self::DRAFT_2019_09: - return 'draft2019-09'; - case self::DRAFT_2020_12: - return 'draft2020-12'; - default: - throw new \Exception('Unsupported schema URI: ' . $this->getValue()); + return self::MAPPING[$this->getValue()]; + } + + public static function fromConstraintName(string $name): DraftIdentifiers + { + $reverseMap = array_flip(self::MAPPING); + if (!array_key_exists($name, $reverseMap)) { + if (array_key_exists($name, self::FALLBACK_MAPPING)) { + return DraftIdentifiers::byValue(self::FALLBACK_MAPPING[$name]); + } + throw new \InvalidArgumentException("$name is not a valid constraint name."); } + + return DraftIdentifiers::byValue($reverseMap[$name]); } public function withoutFragment(): string diff --git a/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php b/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php index e674fa6f8..e33866b68 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php +++ b/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php @@ -122,7 +122,7 @@ public function getPropertyPaths() public function withPropertyPaths(array $propertyPaths) { $new = clone $this; - $new->propertyPaths = array_map(function ($p): string { return (string) $p; }, $propertyPaths); + $new->propertyPaths = array_map(static function ($p): string { return (string) $p; }, $propertyPaths); return $new; } diff --git a/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php b/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php index 05397e7e2..2c5832bf9 100644 --- a/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php +++ b/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php @@ -203,6 +203,11 @@ private function scanForSubschemas($schema, string $parentId): void foreach ($schema as $propertyName => $potentialSubSchema) { if (!is_object($potentialSubSchema)) { + if (is_array($potentialSubSchema)) { + foreach ($potentialSubSchema as $potentialSubSchemaItem) { + $this->scanForSubschemas($potentialSubSchemaItem, $parentId); + } + } continue; } diff --git a/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php b/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php index fd95f7bfd..79fede0d8 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php +++ b/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php @@ -44,14 +44,6 @@ public static function isValid(string $ref): bool return false; // Spaces are not allowed in URIs } - if (preg_match('/^\?#|^#$/', $ref)) { - return false; // Missing path but having query and fragment - } - - if ($ref === '#' || $ref === '?') { - return false; // Missing path and having only fragment or query - } - return true; } } diff --git a/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php b/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php index 361512a9f..76f1c7723 100644 --- a/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php +++ b/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php @@ -11,6 +11,7 @@ namespace JsonSchema\Uri; +use JsonSchema\DraftIdentifiers; use JsonSchema\Exception\InvalidSchemaMediaTypeException; use JsonSchema\Exception\JsonDecodingException; use JsonSchema\Exception\ResourceNotFoundException; @@ -31,7 +32,8 @@ class UriRetriever implements BaseUriRetrieverInterface */ protected $translationMap = [ // use local copies of the spec schemas - '|^https?://json-schema.org/draft-(0[346])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json' + '|^https?://json-schema.org/draft-(0[3467])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json', + '|^https://json-schema.org/draft/2019-09/schema#?|' => 'package://dist/schema/json-schema-draft-2019-09.json' ]; /** @@ -91,7 +93,7 @@ public function confirmMediaType($uriRetriever, $uri) } } - throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); + throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected, but %s given', Validator::SCHEMA_MEDIA_TYPE, $contentType)); } /** @@ -182,11 +184,17 @@ public function retrieve($uri, $baseUri = null, $translate = true) $jsonSchema = $this->loadSchema($fetchUri); + // Detect dialect from the root schema's $schema keyword before + // resolvePointer() may walk into a sub-schema. + $dialect = isset($jsonSchema->{'$schema'}) ? rtrim($jsonSchema->{'$schema'}, '#') : null; + $usesDollarId = !in_array($dialect, [DraftIdentifiers::DRAFT_3()->withoutFragment(), DraftIdentifiers::DRAFT_4()->withoutFragment()], true); + // Use the JSON pointer if specified $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); if ($jsonSchema instanceof \stdClass) { - $jsonSchema->id = $resolvedUri; + $idKeyword = $usesDollarId ? '$id' : 'id'; + $jsonSchema->{$idKeyword} = $resolvedUri; } return $jsonSchema;