Skip to content

Validate value object required fields in the constructor #1488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/CodeGenerator/src/Generator/InputGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ private function inputClassRequestGetters(StructureShape $inputShape, ClassBuild
if ($operation->hasBody()) {
[$body['body'], $hasRequestBody, $overrideArgs] = $serializer->generateRequestBody($operation, $inputShape) + [null, null, []];
if ($hasRequestBody) {
[$returnType, $requestBody, $args] = $serializer->generateRequestBuilder($inputShape) + [null, null, []];
[$returnType, $requestBody, $args] = $serializer->generateRequestBuilder($inputShape, true) + [null, null, []];
if ('' === trim($requestBody)) {
$body['body'] = '$body = "";';
} else {
Expand Down
54 changes: 40 additions & 14 deletions src/CodeGenerator/src/Generator/ObjectGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function generate(StructureShape $shape, bool $forEndpoint = false): Clas

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

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

// To throw an exception in an expression, we need to use a method to support PHP 7.x
$needsThrowMethod = false;

$constructorBody = '';
foreach ($shape->getMembers() as $member) {
$memberShape = $member->getShape();
if ($memberShape instanceof StructureShape) {
$objectClass = $this->generate($memberShape);
$constructorBody .= strtr('$this->PROPERTY = isset($input["NAME"]) ? CLASS::create($input["NAME"]) : null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
$memberCode = strtr('CLASS::create($input["NAME"])', ['NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
} elseif ($memberShape instanceof ListShape) {
$listMemberShape = $memberShape->getMember()->getShape();

// Check if this is a list of objects
if ($listMemberShape instanceof StructureShape) {
$objectClass = $this->generate($listMemberShape);
$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()]);
$memberCode = strtr('array_map([CLASS::class, "create"], $input["NAME"])', ['NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
} else {
$constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]);
$memberCode = strtr('$input["NAME"]', ['NAME' => $member->getName()]);
}
} elseif ($memberShape instanceof MapShape) {
$mapValueShape = $memberShape->getValue()->getShape();

if ($mapValueShape instanceof StructureShape) {
$objectClass = $this->generate($mapValueShape);
$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()]);
$memberCode = strtr('array_map([CLASS::class, "create"], $input["NAME"])', ['NAME' => $member->getName(), 'CLASS' => $objectClass->getName()]);
} else {
$constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]);
$memberCode = strtr('$input["NAME"]', ['NAME' => $member->getName()]);
}
} else {
$constructorBody .= strtr('$this->PROPERTY = $input["NAME"] ?? null;' . "\n", ['PROPERTY' => GeneratorHelper::normalizeName($member->getName()), 'NAME' => $member->getName()]);
$memberCode = strtr('$input["NAME"]', ['NAME' => $member->getName()]);
}
if ($member->isRequired()) {
$fallback = strtr('$this->throwException(new InvalidArgument(\'Missing required field "NAME".\'))', ['NAME' => $member->getName()]);
$classBuilder->addUse(InvalidArgument::class);
$needsThrowMethod = true;
} else {
$fallback = 'null';
}
$constructorBody .= strtr('$this->PROPERTY = isset($input["NAME"]) ? MEMBER_CODE : FALLBACK;' . "\n", [
'PROPERTY' => GeneratorHelper::normalizeName($member->getName()),
'NAME' => $member->getName(),
'MEMBER_CODE' => $memberCode,
'FALLBACK' => $fallback,
]);
}
$constructor->setBody($constructorBody);

if ($needsThrowMethod) {
$throwMethod = $classBuilder->addMethod('throwException');
$throwMethod->setPrivate();
$throwMethod->addComment('@return never');
$throwMethod->addParameter('exception')->setType(\Throwable::class);
$throwMethod->setBody('throw $exception;');
}
}

