Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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/Command/Api/ApiBaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ private function castParamType(array $paramSpec, array|string|bool|int $value):
if (in_array('integer', $types, true) && ctype_digit($value)) {
return $this->doCastParamType('integer', $value);
}
} elseif ($paramSpec['type'] === 'array') {
} elseif ($this->getParamType($paramSpec) === 'array') {
if (is_array($value) && count($value) === 1) {
return $this->castParamToArray($paramSpec, $value[0]);
}
Expand Down
50 changes: 42 additions & 8 deletions src/Command/Api/ApiCommandHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private function addApiCommandParameters(array $schema, array $acquiaCloudSpec,
$requestBodySchema = $this->getRequestBodyFromParameterSchema($schema, $acquiaCloudSpec);
/** @var \Symfony\Component\Console\Input\InputOption|InputArgument $parameterDefinition */
foreach ($bodyInputDefinition as $parameterDefinition) {
$parameterSpecification = $this->getPropertySpecFromRequestBodyParam($requestBodySchema, $parameterDefinition);
$parameterSpecification = $this->getPropertySpecFromRequestBodyParam($requestBodySchema, $parameterDefinition, $acquiaCloudSpec);
$command->addPostParameter($parameterDefinition->getName(), $parameterSpecification);
}
$usage .= $requestBodyParamUsageSuffix;
Expand Down Expand Up @@ -125,6 +125,12 @@ private function addApiCommandParametersForRequestBody(array $schema, array $acq
$requestBodySchema['properties'] = [];
}
foreach ($requestBodySchema['properties'] as $propKey => $paramDefinition) {
// Resolve $ref inside individual property definitions.
if (array_key_exists('$ref', $paramDefinition)) {
$parts = explode('/', $paramDefinition['$ref']);
$paramKey = end($parts);
$paramDefinition = $this->getParameterSchemaFromSpec($paramKey, $acquiaCloudSpec);
}
$isRequired = array_key_exists('required', $requestBodySchema) && in_array($propKey, $requestBodySchema['required'], true);
$propKey = self::renameParameter($propKey);

Expand Down Expand Up @@ -168,31 +174,49 @@ private function addApiCommandParametersForRequestBody(array $schema, array $acq
private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKey, mixed $paramDefinition, string $type, string $usage, array $acquiaCloudSpec): string
{
$requestBodyContent = $this->getRequestBodyContent($requestBody, $acquiaCloudSpec);

// Example may live directly on the content-type object (inline requestBody),
// or nested inside schema (e.g. $ref-resolved requestBodies).
if (array_key_exists('example', $requestBodyContent)) {
$example = $requestBodyContent['example'];
} elseif (array_key_exists('schema', $requestBodyContent) && array_key_exists('example', $requestBodyContent['schema'])) {
$example = $requestBodyContent['schema']['example'];
} else {
return $usage;
}

if ($example) {
$prefix = $type === 'argument' ? '' : "--$propKey=";
if (array_key_exists($propKey, $example)) {
if (!array_key_exists('type', $paramDefinition)) {
return $usage;
}
/**
* @infection-ignore-all
*
* This is being ignored for now due to CLI-1745. We use single quotes in json_encode()
* to preserve inner double quotes, which causes mutation tests to fail. Fixing this
* could introduce regressions, so a dedicated ticket is needed to
* properly address and validate the changes without breaking existing functionality.
*/
switch ($paramDefinition['type']) {
case 'object':
$usage .= $prefix . '"' . json_encode($example[$propKey], JSON_THROW_ON_ERROR) . '"" ';
// Wrap JSON in single quotes so inner double quotes remain shell-safe.
$usage .= $prefix . "'" . json_encode($example[$propKey], JSON_THROW_ON_ERROR) . "' ";
break;

case 'array':
$isMultidimensional = count($example[$propKey]) !== count($example[$propKey], COUNT_RECURSIVE);
if (!$isMultidimensional) {
foreach ($example[$propKey] as $value) {
$usage .= $prefix . "\"$value\" ";
$usage .= $prefix . "'$value' ";
}
} else {
// @todo Pretty sure prevents the user from using the arguments.
// Probably a bug. How can we allow users to specify a multidimensional array as an
// argument?
$value = json_encode($example[$propKey], JSON_THROW_ON_ERROR);
$usage .= $prefix . "\"$value\" ";
// Wrap JSON in single quotes so inner double quotes remain shell-safe.
$usage .= $prefix . "'$value' ";
}
break;

Expand All @@ -204,7 +228,7 @@ private function addPostArgumentUsageToExample(mixed $requestBody, mixed $propKe
} else {
$value = $example[$propKey];
}
$usage .= $prefix . "\"$value\" ";
$usage .= $prefix . "'$value' ";
break;
}
}
Expand Down Expand Up @@ -482,10 +506,20 @@ private function getRequestBodyFromParameterSchema(array $schema, array $acquiaC
return $requestBodySchema;
}

private function getPropertySpecFromRequestBodyParam(array $requestBodySchema, mixed $parameterDefinition): mixed
private function getPropertySpecFromRequestBodyParam(array $requestBodySchema, mixed $parameterDefinition, array $acquiaCloudSpec = []): mixed
{
$name = self::restoreRenamedParameter($parameterDefinition->getName());
return $requestBodySchema['properties'][$name] ?? null;
$spec = $requestBodySchema['properties'][$name] ?? [];

// Resolve $ref in the property spec so downstream code (e.g. castParamType) always
// receives a fully resolved spec with a 'type' key rather than a bare $ref object.
if (array_key_exists('$ref', $spec)) {
$parts = explode('/', $spec['$ref']);
$paramKey = end($parts);
$spec = $this->getParameterSchemaFromSpec($paramKey, $acquiaCloudSpec);
}

return $spec;
}

/**
Expand Down
19 changes: 16 additions & 3 deletions tests/phpunit/src/Commands/Api/ApiCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,17 @@ public static function providerTestApiCommandDefinitionRequestBody(): array
[
'api:accounts:ssh-key-create',
'post',
'api:accounts:ssh-key-create "mykey" "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChwPHzTTDKDpSbpa2+d22LcbQmsw92eLsUK3Fmei1fiGDkd34NsYCN8m7lsi3NbvdMS83CtPQPWiCveYPzFs1/hHc4PYj8opD2CNnr5iWVVbyaulCYHCgVv4aB/ojcexg8q483A4xJeF15TiCr/gu34rK6ucTvC/tn/rCwJBudczvEwt0klqYwv8Cl/ytaQboSuem5KgSjO3lMrb6CWtfSNhE43ZOw+UBFBqxIninN868vGMkIv9VY34Pwj54rPn/ItQd6Ef4B0KHHaGmzK0vfP+AK7FxNMoHnj3iYT33KZNqtDozdn5tYyH/bThPebEtgqUn+/w5l6wZIC/8zzvls/127ngHk+jNa0PlNyS2TxhPUK4NaPHIEnnrlp07JEYC4ImcBjaYCWAdcTcUkcJjwZQkN4bGmyO9cjICH98SdLD/HxqzTHeaYDbAX/Hu9HfaBb5dXLWsjw3Xc6hoVnUUZbMQyfgb0KgxDLh92eNGxJkpZiL0VDNOWCxDWsNpzwhLNkLqCvI6lyxiLaUzvJAk6dPaRhExmCbU1lDO2eR0FdSwC1TEhJOT9eDIK1r2hztZKs2oa5FNFfB/IFHVWasVFC9N2h/r/egB5zsRxC9MqBLRBq95NBxaRSFng6ML5WZSw41Qi4C/JWVm89rdj2WqScDHYyAdwyyppWU4T5c9Fmw== example@example.com"',
'api:accounts:ssh-key-create \'mykey\' \'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChwPHzTTDKDpSbpa2+d22LcbQmsw92eLsUK3Fmei1fiGDkd34NsYCN8m7lsi3NbvdMS83CtPQPWiCveYPzFs1/hHc4PYj8opD2CNnr5iWVVbyaulCYHCgVv4aB/ojcexg8q483A4xJeF15TiCr/gu34rK6ucTvC/tn/rCwJBudczvEwt0klqYwv8Cl/ytaQboSuem5KgSjO3lMrb6CWtfSNhE43ZOw+UBFBqxIninN868vGMkIv9VY34Pwj54rPn/ItQd6Ef4B0KHHaGmzK0vfP+AK7FxNMoHnj3iYT33KZNqtDozdn5tYyH/bThPebEtgqUn+/w5l6wZIC/8zzvls/127ngHk+jNa0PlNyS2TxhPUK4NaPHIEnnrlp07JEYC4ImcBjaYCWAdcTcUkcJjwZQkN4bGmyO9cjICH98SdLD/HxqzTHeaYDbAX/Hu9HfaBb5dXLWsjw3Xc6hoVnUUZbMQyfgb0KgxDLh92eNGxJkpZiL0VDNOWCxDWsNpzwhLNkLqCvI6lyxiLaUzvJAk6dPaRhExmCbU1lDO2eR0FdSwC1TEhJOT9eDIK1r2hztZKs2oa5FNFfB/IFHVWasVFC9N2h/r/egB5zsRxC9MqBLRBq95NBxaRSFng6ML5WZSw41Qi4C/JWVm89rdj2WqScDHYyAdwyyppWU4T5c9Fmw== example@example.com\'',
],
[
'api:environments:file-copy',
'post',
'12-d314739e-296f-11e9-b210-d663bd873d93 --source="14-0c7e79ab-1c4a-424e-8446-76ae8be7e851"',
'12-d314739e-296f-11e9-b210-d663bd873d93 --source=\'14-0c7e79ab-1c4a-424e-8446-76ae8be7e851\'',
],
[
'api:private-networks:create',
'post',
'api:private-networks:create \'123e4567-e89b-12d3-a456-426614174000\' \'us-east-1\' \'customer-private-network\' --description=\'Private network for customer\' --label=\'anyLabel\' --isolation=\'{"dedicated_compute":false,"dedicated_network":false}\' --ingress=\'{"drupal_ssh":{"ingress_acls":["test-acls"]}}\' \'{"cidr":"114.7.55.1\/16","private_egress_access":{"drupal":true},"vpns":[{"name":"vpn1","gateway_ip":"10.10.10.10","routes":["127.0.0.1\/32","127.0.0.2\/32"],"tunnel1":{"shared_key":"sharedKey1","internal_cidr":"192.1.1.0\/24","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"},"tunnel2":{"shared_key":"sharedKey2","internal_cidr":"192.1.1.0\/14","ike_versions":"1","startup_action":"start","dpd_timeout_action":"stop"}}],"vpc_peers":[{"name":"vpcPeer1","aws_account":"123456789012","vpc_id":"vpc-1234567890abcdef0","vpc_cidr":"120.24.16.1\/24"}]}\'',
],
];
}
Expand All @@ -563,7 +568,15 @@ public function testApiCommandDefinitionRequestBody(string $commandName, string
{
$this->command = $this->getApiCommandByName($commandName);
$resource = self::getResourceFromSpec($this->command->getPath(), $method);
foreach ($resource['requestBody']['content']['application/hal+json']['example'] as $propKey => $value) {
if (array_key_exists('$ref', $resource['requestBody'])) {
$cloudApiSpec = self::getCloudApiSpec();
$parts = explode('/', $resource['requestBody']['$ref']);
$paramKey = end($parts);
$resource['requestBody'] = $cloudApiSpec['components']['requestBodies'][$paramKey];
}
$example = $resource['requestBody']['content']['application/hal+json']['example'] ?? $resource['requestBody']['content']['application/json']['schema']['example'] ?? null;
self::assertNotEmpty($example);
foreach ($example as $propKey => $value) {
$this->assertTrue(
$this->command->getDefinition()
->hasArgument($propKey) || $this->command->getDefinition()
Expand Down
Loading