Skip to content

Commit e8ff685

Browse files
committed
fix(hydra): require @id and @type for output json schema
1 parent e52e825 commit e8ff685

File tree

3 files changed

+24
-20
lines changed

3 files changed

+24
-20
lines changed

src/Hydra/JsonSchema/SchemaFactory.php

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,18 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
3636
use ResourceMetadataTrait;
3737
use SchemaUriPrefixTrait;
3838

39-
private const ITEM_BASE_SCHEMA_NAME = 'HydraItemBaseSchema';
40-
private const ITEM_BASE_SCHEMA_OUTPUT_NAME = 'HydraOutputBaseSchema';
41-
private const COLLECTION_BASE_SCHEMA_NAME = 'HydraCollectionBaseSchema';
39+
private const INPUT_ITEM_SCHEMA_NAME = 'HydraInputItemBaseSchema';
40+
private const OUTPUT_ITEM_SCHEMA_NAME = 'HydraItemBaseSchema';
41+
private const COLLECTION_SCHEMA_NAME = 'HydraCollectionBaseSchema';
4242
private const BASE_PROP = [
4343
'type' => 'string',
4444
];
4545
private const BASE_PROPS = [
4646
'@id' => self::BASE_PROP,
4747
'@type' => self::BASE_PROP,
4848
];
49-
private const ITEM_BASE_SCHEMA = [
49+
// This is the base for both input and output, and serves as the complete definition for an INPUT schema.
50+
private const BASE_ITEM_SCHEMA = [
5051
'type' => 'object',
5152
'properties' => [
5253
'@context' => [
@@ -70,10 +71,10 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
7071
],
7172
] + self::BASE_PROPS,
7273
];
73-
74-
private const ITEM_BASE_SCHEMA_OUTPUT = [
74+
// The OUTPUT schema is the base schema with the addition of required fields.
75+
private const OUTPUT_ITEM_SCHEMA = [
7576
'required' => ['@id', '@type'],
76-
] + self::ITEM_BASE_SCHEMA;
77+
] + self::BASE_ITEM_SCHEMA;
7778

7879
/**
7980
* @var array<string, true>
@@ -104,9 +105,10 @@ public function __construct(
104105
*/
105106
public function buildSchema(string $className, string $format = 'jsonld', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
106107
{
107-
if ('jsonld' !== $format || 'input' === $type) {
108+
if ('jsonld' !== $format) {
108109
return $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
109110
}
111+
110112
if (!$this->isResourceClass($className)) {
111113
$operation = null;
112114
$inputOrOutputClass = null;
@@ -140,10 +142,11 @@ public function buildSchema(string $className, string $format = 'jsonld', string
140142
return $schema;
141143
}
142144

143-
$baseName = Schema::TYPE_OUTPUT === $type ? self::ITEM_BASE_SCHEMA_NAME : self::ITEM_BASE_SCHEMA_OUTPUT_NAME;
145+
$isOutput = Schema::TYPE_OUTPUT === $type;
146+
$baseName = $isOutput ? self::OUTPUT_ITEM_SCHEMA_NAME : self::INPUT_ITEM_SCHEMA_NAME;
144147

145148
if (!isset($definitions[$baseName])) {
146-
$definitions[$baseName] = Schema::TYPE_OUTPUT === $type ? self::ITEM_BASE_SCHEMA_OUTPUT : self::ITEM_BASE_SCHEMA;
149+
$definitions[$baseName] = $isOutput ? self::OUTPUT_ITEM_SCHEMA : self::BASE_ITEM_SCHEMA;
147150
}
148151

149152
$allOf = new \ArrayObject(['allOf' => [
@@ -171,7 +174,7 @@ public function buildSchema(string $className, string $format = 'jsonld', string
171174

172175
$hydraPrefix = $this->getHydraPrefix($serializerContext + $this->defaultContext);
173176

174-
if (!isset($definitions[self::COLLECTION_BASE_SCHEMA_NAME])) {
177+
if (!isset($definitions[self::COLLECTION_SCHEMA_NAME])) {
175178
switch ($schema->getVersion()) {
176179
// JSON Schema + OpenAPI 3.1
177180
case Schema::VERSION_OPENAPI:
@@ -184,7 +187,7 @@ public function buildSchema(string $className, string $format = 'jsonld', string
184187
break;
185188
}
186189

187-
$definitions[self::COLLECTION_BASE_SCHEMA_NAME] = [
190+
$definitions[self::COLLECTION_SCHEMA_NAME] = [
188191
'type' => 'object',
189192
'required' => [
190193
$hydraPrefix.'member',
@@ -261,7 +264,7 @@ public function buildSchema(string $className, string $format = 'jsonld', string
261264
$schema['type'] = 'object';
262265
$schema['description'] = "$definitionName collection.";
263266
$schema['allOf'] = [
264-
['$ref' => $prefix.self::COLLECTION_BASE_SCHEMA_NAME],
267+
['$ref' => $prefix.self::COLLECTION_SCHEMA_NAME],
265268
[
266269
'type' => 'object',
267270
'properties' => [

tests/Functional/JsonSchema/JsonSchemaTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,24 @@ public function testArraySchemaWithReference(): void
134134

135135
$schema = $this->schemaFactory->buildSchema(BagOfTests::class, 'jsonld', Schema::TYPE_INPUT);
136136

137-
$this->assertEquals($schema['definitions']['BagOfTests.jsonld-write']['properties']['tests'], new \ArrayObject([
137+
$base = $schema['definitions']['BagOfTests.jsonld-write']['allOf'][1];
138+
$this->assertEquals($base['properties']['tests'], new \ArrayObject([
138139
'type' => 'string',
139140
'foo' => 'bar',
140141
]));
141142

142-
$this->assertEquals($schema['definitions']['BagOfTests.jsonld-write']['properties']['nonResourceTests'], new \ArrayObject([
143+
$this->assertEquals($base['properties']['nonResourceTests'], new \ArrayObject([
143144
'type' => 'array',
144145
'items' => [
145146
'$ref' => '#/definitions/NonResourceTestEntity.jsonld-write',
146147
],
147148
]));
148149

149-
$this->assertEquals($schema['definitions']['BagOfTests.jsonld-write']['properties']['description'], new \ArrayObject([
150+
$this->assertEquals($base['properties']['description'], new \ArrayObject([
150151
'maxLength' => 255,
151152
]));
152153

153-
$this->assertEquals($schema['definitions']['BagOfTests.jsonld-write']['properties']['type'], new \ArrayObject([
154+
$this->assertEquals($base['properties']['type'], new \ArrayObject([
154155
'$ref' => '#/definitions/TestEntity.jsonld-write',
155156
]));
156157
}

tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Tests\JsonSchema\Command;
1515

16+
use ApiPlatform\Symfony\Bundle\Test\Constraint\MatchesJsonSchema;
1617
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Animal;
1718
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\AnimalObservation;
1819
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\BackedEnumIntegerResource;
@@ -116,9 +117,8 @@ public function testExecuteWithJsonldTypeInput(): void
116117
$this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies{._format}_post', '--format' => 'jsonld', '--type' => 'input']);
117118
$result = $this->tester->getDisplay();
118119

119-
$this->assertStringNotContainsString('@id', $result);
120-
$this->assertStringNotContainsString('@context', $result);
121-
$this->assertStringNotContainsString('@type', $result);
120+
$constraint = new MatchesJsonSchema(json_decode($result));
121+
static::assertThat(['name' => 'test'], $constraint);
122122
}
123123

124124
public function testExecuteWithJsonMergePatchTypeInput(): void

0 commit comments

Comments
 (0)