Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,28 @@ public static function arrayProvider(): array {
false,
'Missing required field: id.',
],
'tuple validation: valid with different schemas' => [
[ 'type' => 'array', 'items' => [ [ 'type' => 'string' ], [ 'type' => 'integer' ] ] ],
[ 'a string', 123 ],
true,
],
'tuple validation: valid with identical schemas' => [
[ 'type' => 'array', 'items' => [ [ 'type' => 'string' ], [ 'type' => 'string' ] ] ],
[ 'hello', 'world' ],
true,
],
'tuple validation: invalid item type' => [
[ 'type' => 'array', 'items' => [ [ 'type' => 'string' ], [ 'type' => 'integer' ] ] ],
[ 'a string', 'another string' ], // Second item should be an integer
false,
'Expected type "integer" but got type "string".',
],
'tuple validation: too many items' => [
[ 'type' => 'array', 'items' => [ [ 'type' => 'string' ], [ 'type' => 'integer' ] ] ],
[ 'a string', 123, 'extra item' ], // Contains one too many items
false,
'Tuple validation failed: expected at most 2 items but got 3.',
],
];
}

Expand Down Expand Up @@ -1643,4 +1665,77 @@ private function assertIsValid( $result, bool $shouldBeValid = true ) {
private function assertIsInvalid( $result ) {
$this->assertInstanceOf( ValidationError::class, $result );
}

public function testItReturnsValidationErrorForInvalidTupleData() {
$schema_path = __DIR__ . '/../../../Versions/Version2/json-schema/schema-v2.json';
$schema = json_decode( file_get_contents( $schema_path ), true );
$this->assertIsArray( $schema, 'Failed to parse schema file: ' . $schema_path );

$invalid_blueprint_json = '{
"version": 2,
"blueprintMeta": {
"name": "Invalid Blueprint - Wrong Post Type Format",
"description": "This blueprint has invalid post type definitions"
},
"postTypes": {
"book": {
"label": 123,
"public": "not-a-boolean",
"hierarchical": "wrong-type",
"show_in_menu": {},
"capability_type": [
"one",
"two",
"three"
],
"template": [
"invalid-not-an-array",
[
"valid-item-1"
],
[
"valid-item-2",
{}
],
[
123,
"not-string-first-element"
]
],
"supports": [
"title",
123,
true
]
}
},
"content": [
{
"type": "posts",
"source": [
{
"post_title": "Test Post",
"post_content": "This is a test post."
}
]
}
]
}';
$invalid_blueprint = json_decode( $invalid_blueprint_json );

$validator = new HumanFriendlySchemaValidator( $schema );
try {
$error = $validator->validate( $invalid_blueprint );
} catch ( UnsupportedSchemaException $e ) {
$this->fail(
'The validator threw an UnsupportedSchemaException when it should have returned a ValidationError. Exception message: ' . $e->getMessage()
);
}

$this->assertInstanceOf(
ValidationError::class,
$error,
'The validator should return a ValidationError, not throw an UnsupportedSchemaException exception or pass.'
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ private function validate_type( array $path, $data, array $schema ): ?Validation
}
if ( isset( $schema['enum'] ) ) {
foreach ( $schema['enum'] as $enum_value ) {
if ( ! $this->type_matches( $enum_value, $type ) ) {
if ( ! $this->type_matches_any( $enum_value, $type ) ) {
throw new UnsupportedSchemaException(
'Enum value ' . json_encode( $enum_value ) . " does not match the declared type \"{$type}\"."
);
Expand Down Expand Up @@ -731,10 +731,62 @@ private function validate_object( array $path, $data, array $schema ): ?Validati
private function validate_array( array $path, array $data, array $schema ): ?ValidationError {
$children_errors = array();
if ( isset( $schema['items'] ) ) {
foreach ( $data as $idx => $item ) {
$error = $this->validate_node( array_merge( $path, array( $idx ) ), $item, $schema['items'] );
if ( $error ) {
$children_errors[] = $error;
// JSON schema supports two types of array validation:
//
// 1. List validation:
// `{"type":"string"}`
// A single schema object that all items in the data array must conform to.
// In PHP, this is represented as an associative array,
// `["type"=>"string"]`
//
// 2. Tuple validation:
// `[{"type":"string"},{"type":"object"}]`,
// A list of schemas, where each item in the data array is validated
// against the schema at the same index.
// In PHP, this is a numerically indexed array.
// `[0=>['type'=>'string'],1=>['type'=>'object']]`.
$is_tuple_schema = is_array( $schema['items'] ) && array_keys( $schema['items'] ) === range( 0, count( $schema['items'] ) - 1 );

if ( $is_tuple_schema ) {
// Tuple validation.
// Note: We do not support the "additionalItems" keyword.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be cool to support it eventually. Not a blocker here

// Therefore, we treat it as an error if the data array contains more items
// than are defined in the schema's "items" tuple.
if ( count( $data ) > count( $schema['items'] ) ) {
$children_errors[] = new ValidationError(
$this->convert_path_to_string( $path ),
'additional-items-not-allowed',
'Tuple validation failed: expected at most ' . count( $schema['items'] ) . ' items but got ' . count( $data ) . '.',
array(
'expectedMaxCount' => count( $schema['items'] ),
'actualCount' => count( $data ),
)
);
}

foreach ( $data as $idx => $item ) {
if ( isset( $schema['items'][ $idx ] ) ) {
// Guard against a malformed schema where an item in the tuple is not a valid schema object.
if ( ! is_array( $schema['items'][ $idx ] ) ) {
throw new UnsupportedSchemaException( 'Invalid tuple schema: items must be schema objects, but a non-object was found at index ' . $idx );
}
// This item has a specific schema defined in the tuple.
$error = $this->validate_node( array_merge( $path, array( $idx ) ), $item, $schema['items'][ $idx ] );
if ( $error ) {
$children_errors[] = $error;
}
}
// If there's no schema at $schema['items'][$idx],
// the data item is an "additional item",
// handled by the count check above.
}
} else {
// List validation.
foreach ( $data as $idx => $item ) {
$error = $this->validate_node( array_merge( $path, array( $idx ) ), $item, $schema['items'] );
if ( $error ) {
$children_errors[] = $error;
}
}
}
}
Expand Down