updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -12,6 +12,9 @@ abstract class AbstractModel implements \ArrayAccess
/** @var ShapeMap */
protected $shapeMap;
/** @var array */
protected $contextParam;
/**
* @param array $definition Service description
* @param ShapeMap $shapeMap Shapemap used for creating shapes
@@ -20,6 +23,9 @@ abstract class AbstractModel implements \ArrayAccess
{
$this->definition = $definition;
$this->shapeMap = $shapeMap;
if (isset($definition['contextParam'])) {
$this->contextParam = $definition['contextParam'];
}
}
public function toArray()
@@ -27,22 +33,38 @@ abstract class AbstractModel implements \ArrayAccess
return $this->definition;
}
/**
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return isset($this->definition[$offset])
? $this->definition[$offset] : null;
}
/**
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->definition[$offset] = $value;
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->definition[$offset]);
}
/**
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
unset($this->definition[$offset]);

View File

@@ -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.

View File

@@ -1,6 +1,12 @@
<?php
namespace Aws\Api;
use Aws\Api\Parser\Exception\ParserException;
use DateTime;
use DateTimeZone;
use Exception;
/**
* DateTime overrides that make DateTime work more seamlessly as a string,
* with JSON documents, and with JMESPath.
@@ -9,14 +15,90 @@ class DateTimeResult extends \DateTime implements \JsonSerializable
{
/**
* Create a new DateTimeResult from a unix timestamp.
*
* @param $unixTimestamp
* 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
* @throws Exception
*/
public static function fromEpoch($unixTimestamp)
{
return new self(gmdate('c', $unixTimestamp));
if (!is_numeric($unixTimestamp)) {
throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromEpoch');
}
// PHP 5.5 does not support sub-second precision
if (\PHP_VERSION_ID < 56000) {
return new self(gmdate('c', $unixTimestamp));
}
$decimalSeparator = isset(localeconv()['decimal_point']) ? localeconv()['decimal_point'] : ".";
$formatString = "U" . $decimalSeparator . "u";
$dateTime = DateTime::createFromFormat(
$formatString,
sprintf('%0.6f', $unixTimestamp),
new DateTimeZone('UTC')
);
if (false === $dateTime) {
throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromEpoch');
}
return new self(
$dateTime->format('Y-m-d H:i:s.u'),
new DateTimeZone('UTC')
);
}
/**
* @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.
*
* @return DateTimeResult
* @throws 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');
}
}
/**
@@ -32,8 +114,9 @@ class DateTimeResult extends \DateTime implements \JsonSerializable
/**
* Serialize the date as an ISO 8601 date when serializing as JSON.
*
* @return mixed|string
* @return string
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return (string) $this;

View File

@@ -109,7 +109,7 @@ class DocModel
return '';
}
$tidy = new \Tidy();
$tidy = new \tidy();
$tidy->parseString($content, [
'indent' => true,
'doctype' => 'omit',

View 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;
}
}

View File

@@ -2,6 +2,7 @@
namespace Aws\Api\ErrorParser;
use Aws\Api\Parser\PayloadParserTrait;
use Aws\Api\StructureShape;
use Psr\Http\Message\ResponseInterface;
/**
@@ -14,13 +15,38 @@ trait JsonParserTrait
private function genericHandler(ResponseInterface $response)
{
$code = (string) $response->getStatusCode();
if ($this->api
&& $this->api->getMetadata('awsQueryCompatible')
&& $response->getHeaderLine('x-amzn-query-error')
) {
$queryError = $response->getHeaderLine('x-amzn-query-error');
$parts = explode(';', $queryError);
if (isset($parts) && count($parts) == 2 && $parts[0] && $parts[1]) {
$error_code = $parts[0];
$error_type = $parts[1];
}
}
if (!isset($error_type)) {
$error_type = $code[0] == '4' ? 'client' : 'server';
}
return [
'request_id' => (string) $response->getHeaderLine('x-amzn-requestid'),
'code' => null,
'code' => isset($error_code) ? $error_code : null,
'message' => null,
'type' => $code[0] == '4' ? 'client' : 'server',
'type' => $error_type,
'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);
}
}
}

View File

@@ -1,31 +1,49 @@
<?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']);
}
if (isset($data['parsed']['__type'])) {
$parts = explode('#', $data['parsed']['__type']);
$data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
if (!isset($data['code'])) {
$parts = explode('#', $data['parsed']['__type']);
$data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
}
$data['message'] = isset($data['parsed']['message'])
? $data['parsed']['message']
: null;
}
$this->populateShape($data, $response, $command);
return $data;
}
}

View File

@@ -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;
}
}

View File

@@ -2,25 +2,39 @@
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();
@@ -30,6 +44,8 @@ class XmlErrorParser
$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]);
}
}
}

View File

@@ -9,6 +9,8 @@ class Operation extends AbstractModel
private $input;
private $output;
private $errors;
private $staticContextParams = [];
private $contextParams;
public function __construct(array $definition, ShapeMap $shapeMap)
{
@@ -22,7 +24,12 @@ class Operation extends AbstractModel
$definition['http']['requestUri'] = '/';
}
if (isset($definition['staticContextParams'])) {
$this->staticContextParams = $definition['staticContextParams'];
}
parent::__construct($definition, $shapeMap);
$this->contextParams = $this->setContextParams();
}
/**
@@ -94,4 +101,42 @@ class Operation extends AbstractModel
return $this->errors;
}
/**
* Gets static modeled static values used for
* endpoint resolution.
*
* @return array
*/
public function getStaticContextParams()
{
return $this->staticContextParams;
}
/**
* Gets definition of modeled dynamic values used
* for endpoint resolution
*
* @return array
*/
public function getContextParams()
{
return $this->contextParams;
}
private function setContextParams()
{
$members = $this->getInput()->getMembers();
$contextParams = [];
foreach($members as $name => $shape) {
if (!empty($contextParam = $shape->getContextParam())) {
$contextParams[$contextParam['name']] = [
'shape' => $name,
'type' => $shape->getType()
];
}
}
return $contextParams;
}
}

