mirror of
https://github.com/SociallyDev/Spaces-API.git
synced 2025-08-20 13:23:47 -07:00
v2: Updates
* Simplifies & beautifies everything * Introduces a new Class system. * Errors are defaulted to AWS's handler. * New function names & more efficient handling. * Should fix a majority of the errors. Please read the README for more!
This commit is contained in:
parent
ad0726e41e
commit
e6d7753dc8
1095 changed files with 45088 additions and 2911 deletions
|
@ -94,7 +94,7 @@ class ApiProvider
|
|||
*/
|
||||
public static function defaultProvider()
|
||||
{
|
||||
return new self(dirname(__FILE__) . '/../data', \Aws\manifest());
|
||||
return new self(__DIR__ . '/../data', \Aws\manifest());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,7 +165,7 @@ class ApiProvider
|
|||
}
|
||||
|
||||
/**
|
||||
* Execute the the provider.
|
||||
* Execute the provider.
|
||||
*
|
||||
* @param string $type Type of data ('api', 'waiter', 'paginator').
|
||||
* @param string $service Service name.
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Aws\Api;
|
||||
|
||||
use Aws\Api\Parser\Exception\ParserException;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* DateTime overrides that make DateTime work more seamlessly as a string,
|
||||
* with JSON documents, and with JMESPath.
|
||||
|
@ -9,16 +13,72 @@ class DateTimeResult extends \DateTime implements \JsonSerializable
|
|||
{
|
||||
/**
|
||||
* Create a new DateTimeResult from a unix timestamp.
|
||||
*
|
||||
* The Unix epoch (or Unix time or POSIX time or Unix
|
||||
* timestamp) is the number of seconds that have elapsed since
|
||||
* January 1, 1970 (midnight UTC/GMT).
|
||||
* @param $unixTimestamp
|
||||
*
|
||||
* @return DateTimeResult
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromEpoch($unixTimestamp)
|
||||
{
|
||||
return new self(gmdate('c', $unixTimestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $iso8601Timestamp
|
||||
*
|
||||
* @return DateTimeResult
|
||||
*/
|
||||
public static function fromISO8601($iso8601Timestamp)
|
||||
{
|
||||
if (is_numeric($iso8601Timestamp) || !is_string($iso8601Timestamp)) {
|
||||
throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromISO8601');
|
||||
}
|
||||
return new DateTimeResult($iso8601Timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DateTimeResult from an unknown timestamp.
|
||||
*
|
||||
* @param $timestamp
|
||||
*
|
||||
* @return DateTimeResult
|
||||
* @throws ParserException|Exception
|
||||
*/
|
||||
public static function fromTimestamp($timestamp, $expectedFormat = null)
|
||||
{
|
||||
if (empty($timestamp)) {
|
||||
return self::fromEpoch(0);
|
||||
}
|
||||
|
||||
if (!(is_string($timestamp) || is_numeric($timestamp))) {
|
||||
throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromTimestamp');
|
||||
}
|
||||
|
||||
try {
|
||||
if ($expectedFormat == 'iso8601') {
|
||||
try {
|
||||
return self::fromISO8601($timestamp);
|
||||
} catch (Exception $exception) {
|
||||
return self::fromEpoch($timestamp);
|
||||
}
|
||||
} else if ($expectedFormat == 'unixTimestamp') {
|
||||
try {
|
||||
return self::fromEpoch($timestamp);
|
||||
} catch (Exception $exception) {
|
||||
return self::fromISO8601($timestamp);
|
||||
}
|
||||
} else if (\Aws\is_valid_epoch($timestamp)) {
|
||||
return self::fromEpoch($timestamp);
|
||||
}
|
||||
return self::fromISO8601($timestamp);
|
||||
} catch (Exception $exception) {
|
||||
throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromTimestamp');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the DateTimeResult as an ISO 8601 date string.
|
||||
*
|
||||
|
@ -39,3 +99,4 @@ class DateTimeResult extends \DateTime implements \JsonSerializable
|
|||
return (string) $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ class DocModel
|
|||
return '';
|
||||
}
|
||||
|
||||
$tidy = new \Tidy();
|
||||
$tidy = new \tidy();
|
||||
$tidy->parseString($content, [
|
||||
'indent' => true,
|
||||
'doctype' => 'omit',
|
||||
|
|
95
aws/Aws/Api/ErrorParser/AbstractErrorParser.php
Normal file
95
aws/Aws/Api/ErrorParser/AbstractErrorParser.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
namespace Aws\Api\ErrorParser;
|
||||
|
||||
use Aws\Api\Parser\MetadataParserTrait;
|
||||
use Aws\Api\Parser\PayloadParserTrait;
|
||||
use Aws\Api\Service;
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\CommandInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
abstract class AbstractErrorParser
|
||||
{
|
||||
use MetadataParserTrait;
|
||||
use PayloadParserTrait;
|
||||
|
||||
/**
|
||||
* @var Service
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @param Service $api
|
||||
*/
|
||||
public function __construct(Service $api = null)
|
||||
{
|
||||
$this->api = $api;
|
||||
}
|
||||
|
||||
abstract protected function payload(
|
||||
ResponseInterface $response,
|
||||
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,
|
||||
CommandInterface $command = null
|
||||
) {
|
||||
$data['body'] = [];
|
||||
|
||||
if (!empty($command) && !empty($this->api)) {
|
||||
|
||||
// If modeled error code is indicated, check for known error shape
|
||||
if (!empty($data['code'])) {
|
||||
|
||||
$errors = $this->api->getOperation($command->getName())->getErrors();
|
||||
foreach ($errors as $key => $error) {
|
||||
|
||||
// If error code matches a known error shape, populate the body
|
||||
if ($data['code'] == $error['name']
|
||||
&& $error instanceof StructureShape
|
||||
) {
|
||||
$modeledError = $error;
|
||||
$data['body'] = $this->extractPayload(
|
||||
$modeledError,
|
||||
$response
|
||||
);
|
||||
$data['error_shape'] = $modeledError;
|
||||
|
||||
foreach ($error->getMembers() as $name => $member) {
|
||||
switch ($member['location']) {
|
||||
case 'header':
|
||||
$this->extractHeader($name, $member, $response, $data['body']);
|
||||
break;
|
||||
case 'headers':
|
||||
$this->extractHeaders($name, $member, $response, $data['body']);
|
||||
break;
|
||||
case 'statusCode':
|
||||
$this->extractStatus($name, $response, $data['body']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
namespace Aws\Api\ErrorParser;
|
||||
|
||||
use Aws\Api\Parser\PayloadParserTrait;
|
||||
use Aws\Api\StructureShape;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
|
@ -20,7 +21,18 @@ trait JsonParserTrait
|
|||
'code' => null,
|
||||
'message' => null,
|
||||
'type' => $code[0] == '4' ? 'client' : 'server',
|
||||
'parsed' => $this->parseJson($response->getBody())
|
||||
'parsed' => $this->parseJson($response->getBody(), $response)
|
||||
];
|
||||
}
|
||||
|
||||
protected function payload(
|
||||
ResponseInterface $response,
|
||||
StructureShape $member
|
||||
) {
|
||||
$jsonBody = $this->parseJson($response->getBody(), $response);
|
||||
|
||||
if ($jsonBody) {
|
||||
return $this->parser->parse($member, $jsonBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
<?php
|
||||
namespace Aws\Api\ErrorParser;
|
||||
|
||||
use Aws\Api\Parser\JsonParser;
|
||||
use Aws\Api\Service;
|
||||
use Aws\CommandInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Parsers JSON-RPC errors.
|
||||
*/
|
||||
class JsonRpcErrorParser
|
||||
class JsonRpcErrorParser extends AbstractErrorParser
|
||||
{
|
||||
use JsonParserTrait;
|
||||
|
||||
public function __invoke(ResponseInterface $response)
|
||||
private $parser;
|
||||
|
||||
public function __construct(Service $api = null, JsonParser $parser = null)
|
||||
{
|
||||
parent::__construct($api);
|
||||
$this->parser = $parser ?: new JsonParser();
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
ResponseInterface $response,
|
||||
CommandInterface $command = null
|
||||
) {
|
||||
$data = $this->genericHandler($response);
|
||||
|
||||
// Make the casing consistent across services.
|
||||
if ($data['parsed']) {
|
||||
$data['parsed'] = array_change_key_case($data['parsed']);
|
||||
|
@ -26,6 +40,8 @@ class JsonRpcErrorParser
|
|||
: null;
|
||||
}
|
||||
|
||||
$this->populateShape($data, $response, $command);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
<?php
|
||||
namespace Aws\Api\ErrorParser;
|
||||
|
||||
use Aws\Api\Parser\JsonParser;
|
||||
use Aws\Api\Service;
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\CommandInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Parses JSON-REST errors.
|
||||
*/
|
||||
class RestJsonErrorParser
|
||||
class RestJsonErrorParser extends AbstractErrorParser
|
||||
{
|
||||
use JsonParserTrait;
|
||||
|
||||
public function __invoke(ResponseInterface $response)
|
||||
private $parser;
|
||||
|
||||
public function __construct(Service $api = null, JsonParser $parser = null)
|
||||
{
|
||||
parent::__construct($api);
|
||||
$this->parser = $parser ?: new JsonParser();
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
ResponseInterface $response,
|
||||
CommandInterface $command = null
|
||||
) {
|
||||
$data = $this->genericHandler($response);
|
||||
|
||||
// Merge in error data from the JSON body
|
||||
|
@ -30,6 +44,15 @@ class RestJsonErrorParser
|
|||
$data['code'] = $colon ? substr($code, 0, $colon) : $code;
|
||||
}
|
||||
|
||||
// Retrieve error message directly
|
||||
$data['message'] = isset($data['parsed']['message'])
|
||||
? $data['parsed']['message']
|
||||
: (isset($data['parsed']['Message'])
|
||||
? $data['parsed']['Message']
|
||||
: null);
|
||||
|
||||
$this->populateShape($data, $response, $command);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,34 +2,50 @@
|
|||
namespace Aws\Api\ErrorParser;
|
||||
|
||||
use Aws\Api\Parser\PayloadParserTrait;
|
||||
use Aws\Api\Parser\XmlParser;
|
||||
use Aws\Api\Service;
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\CommandInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Parses XML errors.
|
||||
*/
|
||||
class XmlErrorParser
|
||||
class XmlErrorParser extends AbstractErrorParser
|
||||
{
|
||||
use PayloadParserTrait;
|
||||
|
||||
public function __invoke(ResponseInterface $response)
|
||||
protected $parser;
|
||||
|
||||
public function __construct(Service $api = null, XmlParser $parser = null)
|
||||
{
|
||||
parent::__construct($api);
|
||||
$this->parser = $parser ?: new XmlParser();
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
ResponseInterface $response,
|
||||
CommandInterface $command = null
|
||||
) {
|
||||
$code = (string) $response->getStatusCode();
|
||||
|
||||
$data = [
|
||||
'type' => $code[0] == '4' ? 'client' : 'server',
|
||||
'request_id' => null,
|
||||
'code' => null,
|
||||
'message' => null,
|
||||
'parsed' => null
|
||||
'type' => $code[0] == '4' ? 'client' : 'server',
|
||||
'request_id' => null,
|
||||
'code' => null,
|
||||
'message' => null,
|
||||
'parsed' => null
|
||||
];
|
||||
|
||||
$body = $response->getBody();
|
||||
if ($body->getSize() > 0) {
|
||||
$this->parseBody($this->parseXml($body), $data);
|
||||
$this->parseBody($this->parseXml($body, $response), $data);
|
||||
} else {
|
||||
$this->parseHeaders($response, $data);
|
||||
}
|
||||
|
||||
$this->populateShape($data, $response, $command);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -51,16 +67,7 @@ class XmlErrorParser
|
|||
private function parseBody(\SimpleXMLElement $body, array &$data)
|
||||
{
|
||||
$data['parsed'] = $body;
|
||||
|
||||
$namespaces = $body->getDocNamespaces();
|
||||
if (!isset($namespaces[''])) {
|
||||
$prefix = '';
|
||||
} else {
|
||||
// Account for the default namespace being defined and PHP not
|
||||
// being able to handle it :(.
|
||||
$body->registerXPathNamespace('ns', $namespaces['']);
|
||||
$prefix = 'ns:';
|
||||
}
|
||||
$prefix = $this->registerNamespacePrefix($body);
|
||||
|
||||
if ($tempXml = $body->xpath("//{$prefix}Code[1]")) {
|
||||
$data['code'] = (string) $tempXml[0];
|
||||
|
@ -71,12 +78,34 @@ class XmlErrorParser
|
|||
}
|
||||
|
||||
$tempXml = $body->xpath("//{$prefix}RequestId[1]");
|
||||
if (empty($tempXml)) {
|
||||
$tempXml = $body->xpath("//{$prefix}RequestID[1]");
|
||||
}
|
||||
|
||||
if (isset($tempXml[0])) {
|
||||
$data['request_id'] = (string) $tempXml[0];
|
||||
$data['request_id'] = (string)$tempXml[0];
|
||||
}
|
||||
}
|
||||
|
||||
protected function registerNamespacePrefix(\SimpleXMLElement $element)
|
||||
{
|
||||
$namespaces = $element->getDocNamespaces();
|
||||
if (!isset($namespaces[''])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Account for the default namespace being defined and PHP not
|
||||
// being able to handle it :(.
|
||||
$element->registerXPathNamespace('ns', $namespaces['']);
|
||||
return 'ns:';
|
||||
}
|
||||
|
||||
protected function payload(
|
||||
ResponseInterface $response,
|
||||
StructureShape $member
|
||||
) {
|
||||
$xmlBody = $this->parseXml($response->getBody(), $response);
|
||||
$prefix = $this->registerNamespacePrefix($xmlBody);
|
||||
$errorBody = $xmlBody->xpath("//{$prefix}Error");
|
||||
|
||||
if (is_array($errorBody) && !empty($errorBody[0])) {
|
||||
return $this->parser->parse($member, $errorBody[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
namespace Aws\Api\Parser;
|
||||
|
||||
use Aws\Api\Service;
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\CommandInterface;
|
||||
use Aws\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -14,6 +16,9 @@ abstract class AbstractParser
|
|||
/** @var \Aws\Api\Service Representation of the service API*/
|
||||
protected $api;
|
||||
|
||||
/** @var callable */
|
||||
protected $parser;
|
||||
|
||||
/**
|
||||
* @param Service $api Service description.
|
||||
*/
|
||||
|
@ -32,4 +37,10 @@ abstract class AbstractParser
|
|||
CommandInterface $command,
|
||||
ResponseInterface $response
|
||||
);
|
||||
|
||||
abstract public function parseMemberFromStream(
|
||||
StreamInterface $stream,
|
||||
StructureShape $member,
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use Psr\Http\Message\ResponseInterface;
|
|||
abstract class AbstractRestParser extends AbstractParser
|
||||
{
|
||||
use PayloadParserTrait;
|
||||
|
||||
/**
|
||||
* Parses a payload from a response.
|
||||
*
|
||||
|
@ -73,7 +74,13 @@ abstract class AbstractRestParser extends AbstractParser
|
|||
) {
|
||||
$member = $output->getMember($payload);
|
||||
|
||||
if ($member instanceof StructureShape) {
|
||||
if (!empty($member['eventstream'])) {
|
||||
$result[$payload] = new EventParsingIterator(
|
||||
$response->getBody(),
|
||||
$member,
|
||||
$this
|
||||
);
|
||||
} else if ($member instanceof StructureShape) {
|
||||
// Structure members parse top-level data into a specific key.
|
||||
$result[$payload] = [];
|
||||
$this->payload($response, $member, $result[$payload]);
|
||||
|
@ -110,7 +117,10 @@ abstract class AbstractRestParser extends AbstractParser
|
|||
break;
|
||||
case 'timestamp':
|
||||
try {
|
||||
$value = new DateTimeResult($value);
|
||||
$value = DateTimeResult::fromTimestamp(
|
||||
$value,
|
||||
!empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null
|
||||
);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
// If the value cannot be parsed, then do not add it to the
|
||||
|
@ -118,10 +128,21 @@ abstract class AbstractRestParser extends AbstractParser
|
|||
return;
|
||||
}
|
||||
case 'string':
|
||||
if ($shape['jsonvalue']) {
|
||||
$value = $this->parseJson(base64_decode($value));
|
||||
try {
|
||||
if ($shape['jsonvalue']) {
|
||||
$value = $this->parseJson(base64_decode($value), $response);
|
||||
}
|
||||
|
||||
// If value is not set, do not add to output structure.
|
||||
if (!isset($value)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
//If the value cannot be parsed, then do not add it to the
|
||||
//output structure.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$result[$name] = $value;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<?php
|
||||
namespace Aws\Api\Parser;
|
||||
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\CommandInterface;
|
||||
use Aws\Exception\AwsException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use GuzzleHttp\Psr7;
|
||||
|
||||
/**
|
||||
|
@ -11,9 +13,6 @@ use GuzzleHttp\Psr7;
|
|||
*/
|
||||
class Crc32ValidatingParser extends AbstractParser
|
||||
{
|
||||
/** @var callable */
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @param callable $parser Parser to wrap.
|
||||
*/
|
||||
|
@ -44,4 +43,12 @@ class Crc32ValidatingParser extends AbstractParser
|
|||
$fn = $this->parser;
|
||||
return $fn($command, $response);
|
||||
}
|
||||
|
||||
public function parseMemberFromStream(
|
||||
StreamInterface $stream,
|
||||
StructureShape $member,
|
||||
$response
|
||||
) {
|
||||
return $this->parser->parseMemberFromStream($stream, $member, $response);
|
||||
}
|
||||
}
|
||||
|
|
335
aws/Aws/Api/Parser/DecodingEventStreamIterator.php
Normal file
335
aws/Aws/Api/Parser/DecodingEventStreamIterator.php
Normal file
|
@ -0,0 +1,335 @@
|
|||
<?php
|
||||
|
||||
namespace Aws\Api\Parser;
|
||||
|
||||
use \Iterator;
|
||||
use Aws\Api\DateTimeResult;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Aws\Api\Parser\Exception\ParserException;
|
||||
|
||||
/**
|
||||
* @internal Implements a decoder for a binary encoded event stream that will
|
||||
* decode, validate, and provide individual events from the stream.
|
||||
*/
|
||||
class DecodingEventStreamIterator implements Iterator
|
||||
{
|
||||
const HEADERS = 'headers';
|
||||
const PAYLOAD = 'payload';
|
||||
|
||||
const LENGTH_TOTAL = 'total_length';
|
||||
const LENGTH_HEADERS = 'headers_length';
|
||||
|
||||
const CRC_PRELUDE = 'prelude_crc';
|
||||
|
||||
const BYTES_PRELUDE = 12;
|
||||
const BYTES_TRAILING = 4;
|
||||
|
||||
private static $preludeFormat = [
|
||||
self::LENGTH_TOTAL => 'decodeUint32',
|
||||
self::LENGTH_HEADERS => 'decodeUint32',
|
||||
self::CRC_PRELUDE => 'decodeUint32',
|
||||
];
|
||||
|
||||
private static $lengthFormatMap = [
|
||||
1 => 'decodeUint8',
|
||||
2 => 'decodeUint16',
|
||||
4 => 'decodeUint32',
|
||||
8 => 'decodeUint64',
|
||||
];
|
||||
|
||||
private static $headerTypeMap = [
|
||||
0 => 'decodeBooleanTrue',
|
||||
1 => 'decodeBooleanFalse',
|
||||
2 => 'decodeInt8',
|
||||
3 => 'decodeInt16',
|
||||
4 => 'decodeInt32',
|
||||
5 => 'decodeInt64',
|
||||
6 => 'decodeBytes',
|
||||
7 => 'decodeString',
|
||||
8 => 'decodeTimestamp',
|
||||
9 => 'decodeUuid',
|
||||
];
|
||||
|
||||
/** @var StreamInterface Stream of eventstream shape to parse. */
|
||||
private $stream;
|
||||
|
||||
/** @var array Currently parsed event. */
|
||||
private $currentEvent;
|
||||
|
||||
/** @var int Current in-order event key. */
|
||||
private $key;
|
||||
|
||||
/** @var resource|\HashContext CRC32 hash context for event validation */
|
||||
private $hashContext;
|
||||
|
||||
/** @var int $currentPosition */
|
||||
private $currentPosition;
|
||||
|
||||
/**
|
||||
* DecodingEventStreamIterator constructor.
|
||||
*
|
||||
* @param StreamInterface $stream
|
||||
*/
|
||||
public function __construct(StreamInterface $stream)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
private function parseHeaders($headerBytes)
|
||||
{
|
||||
$headers = [];
|
||||
$bytesRead = 0;
|
||||
|
||||
while ($bytesRead < $headerBytes) {
|
||||
list($key, $numBytes) = $this->decodeString(1);
|
||||
$bytesRead += $numBytes;
|
||||
|
||||
list($type, $numBytes) = $this->decodeUint8();
|
||||
$bytesRead += $numBytes;
|
||||
|
||||
$f = self::$headerTypeMap[$type];
|
||||
list($value, $numBytes) = $this->{$f}();
|
||||
$bytesRead += $numBytes;
|
||||
|
||||
if (isset($headers[$key])) {
|
||||
throw new ParserException('Duplicate key in event headers.');
|
||||
}
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
|
||||
return [$headers, $bytesRead];
|
||||
}
|
||||
|
||||
private function parsePrelude()
|
||||
{
|
||||
$prelude = [];
|
||||
$bytesRead = 0;
|
||||
|
||||
$calculatedCrc = null;
|
||||
foreach (self::$preludeFormat as $key => $decodeFunction) {
|
||||
if ($key === self::CRC_PRELUDE) {
|
||||
$hashCopy = hash_copy($this->hashContext);
|
||||
$calculatedCrc = hash_final($this->hashContext, true);
|
||||
$this->hashContext = $hashCopy;
|
||||
}
|
||||
list($value, $numBytes) = $this->{$decodeFunction}();
|
||||
$bytesRead += $numBytes;
|
||||
|
||||
$prelude[$key] = $value;
|
||||
}
|
||||
|
||||
if (unpack('N', $calculatedCrc)[1] !== $prelude[self::CRC_PRELUDE]) {
|
||||
throw new ParserException('Prelude checksum mismatch.');
|
||||
}
|
||||
|
||||
return [$prelude, $bytesRead];
|
||||
}
|
||||
|
||||
private function parseEvent()
|
||||
{
|
||||
$event = [];
|
||||
|
||||
if ($this->stream->tell() < $this->stream->getSize()) {
|
||||
$this->hashContext = hash_init('crc32b');
|
||||
|
||||
$bytesLeft = $this->stream->getSize() - $this->stream->tell();
|
||||
list($prelude, $numBytes) = $this->parsePrelude();
|
||||
if ($prelude[self::LENGTH_TOTAL] > $bytesLeft) {
|
||||
throw new ParserException('Message length too long.');
|
||||
}
|
||||
$bytesLeft -= $numBytes;
|
||||
|
||||
if ($prelude[self::LENGTH_HEADERS] > $bytesLeft) {
|
||||
throw new ParserException('Headers length too long.');
|
||||
}
|
||||
|
||||
list(
|
||||
$event[self::HEADERS],
|
||||
$numBytes
|
||||
) = $this->parseHeaders($prelude[self::LENGTH_HEADERS]);
|
||||
|
||||
$event[self::PAYLOAD] = Psr7\stream_for(
|
||||
$this->readAndHashBytes(
|
||||
$prelude[self::LENGTH_TOTAL] - self::BYTES_PRELUDE
|
||||
- $numBytes - self::BYTES_TRAILING
|
||||
)
|
||||
);
|
||||
|
||||
$calculatedCrc = hash_final($this->hashContext, true);
|
||||
$messageCrc = $this->stream->read(4);
|
||||
if ($calculatedCrc !== $messageCrc) {
|
||||
throw new ParserException('Message checksum mismatch.');
|
||||
}
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
// Iterator Functionality
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->currentEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
$this->currentPosition = $this->stream->tell();
|
||||
if ($this->valid()) {
|
||||
$this->key++;
|
||||
$this->currentEvent = $this->parseEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->stream->rewind();
|
||||
$this->key = 0;
|
||||
$this->currentPosition = 0;
|
||||
$this->currentEvent = $this->parseEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->currentPosition < $this->stream->getSize();
|
||||
}
|
||||
|
||||
// Decoding Utilities
|
||||
|
||||
private function readAndHashBytes($num)
|
||||
{
|
||||
$bytes = $this->stream->read($num);
|
||||
hash_update($this->hashContext, $bytes);
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
private function decodeBooleanTrue()
|
||||
{
|
||||
return [true, 0];
|
||||
}
|
||||
|
||||
private function decodeBooleanFalse()
|
||||
{
|
||||
return [false, 0];
|
||||
}
|
||||
|
||||
private function uintToInt($val, $size)
|
||||
{
|
||||
$signedCap = pow(2, $size - 1);
|
||||
if ($val > $signedCap) {
|
||||
$val -= (2 * $signedCap);
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
private function decodeInt8()
|
||||
{
|
||||
$val = (int)unpack('C', $this->readAndHashBytes(1))[1];
|
||||
return [$this->uintToInt($val, 8), 1];
|
||||
}
|
||||
|
||||
private function decodeUint8()
|
||||
{
|
||||
return [unpack('C', $this->readAndHashBytes(1))[1], 1];
|
||||
}
|
||||
|
||||
private function decodeInt16()
|
||||
{
|
||||
$val = (int)unpack('n', $this->readAndHashBytes(2))[1];
|
||||
return [$this->uintToInt($val, 16), 2];
|
||||
}
|
||||
|
||||
private function decodeUint16()
|
||||
{
|
||||
return [unpack('n', $this->readAndHashBytes(2))[1], 2];
|
||||
}
|
||||
|
||||
private function decodeInt32()
|
||||
{
|
||||
$val = (int)unpack('N', $this->readAndHashBytes(4))[1];
|
||||
return [$this->uintToInt($val, 32), 4];
|
||||
}
|
||||
|
||||
private function decodeUint32()
|
||||
{
|
||||
return [unpack('N', $this->readAndHashBytes(4))[1], 4];
|
||||
}
|
||||
|
||||
private function decodeInt64()
|
||||
{
|
||||
$val = $this->unpackInt64($this->readAndHashBytes(8))[1];
|
||||
return [$this->uintToInt($val, 64), 8];
|
||||
}
|
||||
|
||||
private function decodeUint64()
|
||||
{
|
||||
return [$this->unpackInt64($this->readAndHashBytes(8))[1], 8];
|
||||
}
|
||||
|
||||
private function unpackInt64($bytes)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.6.3', '<')) {
|
||||
$d = unpack('N2', $bytes);
|
||||
return [1 => $d[1] << 32 | $d[2]];
|
||||
}
|
||||
return unpack('J', $bytes);
|
||||
}
|
||||
|
||||
private function decodeBytes($lengthBytes=2)
|
||||
{
|
||||
if (!isset(self::$lengthFormatMap[$lengthBytes])) {
|
||||
throw new ParserException('Undefined variable length format.');
|
||||
}
|
||||
$f = self::$lengthFormatMap[$lengthBytes];
|
||||
list($len, $bytes) = $this->{$f}();
|
||||
return [$this->readAndHashBytes($len), $len + $bytes];
|
||||
}
|
||||
|
||||
private function decodeString($lengthBytes=2)
|
||||
{
|
||||
if (!isset(self::$lengthFormatMap[$lengthBytes])) {
|
||||
throw new ParserException('Undefined variable length format.');
|
||||
}
|
||||
$f = self::$lengthFormatMap[$lengthBytes];
|
||||
list($len, $bytes) = $this->{$f}();
|
||||
return [$this->readAndHashBytes($len), $len + $bytes];
|
||||
}
|
||||
|
||||
private function decodeTimestamp()
|
||||
{
|
||||
list($val, $bytes) = $this->decodeInt64();
|
||||
return [
|
||||
DateTimeResult::createFromFormat('U.u', $val / 1000),
|
||||
$bytes
|
||||
];
|
||||
}
|
||||
|
||||
private function decodeUuid()
|
||||
{
|
||||
$val = unpack('H32', $this->readAndHashBytes(16))[1];
|
||||
return [
|
||||
substr($val, 0, 8) . '-'
|
||||
. substr($val, 8, 4) . '-'
|
||||
. substr($val, 12, 4) . '-'
|
||||
. substr($val, 16, 4) . '-'
|
||||
. substr($val, 20, 12),
|
||||
16
|
||||
];
|
||||
}
|
||||
}
|
107
aws/Aws/Api/Parser/EventParsingIterator.php
Normal file
107
aws/Aws/Api/Parser/EventParsingIterator.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace Aws\Api\Parser;
|
||||
|
||||
use \Iterator;
|
||||
use Aws\Exception\EventStreamDataException;
|
||||
use Aws\Api\Parser\Exception\ParserException;
|
||||
use Aws\Api\StructureShape;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal Implements a decoder for a binary encoded event stream that will
|
||||
* decode, validate, and provide individual events from the stream.
|
||||
*/
|
||||
class EventParsingIterator implements Iterator
|
||||
{
|
||||
/** @var StreamInterface */
|
||||
private $decodingIterator;
|
||||
|
||||
/** @var StructureShape */
|
||||
private $shape;
|
||||
|
||||
/** @var AbstractParser */
|
||||
private $parser;
|
||||
|
||||
public function __construct(
|
||||
StreamInterface $stream,
|
||||
StructureShape $shape,
|
||||
AbstractParser $parser
|
||||
) {
|
||||
$this->decodingIterator = new DecodingEventStreamIterator($stream);
|
||||
$this->shape = $shape;
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
public function current()
|
||||
{
|
||||
return $this->parseEvent($this->decodingIterator->current());
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->decodingIterator->key();
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
$this->decodingIterator->next();
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->decodingIterator->rewind();
|
||||
}
|
||||
|
||||
public function valid()
|
||||
{
|
||||
return $this->decodingIterator->valid();
|
||||
}
|
||||
|
||||
private function parseEvent(array $event)
|
||||
{
|
||||
if (!empty($event['headers'][':message-type'])) {
|
||||
if ($event['headers'][':message-type'] === 'error') {
|
||||
return $this->parseError($event);
|
||||
}
|
||||
if ($event['headers'][':message-type'] !== 'event') {
|
||||
throw new ParserException('Failed to parse unknown message type.');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($event['headers'][':event-type'])) {
|
||||
throw new ParserException('Failed to parse without event type.');
|
||||
}
|
||||
$eventShape = $this->shape->getMember($event['headers'][':event-type']);
|
||||
|
||||
$parsedEvent = [];
|
||||
foreach ($eventShape['members'] as $shape => $details) {
|
||||
if (!empty($details['eventpayload'])) {
|
||||
$payloadShape = $eventShape->getMember($shape);
|
||||
if ($payloadShape['type'] === 'blob') {
|
||||
$parsedEvent[$shape] = $event['payload'];
|
||||
} else {
|
||||
$parsedEvent[$shape] = $this->parser->parseMemberFromStream(
|
||||
$event['payload'],
|
||||
$payloadShape,
|
||||
null
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$parsedEvent[$shape] = $event['headers'][$shape];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
$event['headers'][':event-type'] => $parsedEvent
|
||||
];
|
||||
}
|
||||
|
||||
private function parseError(array $event)
|
||||
{
|
||||
throw new EventStreamDataException(
|
||||
$event['headers'][':error-code'],
|
||||
$event['headers'][':error-message']
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,56 @@
|
|||
<?php
|
||||
namespace Aws\Api\Parser\Exception;
|
||||
|
||||
class ParserException extends \RuntimeException {}
|
||||
use Aws\HasMonitoringEventsTrait;
|
||||
use Aws\MonitoringEventsInterface;
|
||||
use Aws\ResponseContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class ParserException extends \RuntimeException implements
|
||||
MonitoringEventsInterface,
|
||||
ResponseContainerInterface
|
||||
{
|
||||
use HasMonitoringEventsTrait;
|
||||
|
||||
private $errorCode;
|
||||
private $requestId;
|
||||
private $response;
|
||||
|
||||
public function __construct($message = '', $code = 0, $previous = null, array $context = [])
|
||||
{
|
||||
$this->errorCode = isset($context['error_code']) ? $context['error_code'] : null;
|
||||
$this->requestId = isset($context['request_id']) ? $context['request_id'] : null;
|
||||
$this->response = isset($context['response']) ? $context['response'] : null;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error code, if any.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getErrorCode()
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request ID, if any.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRequestId()
|
||||
{
|
||||
return $this->requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the received HTTP response if any.
|
||||
*
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,10 @@ class JsonParser
|
|||
return $target;
|
||||
|
||||
case 'timestamp':
|
||||
// The Unix epoch (or Unix time or POSIX time or Unix
|
||||
// timestamp) is the number of seconds that have elapsed since
|
||||
// January 1, 1970 (midnight UTC/GMT).
|
||||
return DateTimeResult::fromEpoch($value);
|
||||
return DateTimeResult::fromTimestamp(
|
||||
$value,
|
||||
!empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null
|
||||
);
|
||||
|
||||
case 'blob':
|
||||
return base64_decode($value);
|
||||
|
@ -56,3 +56,4 @@ class JsonParser
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<?php
|
||||
namespace Aws\Api\Parser;
|
||||
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\Api\Service;
|
||||
use Aws\Result;
|
||||
use Aws\CommandInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal Implements JSON-RPC parsing (e.g., DynamoDB)
|
||||
|
@ -13,8 +15,6 @@ class JsonRpcParser extends AbstractParser
|
|||
{
|
||||
use PayloadParserTrait;
|
||||
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @param Service $api Service description
|
||||
* @param JsonParser $parser JSON body builder
|
||||
|
@ -32,11 +32,20 @@ class JsonRpcParser extends AbstractParser
|
|||
$operation = $this->api->getOperation($command->getName());
|
||||
$result = null === $operation['output']
|
||||
? null
|
||||
: $this->parser->parse(
|
||||
: $this->parseMemberFromStream(
|
||||
$response->getBody(),
|
||||
$operation->getOutput(),
|
||||
$this->parseJson($response->getBody())
|
||||
$response
|
||||
);
|
||||
|
||||
return new Result($result ?: []);
|
||||
}
|
||||
|
||||
public function parseMemberFromStream(
|
||||
StreamInterface $stream,
|
||||
StructureShape $member,
|
||||
$response
|
||||
) {
|
||||
return $this->parser->parse($member, $this->parseJson($stream, $response));
|
||||
}
|
||||
}
|
||||
|
|
90
aws/Aws/Api/Parser/MetadataParserTrait.php
Normal file
90
aws/Aws/Api/Parser/MetadataParserTrait.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
namespace Aws\Api\Parser;
|
||||
|
||||
use Aws\Api\DateTimeResult;
|
||||
use Aws\Api\Shape;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
trait MetadataParserTrait
|
||||
{
|
||||
/**
|
||||
* Extract a single header from the response into the result.
|
||||
*/
|
||||
protected function extractHeader(
|
||||
$name,
|
||||
Shape $shape,
|
||||
ResponseInterface $response,
|
||||
&$result
|
||||
) {
|
||||
$value = $response->getHeaderLine($shape['locationName'] ?: $name);
|
||||
|
||||
switch ($shape->getType()) {
|
||||
case 'float':
|
||||
case 'double':
|
||||
$value = (float) $value;
|
||||
break;
|
||||
case 'long':
|
||||
$value = (int) $value;
|
||||
break;
|
||||
case 'boolean':
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
break;
|
||||
case 'blob':
|
||||
$value = base64_decode($value);
|
||||
break;
|
||||
case 'timestamp':
|
||||
try {
|
||||
$value = DateTimeResult::fromTimestamp(
|
||||
$value,
|
||||
!empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null
|
||||
);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
// If the value cannot be parsed, then do not add it to the
|
||||
// output structure.
|
||||
return;
|
||||
}
|
||||
case 'string':
|
||||
if ($shape['jsonvalue']) {
|
||||
$value = $this->parseJson(base64_decode($value), $response);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$result[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a map of headers with an optional prefix from the response.
|
||||
*/
|
||||
protected function extractHeaders(
|
||||
$name,
|
||||
Shape $shape,
|
||||
ResponseInterface $response,
|
||||
&$result
|
||||
) {
|
||||
// Check if the headers are prefixed by a location name
|
||||
$result[$name] = [];
|
||||
$prefix = $shape['locationName'];
|
||||
$prefixLen = strlen($prefix);
|
||||
|
||||
foreach ($response->getHeaders() as $k => $values) {
|
||||
if (!$prefixLen) {
|
||||
$result[$name][$k] = implode(', ', $values);
|
||||
} elseif (stripos($k, $prefix) === 0) {
|
||||
$result[$name][substr($k, $prefixLen)] = implode(', ', $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the status code of the response into the result array.
|
||||
*/
|
||||
protected function extractStatus(
|
||||
$name,
|
||||
ResponseInterface $response,
|
||||
array &$result
|
||||
) {
|
||||
$result[$name] = (int) $response->getStatusCode();
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
namespace Aws\Api\Parser;
|
||||
|
||||
use Aws\Api\Parser\Exception\ParserException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
trait PayloadParserTrait
|
||||
{
|
||||
|
@ -12,13 +13,17 @@ trait PayloadParserTrait
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseJson($json)
|
||||
private function parseJson($json, $response)
|
||||
{
|
||||
$jsonPayload = json_decode($json, true);
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new ParserException('Error parsing JSON: '
|
||||
. json_last_error_msg());
|
||||
throw new ParserException(
|
||||
'Error parsing JSON: ' . json_last_error_msg(),
|
||||
0,
|
||||
null,
|
||||
['response' => $response]
|
||||
);
|
||||
}
|
||||
|
||||
return $jsonPayload;
|
||||
|
@ -31,7 +36,7 @@ trait PayloadParserTrait
|
|||
*
|
||||
* @return \SimpleXMLElement
|
||||
*/
|
||||
private function parseXml($xml)
|
||||
protected function parseXml($xml, $response)
|
||||
{
|
||||
$priorSetting = libxml_use_internal_errors(true);
|
||||
try {
|
||||
|
@ -41,7 +46,12 @@ trait PayloadParserTrait
|
|||
throw new \RuntimeException($error->message);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new ParserException("Error parsing XML: {$e->getMessage()}", 0, $e);
|
||||
throw new ParserException(
|
||||
"Error parsing XML: {$e->getMessage()}",
|
||||
0,
|
||||
$e,
|
||||
['response' => $response]
|
||||
);
|
||||
} finally {
|
||||
libxml_use_internal_errors($priorSetting);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
namespace Aws\Api\Parser;
|
||||
|
||||
use Aws\Api\Service;
|
||||
use Aws\Api\StructureShape;
|
||||
use Aws\Result;
|
||||
use Aws\CommandInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal Parses query (XML) responses (e.g., EC2, SQS, and many others)
|
||||
|
@ -13,9 +15,6 @@ class QueryParser extends AbstractParser
|
|||
{
|
||||
use PayloadParserTrait;
|
||||
|
||||
/** @var XmlParser */
|
||||
private $xmlParser;
|
||||
|
||||
/** @var bool */
|
||||
private $honorResultWrapper;
|
||||
|
||||
|
@ -32,7 +31,7 @@ class QueryParser extends AbstractParser
|
|||
$honorResultWrapper = true
|
||||
) {
|
||||
parent::__construct($api);
|
||||
$this->xmlParser = $xmlParser ?: new XmlParser();
|
||||
$this->parser = $xmlParser ?: new XmlParser();
|
||||
$this->honorResultWrapper = $honorResultWrapper;
|
||||
}
|
||||
|
||||
|
@ -41,12 +40,21 @@ class QueryParser extends AbstractParser
|
|||
ResponseInterface $response
|
||||
) {
|
||||
$output = $this->api->getOperation($command->getName())->getOutput();
|
||||
$xml = $this->parseXml($response->getBody());
|
||||
$xml = $this->parseXml($response->getBody(), $response);
|
||||
|
||||
if ($this->honorResultWrapper && $output['resultWrapper']) {
|
||||
$xml = $xml->{$output['resultWrapper']};
|
||||
}
|
||||
|
||||
return new Result($this->xmlParser->parse($output, $xml));
|
||||
return new Result($this->parser->parse($output, $xml));
|
||||
}
|
||||
|
||||
public function parseMemberFromStream(
|
||||
StreamInterface $stream,
|
||||
StructureShape $member,
|
||||
$response
|
||||
) {
|
||||
$xml = $this->parseXml($stream, $response);
|
||||
return $this->parser->parse($member, $xml);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Aws\Api\Parser;
|
|||
use Aws\Api\Service;
|
||||
use Aws\Api\StructureShape;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal Implements REST-JSON parsing (e.g., Glacier, Elastic Transcoder)
|
||||
|
@ -12,9 +13,6 @@ class RestJsonParser extends AbstractRestParser
|
|||
{
|
||||
use PayloadParserTrait;
|
||||
|
||||
/** @var JsonParser */
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @param Service $api Service description
|
||||
* @param JsonParser $parser JSON body builder
|
||||
|
@ -30,10 +28,22 @@ class RestJsonParser extends AbstractRestParser
|
|||
StructureShape $member,
|
||||
array &$result
|
||||
) {
|
||||
$jsonBody = $this->parseJson($response->getBody());
|
||||
$jsonBody = $this->parseJson($response->getBody(), $response);
|
||||
|
||||
if ($jsonBody) {
|
||||
$result += $this->parser->parse($member, $jsonBody);
|
||||
}
|
||||
}
|
||||
|
||||
public function parseMemberFromStream(
|
||||
StreamInterface $stream,
|
||||
StructureShape $member,
|
||||
$response
|
||||
) {
|
||||
$jsonBody = $this->parseJson($stream, $response);
|
||||
if ($jsonBody) {
|
||||
return $this->parser->parse($member, $jsonBody);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Aws\Api\Parser;
|
|||
use Aws\Api\StructureShape;
|
||||
use Aws\Api\Service;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* @internal Implements REST-XML parsing (e.g., S3, CloudFront, etc...)
|
||||
|
@ -12,9 +13,6 @@ class RestXmlParser extends AbstractRestParser
|
|||
{
|
||||
use PayloadParserTrait;
|
||||
|
||||
/** @var XmlParser */
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @param Service $api Service description
|
||||
* @param XmlParser $parser XML body parser
|
||||
|
@ -30,7 +28,15 @@ class RestXmlParser extends AbstractRestParser
|
|||
StructureShape $member,
|
||||
array &$result
|
||||
) {
|
||||
$xml = $this->parseXml($response->getBody());
|
||||
$result += $this->parser->parse($member, $xml);
|
||||
$result += $this->parseMemberFromStream($response->getBody(), $member, $response);
|
||||
}
|
||||
|
||||
public function parseMemberFromStream(
|
||||
StreamInterface $stream,
|
||||
StructureShape $member,
|
||||
$response
|
||||
) {
|
||||
$xml = $this->parseXml($stream, $response);
|
||||
return $this->parser->parse($member, $xml);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Aws\Api\Parser;
|
|||
use Aws\Api\DateTimeResult;
|
||||
use Aws\Api\ListShape;
|
||||
use Aws\Api\MapShape;
|
||||
use Aws\Api\Parser\Exception\ParserException;
|
||||
use Aws\Api\Shape;
|
||||
use Aws\Api\StructureShape;
|
||||
|
||||
|
@ -50,6 +51,15 @@ class XmlParser
|
|||
$node = $this->memberKey($member, $name);
|
||||
if (isset($value->{$node})) {
|
||||
$target[$name] = $this->dispatch($member, $value->{$node});
|
||||
} else {
|
||||
$memberShape = $shape->getMember($name);
|
||||
if (!empty($memberShape['xmlAttribute'])) {
|
||||
$target[$name] = $this->parse_xml_attribute(
|
||||
$shape,
|
||||
$memberShape,
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,11 +134,38 @@ class XmlParser
|
|||
|
||||
private function parse_boolean(Shape $shape, $value)
|
||||
{
|
||||
return $value == 'true' ? true : false;
|
||||
return $value == 'true';
|
||||
}
|
||||
|
||||
private function parse_timestamp(Shape $shape, $value)
|
||||
{
|
||||
return new DateTimeResult($value);
|
||||
if (is_string($value)
|
||||
|| is_int($value)
|
||||
|| (is_object($value)
|
||||
&& method_exists($value, '__toString'))
|
||||
) {
|
||||
return DateTimeResult::fromTimestamp(
|
||||
(string) $value,
|
||||
!empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null
|
||||
);
|
||||
}
|
||||
throw new ParserException('Invalid timestamp value passed to XmlParser::parse_timestamp');
|
||||
}
|
||||
|
||||
private function parse_xml_attribute(Shape $shape, Shape $memberShape, $value)
|
||||
{
|
||||
$namespace = $shape['xmlNamespace']['uri']
|
||||
? $shape['xmlNamespace']['uri']
|
||||
: '';
|
||||
$prefix = $shape['xmlNamespace']['prefix']
|
||||
? $shape['xmlNamespace']['prefix']
|
||||
: '';
|
||||
if (!empty($prefix)) {
|
||||
$prefix .= ':';
|
||||
}
|
||||
$key = str_replace($prefix, '', $memberShape['locationName']);
|
||||
|
||||
$attributes = $value->attributes($namespace);
|
||||
return isset($attributes[$key]) ? (string) $attributes[$key] : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ class JsonBody
|
|||
= $this->format($valueShape, $v);
|
||||
}
|
||||
}
|
||||
if (empty($data)) {
|
||||
return new \stdClass;
|
||||
}
|
||||
return $data;
|
||||
|
||||
case 'list':
|
||||
|
@ -81,7 +84,10 @@ class JsonBody
|
|||
return base64_encode($value);
|
||||
|
||||
case 'timestamp':
|
||||
return TimestampShape::format($value, 'unixTimestamp');
|
||||
$timestampFormat = !empty($shape['timestampFormat'])
|
||||
? $shape['timestampFormat']
|
||||
: 'unixTimestamp';
|
||||
return TimestampShape::format($value, $timestampFormat);
|
||||
|
||||
default:
|
||||
return $value;
|
||||
|
|
|
@ -144,7 +144,10 @@ class QueryParamBuilder
|
|||
$prefix,
|
||||
array &$query
|
||||
) {
|
||||
$query[$prefix] = TimestampShape::format($value, 'iso8601');
|
||||
$timestampFormat = !empty($shape['timestampFormat'])
|
||||
? $shape['timestampFormat']
|
||||
: 'iso8601';
|
||||
$query[$prefix] = TimestampShape::format($value, $timestampFormat);
|
||||
}
|
||||
|
||||
protected function format_boolean(Shape $shape, $value, $prefix, array &$query)
|
||||
|
|
|
@ -27,7 +27,7 @@ class RestJsonSerializer extends RestSerializer
|
|||
JsonBody $jsonFormatter = null
|
||||
) {
|
||||
parent::__construct($api, $endpoint);
|
||||
$this->contentType = JsonBody::getContentType($api);
|
||||
$this->contentType = 'application/json';
|
||||
$this->jsonFormatter = $jsonFormatter ?: new JsonBody($api);
|
||||
}
|
||||
|
||||
|
|
|
@ -123,8 +123,11 @@ abstract class RestSerializer
|
|||
|
||||
private function applyHeader($name, Shape $member, $value, array &$opts)
|
||||
{
|
||||
if ($member->getType() == 'timestamp') {
|
||||
$value = TimestampShape::format($value, 'rfc822');
|
||||
if ($member->getType() === 'timestamp') {
|
||||
$timestampFormat = !empty($member['timestampFormat'])
|
||||
? $member['timestampFormat']
|
||||
: 'rfc822';
|
||||
$value = TimestampShape::format($value, $timestampFormat);
|
||||
}
|
||||
if ($member['jsonvalue']) {
|
||||
$value = json_encode($value);
|
||||
|
@ -157,8 +160,14 @@ abstract class RestSerializer
|
|||
? $opts['query'] + $value
|
||||
: $value;
|
||||
} elseif ($value !== null) {
|
||||
if ($member->getType() === 'boolean') {
|
||||
$type = $member->getType();
|
||||
if ($type === 'boolean') {
|
||||
$value = $value ? 'true' : 'false';
|
||||
} elseif ($type === 'timestamp') {
|
||||
$timestampFormat = !empty($member['timestampFormat'])
|
||||
? $member['timestampFormat']
|
||||
: 'iso8601';
|
||||
$value = TimestampShape::format($value, $timestampFormat);
|
||||
}
|
||||
|
||||
$opts['query'][$member['locationName'] ?: $name] = $value;
|
||||
|
@ -186,11 +195,13 @@ abstract class RestSerializer
|
|||
$k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];
|
||||
if (!isset($varspecs[$k])) {
|
||||
return '';
|
||||
} elseif ($isGreedy) {
|
||||
return str_replace('%2F', '/', rawurlencode($varspecs[$k]));
|
||||
} else {
|
||||
return rawurlencode($varspecs[$k]);
|
||||
}
|
||||
|
||||
if ($isGreedy) {
|
||||
return str_replace('%2F', '/', rawurlencode($varspecs[$k]));
|
||||
}
|
||||
|
||||
return rawurlencode($varspecs[$k]);
|
||||
},
|
||||
$operation['http']['requestUri']
|
||||
);
|
||||
|
@ -201,6 +212,12 @@ abstract class RestSerializer
|
|||
$relative .= strpos($relative, '?') ? "&{$append}" : "?$append";
|
||||
}
|
||||
|
||||
// If endpoint has path, remove leading '/' to preserve URI resolution.
|
||||
$path = $this->endpoint->getPath();
|
||||
if ($path && $relative[0] === '/') {
|
||||
$relative = substr($relative, 1);
|
||||
}
|
||||
|
||||
// Expand path place holders using Amazon's slightly different URI
|
||||
// template syntax.
|
||||
return UriResolver::resolve($this->endpoint, new Uri($relative));
|
||||
|
|
|
@ -80,7 +80,7 @@ class XmlBody
|
|||
private function defaultShape(Shape $shape, $name, $value, XMLWriter $xml)
|
||||
{
|
||||
$this->startElement($shape, $name, $xml);
|
||||
$xml->writeRaw($value);
|
||||
$xml->text($value);
|
||||
$xml->endElement();
|
||||
}
|
||||
|
||||
|
@ -187,7 +187,10 @@ class XmlBody
|
|||
XMLWriter $xml
|
||||
) {
|
||||
$this->startElement($shape, $name, $xml);
|
||||
$xml->writeRaw(TimestampShape::format($value, 'iso8601'));
|
||||
$timestampFormat = !empty($shape['timestampFormat'])
|
||||
? $shape['timestampFormat']
|
||||
: 'iso8601';
|
||||
$xml->writeRaw(TimestampShape::format($value, $timestampFormat));
|
||||
$xml->endElement();
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ class Service extends AbstractModel
|
|||
], $defaultMeta = [
|
||||
'apiVersion' => null,
|
||||
'serviceFullName' => null,
|
||||
'serviceId' => null,
|
||||
'endpointPrefix' => null,
|
||||
'signingName' => null,
|
||||
'signatureVersion' => null,
|
||||
|
@ -87,7 +88,9 @@ class Service extends AbstractModel
|
|||
|
||||
if (isset($mapping[$proto])) {
|
||||
return new $mapping[$proto]($api, $endpoint);
|
||||
} elseif ($proto == 'ec2') {
|
||||
}
|
||||
|
||||
if ($proto == 'ec2') {
|
||||
return new QuerySerializer($api, $endpoint, new Ec2ParamBuilder());
|
||||
}
|
||||
|
||||
|
@ -99,12 +102,14 @@ class Service extends AbstractModel
|
|||
/**
|
||||
* Creates an error parser for the given protocol.
|
||||
*
|
||||
* Redundant method signature to preserve backwards compatibility.
|
||||
*
|
||||
* @param string $protocol Protocol to parse (e.g., query, json, etc.)
|
||||
*
|
||||
* @return callable
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public static function createErrorParser($protocol)
|
||||
public static function createErrorParser($protocol, Service $api = null)
|
||||
{
|
||||
static $mapping = [
|
||||
'json' => 'Aws\Api\ErrorParser\JsonRpcErrorParser',
|
||||
|
@ -115,7 +120,7 @@ class Service extends AbstractModel
|
|||
];
|
||||
|
||||
if (isset($mapping[$protocol])) {
|
||||
return new $mapping[$protocol]();
|
||||
return new $mapping[$protocol]($api);
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException("Unknown protocol: $protocol");
|
||||
|
@ -140,7 +145,9 @@ class Service extends AbstractModel
|
|||
$proto = $api->getProtocol();
|
||||
if (isset($mapping[$proto])) {
|
||||
return new $mapping[$proto]($api);
|
||||
} elseif ($proto == 'ec2') {
|
||||
}
|
||||
|
||||
if ($proto == 'ec2') {
|
||||
return new QueryParser($api, null, false);
|
||||
}
|
||||
|
||||
|
@ -159,6 +166,16 @@ class Service extends AbstractModel
|
|||
return $this->definition['metadata']['serviceFullName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceId()
|
||||
{
|
||||
return $this->definition['metadata']['serviceId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API version of the service
|
||||
*
|
||||
|
@ -282,6 +299,24 @@ class Service extends AbstractModel
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the error shapes of the service
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrorShapes()
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->definition['shapes'] as $name => $definition) {
|
||||
if (!empty($definition['exception'])) {
|
||||
$definition['name'] = $name;
|
||||
$result[] = new StructureShape($definition, $this->getShapeMap());
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the service metadata or a specific metadata key value.
|
||||
*
|
||||
|
@ -293,7 +328,9 @@ class Service extends AbstractModel
|
|||
{
|
||||
if (!$key) {
|
||||
return $this['metadata'];
|
||||
} elseif (isset($this->definition['metadata'][$key])) {
|
||||
}
|
||||
|
||||
if (isset($this->definition['metadata'][$key])) {
|
||||
return $this->definition['metadata'][$key];
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,9 @@ class ShapeMap
|
|||
|
||||
$definition = $shapeRef + $this->definitions[$shape];
|
||||
$definition['name'] = $definition['shape'];
|
||||
unset($definition['shape']);
|
||||
if (isset($definition['shape'])) {
|
||||
unset($definition['shape']);
|
||||
}
|
||||
|
||||
$result = Shape::create($definition, $this);
|
||||
|
||||
|
|
|
@ -249,7 +249,20 @@ class Validator
|
|||
|
||||
private function checkAssociativeArray($value)
|
||||
{
|
||||
if (!is_array($value) || isset($value[0])) {
|
||||
$isAssociative = false;
|
||||
|
||||
if (is_array($value)) {
|
||||
$expectedIndex = 0;
|
||||
$key = key($value);
|
||||
|
||||
do {
|
||||
$isAssociative = $key !== $expectedIndex++;
|
||||
next($value);
|
||||
$key = key($value);
|
||||
} while (!$isAssociative && null !== $key);
|
||||
}
|
||||
|
||||
if (!$isAssociative) {
|
||||
$this->addError('must be an associative array. Found '
|
||||
. Aws\describe_type($value));
|
||||
return false;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue