Skip to content

Commit fad677b

Browse files
authored
Add support for endpoint discovery (#1239)
1 parent 7f6293d commit fad677b

9 files changed

+280
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Added
66

7+
- Added support for endpoint discovery
78
- AWS enhancement: Documentation updates.
89

910
## 1.2.0

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"php": "^7.2.5 || ^8.0",
1515
"ext-filter": "*",
1616
"ext-json": "*",
17-
"async-aws/core": "^1.9",
17+
"async-aws/core": "^1.16",
1818
"symfony/polyfill-uuid": "^1.0"
1919
},
2020
"conflict": {

src/DynamoDbClient.php

+40-15
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use AsyncAws\DynamoDb\Input\CreateTableInput;
3333
use AsyncAws\DynamoDb\Input\DeleteItemInput;
3434
use AsyncAws\DynamoDb\Input\DeleteTableInput;
35+
use AsyncAws\DynamoDb\Input\DescribeEndpointsRequest;
3536
use AsyncAws\DynamoDb\Input\DescribeTableInput;
3637
use AsyncAws\DynamoDb\Input\ExecuteStatementInput;
3738
use AsyncAws\DynamoDb\Input\GetItemInput;
@@ -48,6 +49,7 @@
4849
use AsyncAws\DynamoDb\Result\CreateTableOutput;
4950
use AsyncAws\DynamoDb\Result\DeleteItemOutput;
5051
use AsyncAws\DynamoDb\Result\DeleteTableOutput;
52+
use AsyncAws\DynamoDb\Result\DescribeEndpointsResponse;
5153
use AsyncAws\DynamoDb\Result\DescribeTableOutput;
5254
use AsyncAws\DynamoDb\Result\ExecuteStatementOutput;
5355
use AsyncAws\DynamoDb\Result\GetItemOutput;
@@ -107,7 +109,7 @@ public function batchGetItem($input): BatchGetItemOutput
107109
'ResourceNotFoundException' => ResourceNotFoundException::class,
108110
'RequestLimitExceeded' => RequestLimitExceededException::class,
109111
'InternalServerError' => InternalServerErrorException::class,
110-
]]));
112+
], 'usesEndpointDiscovery' => true]));
111113

112114
return new BatchGetItemOutput($response, $this, $input);
113115
}
@@ -145,7 +147,7 @@ public function batchWriteItem($input): BatchWriteItemOutput
145147
'ItemCollectionSizeLimitExceededException' => ItemCollectionSizeLimitExceededException::class,
146148
'RequestLimitExceeded' => RequestLimitExceededException::class,
147149
'InternalServerError' => InternalServerErrorException::class,
148-
]]));
150+
], 'usesEndpointDiscovery' => true]));
149151

150152
return new BatchWriteItemOutput($response);
151153
}
@@ -184,7 +186,7 @@ public function createTable($input): CreateTableOutput
184186
'ResourceInUseException' => ResourceInUseException::class,
185187
'LimitExceededException' => LimitExceededException::class,
186188
'InternalServerError' => InternalServerErrorException::class,
187-
]]));
189+
], 'usesEndpointDiscovery' => true]));
188190

189191
return new CreateTableOutput($response);
190192
}
@@ -229,7 +231,7 @@ public function deleteItem($input): DeleteItemOutput
229231
'TransactionConflictException' => TransactionConflictException::class,
230232
'RequestLimitExceeded' => RequestLimitExceededException::class,
231233
'InternalServerError' => InternalServerErrorException::class,
232-
]]));
234+
], 'usesEndpointDiscovery' => true]));
233235

234236
return new DeleteItemOutput($response);
235237
}
@@ -262,11 +264,29 @@ public function deleteTable($input): DeleteTableOutput
262264
'ResourceNotFoundException' => ResourceNotFoundException::class,
263265
'LimitExceededException' => LimitExceededException::class,
264266
'InternalServerError' => InternalServerErrorException::class,
265-
]]));
267+
], 'usesEndpointDiscovery' => true]));
266268

267269
return new DeleteTableOutput($response);
268270
}
269271

272+
/**
273+
* Returns the regional endpoint information.
274+
*
275+
* @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeEndpoints.html
276+
* @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-dynamodb-2012-08-10.html#describeendpoints
277+
*
278+
* @param array{
279+
* @region?: string,
280+
* }|DescribeEndpointsRequest $input
281+
*/
282+
public function describeEndpoints($input = []): DescribeEndpointsResponse
283+
{
284+
$input = DescribeEndpointsRequest::create($input);
285+
$response = $this->getResponse($input->request(), new RequestContext(['operation' => 'DescribeEndpoints', 'region' => $input->getRegion()]));
286+
287+
return new DescribeEndpointsResponse($response);
288+
}
289+
270290
/**
271291
* Returns information about the table, including the current status of the table, when it was created, the primary key
272292
* schema, and any indexes on the table.
@@ -288,7 +308,7 @@ public function describeTable($input): DescribeTableOutput
288308
$response = $this->getResponse($input->request(), new RequestContext(['operation' => 'DescribeTable', 'region' => $input->getRegion(), 'exceptionMapping' => [
289309
'ResourceNotFoundException' => ResourceNotFoundException::class,
290310
'InternalServerError' => InternalServerErrorException::class,
291-
]]));
311+
], 'usesEndpointDiscovery' => true]));
292312

293313
return new DescribeTableOutput($response);
294314
}
@@ -366,7 +386,7 @@ public function getItem($input): GetItemOutput
366386
'ResourceNotFoundException' => ResourceNotFoundException::class,
367387
'RequestLimitExceeded' => RequestLimitExceededException::class,
368388
'InternalServerError' => InternalServerErrorException::class,
369-
]]));
389+
], 'usesEndpointDiscovery' => true]));
370390

371391
return new GetItemOutput($response);
372392
}
@@ -391,7 +411,7 @@ public function listTables($input = []): ListTablesOutput
391411
$input = ListTablesInput::create($input);
392412
$response = $this->getResponse($input->request(), new RequestContext(['operation' => 'ListTables', 'region' => $input->getRegion(), 'exceptionMapping' => [
393413
'InternalServerError' => InternalServerErrorException::class,
394-
]]));
414+
], 'usesEndpointDiscovery' => true]));
395415

396416
return new ListTablesOutput($response, $this, $input);
397417
}
@@ -439,7 +459,7 @@ public function putItem($input): PutItemOutput
439459
'TransactionConflictException' => TransactionConflictException::class,
440460
'RequestLimitExceeded' => RequestLimitExceededException::class,
441461
'InternalServerError' => InternalServerErrorException::class,
442-
]]));
462+
], 'usesEndpointDiscovery' => true]));
443463

444464
return new PutItemOutput($response);
445465
}
@@ -486,7 +506,7 @@ public function query($input): QueryOutput
486506
'ResourceNotFoundException' => ResourceNotFoundException::class,
487507
'RequestLimitExceeded' => RequestLimitExceededException::class,
488508
'InternalServerError' => InternalServerErrorException::class,
489-
]]));
509+
], 'usesEndpointDiscovery' => true]));
490510

491511
return new QueryOutput($response, $this, $input);
492512
}
@@ -531,7 +551,7 @@ public function scan($input): ScanOutput
531551
'ResourceNotFoundException' => ResourceNotFoundException::class,
532552
'RequestLimitExceeded' => RequestLimitExceededException::class,
533553
'InternalServerError' => InternalServerErrorException::class,
534-
]]));
554+
], 'usesEndpointDiscovery' => true]));
535555

536556
return new ScanOutput($response, $this, $input);
537557
}
@@ -610,7 +630,7 @@ public function transactWriteItems($input): TransactWriteItemsOutput
610630
'ProvisionedThroughputExceededException' => ProvisionedThroughputExceededException::class,
611631
'RequestLimitExceeded' => RequestLimitExceededException::class,
612632
'InternalServerError' => InternalServerErrorException::class,
613-
]]));
633+
], 'usesEndpointDiscovery' => true]));
614634

615635
return new TransactWriteItemsOutput($response);
616636
}
@@ -659,7 +679,7 @@ public function updateItem($input): UpdateItemOutput
659679
'TransactionConflictException' => TransactionConflictException::class,
660680
'RequestLimitExceeded' => RequestLimitExceededException::class,
661681
'InternalServerError' => InternalServerErrorException::class,
662-
]]));
682+
], 'usesEndpointDiscovery' => true]));
663683

664684
return new UpdateItemOutput($response);
665685
}
@@ -697,7 +717,7 @@ public function updateTable($input): UpdateTableOutput
697717
'ResourceNotFoundException' => ResourceNotFoundException::class,
698718
'LimitExceededException' => LimitExceededException::class,
699719
'InternalServerError' => InternalServerErrorException::class,
700-
]]));
720+
], 'usesEndpointDiscovery' => true]));
701721

702722
return new UpdateTableOutput($response);
703723
}
@@ -730,11 +750,16 @@ public function updateTimeToLive($input): UpdateTimeToLiveOutput
730750
'ResourceNotFoundException' => ResourceNotFoundException::class,
731751
'LimitExceededException' => LimitExceededException::class,
732752
'InternalServerError' => InternalServerErrorException::class,
733-
]]));
753+
], 'usesEndpointDiscovery' => true]));
734754

735755
return new UpdateTimeToLiveOutput($response);
736756
}
737757

758+
protected function discoverEndpoints(?string $region): array
759+
{
760+
return $this->describeEndpoints($region ? ['@region' => $region] : [])->getEndpoints();
761+
}
762+
738763
protected function getAwsErrorFactory(): AwsErrorFactoryInterface
739764
{
740765
return new JsonRpcAwsErrorFactory();
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace AsyncAws\DynamoDb\Input;
4+
5+
use AsyncAws\Core\Input;
6+
use AsyncAws\Core\Request;
7+
use AsyncAws\Core\Stream\StreamFactory;
8+
9+
final class DescribeEndpointsRequest extends Input
10+
{
11+
/**
12+
* @param array{
13+
* @region?: string,
14+
* } $input
15+
*/
16+
public function __construct(array $input = [])
17+
{
18+
parent::__construct($input);
19+
}
20+
21+
public static function create($input): self
22+
{
23+
return $input instanceof self ? $input : new self($input);
24+
}
25+
26+
/**
27+
* @internal
28+
*/
29+
public function request(): Request
30+
{
31+
// Prepare headers
32+
$headers = [
33+
'Content-Type' => 'application/x-amz-json-1.0',
34+
'X-Amz-Target' => 'DynamoDB_20120810.DescribeEndpoints',
35+
];
36+
37+
// Prepare query
38+
$query = [];
39+
40+
// Prepare URI
41+
$uriString = '/';
42+
43+
// Prepare Body
44+
$bodyPayload = $this->requestBody();
45+
$body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304);
46+
47+
// Return the Request
48+
return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body));
49+
}
50+
51+
private function requestBody(): array
52+
{
53+
$payload = [];
54+
55+
return $payload;
56+
}
57+
}
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace AsyncAws\DynamoDb\Result;
4+
5+
use AsyncAws\Core\Response;
6+
use AsyncAws\Core\Result;
7+
use AsyncAws\DynamoDb\ValueObject\Endpoint;
8+
9+
class DescribeEndpointsResponse extends Result
10+
{
11+
/**
12+
* List of endpoints.
13+
*/
14+
private $endpoints;
15+
16+
/**
17+
* @return Endpoint[]
18+
*/
19+
public function getEndpoints(): array
20+
{
21+
$this->initialize();
22+
23+
return $this->endpoints;
24+
}
25+
26+
protected function populateResult(Response $response): void
27+
{
28+
$data = $response->toArray();
29+
30+
$this->endpoints = $this->populateResultEndpoints($data['Endpoints']);
31+
}
32+
33+
private function populateResultEndpoint(array $json): Endpoint
34+
{
35+
return new Endpoint([
36+
'Address' => (string) $json['Address'],
37+
'CachePeriodInMinutes' => (string) $json['CachePeriodInMinutes'],
38+
]);
39+
}
40+
41+
/**
42+
* @return Endpoint[]
43+
*/
44+
private function populateResultEndpoints(array $json): array
45+
{
46+
$items = [];
47+
foreach ($json as $item) {
48+
$items[] = $this->populateResultEndpoint($item);
49+
}
50+
51+
return $items;
52+
}
53+
}