/**
Expand Down Expand Up @@ -249,12 +273,12 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
$enumClassName = $this->enumGenerator->generate($memberShape);
$classBuilder->addUse($enumClassName->getFqdn());
}
$getterSetterNullable = true;
$getterSetterNullable = null;

if ($memberShape instanceof StructureShape) {
$this->generate($memberShape);
} elseif ($memberShape instanceof MapShape) {
$nullable = $getterSetterNullable = false;
$getterSetterNullable = false;
$mapKeyShape = $memberShape->getKey()->getShape();
if ('string' !== $mapKeyShape->getType()) {
throw new \RuntimeException('Complex maps are not supported');
Expand All @@ -271,7 +295,7 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
$classBuilder->addUse($enumClassName->getFqdn());
}
} elseif ($memberShape instanceof ListShape) {
$nullable = $getterSetterNullable = false;
$getterSetterNullable = false;
$memberShape->getMember()->getShape();

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

if ($getterSetterNullable) {
$nullable = $nullable ?? !$member->isRequired();
$getterSetterNullable = $getterSetterNullable ?? $nullable;

if ($getterSetterNullable || !$nullable) {
$method->setBody($deprecation . strtr('
return $this->PROPERTY;
', [
Expand All @@ -311,11 +338,10 @@ private function addProperties(StructureShape $shape, ClassBuilder $classBuilder
]));
}

$nullable = $nullable ?? !$member->isRequired();
if ($parameterType && $parameterType !== $returnType && (empty($memberClassNames) || $memberClassNames[0]->getName() !== $parameterType)) {
$method->addComment('@return ' . $parameterType . ($nullable ? '|null' : ''));
$method->addComment('@return ' . $parameterType . ($getterSetterNullable ? '|null' : ''));
}
$method->setReturnNullable($nullable);
$method->setReturnNullable($getterSetterNullable);
}

foreach ($forEndpointProps as $key => $ok) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
]), true];
}

public function generateRequestBuilder(StructureShape $shape): array
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array
{
$body = implode("\n", array_map(function (StructureMember $member) {
$body = implode("\n", array_map(function (StructureMember $member) use ($needsChecks) {
if (null !== $member->getLocation()) {
return '';
}
Expand All @@ -84,10 +84,15 @@ public function generateRequestBuilder(StructureShape $shape): array
MEMBER_CODE';
$inputElement = '$v';
} elseif ($member->isRequired()) {
$body = 'if (null === $v = $this->PROPERTY) {
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
if ($needsChecks) {
$body = 'if (null === $v = $this->PROPERTY) {
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
}
MEMBER_CODE';
} else {
$body = '$v = $this->PROPERTY;
MEMBER_CODE';
}
MEMBER_CODE';
$inputElement = '$v';
} else {
$body = 'if (null !== $v = $this->PROPERTY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
return ['$bodyPayload = $this->requestBody(); $body = empty($bodyPayload) ? "{}" : \json_encode($bodyPayload, ' . \JSON_THROW_ON_ERROR . ');', true];
}

public function generateRequestBuilder(StructureShape $shape): array
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array
{
$body = implode("\n", array_map(function (StructureMember $member) {
$body = implode("\n", array_map(function (StructureMember $member) use ($needsChecks) {
if (null !== $member->getLocation()) {
return '';
}
Expand All @@ -83,10 +83,15 @@ public function generateRequestBuilder(StructureShape $shape): array
MEMBER_CODE';
$inputElement = '$v';
} elseif ($member->isRequired()) {
$body = 'if (null === $v = $this->PROPERTY) {
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
if ($needsChecks) {
$body = 'if (null === $v = $this->PROPERTY) {
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
}
MEMBER_CODE';
} else {
$body = '$v = $this->PROPERTY;
MEMBER_CODE';
}
MEMBER_CODE';
$inputElement = '$v';
} else {
$body = 'if (null !== $v = $this->PROPERTY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
', true, ['node' => \DOMNode::class]];
}

public function generateRequestBuilder(StructureShape $shape): array
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array
{
$body = implode("\n", array_map(function (StructureMember $member) {
$body = implode("\n", array_map(function (StructureMember $member) use ($needsChecks) {
if (null !== $member->getLocation()) {
return '';
}
Expand All @@ -108,10 +108,15 @@ public function generateRequestBuilder(StructureShape $shape): array
MEMBER_CODE';
$inputElement = '$v';
} elseif ($member->isRequired()) {
$body = 'if (null === $v = $this->PROPERTY) {
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
if ($needsChecks) {
$body = 'if (null === $v = $this->PROPERTY) {
throw new InvalidArgument(sprintf(\'Missing parameter "NAME" for "%s". The value cannot be null.\', __CLASS__));
}
MEMBER_CODE';
} else {
$body = '$v = $this->PROPERTY;
MEMBER_CODE';
}
MEMBER_CODE';
$inputElement = '$v';
} else {
$body = 'if (null !== $v = $this->PROPERTY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function generateRequestBody(Operation $operation, StructureShape $shape)
*
* @return array{0: string, 1: string, 2?: array<string, string>}
*/
public function generateRequestBuilder(StructureShape $shape): array;
public function generateRequestBuilder(StructureShape $shape, bool $needsChecks): array;

public function getHeaders(Operation $operation): string;
}
14 changes: 12 additions & 2 deletions src/Core/src/Sts/ValueObject/AssumedRoleUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace AsyncAws\Core\Sts\ValueObject;

use AsyncAws\Core\Exception\InvalidArgument;

