Skip to content

Commit 5ce343e

Browse files
authored
Implement the Union Shape (#2019)
fix #1900
1 parent 104b842 commit 5ce343e

23 files changed

Lines changed: 684 additions & 289 deletions

src/Definition/ObjectShape.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AsyncAws\CodeGenerator\Definition;
6+
7+
/**
8+
* @internal
9+
*/
10+
abstract class ObjectShape extends Shape
11+
{
12+
}

src/Definition/Shape.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static function create(string $name, array $data, \Closure $shapeLocator,
4646
} elseif ($data['document'] ?? false) {
4747
$shape = new DocumentShape();
4848
} elseif ($data['union'] ?? false) {
49-
throw new \UnexpectedValueException(\sprintf('Union shapes are not supported yet by the code generator. The shape "%s" is a union shape.', $name));
49+
$shape = new UnionShape();
5050
} else {
5151
$shape = new StructureShape();
5252
}

src/Definition/StructureMember.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function getName(): string
1818
return $this->data['_name'];
1919
}
2020

21-
public function getOwnerShape(): StructureShape
21+
public function getOwnerShape(): ObjectShape
2222
{
2323
return $this->data['_owner'];
2424
}

src/Definition/StructureShape.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* @internal
99
*/
10-
class StructureShape extends Shape
10+
class StructureShape extends ObjectShape
1111
{
1212
/**
1313
* @return StructureMember[]
@@ -18,7 +18,7 @@ public function getMembers(): array
1818
$members = [];
1919
foreach ($this->data['members'] as $name => $member) {
2020
$members[] = new StructureMember(
21-
$member + ['_name' => $name, '_owner' => $this, '_required' => \in_array($name, $required)],
21+
$member + ['_name' => $name, '_owner' => $this->data['_members_owner'] ?? $this, '_required' => \in_array($name, $required)],
2222
$this->shapeLocator
2323
);
2424
}

src/Definition/UnionShape.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AsyncAws\CodeGenerator\Definition;
6+
7+
/**
8+
* @internal
9+
*/
10+
class UnionShape extends ObjectShape
11+
{
12+
public function getChildren(): array
13+
{
14+
$children = [];
15+
foreach ($this->data['members'] as $name => $member) {
16+
$locationName = $member['locationName'] ?? $name;
17+
$children[$locationName] = Shape::create(
18+
$this->getChildName($name),
19+
[
20+
'type' => 'structure',
21+
'_members_owner' => $this,
22+
'required' => [$name],
23+
'members' => [$name => $member],
24+
],
25+
$this->shapeLocator,
26+
$this->serviceLocator,
27+
);
28+
}
29+
30+
return $children;
31+
}
32+
33+
public function getChildForUnknown(): StructureShape
34+
{
35+
/** @phpstan-ignore return.type */
36+
return Shape::create(
37+
$this->getChildName('UnknownToSdk'),
38+
[
39+
'type' => 'structure',
40+
'_members_owner' => $this,
41+
'required' => [],
42+
'members' => [],
43+
],
44+
$this->shapeLocator,
45+
$this->serviceLocator,
46+
);
47+
}
48+
49+
private function getChildName(string $name): string
50+
{
51+
return $this->getName() . 'Member' . ucfirst($name);
52+
}
53+
}

src/Generator/CodeGenerator/PopulatorGenerator.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use AsyncAws\CodeGenerator\Definition\DocumentShape;
88
use AsyncAws\CodeGenerator\Definition\ListShape;
99
use AsyncAws\CodeGenerator\Definition\MapShape;
10+
use AsyncAws\CodeGenerator\Definition\ObjectShape;
1011
use AsyncAws\CodeGenerator\Definition\Operation;
1112
use AsyncAws\CodeGenerator\Definition\Shape;
1213
use AsyncAws\CodeGenerator\Definition\StructureShape;
@@ -18,6 +19,7 @@
1819
use AsyncAws\CodeGenerator\Generator\PhpGenerator\ClassBuilder;
1920
use AsyncAws\CodeGenerator\Generator\PhpGenerator\ClassRegistry;
2021
use AsyncAws\CodeGenerator\Generator\ResponseParser\ParserProvider;
22+
use AsyncAws\CodeGenerator\Generator\ShapeUsageHelper;
2123
use AsyncAws\Core\Response;
2224
use AsyncAws\Core\Stream\ResponseBodyStream;
2325
use AsyncAws\Core\Stream\ResultStream;
@@ -58,12 +60,12 @@ class PopulatorGenerator
5860
*/
5961
private $parserProvider;
6062

61-
public function __construct(ClassRegistry $classRegistry, NamespaceRegistry $namespaceRegistry, RequirementsRegistry $requirementsRegistry, ObjectGenerator $objectGenerator, array $managedMethods, ?TypeGenerator $typeGenerator = null, ?EnumGenerator $enumGenerator = null, ?ParserProvider $parserProvider = null)
63+
public function __construct(ClassRegistry $classRegistry, NamespaceRegistry $namespaceRegistry, RequirementsRegistry $requirementsRegistry, ObjectGenerator $objectGenerator, ShapeUsageHelper $shapeUsageHelper, ?TypeGenerator $typeGenerator = null, ?EnumGenerator $enumGenerator = null, ?ParserProvider $parserProvider = null)
6264
{
6365
$this->objectGenerator = $objectGenerator;
6466
$this->requirementsRegistry = $requirementsRegistry;
6567
$this->typeGenerator = $typeGenerator ?? new TypeGenerator($namespaceRegistry);
66-
$this->enumGenerator = $enumGenerator ?? new EnumGenerator($classRegistry, $namespaceRegistry, $managedMethods);
68+
$this->enumGenerator = $enumGenerator ?? new EnumGenerator($classRegistry, $namespaceRegistry, $shapeUsageHelper);
6769
$this->parserProvider = $parserProvider ?? new ParserProvider($namespaceRegistry, $requirementsRegistry, $this->typeGenerator);
6870
}
6971

@@ -100,7 +102,7 @@ private function generateProperties(StructureShape $shape, ClassBuilder $classBu
100102
$this->enumGenerator->generate($memberShape);
101103
}
102104

103-
if ($memberShape instanceof StructureShape) {
105+
if ($memberShape instanceof ObjectShape) {
104106
$this->objectGenerator->generate($memberShape);
105107
} elseif ($memberShape instanceof MapShape) {
106108
$mapKeyShape = $memberShape->getKey()->getShape();
@@ -111,7 +113,7 @@ private function generateProperties(StructureShape $shape, ClassBuilder $classBu
111113
$this->enumGenerator->generate($mapKeyShape);
112114
}
113115

114-
if (($valueShape = $memberShape->getValue()->getShape()) instanceof StructureShape) {
116+
if (($valueShape = $memberShape->getValue()->getShape()) instanceof ObjectShape) {
115117
$this->objectGenerator->generate($valueShape);
116118
}
117119
if (!empty($valueShape->getEnum())) {
@@ -300,7 +302,7 @@ private function generatePopulator(Operation $operation, StructureShape $shape,
300302

301303
private function generateListShapeMemberShape(Shape $memberShape, bool $forEndpoint): void
302304
{
303-
if ($memberShape instanceof StructureShape) {
305+
if ($memberShape instanceof ObjectShape) {
304306
$this->objectGenerator->generate($memberShape, $forEndpoint);
305307
} elseif ($memberShape instanceof ListShape) {
306308
$this->generateListShapeMemberShape($memberShape->getMember()->getShape(), $forEndpoint);

src/Generator/CodeGenerator/TypeGenerator.php

Lines changed: 98 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
use AsyncAws\CodeGenerator\Definition\DocumentShape;
88
use AsyncAws\CodeGenerator\Definition\ListShape;
99
use AsyncAws\CodeGenerator\Definition\MapShape;
10+
use AsyncAws\CodeGenerator\Definition\ObjectShape;
1011
use AsyncAws\CodeGenerator\Definition\Shape;
1112
use AsyncAws\CodeGenerator\Definition\StructureShape;
13+
use AsyncAws\CodeGenerator\Definition\UnionShape;
1214
use AsyncAws\CodeGenerator\Generator\Naming\ClassName;
1315
use AsyncAws\CodeGenerator\Generator\Naming\NamespaceRegistry;
1416

@@ -39,30 +41,116 @@ public function __construct(NamespaceRegistry $namespaceRegistry)
3941
*
4042
* @return array{string, ClassName[]} [docblock representation, ClassName related]
4143
*/
42-
public function generateDocblock(StructureShape $shape, ClassName $shapeClassName, bool $alternateClass = true, bool $allNullable = false, bool $isObject = false, array $extra = []): array
44+
public function generateDocblock(ObjectShape $shape, ClassName $shapeClassName, bool $alternateClass = true, bool $allNullable = false, bool $isObject = false, array $extra = []): array
4345
{
44-
$classNames = [];
46+
if ($shape instanceof StructureShape) {
47+
[$definitions, $classNames] = $this->getDefinitionForStructure($shape, $allNullable, $isObject, $extra);
48+
} elseif ($shape instanceof UnionShape) {
49+
[$definitions, $classNames] = $this->getDefinitionForUnion($shape, $allNullable, $isObject, $extra);
50+
} else {
51+
throw new \RuntimeException('Unsupported shape type: ' . \get_class($shape));
52+
}
53+
4554
if ($alternateClass) {
4655
$classNames[] = $shapeClassName;
56+
$definitions[] = $shapeClassName->getName();
57+
}
58+
59+
$body = '@param ' . implode('|', $definitions) . ' $input';
60+
61+
return [$body, array_unique($classNames, \SORT_REGULAR)];
62+
}
63+
64+
/**
65+
* Return php type information for the given shape.
66+
*
67+
* @return array{string, string, ClassName[]} [typeHint value, docblock representation, ClassName related]
68+
*/
69+
public function getPhpType(Shape $shape): array
70+
{
71+
$memberClassNames = [];
72+
if ($shape instanceof ObjectShape) {
73+
$memberClassNames[] = $className = $this->namespaceRegistry->getObject($shape);
74+
75+
return [$className->getFqdn(), $className->getName(), $memberClassNames];
76+
}
77+
78+
if ($shape instanceof ListShape) {
79+
$listMemberShape = $shape->getMember()->getShape();
80+
[$type, $doc, $memberClassNames] = $this->getPhpType($listMemberShape);
81+
if (str_ends_with($doc, '::*')) {
82+
$doc = "list<$doc>";
83+
} else {
84+
$doc .= '[]';
85+
}
86+
87+
return ['array', $doc, $memberClassNames];
4788
}
89+
90+
if ($shape instanceof MapShape) {
91+
$mapKeyShape = $shape->getKey()->getShape();
92+
$mapValueShape = $shape->getValue()->getShape();
93+
[$type, $doc, $memberClassNames] = $this->getPhpType($mapValueShape);
94+
if (!empty($mapKeyShape->getEnum())) {
95+
$memberClassNames[] = $memberClassName = $this->namespaceRegistry->getEnum($mapKeyShape);
96+
$doc = "array<{$memberClassName->getName()}::*, $doc>";
97+
} else {
98+
$doc = "array<string, $doc>";
99+
}
100+
101+
return ['array', $doc, $memberClassNames];
102+
}
103+
104+
if ($shape instanceof DocumentShape) {
105+
return ['bool|string|int|float|array|null', 'bool|string|int|float|list<mixed>|array<string, mixed>|null', []];
106+
}
107+
108+
$type = $doc = $this->getNativePhpType($shape->getType());
109+
if (!empty($shape->getEnum())) {
110+
$memberClassNames[] = $memberClassName = $this->namespaceRegistry->getEnum($shape);
111+
112+
$doc = $memberClassName->getName() . '::*';
113+
}
114+
115+
return [$type, $doc, $memberClassNames];
116+
}
117+
118+
private function getDefinitionForUnion(UnionShape $shape, bool $allNullable, bool $isObject, array $extra): array
119+
{
120+
$definitions = [];
121+
$classNames = [];
122+
foreach ($shape->getChildren() as $child) {
123+
[$childDefinitions, $childClassNames] = $this->getDefinitionForStructure($child, $allNullable, $isObject, $extra);
124+
$definitions = array_merge($definitions, $childDefinitions);
125+
$classNames = array_merge($classNames, $childClassNames);
126+
}
127+
128+
$definitions = array_unique($definitions);
129+
130+
return [$definitions, $classNames];
131+
}
132+
133+
private function getDefinitionForStructure(StructureShape $shape, bool $allNullable, bool $isObject, array $extra): array
134+
{
135+
$classNames = [];
48136
if (empty($shape->getMembers()) && empty($extra)) {
49137
// No input array
50-
return ['@param array' . ($alternateClass ? '|' . $shapeClassName->getName() : '') . ' $input', $classNames];
138+
return [['array'], $classNames];
51139
}
52140

53-
$body = ['@param array{'];
141+
$body = ['array{'];
54142
foreach ($shape->getMembers() as $member) {
55143
$nullable = !$member->isRequired();
56144
$memberShape = $member->getShape();
57145

58-
if ($memberShape instanceof StructureShape) {
146+
if ($memberShape instanceof ObjectShape) {
59147
$classNames[] = $className = $this->namespaceRegistry->getObject($memberShape);
60148
$param = $className->getName() . '|array';
61149
} elseif ($memberShape instanceof ListShape) {
62150
$listMemberShape = $memberShape->getMember()->getShape();
63151

64152
// is the list item an object?
65-
if ($listMemberShape instanceof StructureShape) {
153+
if ($listMemberShape instanceof ObjectShape) {
66154
$classNames[] = $className = $this->namespaceRegistry->getObject($listMemberShape);
67155
$param = 'array<' . $className->getName() . '|array>';
68156
} elseif (!empty($listMemberShape->getEnum())) {
@@ -75,7 +163,7 @@ public function generateDocblock(StructureShape $shape, ClassName $shapeClassNam
75163
$mapValueShape = $memberShape->getValue()->getShape();
76164

77165
// is the map item an object?
78-
if ($mapValueShape instanceof StructureShape) {
166+
if ($mapValueShape instanceof ObjectShape) {
79167
$classNames[] = $className = $this->namespaceRegistry->getObject($mapValueShape);
80168
$param = $className->getName() . '|array';
81169
} elseif (!empty($mapValueShape->getEnum())) {
@@ -125,64 +213,11 @@ public function generateDocblock(StructureShape $shape, ClassName $shapeClassNam
125213
$body[] = \sprintf(' %s: %s,', $phpdocMemberName, $param);
126214
}
127215
}
128-
$body = array_merge($body, $extra);
129-
$body[] = '}' . ($alternateClass ? '|' . $shapeClassName->getName() : '') . ' $input';
130216

131-
return [implode("\n", $body), $classNames];
132-
}
133-
134-
/**
135-
* Return php type information for the given shape.
136-
*
137-
* @return array{string, string, ClassName[]} [typeHint value, docblock representation, ClassName related]
138-
*/
139-
public function getPhpType(Shape $shape): array
140-
{
141-
$memberClassNames = [];
142-
if ($shape instanceof StructureShape) {
143-
$memberClassNames[] = $className = $this->namespaceRegistry->getObject($shape);
144-
145-
return [$className->getFqdn(), $className->getName(), $memberClassNames];
146-
}
147-
148-
if ($shape instanceof ListShape) {
149-
$listMemberShape = $shape->getMember()->getShape();
150-
[$type, $doc, $memberClassNames] = $this->getPhpType($listMemberShape);
151-
if ('::*' === substr($doc, -3)) {
152-
$doc = "list<$doc>";
153-
} else {
154-
$doc .= '[]';
155-
}
156-
157-
return ['array', $doc, $memberClassNames];
158-
}
159-
160-
if ($shape instanceof MapShape) {
161-
$mapKeyShape = $shape->getKey()->getShape();
162-
$mapValueShape = $shape->getValue()->getShape();
163-
[$type, $doc, $memberClassNames] = $this->getPhpType($mapValueShape);
164-
if (!empty($mapKeyShape->getEnum())) {
165-
$memberClassNames[] = $memberClassName = $this->namespaceRegistry->getEnum($mapKeyShape);
166-
$doc = "array<{$memberClassName->getName()}::*, $doc>";
167-
} else {
168-
$doc = "array<string, $doc>";
169-
}
170-
171-
return ['array', $doc, $memberClassNames];
172-
}
173-
174-
if ($shape instanceof DocumentShape) {
175-
return ['bool|string|int|float|array|null', 'bool|string|int|float|list<mixed>|array<string, mixed>|null', []];
176-
}
177-
178-
$type = $doc = $this->getNativePhpType($shape->getType());
179-
if (!empty($shape->getEnum())) {
180-
$memberClassNames[] = $memberClassName = $this->namespaceRegistry->getEnum($shape);
181-
182-
$doc = $memberClassName->getName() . '::*';
183-
}
217+
$body = array_merge($body, $extra);
218+
$body[] = '}';
184219

185-
return [$type, $doc, $memberClassNames];
220+
return [[implode("\n", $body)], $classNames];
186221
}
187222

188223
private function getNativePhpType(string $parameterType): string

0 commit comments

Comments
 (0)