From 00ddad7e725930f326170a863d8d96e4a3caf17d Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Wed, 15 May 2024 12:31:25 +0200 Subject: [PATCH 1/2] Add feature to treat anyOf and oneOf union types as allOf when creating a serialization context with the SerializationContextBuilder --- .../SerializationContextBuilder.php | 31 +++-- .../SerializationContextBuilderInterface.php | 2 +- .../SerializationContextBuilderTest.php | 121 ++++++++++++++++++ 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/src/Serialization/SerializationContextBuilder.php b/src/Serialization/SerializationContextBuilder.php index d5c50ef..d213628 100644 --- a/src/Serialization/SerializationContextBuilder.php +++ b/src/Serialization/SerializationContextBuilder.php @@ -40,26 +40,26 @@ public function __construct(SchemaLoaderInterface $schemaLoader) $this->schemaLoader = $schemaLoader; } - public function getContextForSchemaObject(string $schemaObjectName, string $openApiSpecificationFile): array + public function getContextForSchemaObject(string $schemaObjectName, string $openApiSpecificationFile, bool $treatAnyOfAndOneOfAsAllOf = false): array { $jsonPointer = new JsonPointer($this->schemaLoader->load($openApiSpecificationFile)); $schemaObject = $jsonPointer->get(sprintf('/components/schemas/%s', $schemaObjectName)); return [ AbstractObjectNormalizer::SKIP_NULL_VALUES => true, - AbstractNormalizer::ATTRIBUTES => $this->getAttributeContextFromSchemaObject($schemaObject), + AbstractNormalizer::ATTRIBUTES => $this->getAttributeContextFromSchemaObject($schemaObject, $treatAnyOfAndOneOfAsAllOf), ]; } /** * @param stdClass|Reference $schemaObject */ - private function getAttributeContextFromSchemaObject($schemaObject): array + private function getAttributeContextFromSchemaObject($schemaObject, bool $treatAnyOfAndOneOfAsAllOf): array { $schemaObject = $this->dereference($schemaObject); - if (isset($schemaObject->allOf)) { - return $this->getAttributeContextFromCombinedSchemaObject($schemaObject); + if (isset($schemaObject->allOf) || ($treatAnyOfAndOneOfAsAllOf && (isset($schemaObject->anyOf) || isset($schemaObject->oneOf)))) { + return $this->getAttributeContextFromCombinedSchemaObject($schemaObject, $treatAnyOfAndOneOfAsAllOf); } if (isset($schemaObject->type) === false) { @@ -68,30 +68,35 @@ private function getAttributeContextFromSchemaObject($schemaObject): array switch ($schemaObject->type) { case 'object': - return $this->getAttributeContextFromSchemaObjectProperties($schemaObject); + return $this->getAttributeContextFromSchemaObjectProperties($schemaObject, $treatAnyOfAndOneOfAsAllOf); case 'array': - return $this->getAttributeContextFromSchemaObject($schemaObject->items); + return $this->getAttributeContextFromSchemaObject($schemaObject->items, $treatAnyOfAndOneOfAsAllOf); } return []; } - private function getAttributeContextFromCombinedSchemaObject(stdClass $schemaObject): array + private function getAttributeContextFromCombinedSchemaObject(stdClass $schemaObject, bool $treatAnyOfAndOneOfAsAllOf): array { $context = []; - foreach ($schemaObject->allOf as $allOfSchemaObject) { - $context = array_merge($context, $this->getAttributeContextFromSchemaObject($allOfSchemaObject)); + $allOfSchemaObjects = $schemaObject->allOf ?? []; + if ($treatAnyOfAndOneOfAsAllOf) { + $allOfSchemaObjects = array_merge($allOfSchemaObjects, $schemaObject->anyOf ?? [], $schemaObject->oneOf ?? []); + } + + foreach ($allOfSchemaObjects as $allOfSchemaObject) { + $context = array_merge($context, $this->getAttributeContextFromSchemaObject($allOfSchemaObject, $treatAnyOfAndOneOfAsAllOf)); } return $context; } - private function getAttributeContextFromSchemaObjectProperties(stdClass $schemaObject): array + private function getAttributeContextFromSchemaObjectProperties(stdClass $schemaObject, bool $treatAnyOfAndOneOfAsAllOf): array { $objectContext = []; $properties = $schemaObject->properties ?? []; foreach ($properties as $propertyKey => $property) { - $propertyContext = $this->getAttributeContextFromSchemaObject($property); + $propertyContext = $this->getAttributeContextFromSchemaObject($property, $treatAnyOfAndOneOfAsAllOf); if ($this->isType($property, 'object') || count($propertyContext) > 0) { $objectContext[$propertyKey] = $propertyContext; @@ -105,7 +110,7 @@ private function getAttributeContextFromSchemaObjectProperties(stdClass $schemaO if (isset($schemaObject->additionalProperties) && $schemaObject->additionalProperties !== false) { $objectContext = array_merge( $objectContext, - $this->getAttributeContextFromSchemaObject($schemaObject->additionalProperties) + $this->getAttributeContextFromSchemaObject($schemaObject->additionalProperties, $treatAnyOfAndOneOfAsAllOf) ); } diff --git a/src/Serialization/SerializationContextBuilderInterface.php b/src/Serialization/SerializationContextBuilderInterface.php index 1da928f..1098461 100644 --- a/src/Serialization/SerializationContextBuilderInterface.php +++ b/src/Serialization/SerializationContextBuilderInterface.php @@ -20,5 +20,5 @@ */ interface SerializationContextBuilderInterface { - public function getContextForSchemaObject(string $schemaObjectName, string $openApiSpecificationFile): array; + public function getContextForSchemaObject(string $schemaObjectName, string $openApiSpecificationFile/*, bool $treatAnyOfAndOneOfAsAllOf = false*/): array; } diff --git a/tests/Serialization/SerializationContextBuilderTest.php b/tests/Serialization/SerializationContextBuilderTest.php index 49ca952..9835846 100644 --- a/tests/Serialization/SerializationContextBuilderTest.php +++ b/tests/Serialization/SerializationContextBuilderTest.php @@ -492,6 +492,127 @@ public function testCanCreateContextForReferencedCombinedObjectSchemaWithoutType ); } + public function testCanCreateContextForReferencedCombinedObjectSchemaWithAnyOfAndOneOfTreatedAsAllOf(): void + { + $schema = $this->convertToObject([ + 'components' => [ + 'schemas' => [ + 'Pet' => [ + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'type' => 'string', + ], + 'owner' => [ + 'anyOf' => [ + [ + '$ref' => '#/components/schemas/Robot', + ], + [ + '$ref' => '#/components/schemas/Human', + ], + ], + ], + ], + ], + 'Robot' => [ + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'type' => 'integer', + ], + ], + ], + 'Human' => [ + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'type' => 'string', + ], + ], + ], + ], + ], + ]); + $schema->components->schemas->Pet->properties->owner->anyOf[0] = new Reference('#/components/schemas/Robot', $schema); + $schema->components->schemas->Pet->properties->owner->anyOf[1] = new Reference('#/components/schemas/Human', $schema); + + $this->schemaLoader->setSchema($schema); + + $this->assertSame( + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + AbstractNormalizer::ATTRIBUTES => [ + 'name', + 'owner' => [ + 'id', + 'name', + ], + ], + ], + $this->serializationContextBuilder->getContextForSchemaObject('Pet', '', true) + ); + } + + public function testCanCreateContextForReferencedCombinedObjectSchemaWithAnyOfAndOneOfNotTreatedAsAllOf(): void + { + $schema = $this->convertToObject([ + 'components' => [ + 'schemas' => [ + 'Pet' => [ + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'type' => 'string', + ], + 'owner' => [ + 'anyOf' => [ + [ + '$ref' => '#/components/schemas/Robot', + ], + [ + '$ref' => '#/components/schemas/Human', + ], + ], + ], + ], + ], + 'Robot' => [ + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'type' => 'integer', + ], + ], + ], + 'Human' => [ + 'type' => 'object', + 'properties' => [ + 'name' => [ + 'type' => 'string', + ], + ], + ], + ], + ], + ]); + $schema->components->schemas->Pet->properties->owner->anyOf[0] = new Reference('#/components/schemas/Robot', $schema); + $schema->components->schemas->Pet->properties->owner->anyOf[1] = new Reference('#/components/schemas/Human', $schema); + + $this->schemaLoader->setSchema($schema); + + $this->assertSame( + [ + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + AbstractNormalizer::ATTRIBUTES => [ + 'name', + 'owner', + ], + ], + $this->serializationContextBuilder->getContextForSchemaObject('Pet', '') + ); + } + public function testCanCreateContextForUnimplementedJsonSchemaKeywordsWithoutErrors(): void { $schema = $this->convertToObject([ From 2a36d4cd22b786c8112d0e83dd9ac7ded803a1b0 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Wed, 15 May 2024 12:32:59 +0200 Subject: [PATCH 2/2] Fix code style in SerializationContextBuilderInterface --- src/Serialization/SerializationContextBuilderInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serialization/SerializationContextBuilderInterface.php b/src/Serialization/SerializationContextBuilderInterface.php index 1098461..d054148 100644 --- a/src/Serialization/SerializationContextBuilderInterface.php +++ b/src/Serialization/SerializationContextBuilderInterface.php @@ -20,5 +20,5 @@ */ interface SerializationContextBuilderInterface { - public function getContextForSchemaObject(string $schemaObjectName, string $openApiSpecificationFile/*, bool $treatAnyOfAndOneOfAsAllOf = false*/): array; + public function getContextForSchemaObject(string $schemaObjectName, string $openApiSpecificationFile/* , bool $treatAnyOfAndOneOfAsAllOf = false */): array; }