/**
* The identifiers for the temporary security credentials that the operation returns.
*/
Expand Down Expand Up @@ -29,8 +31,8 @@ final class AssumedRoleUser
*/
public function __construct(array $input)
{
$this->assumedRoleId = $input['AssumedRoleId'] ?? null;
$this->arn = $input['Arn'] ?? null;
$this->assumedRoleId = $input['AssumedRoleId'] ?? $this->throwException(new InvalidArgument('Missing required field "AssumedRoleId".'));
$this->arn = $input['Arn'] ?? $this->throwException(new InvalidArgument('Missing required field "Arn".'));
}

/**
Expand All @@ -53,4 +55,12 @@ public function getAssumedRoleId(): string
{
return $this->assumedRoleId;
}

/**
* @return never
*/
private function throwException(\Throwable $exception)
{
throw $exception;
}
}
18 changes: 14 additions & 4 deletions src/Core/src/Sts/ValueObject/Credentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace AsyncAws\Core\Sts\ValueObject;

use AsyncAws\Core\Exception\InvalidArgument;

/**
* Amazon Web Services credentials for API authentication.
*/
Expand Down Expand Up @@ -37,10 +39,10 @@ final class Credentials
*/
public function __construct(array $input)
{
$this->accessKeyId = $input['AccessKeyId'] ?? null;
$this->secretAccessKey = $input['SecretAccessKey'] ?? null;
$this->sessionToken = $input['SessionToken'] ?? null;
$this->expiration = $input['Expiration'] ?? null;
$this->accessKeyId = $input['AccessKeyId'] ?? $this->throwException(new InvalidArgument('Missing required field "AccessKeyId".'));
$this->secretAccessKey = $input['SecretAccessKey'] ?? $this->throwException(new InvalidArgument('Missing required field "SecretAccessKey".'));
$this->sessionToken = $input['SessionToken'] ?? $this->throwException(new InvalidArgument('Missing required field "SessionToken".'));
$this->expiration = $input['Expiration'] ?? $this->throwException(new InvalidArgument('Missing required field "Expiration".'));
}

/**
Expand Down Expand Up @@ -75,4 +77,12 @@ public function getSessionToken(): string
{
return $this->sessionToken;
}

/**
* @return never
*/
private function throwException(\Throwable $exception)
{
throw $exception;
}
}
20 changes: 12 additions & 8 deletions src/Core/src/Sts/ValueObject/Tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ final class Tag
*/
public function __construct(array $input)
{
$this->key = $input['Key'] ?? null;
$this->value = $input['Value'] ?? null;
$this->key = $input['Key'] ?? $this->throwException(new InvalidArgument('Missing required field "Key".'));
$this->value = $input['Value'] ?? $this->throwException(new InvalidArgument('Missing required field "Value".'));
}

/**
Expand Down Expand Up @@ -72,15 +72,19 @@ public function getValue(): string
public function requestBody(): array
{
$payload = [];
if (null === $v = $this->key) {
throw new InvalidArgument(sprintf('Missing parameter "Key" for "%s". The value cannot be null.', __CLASS__));
}
$v = $this->key;
$payload['Key'] = $v;
if (null === $v = $this->value) {
throw new InvalidArgument(sprintf('Missing parameter "Value" for "%s". The value cannot be null.', __CLASS__));
}
$v = $this->value;
$payload['Value'] = $v;

return $payload;
}

/**
* @return never
*/
private function throwException(\Throwable $exception)
{
throw $exception;
}
}
20 changes: 12 additions & 8 deletions src/Service/AppSync/src/ValueObject/AppSyncRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ final class AppSyncRuntime
*/
public function __construct(array $input)
{
$this->name = $input['name'] ?? null;
$this->runtimeVersion = $input['runtimeVersion'] ?? null;
$this->name = $input['name'] ?? $this->throwException(new InvalidArgument('Missing required field "name".'));
$this->runtimeVersion = $input['runtimeVersion'] ?? $this->throwException(new InvalidArgument('Missing required field "runtimeVersion".'));
}

/**
Expand Down Expand Up @@ -64,18 +64,22 @@ public function getRuntimeVersion(): string
public function requestBody(): array
{
$payload = [];
if (null === $v = $this->name) {
throw new InvalidArgument(sprintf('Missing parameter "name" for "%s". The value cannot be null.', __CLASS__));
}
$v = $this->name;
if (!RuntimeName::exists($v)) {
throw new InvalidArgument(sprintf('Invalid parameter "name" for "%s". The value "%s" is not a valid "RuntimeName".', __CLASS__, $v));
}
$payload['name'] = $v;
if (null === $v = $this->runtimeVersion) {
throw new InvalidArgument(sprintf('Missing parameter "runtimeVersion" for "%s". The value cannot be null.', __CLASS__));
}
$v = $this->runtimeVersion;
$payload['runtimeVersion'] = $v;

return $payload;
}

/**
* @return never
*/
private function throwException(\Throwable $exception)
{
throw $exception;
}
}
Loading