View File

@@ -14,6 +14,7 @@ use Psr\Http\Message\ResponseInterface;
abstract class AbstractRestParser extends AbstractParser
{
use PayloadParserTrait;
/**
* Parses a payload from a response.
*
@@ -116,11 +117,10 @@ abstract class AbstractRestParser extends AbstractParser
break;
case 'timestamp':
try {
if (!empty($shape['timestampFormat'])
&& $shape['timestampFormat'] === 'unixTimestamp') {
$value = DateTimeResult::fromEpoch($value);
}
$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
@@ -128,10 +128,21 @@ abstract class AbstractRestParser extends AbstractParser
return;
}
case 'string':
if ($shape['jsonvalue']) {
$value = $this->parseJson(base64_decode($value), $response);
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;
@@ -149,7 +160,7 @@ abstract class AbstractRestParser extends AbstractParser
// Check if the headers are prefixed by a location name
$result[$name] = [];
$prefix = $shape['locationName'];
$prefixLen = strlen($prefix);
$prefixLen = $prefix !== null ? strlen($prefix) : 0;
foreach ($response->getHeaders() as $k => $values) {
if (!$prefixLen) {

View File

@@ -26,7 +26,7 @@ class Crc32ValidatingParser extends AbstractParser
ResponseInterface $response
) {
if ($expected = $response->getHeaderLine('x-amz-crc32')) {
$hash = hexdec(Psr7\hash($response->getBody(), 'crc32b'));
$hash = hexdec(Psr7\Utils::hash($response->getBody(), 'crc32b'));
if ($expected != $hash) {
throw new AwsException(
"crc32 mismatch. Expected {$expected}, found {$hash}.",

View File

@@ -60,7 +60,7 @@ class DecodingEventStreamIterator implements Iterator
/** @var int Current in-order event key. */
private $key;
/** @var resource|HashContext CRC32 hash context for event validation */
/** @var resource|\HashContext CRC32 hash context for event validation */
private $hashContext;
/** @var int $currentPosition */
@@ -150,7 +150,7 @@ class DecodingEventStreamIterator implements Iterator
$numBytes
) = $this->parseHeaders($prelude[self::LENGTH_HEADERS]);
$event[self::PAYLOAD] = Psr7\stream_for(
$event[self::PAYLOAD] = Psr7\Utils::streamFor(
$this->readAndHashBytes(
$prelude[self::LENGTH_TOTAL] - self::BYTES_PRELUDE
- $numBytes - self::BYTES_TRAILING
@@ -172,6 +172,7 @@ class DecodingEventStreamIterator implements Iterator
/**
* @return array
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->currentEvent;
@@ -180,11 +181,13 @@ class DecodingEventStreamIterator implements Iterator
/**
* @return int
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->key;
}
#[\ReturnTypeWillChange]
public function next()
{
$this->currentPosition = $this->stream->tell();
@@ -194,6 +197,7 @@ class DecodingEventStreamIterator implements Iterator
}
}
#[\ReturnTypeWillChange]
public function rewind()
{
$this->stream->rewind();
@@ -205,6 +209,7 @@ class DecodingEventStreamIterator implements Iterator
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->currentPosition < $this->stream->getSize();

View File

@@ -33,26 +33,31 @@ class EventParsingIterator implements Iterator
$this->parser = $parser;
}
#[\ReturnTypeWillChange]
public function current()
{
return $this->parseEvent($this->decodingIterator->current());
}
#[\ReturnTypeWillChange]
public function key()
{
return $this->decodingIterator->key();
}
#[\ReturnTypeWillChange]
public function next()
{
$this->decodingIterator->next();
}
#[\ReturnTypeWillChange]
public function rewind()
{
$this->decodingIterator->rewind();
}
#[\ReturnTypeWillChange]
public function valid()
{
return $this->decodingIterator->valid();

View File

@@ -4,6 +4,7 @@ namespace Aws\Api\Parser\Exception;
use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;
use Aws\ResponseContainerInterface;
use Psr\Http\Message\ResponseInterface;
class ParserException extends \RuntimeException implements
MonitoringEventsInterface,
@@ -11,14 +12,38 @@ class ParserException extends \RuntimeException implements
{
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.
*

View File

@@ -17,6 +17,9 @@ class JsonParser
switch ($shape['type']) {
case 'structure':
if (isset($shape['document']) && $shape['document']) {
return $value;
}
$target = [];
foreach ($shape->getMembers() as $name => $member) {
$locationName = $member['locationName'] ?: $name;
@@ -24,6 +27,15 @@ class JsonParser
$target[$name] = $this->parse($member, $value[$locationName]);
}
}
if (isset($shape['union'])
&& $shape['union']
&& is_array($value)
&& empty($target)
) {
foreach ($value as $key => $val) {
$target['Unknown'][$key] = $val;
}
}
return $target;
case 'list':
@@ -43,14 +55,10 @@ class JsonParser
return $target;
case 'timestamp':
if (!empty($shape['timestampFormat'])
&& $shape['timestampFormat'] !== 'unixTimestamp') {
return new DateTimeResult($value);
}
// 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);
@@ -60,3 +68,4 @@ class JsonParser
}
}
}

View 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();
}
}

View File

@@ -36,7 +36,7 @@ trait PayloadParserTrait
*
* @return \SimpleXMLElement
*/
private function parseXml($xml, $response)
protected function parseXml($xml, $response)
{
$priorSetting = libxml_use_internal_errors(true);
try {

View File

@@ -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,9 +51,26 @@ 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
);
}
}
}
if (isset($shape['union'])
&& $shape['union']
&& empty($target)
) {
foreach ($value as $key => $val) {
$name = $val->children()->getName();
$target['Unknown'][$name] = $val->$name;
}
}
return $target;
}
@@ -129,10 +147,33 @@ class XmlParser
private function parse_timestamp(Shape $shape, $value)
{
if (!empty($shape['timestampFormat'])
&& $shape['timestampFormat'] === 'unixTimestamp') {
return DateTimeResult::fromEpoch((string) $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
);
}
return new DateTimeResult($value);
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;
}
}

