Skip to content

Commit 868ebbe

Browse files
authored
Validate value object required fields in the constructor (#1488)
* Refactor the code generating object properties * Refactor the generation of array getters Optional array fields still need to consider the property as nullable even though the getter will return an empty array as AWS does not treat absent lists the same than empty lists in input. * Add validation of required parameters in value object constructors * Remove checks for missing properties for value object request bodies This is already validated in the constructor now.
1 parent cb4d8bf commit 868ebbe

File tree

172 files changed

+2017
-911
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+2017
-911
lines changed

src/CodeGenerator/src/Generator/InputGenerator.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ private function inputClassRequestGetters(StructureShape $inputShape, ClassBuild
429429
if ($operation->hasBody()) {
430430
[$body['body'], $hasRequestBody, $overrideArgs] = $serializer->generateRequestBody($operation, $inputShape) + [null, null, []];
431431
if ($hasRequestBody) {
432-
[$returnType, $requestBody, $args] = $serializer->generateRequestBuilder($inputShape) + [null, null, []];
432+
[$returnType, $requestBody, $args] = $serializer->generateRequestBuilder($inputShape, true) + [null, null, []];
433433
if ('' === trim($requestBody)) {
434434
$body['body'] = '$body = "";';
435435
} else {

src/CodeGenerator/src/Generator/ObjectGenerator.php

+40-14
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function generate(StructureShape $shape, bool $forEndpoint = false): Clas
106106

107107
$serializer = $this->serializer->get($shape->getService());
108108
if ($this->isShapeUsedInput($shape)) {
109-
[$returnType, $requestBody, $args] = $serializer->generateRequestBuilder($shape) + [null, null, []];
109+
[$returnType, $requestBody, $args] = $serializer->generateRequestBuilder($shape, false) + [null, null, []];
110110
$method = $classBuilder->addMethod('requestBody')->setReturnType($returnType)->setBody($requestBody)->setPublic()->setComment('@internal');
111111
foreach ($args as $arg => $type) {
112112
$method->addParameter($arg)->setType($type);
@@ -191,36 +191,60 @@ private function namedConstructor(StructureShape $shape, ClassBuilder $classBuil
191191

192192
$constructor->addParameter('input')->setType('array');
193193

194+
// To throw an exception in an expression, we need to use a method to support PHP 7.x
195+
$needsThrowMethod = false;
196+
194197
$constructorBody = '';
195198
foreach ($shape->getMembers() as $member) {
196199
$memberShape = $member->getShape();
197200
if ($memberShape instanceof StructureShape) {
198201
$objectClass = $this->generate($memberShape);
199-
$constructorBody .= strtr('$this->PROPERTY = isset($input["NAME"]) ? CLASS::create($input["NAME"]) : null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
202+
$memberCode = strtr('CLASS::create($input["NAME"])', ['NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
200203
} elseif ($memberShape instanceof ListShape) {
201204
$listMemberShape = $memberShape->getMember()->getShape();
202205

203206
// Check if this is a list of objects
204207
if ($listMemberShape instanceof StructureShape) {
205208
$objectClass = $this->generate($listMemberShape);
206-
$constructorBody .= strtr('$this->PROPERTY = isset($input["NAME"]) ? array_map([CLASS::class, "create"], $input["NAME"]) : null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
209+
$memberCode = strtr('array_map([CLASS::class, "create"], $input["NAME"])', ['NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
207210
} else {
208-
$constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]);
211+
$memberCode = strtr('$input["NAME"]', ['NAME' => $member->getName()]);
209212
}
210213
} elseif ($memberShape instanceof MapShape) {
211214
$mapValueShape = $memberShape->getValue()->getShape();
212215

213216
if ($mapValueShape instanceof StructureShape) {
214217
$objectClass = $this->generate($mapValueShape);
215-
$constructorBody .= strtr('$this->PROPERTY = isset($input["NAME"]) ? array_map([CLASS::class, "create"], $input["NAME"]) : null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
218+
$memberCode = strtr('array_map([CLASS::class, "create"], $input["NAME"])', ['NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
216219
} else {
217-
$constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]);
220+
$memberCode = strtr('$input["NAME"]', ['NAME' => $member->getName()]);
218221
}
219222
} else {
220-
$constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]);
223+
$memberCode = strtr('$input["NAME"]', ['NAME' => $member->getName()]);
224+
}
225+
if ($member->isRequired()) {
226+
$fallback = strtr('$this->throwException(new InvalidArgument(\'Missing required field "NAME".\'))', ['NAME' => $member->getName()]);
227+
$classBuilder->addUse(InvalidArgument::class);
228+
$needsThrowMethod = true;
229+
} else {
230+
$fallback = 'null';
221231
}
232+
$constructorBody .= strtr('$this->PROPERTY = isset($input["NAME"]) ? MEMBER_CODE : FALLBACK;' . "\n", [
233+
'PROPERTY' => GeneratorHelper::normalizeName($member->getName()),
234+
'NAME' => $member->getName(),
235+
'MEMBER_CODE' => $memberCode,
236+
'FALLBACK' => $fallback,
237+
]);
222238
}
223239
$constructor->setBody($constructorBody);
240+
241+
if ($needsThrowMethod) {
242+
$throwMethod = $classBuilder->addMethod('throwException');
243+
$throwMethod->setPrivate();
244+
$throwMethod->addComment('@return never');
245+
$throwMethod->addParameter('exception')->setType(\Throwable::class);
246+
$throwMethod->setBody('throw $exception;');
247+
}
224248
}
225249

226250
/**
@@ -249,12 +273,12 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
249273
$enumClassName = $this->enumGenerator->generate($memberShape);
250274
$classBuilder->addUse($enumClassName->getFqdn());
251275
}
252-
$getterSetterNullable = true;
276+
$getterSetterNullable = null;
253277

254278
if ($memberShape instanceof StructureShape) {
255279
$this->generate($memberShape);
256280
} elseif ($memberShape instanceof MapShape) {
257-
$nullable = $getterSetterNullable = false;
281+
$getterSetterNullable = false;
258282
$mapKeyShape = $memberShape->getKey()->getShape();
259283
if ('string' !== $mapKeyShape->getType()) {
260284
throw new \RuntimeException('Complex maps are not supported');
@@ -271,7 +295,7 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
271295
$classBuilder->addUse($enumClassName->getFqdn());
272296
}
273297
} elseif ($memberShape instanceof ListShape) {
274-
$nullable = $getterSetterNullable = false;
298+
$getterSetterNullable = false;
275299
$memberShape->getMember()->getShape();
276300

277301
if (($memberShape = $memberShape->getMember()->getShape()) instanceof StructureShape) {
@@ -297,7 +321,10 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
297321
$deprecation = strtr('@trigger_error(\sprintf(\'The property "NAME" of "%s" is deprecated by AWS.\', __CLASS__), E_USER_DEPRECATED);', ['NAME' => $member->getName()]);
298322
}
299323

300-
if ($getterSetterNullable) {
324+
$nullable = $nullable ?? !$member->isRequired();
325+
$getterSetterNullable = $getterSetterNullable ?? $nullable;
326+
327+
if ($getterSetterNullable || !$nullable) {
301328
$method->setBody($deprecation . strtr('
302329
return $this->PROPERTY;
303330
', [
@@ -311,11 +338,10 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
311338
]));
312339
}
313340

314-
$nullable = $nullable ?? !$member->isRequired();
315341
if ($parameterType && $parameterType !== $returnType && (empty($memberClassNames) || $memberClassNames[0]->getName() !== $parameterType)) {
316-
$method->addComment('@return ' . $parameterType . ($nullable ? '|null' : ''));
342+
$method->addComment('@return ' . $parameterType . ($getterSetterNullable ? '|null' : ''));
317343
}
318-
$method->setReturnNullable($nullable);
344+
$method->setReturnNullable($getterSetterNullable);
319345
}
320346

321347
foreach ($forEndpointProps as $key => $ok) {

src/CodeGenerator/src/Generator/RequestSerializer/QuerySerializer.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
6969
]), true];
7070
}
7171

72-
public function generateRequestBuilder(StructureShape $shape): array
72+
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array
7373
{
74-
$body = implode("\n", array_map(function (StructureMember $member) {
74+
$body = implode("\n", array_map(function (StructureMember $member) use ($needsChecks) {
7575
if (null !== $member->getLocation()) {
7676
return '';
7777
}
@@ -84,10 +84,15 @@ public function generateRequestBuilder(StructureShape $shape): array
8484
MEMBER_CODE';
8585
$inputElement = '$v';
8686
} elseif ($member->isRequired()) {
87-
$body = 'if (null === $v = $this->PROPERTY) {
88-
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
87+
if ($needsChecks) {
88+
$body = 'if (null === $v = $this->PROPERTY) {
89+
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
90+
}
91+
MEMBER_CODE';
92+
} else {
93+
$body = '$v = $this->PROPERTY;
94+
MEMBER_CODE';
8995
}
90-
MEMBER_CODE';
9196
$inputElement = '$v';
9297
} else {
9398
$body = 'if (null !== $v = $this->PROPERTY) {

src/CodeGenerator/src/Generator/RequestSerializer/RestJsonSerializer.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
6868
return ['$bodyPayload = $this->requestBody(); $body = empty($bodyPayload) ? "{}" : \json_encode($bodyPayload, ' . \JSON_THROW_ON_ERROR . ');', true];
6969
}
7070

71-
public function generateRequestBuilder(StructureShape $shape): array
71+
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array
7272
{
73-
$body = implode("\n", array_map(function (StructureMember $member) {
73+
$body = implode("\n", array_map(function (StructureMember $member) use ($needsChecks) {
7474
if (null !== $member->getLocation()) {
7575
return '';
7676
}
@@ -83,10 +83,15 @@ public function generateRequestBuilder(StructureShape $shape): array
8383
MEMBER_CODE';
8484
$inputElement = '$v';
8585
} elseif ($member->isRequired()) {
86-
$body = 'if (null === $v = $this->PROPERTY) {
87-
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
86+
if ($needsChecks) {
87+
$body = 'if (null === $v = $this->PROPERTY) {
88+
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
89+
}
90+
MEMBER_CODE';
91+
} else {
92+
$body = '$v = $this->PROPERTY;
93+
MEMBER_CODE';
8894
}
89-
MEMBER_CODE';
9095
$inputElement = '$v';
9196
} else {
9297
$body = 'if (null !== $v = $this->PROPERTY) {

src/CodeGenerator/src/Generator/RequestSerializer/RestXmlSerializer.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
9393
', true, ['node' => \DOMNode::class]];
9494
}
9595

96-
public function generateRequestBuilder(StructureShape $shape): array
96+
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array
9797
{
98-
$body = implode("\n", array_map(function (StructureMember $member) {
98+
$body = implode("\n", array_map(function (StructureMember $member) use ($needsChecks) {
9999
if (null !== $member->getLocation()) {
100100
return '';
101101
}
@@ -108,10 +108,15 @@ public function generateRequestBuilder(StructureShape $shape): array
108108
MEMBER_CODE';
109109
$inputElement = '$v';
110110
} elseif ($member->isRequired()) {
111-
$body = 'if (null === $v = $this->PROPERTY) {
112-
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
111+
if ($needsChecks) {
112+
$body = 'if (null === $v = $this->PROPERTY) {
113+
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
114+
}
115+
MEMBER_CODE';
116+
} else {
117+
$body = '$v = $this->PROPERTY;
118+
MEMBER_CODE';
113119
}
114-
MEMBER_CODE';
115120
$inputElement = '$v';
116121
} else {
117122
$body = 'if (null !== $v = $this->PROPERTY) {

src/CodeGenerator/src/Generator/RequestSerializer/Serializer.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
2929
*
3030
* @return array{0: string, 1: string, 2?: array<string, string>}
3131
*/
32-
public function generateRequestBuilder(StructureShape $shape): array;
32+
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array;
3333

3434
public function getHeaders(Operation $operation): string;
3535
}

src/Core/src/Sts/ValueObject/AssumedRoleUser.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace AsyncAws\Core\Sts\ValueObject;
44

5+
use AsyncAws\Core\Exception\InvalidArgument;
6+
57
/**
68
* The identifiers for the temporary security credentials that the operation returns.
79
*/
@@ -29,8 +31,8 @@ final class AssumedRoleUser
2931
*/
3032
public function __construct(array $input)
3133
{
32-
$this->assumedRoleId = $input['AssumedRoleId'] ?? null;
33-
$this->arn = $input['Arn'] ?? null;
34+
$this->assumedRoleId = $input['AssumedRoleId'] ?? $this->throwException(new InvalidArgument('Missing required field "AssumedRoleId".'));
35+
$this->arn = $input['Arn'] ?? $this->throwException(new InvalidArgument('Missing required field "Arn".'));
3436
}
3537

3638
/**
@@ -53,4 +55,12 @@ public function getAssumedRoleId(): string
5355
{
5456
return $this->assumedRoleId;
5557
}
58+
59+
/**
60+
* @return never
61+
*/
62+
private function throwException(\Throwable $exception)
63+
{
64+
throw $exception;
65+
}
5666
}

src/Core/src/Sts/ValueObject/Credentials.php

+14-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace AsyncAws\Core\Sts\ValueObject;
44

5+
use AsyncAws\Core\Exception\InvalidArgument;
6+
57
/**
68
* Amazon Web Services credentials for API authentication.
79
*/
@@ -37,10 +39,10 @@ final class Credentials
3739
*/
3840
public function __construct(array $input)
3941
{
40-
$this->accessKeyId = $input['AccessKeyId'] ?? null;
41-
$this->secretAccessKey = $input['SecretAccessKey'] ?? null;
42-
$this->sessionToken = $input['SessionToken'] ?? null;
43-
$this->expiration = $input['Expiration'] ?? null;
42+
$this->accessKeyId = $input['AccessKeyId'] ?? $this->throwException(new InvalidArgument('Missing required field "AccessKeyId".'));
43+
$this->secretAccessKey = $input['SecretAccessKey'] ?? $this->throwException(new InvalidArgument('Missing required field "SecretAccessKey".'));
44+
$this->sessionToken = $input['SessionToken'] ?? $this->throwException(new InvalidArgument('Missing required field "SessionToken".'));
45+
$this->expiration = $input['Expiration'] ?? $this->throwException(new InvalidArgument('Missing required field "Expiration".'));
4446
}
4547

4648
/**
@@ -75,4 +77,12 @@ public function getSessionToken(): string
7577
{
7678
return $this->sessionToken;
7779
}
80+
81+
/**
82+
* @return never
83+
*/
84+
private function throwException(\Throwable $exception)
85+
{
86+
throw $exception;
87+
}
7888
}

src/Core/src/Sts/ValueObject/Tag.php

+12-8
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ final class Tag
4141
*/
4242
public function __construct(array $input)
4343
{
44-
$this->key = $input['Key'] ?? null;
45-
$this->value = $input['Value'] ?? null;
44+
$this->key = $input['Key'] ?? $this->throwException(new InvalidArgument('Missing required field "Key".'));
45+
$this->value = $input['Value'] ?? $this->throwException(new InvalidArgument('Missing required field "Value".'));
4646
}
4747

4848
/**
@@ -72,15 +72,19 @@ public function getValue(): string
7272
public function requestBody(): array
7373
{
7474
$payload = [];
75-
if (null === $v = $this->key) {
76-
throw new InvalidArgument(sprintf('Missing parameter "Key" for "%s". The value cannot be null.', __CLASS__));
77-
}
75+
$v = $this->key;
7876
$payload['Key'] = $v;
79-
if (null === $v = $this->value) {
80-
throw new InvalidArgument(sprintf('Missing parameter "Value" for "%s". The value cannot be null.', __CLASS__));
81-
}
77+
$v = $this->value;
8278
$payload['Value'] = $v;
8379

8480
return $payload;
8581
}
82+
83+
/**
84+
* @return never
85+
*/
86+
private function throwException(\Throwable $exception)
87+
{
88+
throw $exception;
89+
}
8690
}

src/Service/AppSync/src/ValueObject/AppSyncRuntime.php

+12-8
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ final class AppSyncRuntime
3030
*/
3131
public function __construct(array $input)
3232
{
33-
$this->name = $input['name'] ?? null;
34-
$this->runtimeVersion = $input['runtimeVersion'] ?? null;
33+
$this->name = $input['name'] ?? $this->throwException(new InvalidArgument('Missing required field "name".'));
34+
$this->runtimeVersion = $input['runtimeVersion'] ?? $this->throwException(new InvalidArgument('Missing required field "runtimeVersion".'));
3535
}
3636

3737
/**
@@ -64,18 +64,22 @@ public function getRuntimeVersion(): string
6464
public function requestBody(): array
6565
{
6666
$payload = [];
67-
if (null === $v = $this->name) {
68-
throw new InvalidArgument(sprintf('Missing parameter "name" for "%s". The value cannot be null.', __CLASS__));
69-
}
67+
$v = $this->name;
7068
if (!RuntimeName::exists($v)) {
7169
throw new InvalidArgument(sprintf('Invalid parameter "name" for "%s". The value "%s" is not a valid "RuntimeName".', __CLASS__, $v));
7270
}
7371
$payload['name'] = $v;
74-
if (null === $v = $this->runtimeVersion) {
75-
throw new InvalidArgument(sprintf('Missing parameter "runtimeVersion" for "%s". The value cannot be null.', __CLASS__));
76-
}
72+
$v = $this->runtimeVersion;
7773
$payload['runtimeVersion'] = $v;
7874

7975
return $payload;
8076
}
77+
78+
/**
79+
* @return never
80+
*/
81+
private function throwException(\Throwable $exception)
82+
{
83+
throw $exception;
84+
}
8185
}

0 commit comments

Comments
 (0)