Skip to content

Commit

Permalink
Merge pull request #93 from niels-nijens/add-treating-anyof-and-oneof…
Browse files Browse the repository at this point in the history
…-as-allof

Add feature to treat anyOf and oneOf union types as allOf when creating a serialization context with the SerializationContextBuilder
  • Loading branch information
niels-nijens committed May 15, 2024
2 parents 0c282f5 + 2a36d4c commit ff8fe1d
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 14 deletions.
31 changes: 18 additions & 13 deletions src/Serialization/SerializationContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Serialization/SerializationContextBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
121 changes: 121 additions & 0 deletions tests/Serialization/SerializationContextBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down

0 comments on commit ff8fe1d

Please sign in to comment.