View File

@@ -12,7 +12,7 @@ class Ec2ParamBuilder extends QueryParamBuilder
protected function queryName(Shape $shape, $default = null)
{
return ($shape['queryName']
?: ucfirst($shape['locationName']))
?: ucfirst(@$shape['locationName'] ?: ""))
?: $default;
}

View File

@@ -4,6 +4,7 @@ namespace Aws\Api\Serializer;
use Aws\Api\Service;
use Aws\Api\Shape;
use Aws\Api\TimestampShape;
use Aws\Exception\InvalidJsonException;
/**
* Formats the JSON body of a JSON-REST or JSON-RPC operation.
@@ -27,8 +28,17 @@ class JsonBody
*/
public static function getContentType(Service $service)
{
return 'application/x-amz-json-'
. number_format($service->getMetadata('jsonVersion'), 1);
if ($service->getMetadata('protocol') === 'rest-json') {
return 'application/json';
}
$jsonVersion = $service->getMetadata('jsonVersion');
if (empty($jsonVersion)) {
throw new \InvalidArgumentException('invalid json');
} else {
return 'application/x-amz-json-'
. @number_format($service->getMetadata('jsonVersion'), 1);
}
}
/**
@@ -42,7 +52,6 @@ class JsonBody
public function build(Shape $shape, array $args)
{
$result = json_encode($this->format($shape, $args));
return $result == '[]' ? '{}' : $result;
}
@@ -51,6 +60,9 @@ class JsonBody
switch ($shape['type']) {
case 'structure':
$data = [];
if (isset($shape['document']) && $shape['document']) {
return $value;
}
foreach ($value as $k => $v) {
if ($v !== null && $shape->hasMember($k)) {
$valueShape = $shape->getMember($k);

View File

@@ -3,6 +3,8 @@ namespace Aws\Api\Serializer;
use Aws\Api\Service;
use Aws\CommandInterface;
use Aws\EndpointV2\EndpointProviderV2;
use Aws\EndpointV2\EndpointV2SerializerTrait;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
@@ -12,6 +14,8 @@ use Psr\Http\Message\RequestInterface;
*/
class JsonRpcSerializer
{
use EndpointV2SerializerTrait;
/** @var JsonBody */
private $jsonFormatter;
@@ -44,25 +48,44 @@ class JsonRpcSerializer
* When invoked with an AWS command, returns a serialization array
* containing "method", "uri", "headers", and "body" key value pairs.
*
* @param CommandInterface $command
* @param CommandInterface $command Command to serialize into a request.
* @param $endpointProvider Provider used for dynamic endpoint resolution.
* @param $clientArgs Client arguments used for dynamic endpoint resolution.
*
* @return RequestInterface
*/
public function __invoke(CommandInterface $command)
public function __invoke(
CommandInterface $command,
$endpointProvider = null,
$clientArgs = null
)
{
$name = $command->getName();
$operation = $this->api->getOperation($name);
$operationName = $command->getName();
$operation = $this->api->getOperation($operationName);
$commandArgs = $command->toArray();
$headers = [
'X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $operationName,
'Content-Type' => $this->contentType
];
if ($endpointProvider instanceof EndpointProviderV2) {
$this->setRequestOptions(
$endpointProvider,
$command,
$operation,
$commandArgs,
$clientArgs,
$headers
);
}
return new Request(
$operation['http']['method'],
$this->endpoint,
[
'X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $name,
'Content-Type' => $this->contentType
],
$headers,
$this->jsonFormatter->build(
$operation->getInput(),
$command->toArray()
$commandArgs
)
);
}

View File

@@ -3,6 +3,8 @@ namespace Aws\Api\Serializer;
use Aws\Api\Service;
use Aws\CommandInterface;
use Aws\EndpointV2\EndpointProviderV2;
use Aws\EndpointV2\EndpointV2SerializerTrait;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
@@ -12,6 +14,8 @@ use Psr\Http\Message\RequestInterface;
*/
class QuerySerializer
{
use EndpointV2SerializerTrait;
private $endpoint;
private $api;
private $paramBuilder;
@@ -30,39 +34,54 @@ class QuerySerializer
* When invoked with an AWS command, returns a serialization array
* containing "method", "uri", "headers", and "body" key value pairs.
*
* @param CommandInterface $command
* @param CommandInterface $command Command to serialize into a request.
* @param $endpointProvider Provider used for dynamic endpoint resolution.
* @param $clientArgs Client arguments used for dynamic endpoint resolution.
*
* @return RequestInterface
*/
public function __invoke(CommandInterface $command)
public function __invoke(
CommandInterface $command,
$endpointProvider = null,
$clientArgs = null
)
{
$operation = $this->api->getOperation($command->getName());
$body = [
'Action' => $command->getName(),
'Version' => $this->api->getMetadata('apiVersion')
];
$params = $command->toArray();
$commandArgs = $command->toArray();
// Only build up the parameters when there are parameters to build
if ($params) {
if ($commandArgs) {
$body += call_user_func(
$this->paramBuilder,
$operation->getInput(),
$params
$commandArgs
);
}
$body = http_build_query($body, '', '&', PHP_QUERY_RFC3986);
$headers = [
'Content-Length' => strlen($body),
'Content-Type' => 'application/x-www-form-urlencoded'
];
$body = http_build_query($body, null, '&', PHP_QUERY_RFC3986);
if ($endpointProvider instanceof EndpointProviderV2) {
$this->setRequestOptions(
$endpointProvider,
$command,
$operation,
$commandArgs,
$clientArgs,
$headers
);
}
return new Request(
'POST',
$this->endpoint,
[
'Content-Length' => strlen($body),
'Content-Type' => 'application/x-www-form-urlencoded'
],
$headers,
$body
);
}

View File

@@ -33,7 +33,10 @@ class RestJsonSerializer extends RestSerializer
protected function payload(StructureShape $member, array $value, array &$opts)
{
$body = isset($value) ?
((string) $this->jsonFormatter->build($member, $value))
: "{}";
$opts['headers']['Content-Type'] = $this->contentType;
$opts['body'] = (string) $this->jsonFormatter->build($member, $value);
$opts['body'] = $body;
}
}

View File

@@ -8,7 +8,10 @@ use Aws\Api\Shape;
use Aws\Api\StructureShape;
use Aws\Api\TimestampShape;
use Aws\CommandInterface;
use Aws\EndpointV2\EndpointProviderV2;
use Aws\EndpointV2\EndpointV2SerializerTrait;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7\UriResolver;
use Psr\Http\Message\RequestInterface;
@@ -19,10 +22,12 @@ use Psr\Http\Message\RequestInterface;
*/
abstract class RestSerializer
{
use EndpointV2SerializerTrait;
/** @var Service */
private $api;
/** @var Psr7\Uri */
/** @var Uri */
private $endpoint;
/**
@@ -32,25 +37,44 @@ abstract class RestSerializer
public function __construct(Service $api, $endpoint)
{
$this->api = $api;
$this->endpoint = Psr7\uri_for($endpoint);
$this->endpoint = Psr7\Utils::uriFor($endpoint);
}
/**
* @param CommandInterface $command Command to serialized
* @param CommandInterface $command Command to serialize into a request.
* @param $endpointProvider Provider used for dynamic endpoint resolution.
* @param $clientArgs Client arguments used for dynamic endpoint resolution.
*
* @return RequestInterface
*/
public function __invoke(CommandInterface $command)
public function __invoke(
CommandInterface $command,
$endpointProvider = null,
$clientArgs = null
)
{
$operation = $this->api->getOperation($command->getName());
$args = $command->toArray();
$opts = $this->serialize($operation, $args);
$uri = $this->buildEndpoint($operation, $args, $opts);
$commandArgs = $command->toArray();
$opts = $this->serialize($operation, $commandArgs);
$headers = isset($opts['headers']) ? $opts['headers'] : [];
return new Psr7\Request(
if ($endpointProvider instanceof EndpointProviderV2) {
$this->setRequestOptions(
$endpointProvider,
$command,
$operation,
$commandArgs,
$clientArgs,
$headers
);
$this->endpoint = new Uri($this->endpoint);
}
$uri = $this->buildEndpoint($operation, $commandArgs, $opts);
return new Request(
$operation['http']['method'],
$uri,
isset($opts['headers']) ? $opts['headers'] : [],
$headers,
isset($opts['body']) ? $opts['body'] : null
);
}
@@ -96,6 +120,8 @@ abstract class RestSerializer
if (isset($bodyMembers)) {
$this->payload($operation->getInput(), $bodyMembers, $opts);
} else if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) {
$this->payload($operation->getInput(), [], $opts);
}
return $opts;
@@ -114,7 +140,7 @@ abstract class RestSerializer
) {
// Streaming bodies or payloads that are strings are
// always just a stream of data.
$opts['body'] = Psr7\stream_for($args[$name]);
$opts['body'] = Psr7\Utils::streamFor($args[$name]);
return;
}
@@ -128,7 +154,10 @@ abstract class RestSerializer
? $member['timestampFormat']
: 'rfc822';
$value = TimestampShape::format($value, $timestampFormat);
} elseif ($member->getType() === 'boolean') {
$value = $value ? 'true' : 'false';
}
if ($member['jsonvalue']) {
$value = json_encode($value);
if (empty($value) && JSON_ERROR_NONE !== json_last_error()) {
@@ -176,44 +205,103 @@ abstract class RestSerializer
private function buildEndpoint(Operation $operation, array $args, array $opts)
{
$varspecs = [];
// Create an associative array of varspecs used in expansions
foreach ($operation->getInput()->getMembers() as $name => $member) {
if ($member['location'] == 'uri') {
$varspecs[$member['locationName'] ?: $name] =
isset($args[$name])
? $args[$name]
: null;
}
}
// Create an associative array of variable definitions used in expansions
$varDefinitions = $this->getVarDefinitions($operation, $args);
$relative = preg_replace_callback(
'/\{([^\}]+)\}/',
function (array $matches) use ($varspecs) {
function (array $matches) use ($varDefinitions) {
$isGreedy = substr($matches[1], -1, 1) == '+';
$k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];
if (!isset($varspecs[$k])) {
if (!isset($varDefinitions[$k])) {
return '';
}
if ($isGreedy) {
return str_replace('%2F', '/', rawurlencode($varspecs[$k]));
return str_replace('%2F', '/', rawurlencode($varDefinitions[$k]));
}
return rawurlencode($varspecs[$k]);
return rawurlencode($varDefinitions[$k]);
},
$operation['http']['requestUri']
);
// Add the query string variables or appending to one if needed.
if (!empty($opts['query'])) {
$append = Psr7\build_query($opts['query']);
$relative .= strpos($relative, '?') ? "&{$append}" : "?$append";
$relative = $this->appendQuery($opts['query'], $relative);
}
$path = $this->endpoint->getPath();
//Accounts for trailing '/' in path when custom endpoint
//is provided to endpointProviderV2
if ($this->api->isModifiedModel()
&& $this->api->getServiceName() === 's3'
) {
if (substr($path, -1) === '/' && $relative[0] === '/') {
$path = rtrim($path, '/');
}
$relative = $path . $relative;
}
// If endpoint has path, remove leading '/' to preserve URI resolution.
if ($path && $relative[0] === '/') {
$relative = substr($relative, 1);
}
//Append path to endpoint when leading '//...' present
// as uri cannot be properly resolved
if ($this->api->isModifiedModel()
&& strpos($relative, '//') === 0
) {
return new Uri($this->endpoint . $relative);
}
// Expand path place holders using Amazon's slightly different URI
// template syntax.
return UriResolver::resolve($this->endpoint, new Uri($relative));
}
/**
* @param StructureShape $input
*/
private function hasPayloadParam(StructureShape $input, $payload)
{
if ($payload) {
$potentiallyEmptyTypes = ['blob','string'];
if ($this->api->getMetadata('protocol') == 'rest-xml') {
$potentiallyEmptyTypes[] = 'structure';
}
$payloadMember = $input->getMember($payload);
if (in_array($payloadMember['type'], $potentiallyEmptyTypes)) {
return false;
}
}
foreach ($input->getMembers() as $member) {
if (!isset($member['location'])) {
return true;
}
}
return false;
}
private function appendQuery($query, $endpoint)
{
$append = Psr7\Query::build($query);
return $endpoint .= strpos($endpoint, '?') !== false ? "&{$append}" : "?{$append}";
}
private function getVarDefinitions($command, $args)
{
$varDefinitions = [];
foreach ($command->getInput()->getMembers() as $name => $member) {
if ($member['location'] == 'uri') {
$varDefinitions[$member['locationName'] ?: $name] =
isset($args[$name])
? $args[$name]
: null;
}
}
return $varDefinitions;
}
}

View File

@@ -29,6 +29,20 @@ class RestXmlSerializer extends RestSerializer
protected function payload(StructureShape $member, array $value, array &$opts)
{
$opts['headers']['Content-Type'] = 'application/xml';
$opts['body'] = (string) $this->xmlBody->build($member, $value);
$opts['body'] = $this->getXmlBody($member, $value);
}
/**
* @param StructureShape $member
* @param array $value
* @return string
*/
private function getXmlBody(StructureShape $member, array $value)
{
$xmlBody = (string)$this->xmlBody->build($member, $value);
$xmlBody = str_replace("'", "&apos;", $xmlBody);
$xmlBody = str_replace('\r', "&#13;", $xmlBody);
$xmlBody = str_replace('\n', "&#10;", $xmlBody);
return $xmlBody;
}
}

View File

@@ -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();
}

View File

@@ -19,6 +19,9 @@ class Service extends AbstractModel
/** @var string */
private $apiVersion;
/** @var array */
private $clientContextParams = [];
/** @var Operation[] */
private $operations = [];
@@ -28,6 +31,9 @@ class Service extends AbstractModel
/** @var array */
private $waiters = null;
/** @var boolean */
private $modifiedModel = false;
/**
* @param array $definition
* @param callable $provider
@@ -39,7 +45,8 @@ class Service extends AbstractModel
static $defaults = [
'operations' => [],
'shapes' => [],
'metadata' => []
'metadata' => [],
'clientContextParams' => []
], $defaultMeta = [
'apiVersion' => null,
'serviceFullName' => null,
@@ -62,8 +69,10 @@ class Service extends AbstractModel
} else {
$this->serviceName = $this->getEndpointPrefix();
}
$this->apiVersion = $this->getApiVersion();
if (isset($definition['clientContextParams'])) {
$this->clientContextParams = $definition['clientContextParams'];
}
}
/**
@@ -102,12 +111,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',
@@ -118,7 +129,7 @@ class Service extends AbstractModel
];
if (isset($mapping[$protocol])) {
return new $mapping[$protocol]();
return new $mapping[$protocol]($api);
}
throw new \UnexpectedValueException("Unknown protocol: $protocol");
@@ -277,6 +288,11 @@ class Service extends AbstractModel
$this->definition['operations'][$name],
$this->shapeMap
);
} else if ($this->modifiedModel) {
$this->operations[$name] = new Operation(
$this->definition['operations'][$name],
$this->shapeMap
);
}
return $this->operations[$name];
@@ -297,6 +313,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.
*
@@ -445,4 +479,61 @@ class Service extends AbstractModel
{
return $this->shapeMap;
}
/**
* Get all the context params of the description.
*
* @return array
*/
public function getClientContextParams()
{
return $this->clientContextParams;
}
/**
* Get the service's api provider.
*
* @return callable
*/
public function getProvider()
{
return $this->apiProvider;
}
/**
* Get the service's definition.
*
* @return callable
*/
public function getDefinition()
{
return $this->definition;
}
/**
* Sets the service's api definition.
* Intended for internal use only.
*
* @return void
*
* @internal
*/
public function setDefinition($definition)
{
$this->definition = $definition;
$this->modifiedModel = true;
}
/**
* Denotes whether or not a service's definition has
* been modified. Intended for internal use only.
*
* @return bool
*
* @internal
*/
public function isModifiedModel()
{
return $this->modifiedModel;
}
}