src/ValueObject/Endpoint.php

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace AsyncAws\DynamoDb\ValueObject;
4+
5+
use AsyncAws\Core\EndpointDiscovery\EndpointInterface;
6+
7+
/**
8+
* An endpoint information details.
9+
*/
10+
final class Endpoint implements EndpointInterface
11+
{
12+
/**
13+
* IP address of the endpoint.
14+
*/
15+
private $address;
16+
17+
/**
18+
* Endpoint cache time to live (TTL) value.
19+
*/
20+
private $cachePeriodInMinutes;
21+
22+
/**
23+
* @param array{
24+
* Address: string,
25+
* CachePeriodInMinutes: string,
26+
* } $input
27+
*/
28+
public function __construct(array $input)
29+
{
30+
$this->address = $input['Address'] ?? null;
31+
$this->cachePeriodInMinutes = $input['CachePeriodInMinutes'] ?? null;
32+
}
33+
34+
public static function create($input): self
35+
{
36+
return $input instanceof self ? $input : new self($input);
37+
}
38+
39+
public function getAddress(): string
40+
{
41+
return $this->address;
42+
}
43+
44+
public function getCachePeriodInMinutes(): int
45+
{
46+
return $this->cachePeriodInMinutes;
47+
}
48+
}

tests/Integration/DynamoDbClientTest.php

+17
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,23 @@ public function testUpdateTimeToLive(): void
519519
self::assertSame('attribute', $result->getTimeToLiveSpecification()->getAttributeName());
520520
}
521521

522+
public function testDiscoveredEndpoint(): void
523+
{
524+
$client = new DynamoDbClient([
525+
'endpoint' => 'http://localhost:4575',
526+
'endpointDiscoveryEnabled' => true,
527+
], new Credentials('aws_id', 'aws_secret'));
528+
529+
$input = new ListTablesInput([
530+
'ExclusiveStartTableName' => 'Thr',
531+
'Limit' => 5,
532+
]);
533+
$result = $client->ListTables($input);
534+
535+
$names = iterator_to_array($result->getTableNames(true));
536+
self::assertTrue(\count($names) >= 0);
537+
}
538+
522539
private function getClient(): DynamoDbClient
523540
{
524541
if ($this->client instanceof DynamoDbClient) {

0 commit comments

Comments
 (0)