Skip to content

Commit 2dac107

Browse files
authored
Refactor endpoint generation (async-aws#605)
* Refactor endpoint generation * Regenerate code * Use default case for "global" endpoint * Add an UnsupportedRegion exception * Handle null region * Check region for global * Display only supported versions * Fix cs * Add blank line, and replace PHP_EOL bt \n
1 parent a186046 commit 2dac107

File tree

22 files changed

+1869
-49
lines changed

22 files changed

+1869
-49
lines changed

manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"variables": {
33
"${LATEST}": "3.137.5"
44
},
5+
"endpoints": "https:\/\/raw.githubusercontent.com\/aws\/aws-sdk-php\/${LATEST}\/src\/data\/endpoints.json",
56
"services": {
67
"CloudFormation": {
78
"source": "https://raw.githubusercontent.com/aws/aws-sdk-php/${LATEST}/src/data/cloudformation/2010-05-15/api-2.json",

src/CodeGenerator/src/Command/GenerateCommand.php

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,16 @@ protected function execute(InputInterface $input, OutputInterface $output)
6767

6868
/** @var ConsoleOutputInterface $output */
6969
$manifest = $this->loadManifest();
70+
$endpoints = $this->loadFile($manifest['endpoints'], 'endpoints');
7071
$serviceNames = $this->getServiceNames($input->getArgument('service'), $input->getOption('all'), $io, $manifest['services']);
7172
if (\is_int($serviceNames)) {
7273
return $serviceNames;
7374
}
7475

7576
if (\count($serviceNames) > 1 && $input->getOption('all') && \extension_loaded('pcntl')) {
76-
$manifest = $this->generateServicesParallel($io, $input, $output, $manifest, $serviceNames);
77+
$manifest = $this->generateServicesParallel($io, $input, $output, $manifest, $endpoints, $serviceNames);
7778
} else {
78-
$manifest = $this->generateServicesSequential($io, $input, $output, $manifest, $serviceNames);
79+
$manifest = $this->generateServicesSequential($io, $input, $output, $manifest, $endpoints, $serviceNames);
7980
}
8081

8182
if (\is_int($manifest)) {
@@ -88,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
8889
return 0;
8990
}
9091

91-
private function generateServicesParallel(SymfonyStyle $io, InputInterface $input, ConsoleOutputInterface $output, array $manifest, array $serviceNames)
92+
private function generateServicesParallel(SymfonyStyle $io, InputInterface $input, ConsoleOutputInterface $output, array $manifest, array $endpoints, array $serviceNames)
9293
{
9394
$progress = (new SymfonyStyle($input, $output->section()))->createProgressBar();
9495
$progress->setFormat(' [%bar%] %message%');
@@ -102,7 +103,7 @@ private function generateServicesParallel(SymfonyStyle $io, InputInterface $inpu
102103
throw new \RuntimeException('Failed to fork');
103104
}
104105
if (!$pid) {
105-
$code = $this->generateService($io, $input, $manifest, $serviceName);
106+
$code = $this->generateService($io, $input, $manifest, $endpoints, $serviceName);
106107
if (\is_int($code)) {
107108
die($code);
108109
}
@@ -128,7 +129,7 @@ private function generateServicesParallel(SymfonyStyle $io, InputInterface $inpu
128129
return $manifest;
129130
}
130131

131-
private function generateServicesSequential(SymfonyStyle $io, InputInterface $input, ConsoleOutputInterface $output, array $manifest, array $serviceNames)
132+
private function generateServicesSequential(SymfonyStyle $io, InputInterface $input, ConsoleOutputInterface $output, array $manifest, array $endpoints, array $serviceNames)
132133
{
133134
if (\count($serviceNames) > 1) {
134135
$progress = (new SymfonyStyle($input, $output->section()))->createProgressBar();
@@ -138,7 +139,7 @@ private function generateServicesSequential(SymfonyStyle $io, InputInterface $in
138139
}
139140

140141
foreach ($serviceNames as $serviceName) {
141-
$manifest = $this->generateService($io, $input, $manifest, $serviceName);
142+
$manifest = $this->generateService($io, $input, $manifest, $endpoints, $serviceName);
142143
if (\is_int($manifest)) {
143144
return $manifest;
144145
}
@@ -157,9 +158,80 @@ private function generateServicesSequential(SymfonyStyle $io, InputInterface $in
157158
return $manifest;
158159
}
159160

160-
private function generateService(SymfonyStyle $io, InputInterface $input, array $manifest, string $serviceName)
161+
private function extractEndpointsForService(array $endpoints, string $prefix): array
162+
{
163+
$serviceEndpoints = [];
164+
foreach ($endpoints['partitions'] as $partition) {
165+
$suffix = $partition['dnsSuffix'];
166+
$service = $partition['services'][$prefix] ?? [];
167+
foreach ($service['endpoints'] ?? [] as $region => $config) {
168+
$hostname = $config['hostname'] ?? $service['defaults']['hostname'] ?? $partition['defaults']['hostname'];
169+
$protocols = $config['protocols'] ?? $service['defaults']['protocols'] ?? $partition['defaults']['protocols'] ?? [];
170+
$signRegion = $config['credentialScope']['region'] ?? $service['defaults']['credentialScope']['region'] ?? $partition['defaults']['credentialScope']['region'] ?? $region;
171+
$signService = $config['credentialScope']['service'] ?? $service['defaults']['credentialScope']['service'] ?? $partition['defaults']['credentialScope']['service'] ?? $prefix;
172+
$signVersions = \array_unique($config['signatureVersions'] ?? $service['defaults']['signatureVersions'] ?? $partition['defaults']['signatureVersions'] ?? []);
173+
174+
if (empty($config)) {
175+
if (!isset($serviceEndpoints['_default'][$partition['partition']])) {
176+
$endpoint = strtr(sprintf('http%s://%s', \in_array('https', $protocols) ? 's' : '', $hostname), [
177+
'{service}' => $prefix,
178+
'{region}' => '%region%',
179+
'{dnsSuffix}' => $suffix,
180+
]);
181+
$serviceEndpoints['_default'][$partition['partition']] = [
182+
'endpoint' => $endpoint,
183+
'regions' => [$region],
184+
'signService' => $signService,
185+
'signVersions' => $signVersions,
186+
];
187+
} else {
188+
$serviceEndpoints['_default'][$partition['partition']]['regions'][] = $region;
189+
}
190+
} else {
191+
$endpoint = strtr(sprintf('http%s://%s', \in_array('https', $protocols) ? 's' : '', $hostname), [
192+
'{service}' => $prefix,
193+
'{region}' => $region,
194+
'{dnsSuffix}' => $suffix,
195+
]);
196+
197+
$serviceEndpoints[$region] = [
198+
'endpoint' => $endpoint,
199+
'signRegion' => $signRegion,
200+
'signService' => $signService,
201+
'signVersions' => $signVersions,
202+
];
203+
}
204+
}
205+
if (isset($service['partitionEndpoint'])) {
206+
if (!isset($serviceEndpoints[$service['partitionEndpoint']])) {
207+
throw new \RuntimeException('Missing global region config');
208+
}
209+
$serviceEndpoints['_global'][$partition['partition']] = $serviceEndpoints[$service['partitionEndpoint']];
210+
unset($serviceEndpoints[$service['partitionEndpoint']]);
211+
unset($serviceEndpoints['_global'][$partition['partition']]['region']);
212+
$serviceEndpoints['_global'][$partition['partition']]['regions'] = [];
213+
if (!($service['isRegionalized'] ?? true)) {
214+
foreach ($partition['regions'] as $region => $_) {
215+
if (isset($serviceEndpoints[$region])) {
216+
continue;
217+
}
218+
if (\in_array($region, $serviceEndpoints['_default'][$partition['partition']]['regions'] ?? [])) {
219+
continue;
220+
}
221+
$serviceEndpoints['_global'][$partition['partition']]['regions'][] = $region;
222+
}
223+
}
224+
}
225+
}
226+
227+
return $serviceEndpoints;
228+
}
229+
230+
private function generateService(SymfonyStyle $io, InputInterface $input, array $manifest, array $endpoints, string $serviceName)
161231
{
162232
$definitionArray = $this->loadFile($manifest['services'][$serviceName]['source'], "$serviceName-source");
233+
$endpoints = $this->extractEndpointsForService($endpoints, $definitionArray['metadata']['endpointPrefix']);
234+
163235
$documentationArray = $this->loadFile($manifest['services'][$serviceName]['documentation'], "$serviceName-documentation");
164236
$paginationArray = $this->loadFile($manifest['services'][$serviceName]['pagination'], "$serviceName-pagination");
165237
$waiterArray = isset($manifest['services'][$serviceName]['waiter']) ? $this->loadFile($manifest['services'][$serviceName]['waiter'], "$serviceName-waiter") : ['waiters' => []];
@@ -171,7 +243,7 @@ private function generateService(SymfonyStyle $io, InputInterface $input, array
171243
}
172244

173245
$managedOperations = \array_unique(\array_merge($manifest['services'][$serviceName]['methods'], $operationNames));
174-
$definition = new ServiceDefinition($serviceName, $definitionArray, $documentationArray, $paginationArray, $waiterArray, $exampleArray);
246+
$definition = new ServiceDefinition($serviceName, $endpoints, $definitionArray, $documentationArray, $paginationArray, $waiterArray, $exampleArray);
175247
$serviceGenerator = $this->generator->service($manifest['services'][$serviceName]['namespace'] ?? \sprintf('AsyncAws\\%s', $serviceName), $managedOperations);
176248

177249
$clientClass = $serviceGenerator->client()->generate($definition);

src/CodeGenerator/src/Definition/ServiceDefinition.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class ServiceDefinition
1313
{
1414
private $name;
1515

16+
private $endpoints;
17+
1618
private $definition;
1719

1820
private $documentation;
@@ -23,9 +25,10 @@ class ServiceDefinition
2325

2426
private $example;
2527

26-
public function __construct(string $name, array $definition, array $documentation, array $pagination, array $waiter, array $example)
28+
public function __construct(string $name, array $endpoints, array $definition, array $documentation, array $pagination, array $waiter, array $example)
2729
{
2830
$this->name = $name;
31+
$this->endpoints = $endpoints;
2932
$this->definition = $definition;
3033
$this->documentation = $documentation;
3134
$this->pagination = $pagination;
@@ -38,6 +41,11 @@ public function getName(): string
3841
return $this->name;
3942
}
4043

44+
public function getEndpoints(): array
45+
{
46+
return $this->endpoints;
47+
}
48+
4149
public function getOperation(string $name): ?Operation
4250
{
4351
if (isset($this->definition['operations'][$name])) {
@@ -85,11 +93,6 @@ public function getEndpointPrefix(): string
8593
return $this->definition['metadata']['endpointPrefix'];
8694
}
8795

88-
public function getGlobalEndpoint(): ?string
89-
{
90-
return $this->definition['metadata']['globalEndpoint'] ?? null;
91-
}
92-
9396
public function getTargetPrefix(): string
9497
{
9598
return $this->definition['metadata']['targetPrefix'];

src/CodeGenerator/src/Generator/ClientGenerator.php

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use AsyncAws\CodeGenerator\Generator\Naming\ClassName;
1010
use AsyncAws\CodeGenerator\Generator\Naming\NamespaceRegistry;
1111
use AsyncAws\CodeGenerator\Generator\PhpGenerator\ClassFactory;
12+
use AsyncAws\Core\Configuration;
13+
use AsyncAws\Core\Exception\UnsupportedRegion;
1214
use Nette\PhpGenerator\ClassType;
1315

1416
/**
@@ -52,16 +54,89 @@ public function generate(ServiceDefinition $definition): ClassName
5254
->setVisibility(ClassType::VISIBILITY_PROTECTED)
5355
->setBody("return '$prefix';");
5456
}
55-
if (null !== $endpoint = $definition->getGlobalEndpoint()) {
56-
$class->addMethod('getEndpointPattern')
57-
->setReturnType('string')
58-
->setVisibility(ClassType::VISIBILITY_PROTECTED)
59-
->setBody("return \$region ? parent::getEndpointPattern(\$region) : 'https://$endpoint';")
60-
->addParameter('region')
61-
->setType('string')
62-
->setNullable(true)
63-
;
57+
58+
$supportedVersions = eval(sprintf('class A%s extends %s {
59+
public function __construct() {}
60+
public function getVersion() {
61+
return array_keys($this->getSignerFactories());
62+
}
63+
} return (new A%1$s)->getVersion();', sha1(\uniqid('', true)), $className->getFqdn()));
64+
65+
$endpoints = $definition->getEndpoints();
66+
$dumpConfig = static function ($config) use ($supportedVersions) {
67+
$signatureVersions = \array_intersect($supportedVersions, $config['signVersions']);
68+
rsort($signatureVersions);
69+
70+
return strtr(sprintf(" return %s;\n", \var_export([
71+
'endpoint' => $config['endpoint'],
72+
'signRegion' => $config['signRegion'] ?? '%region%',
73+
'signService' => $config['signService'],
74+
'signVersions' => \array_values($signatureVersions),
75+
], true)), ['\'%region%\'' => '$region']);
76+
};
77+
78+
$body = '';
79+
if (!isset($endpoints['_global']['aws'])) {
80+
$namespace->addUse(Configuration::class);
81+
$body .= 'if ($region === null) {
82+
$region = Configuration::DEFAULT_REGION;
83+
}
84+
85+
';
86+
} else {
87+
if (empty($endpoints['_global']['aws']['signRegion'])) {
88+
throw new \RuntimeException('Global endpoint without signRegion is not yet supported');
89+
}
90+
$body .= 'if ($region === null) {
91+
' . $dumpConfig($endpoints['_global']['aws']) . '
92+
}
93+
94+
';
95+
}
96+
$body .= "switch (\$region) {\n";
97+
98+
foreach ($endpoints['_global'] ?? [] as $partitionName => $config) {
99+
if (empty($config['regions'])) {
100+
continue;
101+
}
102+
sort($config['regions']);
103+
foreach ($config['regions'] as $region) {
104+
$body .= sprintf(" case %s:\n", \var_export($region, true));
105+
}
106+
$body .= $dumpConfig($config);
107+
}
108+
foreach ($endpoints['_default'] ?? [] as $config) {
109+
if (empty($config['regions'])) {
110+
continue;
111+
}
112+
sort($config['regions']);
113+
foreach ($config['regions'] as $region) {
114+
$body .= sprintf(" case %s:\n", \var_export($region, true));
115+
}
116+
$body .= $dumpConfig($config);
64117
}
118+
ksort($endpoints);
119+
foreach ($endpoints as $region => $config) {
120+
if ('_' === $region[0]) {
121+
continue; // skip `_default` and `_global`
122+
}
123+
$body .= sprintf(" case %s:\n", \var_export($region, true));
124+
$body .= $dumpConfig($config);
125+
}
126+
$body .= '}
127+
throw new UnsupportedRegion(sprintf(\'The region "%s" is not supported by "' . $definition->getName() . '".\', $region));
128+
';
129+
$namespace->addUse(UnsupportedRegion::class);
130+
131+
$class->addMethod('getEndpointMetadata')
132+
->setReturnType('array')
133+
->setVisibility(ClassType::VISIBILITY_PROTECTED)
134+
->setBody($body)
135+
->addParameter('region')
136+
->setType('string')
137+
->setNullable(true)
138+
;
139+
65140
if (null !== $signatureVersion = $definition->getSignatureVersion()) {
66141
$class->addMethod('getSignatureVersion')
67142
->setReturnType('string')

src/Core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Support for EventBridge in `AwsClientFactory`
88
- Support for IAM in `AwsClientFactory`
9+
- Support for global and regional endpoints
910

1011
## 1.1.0
1112

0 commit comments

Comments
 (0)