diff --git a/src/JsonSchema/ConstraintError.php b/src/JsonSchema/ConstraintError.php index 081e38ad..07b489a9 100644 --- a/src/JsonSchema/ConstraintError.php +++ b/src/JsonSchema/ConstraintError.php @@ -60,6 +60,7 @@ class ConstraintError extends Enum public const PROPERTY_NAMES = 'propertyNames'; public const TYPE = 'type'; public const UNIQUE_ITEMS = 'uniqueItems'; + public const UNEVALUATED_PROPERTIES = 'unevaluatedProperties'; public const CONTENT_MEDIA_TYPE = 'contentMediaType'; public const CONTENT_ENCODING = 'contentEncoding'; @@ -122,6 +123,7 @@ public function getMessage() 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::UNEVALUATED_PROPERTIES => 'The property %s is not evaluated and the definition does not allow unevaluated properties', self::CONTENT_MEDIA_TYPE => 'Value is not valid with content media type', self::CONTENT_ENCODING => 'Value is not valid with content encoding', ]; diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php index 9406c477..e0e42446 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php @@ -58,7 +58,10 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n 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 + $propertyPath = ($path ?? new JsonPointer(''))->withPropertyPaths( + array_merge(($path ?? new JsonPointer(''))->getPropertyPaths(), [$key]) + ); + $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $propertyPath, $i); if ($schemaConstraint->isValid()) { unset($additionalProperties[$key]); } @@ -66,7 +69,10 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n } foreach ($additionalProperties as $key => $additionalPropertiesValue) { - $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $key]); + $propertyPath = ($path ?? new JsonPointer(''))->withPropertyPaths( + array_merge(($path ?? new JsonPointer(''))->getPropertyPaths(), [$key]) + ); + $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $propertyPath, ['found' => $key]); } } diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php b/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php index 0b003b54..da459bb7 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php @@ -46,6 +46,7 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n $this->checkForKeyword('anyOf', $value, $schema, $path, $i); $this->checkForKeyword('oneOf', $value, $schema, $path, $i); $this->checkForKeyword('ifThenElse', $value, $schema, $path, $i); + $this->checkForKeyword('unevaluatedProperties', $value, $schema, $path, $i); $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); $this->checkForKeyword('items', $value, $schema, $path, $i); diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php b/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php index cc490225..0c8e9f17 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/Factory.php @@ -12,6 +12,7 @@ class Factory extends \JsonSchema\Constraints\Factory protected $constraintMap = [ 'schema' => Draft2019Constraint::class, 'additionalProperties' => AdditionalPropertiesConstraint::class, + 'unevaluatedProperties' => UnevaluatedPropertiesConstraint::class, 'additionalItems' => AdditionalItemsConstraint::class, 'dependentSchemas' => DependentSchemasConstraint::class, 'dependentRequired' => DependentRequiredConstraint::class, diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/UnevaluatedPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft2019/UnevaluatedPropertiesConstraint.php new file mode 100644 index 00000000..21a73296 --- /dev/null +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/UnevaluatedPropertiesConstraint.php @@ -0,0 +1,221 @@ +factory = $factory ?: new Factory(); + $this->initialiseErrorBag($this->factory); + } + + public function check(& $value, $schema = null, ?JsonPointer $path = null, $i = null): void + { + if (!is_object($schema) || !property_exists($schema, 'unevaluatedProperties') || !is_object($value)) { + return; + } + + if ($schema->unevaluatedProperties === true) { + return; + } + + $evaluated = $this->collectEvaluatedProperties($schema, $value, $path); + $unevaluated = array_diff_key(get_object_vars($value), array_flip($evaluated)); + if (!$unevaluated) { + return; + } + + $basePath = $path ?? new JsonPointer(''); + foreach ($unevaluated as $propertyName => $propertyValue) { + $propertyPath = $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); + + if (is_object($schema->unevaluatedProperties)) { + $propertyConstraint = $this->factory->createInstanceFor('schema'); + $propertyConstraint->check($propertyValue, $schema->unevaluatedProperties, $propertyPath, $i); + if ($propertyConstraint->isValid()) { + continue; + } + + $this->addErrors($propertyConstraint->getErrors()); + continue; + } + + $this->addError(ConstraintError::UNEVALUATED_PROPERTIES(), $propertyPath, ['found' => $propertyName]); + } + } + + /** + * The validator does not propagate annotations between applicators, so this + * constraint derives the evaluated property names from the supported schema branches. + * + * @param array $visitedRefs + * + * @return array + */ + private function collectEvaluatedProperties(object $schema, object $value, ?JsonPointer $path = null, array $visitedRefs = []): array + { + $evaluated = []; + if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { + $reference = $schema->{'$ref'}; + if (in_array($reference, $visitedRefs, true)) { + return []; + } + + try { + $visitedRefs[] = $reference; + $resolvedSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); + if (is_object($resolvedSchema)) { + $evaluated = array_merge( + $evaluated, + $this->collectEvaluatedProperties($resolvedSchema, $value, $path, $visitedRefs) + ); + } + } catch (\Exception $e) { + // Let the normal reference validation report resolution errors. + } + } + + $properties = get_object_vars($value); + + if (property_exists($schema, 'unevaluatedProperties') && $schema->unevaluatedProperties === true) { + $evaluated = array_merge($evaluated, array_keys($properties)); + } + + if (isset($schema->properties) && is_object($schema->properties)) { + $evaluated = array_merge( + $evaluated, + array_intersect(array_keys(get_object_vars($schema->properties)), array_keys($properties)) + ); + } + + if (isset($schema->patternProperties) && is_object($schema->patternProperties)) { + foreach ($properties as $propertyName => $_) { + foreach (array_keys(get_object_vars($schema->patternProperties)) as $pattern) { + if (preg_match($this->createPregMatchPattern($pattern), (string) $propertyName)) { + $evaluated[] = $propertyName; + break; + } + } + } + } + + if (property_exists($schema, 'additionalProperties')) { + if ($schema->additionalProperties === true) { + $evaluated = array_merge($evaluated, array_keys($properties)); + } elseif (is_object($schema->additionalProperties)) { + foreach (array_diff(array_keys($properties), $evaluated) as $propertyName) { + $propertyPath = $this->propertyPath($path, $propertyName); + if ($this->schemaIsValid($schema->additionalProperties, $properties[$propertyName], $propertyPath)) { + $evaluated[] = $propertyName; + } + } + } + } + + if (isset($schema->allOf) && is_array($schema->allOf)) { + foreach ($schema->allOf as $branch) { + if (!is_object($branch) || !$this->schemaIsValid($branch, $value, $path)) { + continue; + } + + $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($branch, $value, $path, $visitedRefs)); + } + } + + if (isset($schema->anyOf) && is_array($schema->anyOf)) { + foreach ($schema->anyOf as $branch) { + if (!is_object($branch) || !$this->schemaIsValid($branch, $value, $path)) { + continue; + } + + $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($branch, $value, $path, $visitedRefs)); + } + } + + if (isset($schema->oneOf) && is_array($schema->oneOf)) { + $validBranchCount = 0; + $validBranches = []; + foreach ($schema->oneOf as $branch) { + if ($this->schemaIsValid($branch, $value, $path)) { + ++$validBranchCount; + if (is_object($branch)) { + $validBranches[] = $branch; + } + } + } + + if ($validBranchCount === 1 && count($validBranches) === 1) { + $evaluated = array_merge( + $evaluated, + $this->collectEvaluatedProperties($validBranches[0], $value, $path, $visitedRefs) + ); + } + } + + if (property_exists($schema, 'if')) { + $ifMatches = $this->schemaIsValid($schema->if, $value, $path); + if ($ifMatches) { + if (is_object($schema->if)) { + $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($schema->if, $value, $path, $visitedRefs)); + } + if (property_exists($schema, 'then') && is_object($schema->then) && $this->schemaIsValid($schema->then, $value, $path)) { + $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($schema->then, $value, $path, $visitedRefs)); + } + } elseif (property_exists($schema, 'else') && is_object($schema->else) && $this->schemaIsValid($schema->else, $value, $path)) { + $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($schema->else, $value, $path, $visitedRefs)); + } + } + + if (isset($schema->dependentSchemas) && is_object($schema->dependentSchemas)) { + foreach (get_object_vars($schema->dependentSchemas) as $propertyName => $dependentSchema) { + if (!array_key_exists($propertyName, $properties) || !is_object($dependentSchema) || !$this->schemaIsValid($dependentSchema, $value, $path)) { + continue; + } + + $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($dependentSchema, $value, $path, $visitedRefs)); + } + } + + return array_values(array_unique($evaluated)); + } + + /** + * @param mixed $schema + * @param mixed $value + */ + private function schemaIsValid($schema, $value, ?JsonPointer $path = null): bool + { + $schemaConstraint = $this->factory->createInstanceFor('schema'); + $schemaConstraint->check($value, $schema, $path); + + return $schemaConstraint->isValid(); + } + + private function propertyPath(?JsonPointer $path, string $propertyName): JsonPointer + { + $basePath = $path ?? new JsonPointer(''); + + return $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); + } + + private function createPregMatchPattern(string $pattern): string + { + $pattern = str_replace('\\p{digit}', '\\p{Nd}', $pattern); + $pattern = str_replace('\\p{Letter}', '\\p{L}', $pattern); + + return '/' . str_replace('/', '\\/', $pattern) . '/u'; + } +} diff --git a/tests/Constraints/Drafts/Draft2019/UnevaluatedPropertiesTest.php b/tests/Constraints/Drafts/Draft2019/UnevaluatedPropertiesTest.php new file mode 100644 index 00000000..ec7f4899 --- /dev/null +++ b/tests/Constraints/Drafts/Draft2019/UnevaluatedPropertiesTest.php @@ -0,0 +1,140 @@ +getPathname()), false); foreach ($contents as $testCase) { foreach ($testCase->tests as $test) { - [,$filename] = explode('/tests/', $file->getRealPath(), 2); + $filename = str_replace('\\', '/', preg_replace('#^.*[/\\\\]tests[/\\\\]#', '', $file->getRealPath())); $name = sprintf( '[%s]: %s: %s is expected to be %s', $filename, @@ -116,7 +116,8 @@ private function loadRemotesIntoStorage(SchemaStorageInterface $storage): void continue; } - $id = str_replace($remotesDir, 'http://localhost:1234', $info->getPathname()); + $relativePath = str_replace('\\', '/', substr($info->getPathname(), strlen($remotesDir))); + $id = 'http://localhost:1234' . $relativePath; $storage->addSchema($id, json_decode(file_get_contents($info->getPathname()), false)); } } @@ -203,46 +204,7 @@ private function shouldNotYieldTest(string $name): bool '[draft2019-09/id.json]: Invalid use of fragments in location-independent $id: Identifier path with absolute URI is expected to be invalid', '[draft2019-09/id.json]: Invalid use of fragments in location-independent $id: Identifier name with base URI change in subschema is expected to be invalid', '[draft2019-09/id.json]: Invalid use of fragments in location-independent $id: Identifier path with base URI change in subschema is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties schema: with invalid unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties false: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with adjacent properties: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with adjacent patternProperties: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with nested properties: with additional properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with nested patternProperties: with additional properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with anyOf: when one matches and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with anyOf: when two match and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with oneOf: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with not: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else: when if is true and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else: when if is false and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else, then not defined: when if is true and has no unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else, then not defined: when if is true and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else, then not defined: when if is false and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else, else not defined: when if is true and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else, else not defined: when if is false and has no unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with if/then/else, else not defined: when if is false and has unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with dependentSchemas: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with boolean schemas: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with $ref: with unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties can\'t see inside cousins: always fails is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties can\'t see inside cousins (reverse order): always fails is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: nested unevaluatedProperties, outer true, inner false, properties outside: with no nested unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: nested unevaluatedProperties, outer true, inner false, properties outside: with nested unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: nested unevaluatedProperties, outer true, inner false, properties inside: with nested unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: cousin unevaluatedProperties, true and false, true with properties: with no nested unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: cousin unevaluatedProperties, true and false, true with properties: with nested unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: cousin unevaluatedProperties, true and false, false with properties: with nested unevaluated properties is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: property is evaluated in an uncle schema to unevaluatedProperties: uncle keyword evaluation is not significant is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: in-place applicator siblings, allOf has unevaluated: base case: both properties present is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: in-place applicator siblings, allOf has unevaluated: in place applicator siblings, foo is missing is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: in-place applicator siblings, anyOf has unevaluated: base case: both properties present is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: in-place applicator siblings, anyOf has unevaluated: in place applicator siblings, bar is missing is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties + single cyclic ref: Unevaluated on 1st level is invalid is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties + single cyclic ref: Unevaluated on 2nd level is invalid is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties + single cyclic ref: Unevaluated on 3rd level is invalid is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: dynamic evalation inside nested refs: xx + foo is invalid is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties not affected by propertyNames: string property is invalid is expected to be invalid', - '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties can see annotations from if without then and else: invalid in case if is evaluated is expected to be invalid', + '[draft2019-09/unevaluatedProperties.json]: unevaluatedProperties with $recursiveRef: with no unevaluated properties is expected to be valid', // Recursive references are not supported yet. '[draft2019-09/anchor.json]: Location-independent identifier: mismatch is expected to be invalid', '[draft2019-09/anchor.json]: Location-independent identifier with absolute URI: mismatch is expected to be invalid', '[draft2019-09/anchor.json]: Location-independent identifier with base URI change in subschema: mismatch is expected to be invalid', @@ -291,7 +253,7 @@ private function shouldNotYieldTest(string $name): bool '[draft2019-09/refRemote.json]: remote ref with ref to defs: invalid is expected to be invalid', '[draft2019-09/refRemote.json]: Location-independent identifier in remote ref: string is invalid is expected to be invalid', '[draft2019-09/refRemote.json]: $ref to $ref finds detached $anchor: non-number is invalid is expected to be invalid', - // Optional: bignum — PHP does not natively support arbitrary-precision integers/floats + // Optional: bignum — PHP does not natively support arbitrary-precision integers/floats '[draft3/optional/bignum.json]: integer: a bignum is an integer is expected to be valid', '[draft3/optional/bignum.json]: integer: a negative bignum is an integer is expected to be valid', '[draft3/optional/bignum.json]: float comparison with high precision: comparison works for high numbers is expected to be invalid', @@ -305,11 +267,11 @@ private function shouldNotYieldTest(string $name): bool '[draft6/optional/bignum.json]: float comparison with high precision on negative numbers: comparison works for very negative numbers is expected to be invalid', '[draft7/optional/bignum.json]: float comparison with high precision: comparison works for high numbers is expected to be invalid', '[draft7/optional/bignum.json]: float comparison with high precision on negative numbers: comparison works for very negative numbers is expected to be invalid', - // Optional: float-overflow — PHP float precision differs from the ECMAScript model + // Optional: float-overflow — PHP float precision differs from the ECMAScript model '[draft4/optional/float-overflow.json]: all integers are multiples of 0.5, if overflow is handled: valid if optional overflow handling is implemented is expected to be valid', '[draft6/optional/float-overflow.json]: all integers are multiples of 0.5, if overflow is handled: valid if optional overflow handling is implemented is expected to be valid', '[draft7/optional/float-overflow.json]: all integers are multiples of 0.5, if overflow is handled: valid if optional overflow handling is implemented is expected to be valid', - // Optional: ecmascript-regex — PHP uses PCRE which does not implement ECMAScript regex semantics + // Optional: ecmascript-regex — PHP uses PCRE which does not implement ECMAScript regex semantics '[draft3/optional/ecmascript-regex.json]: ECMA 262 regex dialect recognition: [^] is a valid regex is expected to be valid', '[draft3/optional/ecmascript-regex.json]: ECMA 262 regex dialect recognition: ECMA 262 has no support for lookbehind is expected to be invalid', '[draft4/optional/ecmascript-regex.json]: ECMA 262 \D matches everything but ascii digits: NKO DIGIT ZERO (as \u escape) matches is expected to be valid', @@ -350,12 +312,12 @@ private function shouldNotYieldTest(string $name): bool '[draft7/optional/ecmascript-regex.json]: \w in patternProperties matches [A-Za-z0-9_], not unicode letters: literal unicode character in json string is expected to be invalid', '[draft7/optional/ecmascript-regex.json]: \w in patternProperties matches [A-Za-z0-9_], not unicode letters: unicode character in hex format in string is expected to be invalid', '[draft7/optional/ecmascript-regex.json]: pattern with non-ASCII digits: non-ascii digits (BENGALI DIGIT FOUR, BENGALI DIGIT TWO) is expected to be valid', - // Optional: cross-draft — cross-draft schema resolution ($ref across drafts) is not implemented + // Optional: cross-draft — cross-draft schema resolution ($ref across drafts) is not implemented '[draft7/optional/cross-draft.json]: refs to future drafts are processed as future drafts: missing bar is invalid is expected to be invalid', - // Optional: idn-email — IDN e-mail format validation is not implemented + // Optional: idn-email — IDN e-mail format validation is not implemented '[draft7/optional/format/idn-email.json]: validation of an internationalized e-mail addresses: an invalid e-mail address is expected to be invalid', '[draft7/optional/format/idn-email.json]: validation of an internationalized e-mail addresses: an invalid idn e-mail address is expected to be invalid', - // Optional: idn-hostname — IDN hostname format validation is not implemented + // Optional: idn-hostname — IDN hostname format validation is not implemented '[draft7/optional/format/idn-hostname.json]: validation of internationalized host names: Exceptions that are DISALLOWED, left-to-right chars is expected to be invalid', '[draft7/optional/format/idn-hostname.json]: validation of internationalized host names: Exceptions that are DISALLOWED, right-to-left chars is expected to be invalid', '[draft7/optional/format/idn-hostname.json]: validation of internationalized host names: KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han is expected to be invalid', @@ -368,16 +330,16 @@ private function shouldNotYieldTest(string $name): bool '[draft7/optional/format/idn-hostname.json]: validation of internationalized host names: ZERO WIDTH NON-JOINER not preceded by Virama but matches regexp is expected to be valid', '[draft7/optional/format/idn-hostname.json]: validation of internationalized host names: ZERO WIDTH NON-JOINER preceded by Virama is expected to be valid', '[draft7/optional/format/idn-hostname.json]: validation of internationalized host names: contains illegal char U+302E Hangul single dot tone mark is expected to be invalid', - // Optional: iri / iri-reference — IRI format validation is not implemented + // Optional: iri / iri-reference — IRI format validation is not implemented '[draft7/optional/format/iri-reference.json]: validation of IRI References: an invalid IRI Reference is expected to be invalid', '[draft7/optional/format/iri-reference.json]: validation of IRI References: an invalid IRI fragment is expected to be invalid', '[draft7/optional/format/iri.json]: validation of IRIs: an invalid IRI based on IPv6 is expected to be invalid', '[draft7/optional/format/iri.json]: validation of IRIs: an invalid IRI is expected to be invalid', '[draft7/optional/format/iri.json]: validation of IRIs: an invalid IRI though valid IRI reference is expected to be invalid', '[draft7/optional/format/iri.json]: validation of IRIs: an invalid relative IRI Reference is expected to be invalid', - // Optional: regex format — regex format validation does not check for valid ECMA-262 regex syntax + // Optional: regex format — regex format validation does not check for valid ECMA-262 regex syntax '[draft7/optional/format/regex.json]: validation of regular expressions: a regular expression with unclosed parens is invalid is expected to be invalid', - // Optional: relative-json-pointer — relative JSON pointer format validation is not implemented + // Optional: relative-json-pointer — relative JSON pointer format validation is not implemented '[draft7/optional/format/relative-json-pointer.json]: validation of Relative JSON Pointers (RJP): ## is not a valid json-pointer is expected to be invalid', '[draft7/optional/format/relative-json-pointer.json]: validation of Relative JSON Pointers (RJP): an invalid RJP that is a valid JSON Pointer is expected to be invalid', '[draft7/optional/format/relative-json-pointer.json]: validation of Relative JSON Pointers (RJP): empty string is expected to be invalid',