From 70ea1cc00d4cbe4dc51727323106c11f8afe89f4 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 15 Jul 2025 13:43:00 +0200 Subject: [PATCH] Add support for document shapes in the code generator Such shape accept a raw JSON value. It is supported only in the JSON format. --- src/CodeGenerator/src/Definition/DocumentShape.php | 12 ++++++++++++ src/CodeGenerator/src/Definition/Shape.php | 8 +++++++- .../Generator/CodeGenerator/PopulatorGenerator.php | 3 +++ .../src/Generator/CodeGenerator/TypeGenerator.php | 7 +++++++ src/CodeGenerator/src/Generator/InputGenerator.php | 3 +++ src/CodeGenerator/src/Generator/ObjectGenerator.php | 3 +++ .../Generator/RequestSerializer/QuerySerializer.php | 3 +++ .../RequestSerializer/RestJsonSerializer.php | 3 +++ .../RequestSerializer/RestXmlSerializer.php | 3 +++ .../src/Generator/ResponseParser/RestJsonParser.php | 12 ++++++++++++ .../src/Generator/ResponseParser/RestXmlParser.php | 3 +++ src/CodeGenerator/src/Generator/TestGenerator.php | 3 +++ 12 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/CodeGenerator/src/Definition/DocumentShape.php diff --git a/src/CodeGenerator/src/Definition/DocumentShape.php b/src/CodeGenerator/src/Definition/DocumentShape.php new file mode 100644 index 000000000..d7eb72658 --- /dev/null +++ b/src/CodeGenerator/src/Definition/DocumentShape.php @@ -0,0 +1,12 @@ +isStreaming()) { $returnType = ResultStream::class; $parameterType = 'ResultStream'; diff --git a/src/CodeGenerator/src/Generator/CodeGenerator/TypeGenerator.php b/src/CodeGenerator/src/Generator/CodeGenerator/TypeGenerator.php index f04c9096e..3fefde200 100644 --- a/src/CodeGenerator/src/Generator/CodeGenerator/TypeGenerator.php +++ b/src/CodeGenerator/src/Generator/CodeGenerator/TypeGenerator.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator\CodeGenerator; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Shape; @@ -90,6 +91,8 @@ public function generateDocblock(StructureShape $shape, ClassName $shapeClassNam } else { $param = 'array'; } + } elseif ($memberShape instanceof DocumentShape) { + $param = 'bool|string|int|float|null|list|array'; } elseif ($member->isStreaming()) { $param = 'string|resource|(callable(int): string)|iterable'; } elseif ('timestamp' === $param = $memberShape->getType()) { @@ -168,6 +171,10 @@ public function getPhpType(Shape $shape): array return ['array', $doc, $memberClassNames]; } + if ($shape instanceof DocumentShape) { + return ['bool|string|int|float|null|array', 'bool|string|int|float|null|list|array', []]; + } + $type = $doc = $this->getNativePhpType($shape->getType()); if (!empty($shape->getEnum())) { $memberClassNames[] = $memberClassName = $this->namespaceRegistry->getEnum($shape); diff --git a/src/CodeGenerator/src/Generator/InputGenerator.php b/src/CodeGenerator/src/Generator/InputGenerator.php index 805e9c663..b77a96dff 100644 --- a/src/CodeGenerator/src/Generator/InputGenerator.php +++ b/src/CodeGenerator/src/Generator/InputGenerator.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Member; @@ -210,6 +211,8 @@ public function generate(Operation $operation): ClassName // It is a scalar, like a string $constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]); } + } elseif ($memberShape instanceof DocumentShape) { + $getterSetterNullable = false; // The type itself is already nullable } elseif ($member->isStreaming()) { $parameterType = 'string|resource|(callable(int): string)|iterable'; $returnType = null; diff --git a/src/CodeGenerator/src/Generator/ObjectGenerator.php b/src/CodeGenerator/src/Generator/ObjectGenerator.php index 1237e9372..6126e8ff7 100644 --- a/src/CodeGenerator/src/Generator/ObjectGenerator.php +++ b/src/CodeGenerator/src/Generator/ObjectGenerator.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Shape; @@ -307,6 +308,8 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder $enumClassName = $this->enumGenerator->generate($memberShape); $classBuilder->addUse($enumClassName->getFqdn()); } + } elseif ($memberShape instanceof DocumentShape) { + $nullable = false; } elseif ($member->isStreaming()) { $returnType = ResultStream::class; $parameterType = ResultStream::class; diff --git a/src/CodeGenerator/src/Generator/RequestSerializer/QuerySerializer.php b/src/CodeGenerator/src/Generator/RequestSerializer/QuerySerializer.php index 148040040..25408562f 100644 --- a/src/CodeGenerator/src/Generator/RequestSerializer/QuerySerializer.php +++ b/src/CodeGenerator/src/Generator/RequestSerializer/QuerySerializer.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator\RequestSerializer; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Member; @@ -183,6 +184,8 @@ private function dumpArrayElement(string $output, string $input, string $context return $this->dumpArrayList($output, $input, $contextProperty, $shape); case $shape instanceof MapShape: return $this->dumpArrayMap($output, $input, $contextProperty, $shape); + case $shape instanceof DocumentShape: + throw new \LogicException('Document shapes are not supported in the query serializer for now.'); } switch ($shape->getType()) { diff --git a/src/CodeGenerator/src/Generator/RequestSerializer/RestJsonSerializer.php b/src/CodeGenerator/src/Generator/RequestSerializer/RestJsonSerializer.php index 780e830b5..7bbaa38c6 100644 --- a/src/CodeGenerator/src/Generator/RequestSerializer/RestJsonSerializer.php +++ b/src/CodeGenerator/src/Generator/RequestSerializer/RestJsonSerializer.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator\RequestSerializer; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Member; @@ -180,6 +181,8 @@ private function dumpArrayElement(string $output, string $input, string $context return $this->dumpArrayList($output, $input, $contextProperty, $shape); case $shape instanceof MapShape: return $this->dumpArrayMap($output, $input, $contextProperty, $shape); + case $shape instanceof DocumentShape: + return $this->dumpArrayScalar($output, $input, $contextProperty, $shape); } switch ($shape->getType()) { diff --git a/src/CodeGenerator/src/Generator/RequestSerializer/RestXmlSerializer.php b/src/CodeGenerator/src/Generator/RequestSerializer/RestXmlSerializer.php index 4e6596c64..e5f5bd407 100644 --- a/src/CodeGenerator/src/Generator/RequestSerializer/RestXmlSerializer.php +++ b/src/CodeGenerator/src/Generator/RequestSerializer/RestXmlSerializer.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator\RequestSerializer; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\Member; use AsyncAws\CodeGenerator\Definition\Operation; @@ -158,6 +159,8 @@ private function dumpXmlShape(Member $member, Shape $shape, string $output, stri return $this->dumpXmlShapeStructure($member, $shape, $output, $input); case $shape instanceof ListShape: return $this->dumpXmlShapeList($member, $shape, $output, $input); + case $shape instanceof DocumentShape: + throw new \LogicException('Document shapes are not supported in the XML serializer for now.'); } switch ($shape->getType()) { diff --git a/src/CodeGenerator/src/Generator/ResponseParser/RestJsonParser.php b/src/CodeGenerator/src/Generator/ResponseParser/RestJsonParser.php index d82a7cde7..2cbedeb13 100644 --- a/src/CodeGenerator/src/Generator/ResponseParser/RestJsonParser.php +++ b/src/CodeGenerator/src/Generator/ResponseParser/RestJsonParser.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator\ResponseParser; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Member; @@ -193,6 +194,8 @@ private function parseElement(string $input, Shape $shape, bool $required, bool return $this->parseResponseStructure($shape, $input, $required); case $shape instanceof MapShape: return $this->parseResponseMap($shape, $input, $required, $inObject); + case $shape instanceof DocumentShape: + return $this->parseResponseDocument($input, $required); } switch ($shape->getType()) { @@ -296,6 +299,15 @@ private function parseResponseBlob(string $input, bool $required): string return strtr('isset(INPUT) ? base64_decode((string) INPUT) : null', ['INPUT' => $input]); } + private function parseResponseDocument(string $input, bool $required): string + { + if ($required) { + return strtr('INPUT', ['INPUT' => $input]); + } + + return strtr('INPUT ?? null', ['INPUT' => $input]); + } + private function parseResponseList(ListShape $shape, string $input, bool $required, bool $inObject): string { $shapeMember = $shape->getMember(); diff --git a/src/CodeGenerator/src/Generator/ResponseParser/RestXmlParser.php b/src/CodeGenerator/src/Generator/ResponseParser/RestXmlParser.php index 5b284e381..a8da5db0c 100644 --- a/src/CodeGenerator/src/Generator/ResponseParser/RestXmlParser.php +++ b/src/CodeGenerator/src/Generator/ResponseParser/RestXmlParser.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator\ResponseParser; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Member; @@ -177,6 +178,8 @@ private function parseXmlElement(string $input, Shape $shape, bool $required, bo return $this->parseXmlResponseStructure($shape, $input, $required); case $shape instanceof MapShape: return $this->parseXmlResponseMap($shape, $input, $required, $inObject); + case $shape instanceof DocumentShape: + throw new \LogicException('Document shapes are not supported in the XML parser for now.'); } switch ($shape->getType()) { diff --git a/src/CodeGenerator/src/Generator/TestGenerator.php b/src/CodeGenerator/src/Generator/TestGenerator.php index 1c30e14c0..8acb916ca 100644 --- a/src/CodeGenerator/src/Generator/TestGenerator.php +++ b/src/CodeGenerator/src/Generator/TestGenerator.php @@ -4,6 +4,7 @@ namespace AsyncAws\CodeGenerator\Generator; +use AsyncAws\CodeGenerator\Definition\DocumentShape; use AsyncAws\CodeGenerator\Definition\ListShape; use AsyncAws\CodeGenerator\Definition\MapShape; use AsyncAws\CodeGenerator\Definition\Operation; @@ -292,6 +293,8 @@ private function getInputCode(ClassBuilder $classBuilder, Shape $shape, bool $in return strtr('["change me" => INPUT_ARGUMENTS]', [ 'INPUT_ARGUMENTS' => $this->getInputCode($classBuilder, $shape->getValue()->getShape(), $includeOptionalParameters, $recursion), ]); + case $shape instanceof DocumentShape: + return '"change me"'; } switch ($shape->getType()) {