Skip to content
Open
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
27 changes: 7 additions & 20 deletions src/Api/ErrorParser/AbstractErrorParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Aws\Api\StructureShape;
use Aws\CommandInterface;
use Psr\Http\Message\ResponseInterface;
use SimpleXMLElement;

abstract class AbstractErrorParser
{
Expand All @@ -27,23 +28,10 @@ public function __construct(?Service $api = null)
}

abstract protected function payload(
ResponseInterface $response,
ResponseInterface|SimpleXMLElement|array $responseOrParsedBody,
StructureShape $member
);

protected function extractPayload(
StructureShape $member,
ResponseInterface $response
) {
if ($member instanceof StructureShape) {
// Structure members parse top-level data into a specific key.
return $this->payload($response, $member);
} else {
// Streaming data is just the stream from the response body.
return $response->getBody();
}
}

protected function populateShape(
array &$data,
ResponseInterface $response,
Expand All @@ -57,16 +45,15 @@ protected function populateShape(
if (!empty($data['code'])) {

$errors = $this->api->getOperation($command->getName())->getErrors();
foreach ($errors as $key => $error) {
foreach ($errors as $error) {

// If error code matches a known error shape, populate the body
if ($this->errorCodeMatches($data, $error)) {
$modeledError = $error;
$data['body'] = $this->extractPayload(
$modeledError,
$response
$data['body'] = $this->payload(
$data['parsed'] ?? $response,
$error,
);
$data['error_shape'] = $modeledError;
$data['error_shape'] = $error;

foreach ($error->getMembers() as $name => $member) {
switch ($member['location']) {
Expand Down
33 changes: 24 additions & 9 deletions src/Api/ErrorParser/JsonParserTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

use Aws\Api\Parser\PayloadParserTrait;
use Aws\Api\StructureShape;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\ResponseInterface;
use SimpleXMLElement;

/**
* Provides basic JSON error parsing functionality.
Expand Down Expand Up @@ -37,10 +39,24 @@ private function genericHandler(ResponseInterface $response): array
);
}

$parsedBody = null;
$body = $response->getBody();
if (!$body->isSeekable() || $body->getSize()) {
$parsedBody = $this->parseJson((string) $body, $response);
// If the body is not seekable then, read the full message
// before de-serializing
if (!$body->isSeekable()) {
$tempBody = Utils::streamFor();
Utils::copyToStream($body, $tempBody);
$body = $tempBody;
}

// Cast it to string
$body = (string) $body;
$parsedBody = [];

// Avoid parsing an empty body
if (!empty($body)) {
// Parsing the body to avoid having to read the response body again.
// This will avoid issues when the body is not seekable
$parsedBody = $this->parseJson($body, $response);
}

// Parse error code from response body
Expand Down Expand Up @@ -129,14 +145,13 @@ private function extractErrorCode(string $rawErrorCode): string
}

protected function payload(
ResponseInterface $response,
ResponseInterface|SimpleXMLElement|array $responseOrParsedBody,
StructureShape $member
) {
$body = $response->getBody();
if (!$body->isSeekable() || $body->getSize()) {
$jsonBody = $this->parseJson($body, $response);
} else {
$jsonBody = (string) $body;
$jsonBody = $responseOrParsedBody;
if ($responseOrParsedBody instanceof ResponseInterface) {
$body = $responseOrParsedBody->getBody();
$jsonBody = $this->parseJson($body, $responseOrParsedBody);
}

return $this->parser->parse($member, $jsonBody);
Expand Down
17 changes: 11 additions & 6 deletions src/Api/ErrorParser/JsonRpcErrorParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,25 @@ public function __invoke(
$data = $this->genericHandler($response);

// Make the casing consistent across services.
if ($data['parsed']) {
$data['parsed'] = array_change_key_case($data['parsed']);
$parsed = $data['parsed'] ?? null;
if ($parsed) {
$parsed = array_change_key_case($data['parsed']);
}

if (isset($data['parsed']['__type'])) {
if (isset($parsed['__type'])) {
if (!isset($data['code'])) {
$parts = explode('#', $data['parsed']['__type']);
$data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
$parts = explode('#', $parsed['__type']);
$data['code'] = $parts[1] ?? $parts[0];
}
$data['message'] = $data['parsed']['message'] ?? null;

$data['message'] = $parsed['message'] ?? null;
}

$this->populateShape($data, $response, $command);

// Now lets make parsed to be all lowercase
$data['parsed'] = $parsed;

return $data;
}
}
9 changes: 7 additions & 2 deletions src/Api/ErrorParser/RestJsonErrorParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ public function __invoke(
$data['type'] = strtolower($data['type']);
}

// Make the casing consistent across services.
$parsed = $data['parsed'] ?? null;
if ($parsed) {
$parsed = array_change_key_case($data['parsed']);
}

// Retrieve error message directly
$data['message'] = $data['parsed']['message']
?? ($data['parsed']['Message'] ?? null);
$data['message'] = $parsed['message'] ?? null;

$this->populateShape($data, $response, $command);

Expand Down
30 changes: 27 additions & 3 deletions src/Api/ErrorParser/XmlErrorParser.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<?php
namespace Aws\Api\ErrorParser;

use Aws\Api\Parser\Exception\ParserException;
use Aws\Api\Parser\PayloadParserTrait;
use Aws\Api\Parser\XmlParser;
use Aws\Api\Service;
use Aws\Api\StructureShape;
use Aws\CommandInterface;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\ResponseInterface;

/**
Expand Down Expand Up @@ -38,7 +40,17 @@ public function __invoke(
];

$body = $response->getBody();
if ($body->getSize() > 0) {
if (!$body->isSeekable()) {
$tempBody = Utils::streamFor();
Utils::copyToStream($body, $tempBody);
$body = $tempBody;
}

// Cast it to string
$body = (string) $body;

// Parse just if is not empty
if (!empty($body)) {
$this->parseBody($this->parseXml($body, $response), $data);
} else {
$this->parseHeaders($response, $data);
Expand Down Expand Up @@ -97,15 +109,27 @@ protected function registerNamespacePrefix(\SimpleXMLElement $element)
}

protected function payload(
ResponseInterface $response,
ResponseInterface|\SimpleXMLElement|array $responseOrParsedBody,
StructureShape $member
) {
$xmlBody = $this->parseXml($response->getBody(), $response);
$xmlBody = $responseOrParsedBody;
if ($responseOrParsedBody instanceof ResponseInterface) {
$xmlBody = $this->parseXml(
$responseOrParsedBody->getBody(),
$responseOrParsedBody
);
}


$prefix = $this->registerNamespacePrefix($xmlBody);
$errorBody = $xmlBody->xpath("//{$prefix}Error");

if (is_array($errorBody) && !empty($errorBody[0])) {
return $this->parser->parse($member, $errorBody[0]);
}

throw new ParserException(
"Error element not found in parsed body"
);
}
}
2 changes: 1 addition & 1 deletion src/Api/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public function getOutput()
/**
* Get an array of operation error shapes.
*
* @return Shape[]
* @return StructureShape[]
*/
public function getErrors()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Api/Parser/QueryParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function __invoke(
) {
$output = $this->api->getOperation($command->getName())->getOutput();
$body = $response->getBody();
$xml = !$body->isSeekable() || $body->getSize()
$xml = (!$body->isSeekable() || $body->getSize())
? $this->parseXml($body, $response)
: null;

Expand Down
28 changes: 28 additions & 0 deletions src/AwsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ public function __construct(array $args)
$args['with_resolved']($config);
}
$this->addUserAgentMiddleware($config);
$this->addEventStreamHttpFlagMiddleware();
}

public function getHandlerList()
Expand Down Expand Up @@ -643,6 +644,33 @@ private function addUserAgentMiddleware($args)
);
}

/**
* Enables streaming the response by using the stream flag.
*
* @return void
*/
private function addEventStreamHttpFlagMiddleware(): void
{
$this->getHandlerList()
-> appendInit(
function (callable $handler) {
return function (CommandInterface $command, $request = null) use ($handler) {
$operation = $this->getApi()->getOperation($command->getName());
$output = $operation->getOutput();
foreach ($output->getMembers() as $memberProps) {
if (!empty($memberProps['eventstream'])) {
$command['@http']['stream'] = true;
break;
}
}

return $handler($command, $request);
};
},
'event-streaming-flag-middleware'
);
}

/**
* Retrieves client context param definition from service model,
* creates mapping of client context param names with client-provided
Expand Down
23 changes: 0 additions & 23 deletions src/CloudWatchLogs/CloudWatchLogsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,29 +199,6 @@ class CloudWatchLogsClient extends AwsClient {
public function __construct(array $args)
{
parent::__construct($args);
$this->addStreamingFlagMiddleware();
}

private function addStreamingFlagMiddleware()
{
$this->getHandlerList()
-> appendInit(
$this->getStreamingFlagMiddleware(),
'streaming-flag-middleware'
);
}

private function getStreamingFlagMiddleware(): callable
{
return function (callable $handler) {
return function (CommandInterface $command, $request = null) use ($handler) {
if (!empty(self::$streamingCommands[$command->getName()])) {
$command['@http']['stream'] = true;
}

return $handler($command, $request);
};
};
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/WrappedHttpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,21 @@ private function parseError(
throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.');
}

$serviceError = "AWS HTTP error: " . $err['exception']->getMessage();
$serviceError = "AWS HTTP error:\n";

if (!isset($err['response'])) {
$parts = ['response' => null];
$serviceError .= $err['exception']->getMessage();
} else {
try {
$parts = call_user_func(
$this->errorParser,
$err['response'],
$command
);
$serviceError .= " {$parts['code']} ({$parts['type']}): "
. "{$parts['message']} - " . $err['response']->getBody();

$serviceError .= "{$parts['code']} ({$parts['type']}): "
. "{$parts['message']}";
} catch (ParserException $e) {
$parts = [];
$serviceError .= ' Unable to parse error information from '
Expand Down
Loading