View File

@@ -66,4 +66,12 @@ class Shape extends AbstractModel
{
return $this->definition['name'];
}
/**
* Get a context param definition.
*/
public function getContextParam()
{
return $this->contextParam;
}
}

View File

@@ -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);

View File

@@ -90,7 +90,19 @@ class Validator
private function check_structure(StructureShape $shape, $value)
{
if (!$this->checkAssociativeArray($value)) {
$isDocument = (isset($shape['document']) && $shape['document']);
$isUnion = (isset($shape['union']) && $shape['union']);
if ($isDocument) {
if (!$this->checkDocumentType($value)) {
$this->addError("is not a valid document type");
return;
}
} elseif ($isUnion) {
if (!$this->checkUnion($value)) {
$this->addError("is a union type and must have exactly one non null value");
return;
}
} elseif (!$this->checkAssociativeArray($value)) {
return;
}
@@ -103,15 +115,16 @@ class Validator
}
}
}
foreach ($value as $name => $v) {
if ($shape->hasMember($name)) {
$this->path[] = $name;
$this->dispatch(
$shape->getMember($name),
isset($value[$name]) ? $value[$name] : null
);
array_pop($this->path);
if (!$isDocument) {
foreach ($value as $name => $v) {
if ($shape->hasMember($name)) {
$this->path[] = $name;
$this->dispatch(
$shape->getMember($name),
isset($value[$name]) ? $value[$name] : null
);
array_pop($this->path);
}
}
}
}
@@ -203,6 +216,7 @@ class Validator
return;
}
$value = isset($value) ? $value : '';
$this->validateRange($shape, strlen($value), "string length");
if ($this->constraints['pattern']) {
@@ -232,6 +246,21 @@ class Validator
}
}
private function checkArray($arr)
{
return $this->isIndexed($arr) || $this->isAssociative($arr);
}
private function isAssociative($arr)
{
return count(array_filter(array_keys($arr), "is_string")) == count($arr);
}
private function isIndexed(array $arr)
{
return $arr == array_values($arr);
}
private function checkCanString($value)
{
static $valid = [
@@ -249,7 +278,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;
@@ -258,6 +300,37 @@ class Validator
return true;
}
private function checkDocumentType($value)
{
if (is_array($value)) {
$typeOfFirstKey = gettype(key($value));
foreach ($value as $key => $val) {
if (!$this->checkDocumentType($val) || gettype($key) != $typeOfFirstKey) {
return false;
}
}
return $this->checkArray($value);
}
return is_null($value)
|| is_numeric($value)
|| is_string($value)
|| is_bool($value);
}
private function checkUnion($value)
{
if (is_array($value)) {
$nonNullCount = 0;
foreach ($value as $key => $val) {
if (!is_null($val) && !(strpos($key, "@") === 0)) {
$nonNullCount++;
}
}
return $nonNullCount == 1;
}
return !is_null($value);
}
private function addError($message)
{
$this->errors[] =