<?php

class JsonCompiler
{
    use PhpFileLinterTrait;

    private static $tokensToReplace = [
        '(' => '__OPEN_PARENTHESIS__',
        ')' => '__CLOSE_PARENTHESIS__',
    ];

    /** @var string */
    private $path;

    public function __construct($path)
    {
        if (!file_exists($path)) {
            throw new InvalidArgumentException("No JSON file found at $path");
        }

        $this->path = realpath($path);
    }

    public function compile($outputPath)
    {
        $backup = $this->readPhpFile($outputPath);

        $this->writeFile($outputPath, $this->getTranspiledPhp());
        if (!$this->lintFile($outputPath)) {
            $this->writeFile($outputPath, $backup);
            trigger_error(
                "Unable to compile {$this->path} to valid PHP",
                E_USER_WARNING
            );
        }
    }

    private function getTranspiledPhp()
    {
        // Use var_export as a starting point.
        $code = var_export($this->getDecodedData(), true);

        // Convert "array()" to "[]".
        $code = str_replace('array (', '[', $code);
        $code = str_replace(')', ']', $code);

        // Removing trailing whitespace.
        $code = preg_replace('/\s+$/m', '', $code);

        // Move arrays to the same line.
        $code = preg_replace('/=>\s*\n\s*\[/', '=> [', $code);

        // Get rid of numbered array indexes.
        $code = preg_replace('/(\s*)(\d+ => )/', '$1', $code);

        // Make empty arrays span only a single line.
        $code = preg_replace('/=>\s*\[\n\s*\]/', '=> []', $code);

        // Minify the PHP file
        $code = preg_replace('/\s+/', ' ', str_replace("\n", '', $code));

        $originalPath = preg_replace('/^.+?(?=\/src\/data\/)/', '', $this->path);

        return <<<EOPHP
<?php
// This file was auto-generated from sdk-root$originalPath
return $code;

EOPHP;
    }

    private function getDecodedData()
    {
        return json_decode(
            strtr(file_get_contents($this->path), self::$tokensToReplace),
            true
        );
    }

    private function readPhpFile($path)
    {
        return file_exists($path) ?
            file_get_contents($path)
            : '<?php return [];';
    }

    private function writeFile($path, $contents)
    {
        return file_put_contents(
            $path,
            strtr($contents, array_flip(self::$tokensToReplace)),
            LOCK_EX
        );
    }
}


<?php

/**
 * A trait that provides a method for linting a PHP file. It will use
 * `opcache_compile` if available and fall back to shelling out to `php -l`
 * otherwise.
 *
 * @internal
 */
trait PhpFileLinterTrait
{
    /**
     * @param string $path
     *
     * @return bool
     */
    private function lintFile($path)
    {
        static $linter;

        if (empty($linter)) {
            $linter = function_exists('opcache_get_status')
                && !empty(opcache_get_status(false)['opcache_enabled'])
                    ? [$this, 'opcacheLint']
                    : [$this, 'commandLineLint'];
        }

        return call_user_func($linter, $path);
    }

    /**
     * @param string $path
     *
     * @return bool
     */
    private function commandLineLint($path)
    {
        list($output, $exitCode) = [[], 1];
        exec("php -l $path", $output, $exitCode);

        return 0 === $exitCode;
    }

    /**
     * Uses the Zend OPCache control functions to perform an in-process
     * validation of a file. This function will fail on code that declares new
     * symbols (e.g., classes or functions) if they have already been loaded
     * into the current process.
     *
     * @param string $path
     *
     * @return bool
     */
    private function opcacheLint($path)
    {
        opcache_invalidate($path, true);

        return @opcache_compile_file($path);
    }
}


<?php

namespace Aws\Test;

use Aws;
use Aws\Api\ApiProvider;
use Aws\Api\Service;
use Aws\Result;
use Aws\S3\S3Client;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use DomainException;
use PHPUnit_Framework_Assert as Assert;
use GuzzleHttp\Psr7;

/**
 * Defines application features from the specific context.
 */
class PerformanceContext implements Context, SnippetAcceptingContext
{
    use UsesServiceTrait;

    /** @var array */
    private $resourceUsageSnapshot = [];
    /** @var string[] */
    private $serviceList = [];
    /** @var array */
    private $clients = [];
    /** @var string */
    private $tempFilePath;
    /** @var S3Client */
    private $s3Client;

    /**
     * @BeforeScenario @streaming
     *
     * @param BeforeScenarioScope $scope
     */
    public function setUp(BeforeScenarioScope $scope)
    {
        $this->s3Client = $this->getTestClient('s3');
    }

    /**
     * @AfterScenario @streaming
     *
     * @param AfterScenarioScope $scope
     */
    public function cleanUpFile(AfterScenarioScope $scope)
    {
        if ($this->tempFilePath) {
            unlink($this->tempFilePath);
        }
    }

    /**
     * @Given I have loaded my SDK and its dependencies
     */
    public function iHaveLoadedMySdkAndItsDependencies()
    {
        $this->iCreateAndDiscardClientSForEachService(1);
    }

    /**
     * @Given I take a snapshot of my resources
     */
    public function iTakeASnapshotOfMyResources()
    {
        $this->resourceUsageSnapshot = [
            'memory' => memory_get_usage(),
            'open_file_handles' => $this->getOpenFileHandleCount(),
        ];
    }

    /**
     * @Given I have a list of services
     */
    public function iHaveAListOfServices()
    {
        $this->serviceList = array_keys(Aws\manifest());
    }

    /**
     * @Given /^I have a (\d+) byte file$/
     */
    public function iHaveAByteFile($fileSize)
    {
        $path = tempnam(sys_get_temp_dir(), 'performance_test');
        $kb = ceil($fileSize / 1024);

        if (self::isRunningOnWindows()) {
            // dd is not available, so write out a file of the requisite size
            $handle = fopen($path, 'w');
            $dummyText = str_repeat('x', 1024);
            for ($i = 0; $i < $kb; $i++) {
                fwrite($handle, $dummyText);
            }
            fclose($handle);
        } else {
            shell_exec("dd if=/dev/zero of=$path bs=1024 count=$kb >& /dev/null");
        }

        $this->tempFilePath = $path;
    }

    /**
     * @When I create a client for each service
     */
    public function iCreateAClientForEachService()
    {
        foreach ($this->serviceList as $service) {
            $this->clients[$service] = $this->getTestClient($service, [
                'CloudSearchDomain' => [
                    'endpoint' => 'https://aws.amazon.com',
                ],
            ]);
        }
    }

    /**
     * @When /^I create and discard (\d+) clients for each service$/
     */
    public function iCreateAndDiscardClientSForEachService($numClients)
    {
        foreach ($this->serviceList as $service) {
            for ($i = 0; $i < $numClients; $i++) {
                $this->getTestClient($service, [
                    'CloudSearchDomain' => [
                        'endpoint' => 'https://aws.amazon.com',
                    ],
                ]);
            }
        }
    }

    /**
     * @When /^I execute (\d+) command\(s\) on each client$/
     */
    public function iExecuteCommandsOnEachClient($numCommands)
    {
        foreach ($this->serviceList as $service) {
            try {
                $operation = $this
                    ->findOperationWithNoRequiredParameters($service);
                $this->addMockResults(
                    $this->clients[$service],
                    array_fill(0, $numCommands, new Result)
                );

                for ($i = 0; $i < $numCommands; $i++) {
                    $this->clients[$service]->execute(
                        $this->clients[$service]->getCommand($operation)
                    );
                }
            } catch (DomainException $e) {
                // This step cannot be completed due to service constraints.
            }
        }
    }

    /**
     * @When I upload the file
     */
    public function iUploadTheFile()
    {
        $options = [
            'concurrency' => 5,
            'mup_threshold' => 16 * 1024 * 1024,
            'part_size' => 5 * 1024 * 1024,
        ];
        $putObject = new Result();
        $initiate = new Result(['UploadId' => 'foo']);
        $putPart = new Result(['ETag' => 'bar']);
        $complete = new Result(['Location' => 'https://s3.amazonaws.com/bucket/key']);

        $filesize = filesize($this->tempFilePath);
        if ($filesize < $options['mup_threshold']) {
            $this->addMockResults($this->s3Client, [$putObject]);
        } else {
            $parts = ceil($filesize / $options['part_size']);
            $this->addMockResults(
                $this->s3Client,
                array_merge(
                    [$initiate],
                    array_fill(0, $parts, $putPart),
                    [$complete]
                )
            );
        }

        $this->s3Client->upload(
            'bucket',
            'key',
            fopen($this->tempFilePath, 'rb'),
            'private',
            $options
        );
    }

    /**
     * @When then download the file
     */
    public function thenDownloadTheFile()
    {
        $this->addMockResults($this->s3Client, [new Result([
            'Body' => Psr7\Utils::streamFor(Psr7\Utils::tryFopen($this->tempFilePath, 'rb')),
        ])]);

        $this->s3Client->getObject([
            'Bucket' => 'bucket',
            'Key' => 'key',
        ]);
    }

    /**
     * @When I destroy all the clients
     */
    public function iDestroyAllTheClients()
    {
        $this->clients = [];
        gc_collect_cycles();
    }

    /**
     * @Then I should not have leaked any resources
     */
    public function iShouldNotHaveLeakedAnyResources()
    {
        // These should account for additional memory and files handles used to
        // load class definitions into the PHP runtime
        static $memoryFudge;
        static $handlesFudge;
        if (empty($memoryFudge)) {
            $opcacheEnabled = function_exists('opcache_get_status')
                && !empty(opcache_get_status(false)['opcache_enabled']);
            $memoryFudge = $opcacheEnabled
                ? 256 * 1024 // 256KB if OPCache is enabled
                : 5 * 1024 * 1024; // 5MB otherwise
            $handlesFudge = 5;
        }

        // Make sure you're not counting anything that's eligible for GC
        gc_collect_cycles();

        Assert::assertLessThanOrEqual(
            $this->resourceUsageSnapshot['memory'] + $memoryFudge,
            memory_get_usage()
        );
        Assert::assertLessThanOrEqual(
            $this->resourceUsageSnapshot['open_file_handles'] + $handlesFudge,
            $this->getOpenFileHandleCount()
        );
    }

    private function findOperationWithNoRequiredParameters($service)
    {
        $provider = ApiProvider::defaultProvider();
        $definition = new Service($provider('api', $service, 'latest'), $provider);

        foreach ($definition->getOperations() as $name => $operation) {
            if (empty($operation->getInput()['required'])) {
                return $name;
            }
        }

        throw new DomainException("The $service service has no operations"
            . " without required parameters");
    }

    private function getOpenFileHandleCount()
    {
        if (self::isRunningOnWindows()) {
            return 0;
        }

        return (int) shell_exec('lsof -p ' . getmypid() . ' | wc -l');
    }

    private static function isRunningOnWindows()
    {
        return 'WIN' === strtoupper(substr(PHP_OS, 0, 3));
    }
}


<?php
namespace Aws\Test;

use Aws\AwsClientInterface;
use Aws\Exception\AwsException;
use Aws\MockHandler;
use Aws\Result;
use Aws\Sdk;
use Aws\Api\Service;

/**
 * @internal
 */
trait UsesServiceTrait
{
    private $_mock_handler;

    /**
     * Creates an instance of the AWS SDK for a test
     *
     * @param array $args
     *
     * @return Sdk
     */
    private function getTestSdk(array $args = [])
    {
        return new Sdk($args + [
            'region'      => 'us-east-1',
            'version'     => 'latest',
            'retries'     => 0
        ]);
    }

    /**
     * Creates an instance of a service client for a test
     *
     * @param string $service
     * @param array  $args
     *
     * @return AwsClientInterface
     */
    private function getTestClient($service, array $args = [])
    {
        // Disable network access. If the INTEGRATION envvar is set, then this
        // disabling is not done.
        if (!isset($_SERVER['INTEGRATION'])
            && !isset($args['handler'])
            && !isset($args['http_handler'])
        ) {
            $this->_mock_handler = $args['handler'] = new MockHandler([]);
        }

        return $this->getTestSdk($args)->createClient($service);
    }

    /**
     * Queues up mock Result objects for a client
     *
     * @param AwsClientInterface $client
     * @param Result[]|array[]   $results
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     *
     * @return AwsClientInterface
     */
    private function addMockResults(
        AwsClientInterface $client,
        array $results,
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        foreach ($results as &$res) {
            if (is_array($res)) {
                $res = new Result($res);
            }
        }

        $this->_mock_handler = new MockHandler($results, $onFulfilled, $onRejected);
        $client->getHandlerList()->setHandler($this->_mock_handler);

        return $client;
    }

    private function mockQueueEmpty()
    {
        return 0 === count($this->_mock_handler);
    }

    /**
     * Creates a mock CommandException with a given error code
     *
     * @param string $code
     * @param string $type
     * @param string|null $message
     *
     * @return AwsException
     */
    private function createMockAwsException(
        $code = null,
        $type = null,
        $message = null
    ) {
        $code = $code ?: 'ERROR';
        $type = $type ?: 'Aws\Exception\AwsException';

        $client = $this->getMockBuilder('Aws\AwsClientInterface')
            ->setMethods(['getApi'])
            ->getMockForAbstractClass();

        $client->expects($this->any())
            ->method('getApi')
            ->will($this->returnValue(
                new Service(
                    [
                        'metadata' => [
                            'endpointPrefix' => 'foo',
                            'apiVersion' => 'version'
                        ]
                    ],
                    function () { return []; }
                )));

        return new $type(
            $message ?: 'Test error',
            $this->getMock('Aws\CommandInterface'),
            [
                'message' => $message ?: 'Test error',
                'code'    => $code
            ]
        );
    }
}


<?php

namespace Aws\Test\Integ;

use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\WriteRequestBatch;
use Aws\Exception\AwsException;
use Aws\Result;
use Aws\Sqs\SqsClient;
use Behat\Behat\Hook\Scope\AfterFeatureScope;
use Behat\Behat\Hook\Scope\BeforeFeatureScope;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;

class BatchingContext extends \PHPUnit_Framework_Assert implements
    Context,
    SnippetAcceptingContext
{
    use IntegUtils;

    private static $resource;

    /** @var DynamoDbClient|SqsClient */
    private $client;
    /** @var WriteRequestBatch */
    private $batch;
    /** @var int */
    private $flushCount;
    /** @var Result */
    private $response;

    /**
     * @Given I have a :service client
     */
    public function iHaveAClient($service)
    {
        $this->client = self::getSdk()->createClient($service);
    }

    /**
     * @BeforeFeature @dynamodb
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpDynamoTable(BeforeFeatureScope $scope)
    {
        self::$resource = self::getResourcePrefix()
            . str_replace(' ', '-', strtolower($scope->getName()));

        $client = self::getSdk()->createDynamoDb();
        $client->createTable([
            'TableName' => self::$resource,
            'AttributeDefinitions' => [
                ['AttributeName' => 'id', 'AttributeType' => 'N']
            ],
            'KeySchema' => [
                ['AttributeName' => 'id', 'KeyType' => 'HASH']
            ],
            'ProvisionedThroughput' => [
                'ReadCapacityUnits'  => 1,
                'WriteCapacityUnits' => 1
            ]
        ]);

        $client->waitUntil('TableExists', ['TableName' => self::$resource]);
    }

    /**
     * @AfterFeature @dynamodb
     *
     * @param AfterFeatureScope $scope
     */
    public static function tearDownDynamoTable(AfterFeatureScope $scope)
    {
        self::getSdk()
            ->createDynamoDb()
            ->deleteTable(['TableName' => self::$resource]);

        self::$resource = null;
    }

    /**
     * @BeforeFeature @sqs
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpQueue(BeforeFeatureScope $scope)
    {
        $sqs = self::getSdk()->createSqs();
        self::$resource = self::getResourcePrefix()
            . preg_replace('/\W/', '-', strtolower($scope->getName()));

        $sqs->createQueue(['QueueName' => self::$resource]);
        $sqs->waitUntil('QueueExists', ['QueueName' => self::$resource]);
    }

    /**
     * @AfterFeature @sqs
     *
     * @param AfterFeatureScope $scope
     */
    public static function tearDownQueue(AfterFeatureScope $scope)
    {
        $sqs = self::getSdk()
            ->createSqs();

        $sqs->deleteQueue([
            'QueueUrl' => $sqs->getQueueUrl([
                'QueueName' => self::$resource,
            ])['QueueUrl']
        ]);

        self::$resource = null;
    }

    /**
     * @When /^I create a WriteRequestBatch with a batch size of (\d+) and a pool size of (\d+)$/
     */
    public function iCreateAWriteRequestBatch($batchSize, $poolSize)
    {
        $this->batch = new WriteRequestBatch($this->client, [
            'table' => self::$resource,
            'batch_size' => $batchSize,
            'pool_size' => $poolSize,
            'before' => function () {
                $this->flushCount++;
            },
            'error' => function (AwsException $e) {
                trigger_error($e->getMessage(), E_USER_WARNING);
            }
        ]);
    }

    /**
     * @When /^I put (\d+) items in the batch$/
     */
    public function iPutItemsInTheBatch($itemCount)
    {
        for ($i = 0; $i < $itemCount; $i++) {
            $this->batch->put(['id' => ['N' => (string) $i]]);
        }
    }

    /**
     * @When I flush the batch
     */
    public function iFlushTheBatch()
    {
        $this->batch->flush();
    }

    /**
     * @Then /^(\d+) items should have been written$/
     */
    public function itemsShouldHaveBeenWritten($itemCount)
    {
        $actualItems = $this->client->getIterator('Scan', [
            'TableName' => self::$resource,
        ]);

        $this->assertSame((int) $itemCount, iterator_count($actualItems));
    }

    /**
     * @Then /^the batch should have been flushed at least (\d+) times$/
     */
    public function theBatchShouldHaveBeenFlushedTimes($flushCount)
    {
        $this->assertGreaterThanOrEqual((int) $flushCount, $this->flushCount);
    }

    /**
     * @Given /^I have put (\d+) messages in a queue$/
     */
    public function iHavePutMessagesInAQueue($messageCount)
    {
        $queueUrl = $this->client
            ->getQueueUrl(['QueueName' => self::$resource])['QueueUrl'];
        for ($i = 0; $i < $messageCount; $i++) {
            $this->client->sendMessage([
                'QueueUrl' => $queueUrl,
                'MessageBody' => json_encode(['testing' => 'testing']),
            ]);
        }
    }

    /**
     * @When /^I delete a batch of (\d+) messages$/
     */
    public function iDeleteABatchOfMessages($messageCount)
    {
        $queueUrl = $this->client
            ->getQueueUrl(['QueueName' => self::$resource])['QueueUrl'];
        $messages = [];
        while (count($messages) < $messageCount) {
            $result = $this->client->receiveMessage([
                'QueueUrl' => $queueUrl,
                'MaxNumberOfMessages' => $messageCount,
            ]);

            foreach ($result['Messages'] as $message) {
                $messages[$message['MessageId']] = [
                    'Id' => $message['MessageId'],
                    'ReceiptHandle' => $message['ReceiptHandle'],
                ];
            }
        }

        $this->response = $this->client
            ->deleteMessageBatch([
                'QueueUrl' => $queueUrl,
                'Entries' => array_values($messages),
            ]);
    }

    /**
     * @Then /^(\d+) messages should have been deleted from the queue$/
     */
    public function messagesShouldHaveBeenDeletedFromTheQueue($messageCount)
    {
        $this->assertSame((int) $messageCount, count($this->response['Failed'])
            + count($this->response['Successful']));
    }
}


<?php

namespace Aws\Test\Integ;

use Aws\AwsClient;
use Aws\DynamoDb\Exception\DynamoDbException;
use Behat\Behat\Hook\Scope\AfterFeatureScope;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Promise;

/**
 * Defines application features from the specific context.
 */
class BlockingContext extends \PHPUnit_Framework_Assert implements Context, SnippetAcceptingContext
{
    use IntegUtils;

    /** @var AwsClient */
    private $client;
    /** @var Promise\Promise[] */
    private $promises;

    /**
     * @Given I have a :service client
     */
    public function iHaveAClient($service)
    {
        $this->client = self::getSdk()->createClient($service);
    }

    /**
     * @When I create a table named :table
     */
    public function iCreateATableNamed($table)
    {
        $this->client->createTable([
            'TableName' => self::getResourcePrefix() . "-$table",
            'AttributeDefinitions' => [
                ['AttributeName' => 'id', 'AttributeType' => 'N']
            ],
            'KeySchema' => [
                ['AttributeName' => 'id', 'KeyType' => 'HASH']
            ],
            'ProvisionedThroughput' => [
                'ReadCapacityUnits'  => 20,
                'WriteCapacityUnits' => 20
            ]
        ]);
    }

    /**
     * @When wait for the table named :table to exist
     */
    public function waitForTheTableNamedToExist($table)
    {
        $this->client->waitUntil('TableExists', [
            'TableName' => self::getResourcePrefix() . "-$table",
        ]);
    }

    /**
     * @When the table named :table exists
     */
    public function theTableNamedWillExist($table)
    {
        self::getSdk(['http' => ['synchronous' => true]])
            ->createDynamoDb()
            ->describeTable(['TableName' => self::getResourcePrefix() . "-$table"]);
    }

    /**
     * @Then I can delete the table named :table
     */
    public function iCanDeleteTheTableNamed($table)
    {
        $this->client->deleteTable([
            'TableName' => self::getResourcePrefix() . "-$table",
        ]);
    }

    /**
     * @Then wait for the table named :table to be deleted
     */
    public function waitForTheTableNamedToBeDeleted($table)
    {
        $this->client->waitUntil('TableNotExists', [
            'TableName' => self::getResourcePrefix() . "-$table",
        ]);
    }

    /**
     * @Then the table named :table does not exist
     */
    public function theTableNamedWillNotExist($table)
    {
        try {
            $this->theTableNamedWillExist($table);
            $this->fail("$table exists but should not.");
        } catch (DynamoDbException $e) {
            $this->assertSame('ResourceNotFoundException', $e->getAwsErrorCode());
        }
    }

    /**
     * @When I create a promise to create and await a table named :table
     */
    public function iCreateAPromiseToCreateAndAwaitATableNamed($table)
    {
        $this->promises []= $this->client->createTableAsync([
            'TableName' => self::getResourcePrefix() . "-$table",
            'AttributeDefinitions' => [
                ['AttributeName' => 'id', 'AttributeType' => 'N']
            ],
            'KeySchema' => [
                ['AttributeName' => 'id', 'KeyType' => 'HASH']
            ],
            'ProvisionedThroughput' => [
                'ReadCapacityUnits'  => 20,
                'WriteCapacityUnits' => 20
            ]
        ])
            ->then(function () use ($table) {
                return $this->client
                    ->getWaiter('TableExists', [
                        'TableName' => self::getResourcePrefix() . "-$table",
                    ])->promise();
            });
    }

    /**
     * @Then I can wait on all promises
     */
    public function iCanWaitOnAllPromises()
    {
        Promise\all($this->promises)
            ->wait();
    }

    /**
     * @When I create a promise to delete and await the purging of the table named :table
     */
    public function iCreateAPromiseToDeleteAndAwaitThePurgingOfTheTableNamed($table)
    {
        $this->promises []= $this->client
            ->deleteTableAsync([
                'TableName' => self::getResourcePrefix() . "-$table",
            ])->then(function () use ($table) {
                return $this->client
                    ->getWaiter('TableNotExists', [
                        'TableName' => self::getResourcePrefix() . "-$table",
                    ])->promise();
            });
    }
}


<?php

namespace Aws\Test\Integ;

use Aws\CommandInterface;
use Aws\CommandPool;
use Aws\Exception\AwsException;
use Aws\Result;
use Aws\S3\S3Client;
use Behat\Behat\Hook\Scope\AfterFeatureScope;
use Behat\Behat\Hook\Scope\BeforeFeatureScope;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use JmesPath;

/**
 * Defines application features from the specific context.
 */
class ConcurrencyContext extends \PHPUnit_Framework_Assert implements
    Context,
    SnippetAcceptingContext
{
    use IntegUtils;

    /** @var S3Client */
    private $client;
    /** @var Result|Result[] */
    private $result;
    /** @var PromiseInterface */
    private $promise;
    /** @var CommandInterface[] */
    private $commands;
    /** @var string */
    private static $bucket;

    /**
     * @BeforeFeature @s3
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpS3Bucket(BeforeFeatureScope $scope)
    {
        $client = self::getSdk()
            ->createS3();

        self::$bucket = self::getResourcePrefix()
            . str_replace(' ', '-', strtolower($scope->getName()));

        $client->createBucket(['Bucket' => self::$bucket]);
        $client->waitUntil('BucketExists', ['Bucket' => self::$bucket]);
    }

    /**
     * @AfterFeature @s3
     *
     * @param AfterFeatureScope $scope
     */
    public static function tearDownS3Bucket(AfterFeatureScope $scope)
    {
        $client = self::getSdk()->createS3();

        $client->deleteMatchingObjects(self::$bucket, '', '//');
        $client->deleteBucket(['Bucket' => self::$bucket]);

        self::$bucket = null;
    }

    /**
     * @Given I have a :service client
     */
    public function iHaveAClient($service)
    {
        $this->client = self::getSdk()->createClient($service);
    }

    /**
     * @When I call the :command API
     */
    public function iCallTheApi($command)
    {
        $this->result = $this->client->{$command}();
    }

    /**
     * @Then the value at :key should be a :type
     */
    public function theValueAtShouldBeA($key, $type)
    {
        $this->assertInstanceOf(Result::class, $this->result);
        $this->assertInternalType($type, $this->result->search($key));
    }

    /**
     * @When I call the :command API asynchronously
     */
    public function iCallTheApiAsynchronously($command)
    {
        $this->promise = call_user_func([$this->client, "{$command}Async"]);
    }

    /**
     * @When I wait on the promise
     */
    public function thenWaitOnThePromise()
    {
        $this->result = $this->promise->wait();
    }

    /**
     * @Given a promise composed of the following asynchronous operations:
     */
    public function aPromiseComposedOfTheFollowingAsynchronousOperations(TableNode $table)
    {
        $this->promise = Promise\all(array_map(function (array $row) {
            return call_user_func(
                [
                    self::getSdk()->createClient($row['service']),
                    "{$row['command']}Async",
                ],
                json_decode($row['payload'], true) ?: []
            );
        }, iterator_to_array($table)));
    }

    /**
     * @Given a pool composed of the following commands:
     */
    public function aPoolComposedOfTheFollowingCommands(TableNode $table)
    {
        $this->commands = array_map(function (array $row) {
            return self::getSdk()
                ->createClient($row['service'])
                ->getCommand(
                    $row['command'],
                    json_decode($row['payload'], true) ?: []
                );
        }, iterator_to_array($table));
    }

    /**
     * @When I send the commands as a batch to :service
     */
    public function iSendTheCommandsAsABatchTo($service)
    {
        $this->result = CommandPool::batch(
            self::getSdk()->createClient($service),
            $this->commands
        );
    }

    /**
     * @Then there should be :count results
     */
    public function thereShouldBeResults($count)
    {
        $this->assertCount((int) $count, $this->result);
    }

    /**
     * @Then there should be :count value at :path
     */
    public function thereShouldBeValueAt($count, $path)
    {
        $this->assertCount((int) $count, array_unique(
                JmesPath\search($path, $this->result)
        ));
    }
}


<?php
namespace Aws\Test\Integ;

trait IntegUtils
{
    private static function getSdk(array $args = [])
    {
        return new \Aws\Sdk($args + [
            'region'  => 'us-east-1',
            'version' => 'latest',
            'ua_append' => 'PHPUnit/Integration'
        ]);
    }

    public static function log($message)
    {
        fwrite(STDERR, date('c') . ': ' . $message . "\n");
    }

    /**
     * Get the resource prefix to add to created resources
     *
     * @return string
     */
    public static function getResourcePrefix()
    {
        if (!isset($_SERVER['PREFIX']) || $_SERVER['PREFIX'] == 'hostname') {
            $_SERVER['PREFIX'] = crc32(gethostname()) . rand(0, 10000);
        }

        return $_SERVER['PREFIX'];
    }
}


<?php

namespace Aws\Test\Integ;

use Aws\Exception\MultipartUploadException;
use Aws\Glacier\MultipartUploader as GlacierMultipartUploader;
use Aws\ResultInterface;
use Aws\S3\MultipartUploader as S3MultipartUploader;
use Behat\Behat\Tester\Exception\PendingException;
use Aws\S3\BatchDelete;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;
use PHPUnit_Framework_Assert as Assert;
use Psr\Http\Message\StreamInterface;

/**
 * Defines application features from the specific context.
 */
class MultipartContext implements Context, SnippetAcceptingContext
{
    use IntegUtils;

    const MB = 1048576;
    const RESOURCE_POSTFIX = 'php-integration-multipart-test';

    private static $tempFile;
    /** @var StreamInterface */
    private $stream;
    /** @var ResultInterface */
    private $result;

    /**
     * @Given I have a seekable read stream
     */
    public function iHaveASeekableReadStream()
    {
        $this->stream = Psr7\Utils::streamFor(Psr7\Utils::tryFopen(self::$tempFile, 'r'));
    }

    /**
     * @When /^I upload the stream to S3 with a concurrency factor of "(\d+)"$/
     */
    public function iUploadTheStreamToS3WithAConcurrencyFactorOf($concurrency)
    {
        $client = self::getSdk()->createS3();
        $uploader = new S3MultipartUploader($client, $this->stream, [
            'bucket' => self::getResourceName(),
            'key' => get_class($this->stream) . $concurrency,
            'concurrency' => $concurrency,
        ]);

        try {
            $this->result = $uploader->upload();
        } catch (MultipartUploadException $e) {
            $client->abortMultipartUpload($e->getState()->getId());
            $message = "=====\n";
            while ($e) {
                $message .= $e->getMessage() . "\n";
                $e = $e->getPrevious();
            }
            $message .= "=====\n";
            Assert::fail($message);
        }
    }

    /**
     * @When /^I upload the stream to Glacier with a concurrency factor of "(\d+)"$/
     */
    public function iUploadTheStreamToGlacierWithAConcurrencyFactorOf($concurrency)
    {
        $client = self::getSdk()->createGlacier();
        $uploader = new GlacierMultipartUploader($client, $this->stream, [
            'vault_name' => self::RESOURCE_POSTFIX,
            'archive_description' => get_class($this->stream) . $concurrency,
            'concurrency' => $concurrency,
        ]);

        try {
            $this->result = $uploader->upload();
        } catch (MultipartUploadException $e) {
            $client->abortMultipartUpload($e->getState()->getId());
            $message = "=====\n";
            while ($e) {
                $message .= $e->getMessage() . "\n";
                $e = $e->getPrevious();
            }
            $message .= "=====\n";
            Assert::fail($message);
        }
    }

    /**
     * @Then /^the result should contain a\(n\) "([^"]+)"$/
     */
    public function theResultShouldContainA($key)
    {
        Assert::assertArrayHasKey($key, $this->result);
    }

    /**
     * @Given I have a non-seekable read stream
     */
    public function iHaveANonSeekableReadStream()
    {
        $this->iHaveASeekableReadStream();
        $this->stream = new NoSeekStream($this->stream);
    }

    /**
     * @BeforeSuite
     */
    public static function createTempFile()
    {
        self::$tempFile = tempnam(sys_get_temp_dir(), self::getResourceName());
        file_put_contents(self::$tempFile, str_repeat('x', 10 * self::MB + 1024));
    }

    /**
     * @AfterSuite
     */
    public static function deleteTempFile()
    {
        unlink(self::$tempFile);
    }

    /**
     * @BeforeFeature @s3
     */
    public static function createTestBucket()
    {
        $client = self::getSdk()->createS3();
        if (!$client->doesBucketExist(self::getResourceName())) {
            $client->createBucket(['Bucket' => self::getResourceName()]);
            $client->waitUntil('BucketExists', ['Bucket' => self::getResourceName()]);
        }
    }

    /**
     * @AfterFeature @s3
     */
    public static function deleteTestBucket()
    {
        $client = self::getSdk()->createS3();
        BatchDelete::fromListObjects($client, ['Bucket' => self::getResourceName()])->delete();
        $client->deleteBucket(['Bucket' => self::getResourceName()]);
        $client->waitUntil('BucketNotExists', ['Bucket' => self::getResourceName()]);
    }

    /**
     * @BeforeFeature @glacier
     */
    public static function createTestVault()
    {
        $client = self::getSdk()->createGlacier();
        $client->createVault(['vaultName' => self::RESOURCE_POSTFIX]);
        $client->waitUntil('VaultExists', ['vaultName' => self::RESOURCE_POSTFIX]);
    }

    private static function getResourceName()
    {
        static $bucketName;

        if (empty($bucketName)) {
            $bucketName = self::getResourcePrefix() . self::RESOURCE_POSTFIX;
        }

        return $bucketName;
    }
}


<?php

namespace Aws\Test\Integ;

use Aws\S3\S3Client;
use Behat\Behat\Hook\Scope\AfterFeatureScope;
use Behat\Behat\Hook\Scope\BeforeFeatureScope;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;

/**
 * Defines application features from the specific context.
 */
class NativeStreamContext extends \PHPUnit_Framework_Assert implements
    Context,
    SnippetAcceptingContext
{
    use IntegUtils;

    /** @var S3Client */
    private $client;
    /** @var bool */
    private $callSucceeded;
    /** @var resource */
    private $handle;
    /** @var string */
    private static $bucket;

    public function __destruct()
    {
        if (is_resource($this->handle)) {
            fclose($this->handle);
        }
    }

    /**
     * @BeforeFeature @s3
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpS3Bucket(BeforeFeatureScope $scope)
    {
        $client = self::getSdk()
            ->createS3();

        self::$bucket = self::getResourcePrefix()
            . str_replace(' ', '-', strtolower($scope->getName()));

        $client->createBucket(['Bucket' => self::$bucket]);
        $client->waitUntil('BucketExists', ['Bucket' => self::$bucket]);
    }

    /**
     * @AfterFeature @s3
     *
     * @param AfterFeatureScope $scope
     */
    public static function tearDownS3Bucket(AfterFeatureScope $scope)
    {
        $client = self::getSdk()->createS3();

        $client->deleteMatchingObjects(self::$bucket, '', '//');
        $client->deleteBucket(['Bucket' => self::$bucket]);

        self::$bucket = null;
    }

    /**
     * @Given I have a :service client
     */
    public function iHaveAClient($service)
    {
        $this->client = self::getSdk()->createClient($service);
    }

    /**
     * @Given have registered an s3 stream wrapper
     */
    public function haveRegisteredAnS3StreamWrapper()
    {
        $this->client->registerStreamWrapper();
    }

    /**
     * @Given I create a subdirectory :subdir with mkdir
     */
    public function iCreateASubdirectory($subdir)
    {
        mkdir($this->getS3Path($subdir));
        sleep(1);
    }

    /**
     * @When /^I call (\w+) on the (\S+) path$/
     */
    public function iCallOnThePath($method, $path)
    {
        $this->callSucceeded = call_user_func($method, $this->getS3Path($path));
    }

    /**
     * @Then /^the call should return (true|false)$/
     */
    public function theCallShouldReturn($booleanString)
    {
        $this->assertSame(
            filter_var($booleanString, FILTER_VALIDATE_BOOLEAN),
            $this->callSucceeded
        );
    }

    /**
     * @Given I have a file at :path with the content :contents
     */
    public function iHaveAFileAtWithTheContent($path, $contents)
    {
        $this->assertGreaterThan(
            0,
            file_put_contents($this->getS3Path($path), $contents)
        );
    }

    /**
     * @Given I have a file at :path with no content
     */
    public function iHaveAFileAtWithNoContent($path)
    {
        $this->assertSame(0, file_put_contents($this->getS3Path($path), ''));
    }

    /**
     * @Then the file at :arg1 should contain :arg2
     */
    public function theFileAtShouldContain($key, $contents)
    {
        $this->assertSame($contents, file_get_contents($this->getS3Path($key)));
    }

    /**
     * @Given I have a read handle on the file at :arg1
     */
    public function iHaveAReadHandleOnTheFileAt($key)
    {
        $this->handle = fopen($this->getS3Path($key), 'r');
    }

    /**
     * @Then /^reading (\d+) bytes should return (.+)$/
     */
    public function readingBytesShouldReturn($byteCount, $expected)
    {
        $this->assertSame($expected, fread($this->handle, $byteCount));
    }

    /**
     * @Then /^calling fstat should report a size of (\d+)$/
     */
    public function callingFstatShouldReportASizeOf($size)
    {
        $this->assertSame((int) $size, fstat($this->handle)['size']);
    }

    /**
     * @Given I have cleared the last error
     */
    public function iHaveClearedTheLastError()
    {
        while (error_get_last()) {
            error_clear_last();
        }
    }

    /**
     * @Then no errors should have been raised
     */
    public function errorsShouldHaveBeenRaised()
    {
        $this->assertNull(error_get_last());
    }

    /**
     * @Then scanning the directory at :dir should return a list with one member named :file
     */
    public function scanningTheDirectoryAtShouldReturnAListWithOneMemberNamed($dir, $file)
    {
        $this->assertSame([$file], scandir($this->getS3Path($dir)));
    }

    private function getS3Path($path)
    {
        return 's3://' . self::$bucket . '/' . ltrim($path, '/');
    }
}


<?php
namespace Aws\Test\Integ;

use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Aws\S3\S3Client;
use Aws\S3\PostObject;
use Aws\S3\PostObjectV4;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use PHPUnit_Framework_Assert as Assert;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

class S3Context implements Context, SnippetAcceptingContext
{
    use IntegUtils;

    /** @var RequestInterface */
    private $presignedRequest;

    /** @var S3Client */
    private $s3Client;
    /** @var StreamInterface */
    private $stream;
    private static $tempFile;
    private $inputs;
    private $attributes;
    // for post object
    private $formInputs = [];
    private $jsonPolicy;
    private $options;
    private $expires;

    private static function getResourceName()
    {
        static $bucketName;

        if (empty($bucketName)) {
            $bucketName =
                self::getResourcePrefix() . 'aws-test-integ-s3-context';
        }

        return $bucketName;
    }

    /**
     * @BeforeSuite
     */
    public static function createTempFile()
    {
        self::$tempFile = tempnam(sys_get_temp_dir(), self::getResourceName());
        file_put_contents(self::$tempFile, str_repeat('x', 128 * 1024));
    }

    /**
     * @AfterSuite
     */
    public static function deleteTempFile()
    {
        unlink(self::$tempFile);
    }

    /**
     * @BeforeSuite
     */
    public static function createTestBucket()
    {
        $client = self::getSdk()->createS3();
        if (!$client->doesBucketExist(self::getResourceName())) {
            $client->createBucket(['Bucket' => self::getResourceName()]);
            $client->waitUntil('BucketExists', [
                'Bucket' => self::getResourceName(),
            ]);
        }
    }

    /**
     * @AfterSuite
     */
    public static function deleteTestBucket()
    {
        $client = self::getSdk()->createS3();
        $client->deleteMatchingObjects(self::getResourceName(), '', '//');
        $client->deleteBucket(['Bucket' => self::getResourceName()]);
        $client->waitUntil('BucketNotExists', [
            'Bucket' => self::getResourceName(),
        ]);
    }

    /**
     * @Given I have uploaded an object to S3 with a key of :key and a body of :body
     */
    public function iHaveUploadedThatStringToSWithAKeyOfAndABodyOf($key, $body)
    {
        self::getSdk()
            ->createS3()
            ->putObject([
                'Bucket' => self::getResourceName(),
                'Key' => $key,
                'Body' => $body,
            ]);
    }

    /**
     * @When I create a pre-signed request for a :command command with:
     */
    public function iCreateAPreSignedUrlForACommandWith(
        $commandName,
        TableNode $table
    ) {
        $args = ['Bucket' => self::getResourceName()];
        foreach ($table as $row) {
            $args[$row['key']] = $row['value'];
        }
        $client = self::getSdk()->createS3();
        $command = $client->getCommand($commandName, $args);
        $this->presignedRequest = $client
            ->createPresignedRequest($command, '+1 hour');
    }

    /**
     * @Then the contents of the response to the presigned request should be :body
     */
    public function theContentsAtThePresignedUrlShouldBe($body)
    {
        Assert::assertSame(
            $body,
            file_get_contents((string) $this->presignedRequest->getUri())
        );
    }

    /**
     * @Given I send the pre-signed request
     */
    public function iSendThePreSignedRequest()
    {
        (new Client)->send($this->presignedRequest);
    }

    /**
     * @Given I change the body of the pre-signed request to be :body
     */
    public function iChangeTheBodyOfThePreSignedRequestToBe($body)
    {
        $this->presignedRequest = $this->presignedRequest
            ->withBody(Psr7\Utils::streamFor($body));
    }

    /**
     * @Given I have an s3 client and I have a file
     */
    public function iHaveAnClientAndIHaveAFile()
    {
        $this->s3Client = self::getSdk()->createS3();
        $this->stream = Psr7\Utils::streamFor(Psr7\Utils::tryFopen(self::$tempFile, 'r'));
    }

    /**
     * @Given I have an array of form inputs as following:
     */
    public function iHaveAnArrayOfFormInputsAsFollowing(TableNode $table)
    {
        foreach ($table as $row) {
            $this->formInputs += [$row['key'] => $row['value']];
        }
    }

    /**
     * @Given I provide a json string policy as following:
     */
    public function iProvideAJsonStringPolicyAsFollowing(PyStringNode $string)
    {
        $policy = json_decode($string->getRaw(),true);
        $policy['conditions'][] = ["bucket" => self::getResourceName()];
        $policy['conditions'][] = ["starts-with", '$key', ""];

        $this->jsonPolicy = json_encode($policy);
    }

    /**
     * @When I create a POST object SigV2 with inputs and policy
     */
    public function iCreateAPostObjectSigvWithInputsAndPolicy()
    {
        $postObject = new PostObject(
            $this->s3Client,
            self::getResourceName(),
            $this->formInputs,
            $this->jsonPolicy
        );
        $this->preparePostData($postObject);
    }

    /**
     * @Given I provide an array of policy conditions as following:
     */
    public function iProvideAnArrayOfPolicyConditionsAsFollowing(TableNode $table)
    {
        $this->options = [
            ["bucket" => self::getResourceName()],
            ["starts-with", '$key', ""],
        ];
        foreach ($table as $row) {
            $this->options[] = [$row['key'] => $row['value']];
        }
    }

    /**
     * @Given I want the policy expires after :expires
     */
    public function iWantThePolicyExpiresAfter($expires)
    {
        $this->expires = $expires;
    }

    /**
     * @When I create a POST object SigV4 with inputs and policy
     */
    public function iCreateAPostObjectSigvWithInputsAndPolicy2()
    {
        $postObject = new PostObjectV4(
            $this->s3Client,
            self::getResourceName(),
            $this->formInputs,
            $this->options,
            $this->expires
        );

        $this->preparePostData($postObject);
    }

    /**
     * @When I make a HTTP POST request
     */
    public function iMakeAHttpPostRequest()
    {
        try {
            (new Client)->request(
                $this->attributes['method'],
                $this->attributes['action'],
                [
                    'multipart' => $this->inputs,
                ]
            );
        } catch (\GuzzleHttp\Exception\ClientException $e) {
            echo $e->getResponse()->getBody();
        }
    }

    /**
     * @Then the file called :filename is uploaded
     */
    public function theFileCalledIsUploaded($filename)
    {
        Assert::assertTrue($this->s3Client->doesObjectExist(
            self::getResourceName(),
            $filename
        ));

        $fileContents = str_repeat('x', 128 * 1024);
        Assert::assertEquals(
            $fileContents,
            $this->s3Client->getObject([
                'Bucket' => self::getResourceName(),
                'Key' => $filename,
            ])['Body']->getContents()
        );
    }

    /**
     * Prepare form inputs and attribute for POST
     */
    private function preparePostData($postObject)
    {
        $this->attributes = $postObject->getFormAttributes();
        foreach ($postObject->getFormInputs() as $name => $contents) {
            $this->inputs[] = [
                'name' => $name,
                'contents' => $contents,
            ];
        }
        $this->inputs[] = [
            'name' => 'file',
            'contents' => $this->stream,
            'filename' => 'file.ext',
        ];
    }
}


<?php
namespace Aws\Test\Integ;

use Aws;
use Aws\Exception\AwsException;
use Aws\JsonCompiler;
use Aws\Result;
use Aws\Sdk;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Behat\Hook\Scope\AfterFeatureScope;
use Behat\Behat\Hook\Scope\BeforeFeatureScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use JmesPath\Env;
use PHPUnit_Framework_Assert;

class SmokeContext extends PHPUnit_Framework_Assert implements
    Context,
    SnippetAcceptingContext
{
    use IntegUtils;

    /**
     * @var Sdk
     */
    protected $sdk;

    /**
     * @var string
     */
    protected $serviceName;

    /**
     * @var \Aws\AwsClientInterface
     */
    protected $client;

    /**
     * @var Result
     */
    protected $response;

    /**
     * @var AwsException
     */
    protected $error;

    private static $cloudFrontOriginAccessId;

    private static $cloudFrontETag;

    private static $configOverrides = [
        'DeviceFarm' => [
            'region' => 'us-west-2',
        ],
        'Efs' => [
            'region' => 'us-west-2',
        ],
        'Inspector' => [
            'region' => 'us-west-2',
        ],
    ];

    /**
     * @BeforeSuite
     */
    public static function prepare()
    {
        error_reporting(-1);
        date_default_timezone_set('UTC');

        // Clear out any previously compiled JMESPath files.
        Env::cleanCompileDir();
    }

    /**
     * @BeforeFeature @cloudfront
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpCloudFront(BeforeFeatureScope $scope)
    {
        /** @var \Aws\Result $result */
        $result = self::getSdk(self::$configOverrides)
            ->createCloudFront()
            ->createCloudFrontOriginAccessIdentity([
                'CloudFrontOriginAccessIdentityConfig' => [
                    'CallerReference' => rand(0, PHP_INT_MAX),
                    'Comment' => 'Foo Bar, Baz!',
                ],
            ]);

        self::$cloudFrontOriginAccessId = $result
            ->search('CloudFrontOriginAccessIdentity.Id');
        self::$cloudFrontETag = $result['ETag'];
    }

    /**
     * @AfterFeature @cloudfront
     *
     * @param AfterFeatureScope $scope
     */
    public static function tearDownCloudFront(AfterFeatureScope $scope)
    {
        self::getSdk(self::$configOverrides)
            ->createCloudFront()
            ->deleteCloudFrontOriginAccessIdentity([
                'Id' => self::$cloudFrontOriginAccessId,
                'IfMatch' => self::$cloudFrontETag,
            ]);
    }

    /**
     * @BeforeFeature @efs
     *
     * Ensure that the testing credentials have access to the EFS preview;
     * skip entire feature otherwise.
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpEfs(BeforeFeatureScope $scope)
    {
        try {
            self::getSdk(self::$configOverrides)
                ->createEfs()
                ->describeFileSystems();
        } catch (\Exception $e) {
            // If the test failed because the account has no access to EFS,
            // throw the exception to cause the feature to be skipped.
            if ($e instanceof AwsException
                && 'AccessDeniedException' === $e->getAwsErrorCode()
            ) {
                throw $e;
            }
        }
    }

    /**
     * @BeforeFeature @inspector
     *
     * Ensure that the testing credentials have access to the Inspector preview;
     * skip entire feature otherwise.
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpInspector(BeforeFeatureScope $scope)
    {
        try {
            self::getSdk(self::$configOverrides)
                ->createInspector()
                ->listApplications();
        } catch (\Exception $e) {
            // If the test failed because the account has no access to EFS,
            // throw the exception to cause the feature to be skipped.
            if ($e instanceof AwsException
                && 'AccessDeniedException' === $e->getAwsErrorCode()
            ) {
                throw $e;
            }
        }
    }

    /**
     * @BeforeFeature @marketplacecommerceanalytics
     *
     * Ensure that the testing credentials have a Marketplace Commerce Analytics
     * subscription; skip entire feature otherwise.
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpMarketplaceCommerceAnalytics(BeforeFeatureScope $scope)
    {
        try {
            self::getSdk(self::$configOverrides)
                ->createMarketplaceCommerceAnalytics()
                ->generateDataSet([
                    'dataSetType' => 'fake-type',
                    'dataSetPublicationDate' => 'fake-date',
                    'roleNameArn' => 'fake-arn',
                    'destinationS3BucketName' => 'fake-bucket',
                    'snsTopicArn' => 'fake-arn',
                ]);
        } catch (\Exception $e) {
            // If the test failed because the account has no support subscription,
            // throw the exception to cause the feature to be skipped.
            if ($e instanceof AwsException
                && 'SubscriptionRequiredException' === $e->getAwsErrorCode()
            ) {
                throw $e;
            }
        }
    }

    /**
     * @BeforeFeature @sqs
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpSqs(BeforeFeatureScope $scope)
    {
        $sqs = self::getSdk(self::$configOverrides)
            ->createSqs();
        $queueName = self::getResourcePrefix() . 'testing-queue';

        $sqs->createQueue(['QueueName' => $queueName]);
        $sqs->waitUntil('QueueExists', ['QueueName' => $queueName]);
    }

    /**
     * @AfterFeature @sqs
     *
     * @param AfterFeatureScope $scope
     */
    public static function tearDownSqs(AfterFeatureScope $scope)
    {
        $sqs = self::getSdk(self::$configOverrides)
            ->createSqs();

        $sqs->deleteQueue([
            'QueueUrl' => $sqs->getQueueUrl([
                'QueueName' => self::getResourcePrefix() . 'testing-queue',
            ])['QueueUrl']
        ]);
    }

    /**
     * @BeforeFeature @support
     *
     * Ensure that the testing credentials have a support subscription;
     * skip entire feature otherwise.
     *
     * @param BeforeFeatureScope $scope
     */
    public static function setUpSupport(BeforeFeatureScope $scope)
    {
        try {
            self::getSdk(self::$configOverrides)
                ->createSupport()
                ->describeServices();
        } catch (\Exception $e) {
            // If the test failed because the account has no support subscription,
            // throw the exception to cause the feature to be skipped.
            if ($e instanceof AwsException
                && 'SubscriptionRequiredException' === $e->getAwsErrorCode()
            ) {
                throw $e;
            }
        }
    }

    /**
     * @BeforeScenario
     *
     * @param BeforeScenarioScope $scope
     */
    public function setUp(BeforeScenarioScope $scope)
    {
        foreach ($scope->getFeature()->getTags() as $tag) {
            try{
                $this->serviceName = Aws\manifest($tag)['namespace'];
                break;
            } catch (\Exception $e) {
                // just in case an additional tag managed to sneak into the smoke tests
            }
        }

        if (empty($this->serviceName)) {
            throw new PendingException(
                'No service found for smoke test tagged with: '
                    . implode(', ', $scope->getFeature()->getTags())
            );
        }

        $this->sdk = self::getSdk(self::$configOverrides);

        $this->client = $this->sdk->createClient($this->serviceName);
    }

    /**
     * @When I call the :commandName API
     *
     * @param string $commandName
     * @param array $payload
     */
    public function iCallTheApi($commandName, array $payload = [])
    {
        $this->response = $this->client->{$commandName}($payload);
    }

    /**
     * @When I call the :commandName API with:
     *
     * @param string $command
     * @param TableNode $payload
     */
    public function iCallTheApiWith($command, TableNode $payload)
    {
        $this->iCallTheApi($command, $payload->getRowsHash());
    }

    /**
     * @When I call the :command API with JSON:
     *
     * @param string $command
     * @param PyStringNode $payload
     */
    public function iCallTheApiWithJson($command, PyStringNode $payload)
    {
        $this->iCallTheApi($command, json_decode($payload->getRaw(), true));
    }

    /**
     * @When I attempt to call the :commandName API with:
     *
     * @param string $command
     * @param TableNode $payload
     */
    public function iAttemptToCallTheApiWith($command, TableNode $payload)
    {
        try {
            $this->iCallTheApiWith($command, $payload);
        } catch (AwsException $e) {
            $this->error= $e;
        }
    }

    /**
     * @When I attempt to call the :command API with JSON:
     *
     * @param string $command
     * @param PyStringNode $payload
     */
    public function iAttemptToCallTheApiWithJson($command, PyStringNode $payload)
    {
        try {
            $this->iCallTheApiWithJson($command, $payload);
        } catch (AwsException $e) {
            $this->error= $e;
        }
    }

    /**
     * @Then the value at :key should be a list
     *
     * @param string $key
     */
    public function theValueAtShouldBeAList($key)
    {
        $this->assertInstanceOf(Result::class, $this->response);
        $this->assertInternalType('array', $this->response->search($key));
    }

    /**
     * @Then I expect the response error code to be :errorCode
     *
     * @param string $errorCode
     */
    public function iExpectTheResponseErrorCodeToBe($errorCode)
    {
        $this->assertSame($errorCode, $this->error->getAwsErrorCode());
    }

    /**
     * @Then I expect the response error message to include:
     *
     * @param PyStringNode $string
     */
    public function iExpectTheResponseErrorMessageToInclude(PyStringNode $string)
    {
        $this->assertContains($string->getRaw(), $this->error->getMessage());
    }

    /**
     * @Then the response should contain a :key
     *
     * @param string $key
     */
    public function theResponseShouldContainA($key)
    {
        $this->assertInstanceOf(Result::class, $this->response);
        $this->assertNotNull($this->response->search($key));
    }

    /**
     * @Then the error code should be :errorCode
     *
     * @param string $errorCode
     * @param PyStringNode $string
     */
    public function theErrorCodeShouldBe($errorCode, PyStringNode $string = null)
    {
        $this->iExpectTheResponseErrorCodeToBe($errorCode);

        if (null !== $string) {
            $this->theErrorMessageShouldContain($string);
        }
    }

    /**
     * @Then the request should be successful
     */
    public function theRequestShouldBeSuccessful()
    {
        $this->assertEmpty($this->error);
    }

    /**
     * @Then the error message should contain:
     *
     * @param PyStringNode $string
     */
    public function theErrorMessageShouldContain(PyStringNode $string)
    {
        $this->assertContains($string->getRaw(), $this->error->getMessage());
    }
}


<?php
namespace Aws;

use Aws\Api\BridgeApiProvider;
use Aws\Signature\SignatureProvider;
use Aws\Signature\SignatureV2;

class AwsCompatibilityClient extends AwsClient
{
    private static $compatibilityServices = [
        'simpledb' => 'sdb',
    ];

    public static function getArguments()
    {
        $args = parent::getArguments();
        $args['api_provider']['default']
            = [BridgeApiProvider::class, 'defaultProvider'];
        $args['signature_provider']['default']
            = [__CLASS__, '_default_signature_provider'];

        return $args;
    }

    public function __construct(array $args)
    {
        parent::__construct($args + [
            'service' => $this->getService(),
            'signature_version' => 'v2',
        ]);
    }

    private function getService()
    {
        $klass = get_class($this);
        $service = strtolower(substr($klass, strrpos($klass, '\\') + 1, -6));

        if (isset(self::$compatibilityServices[$service])) {
            return self::$compatibilityServices[$service];
        }

        return $service;
    }

    public static function _default_signature_provider()
    {
        return SignatureProvider::memoize(function ($version, $service, $region) {
            if ('v2' === $version) {
                return new SignatureV2();
            }

            static $fallback;
            if (empty($fallback)) {
                $fallback = SignatureProvider::version();
            }

            return $fallback($version, $service, $region);
        });
    }
}


<?php
namespace Aws\Api;

class BridgeApiProvider
{
    public static function defaultProvider()
    {
        $dir = __DIR__ . '/../data';

        return ApiProvider::manifest(
            $dir,
            \Aws\load_compiled_json("$dir/manifest.json")
        );
    }
}


<?php
// This file was auto-generated from sdk-root/src/data/manifest.json
return [ 'sdb' => [ 'namespace' => 'SimpleDb', 'versions' => [ 'latest' => '2009-04-15', '2009-04-15' => '2009-04-15', ], ],];


<?php
// This file was auto-generated from sdk-root/src/data/sdb/2009-04-15/api-2.json
return [ 'metadata' => [ 'apiVersion' => '2009-04-15', 'endpointPrefix' => 'sdb', 'serviceFullName' => 'Amazon SimpleDB', 'signatureVersion' => 'v2', 'xmlNamespace' => 'http://sdb.amazonaws.com/doc/2009-04-15/', 'protocol' => 'query', ], 'operations' => [ 'BatchDeleteAttributes' => [ 'name' => 'BatchDeleteAttributes', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'BatchDeleteAttributesRequest', ], ], 'BatchPutAttributes' => [ 'name' => 'BatchPutAttributes', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'BatchPutAttributesRequest', ], 'errors' => [ [ 'shape' => 'DuplicateItemName', 'error' => [ 'code' => 'DuplicateItemName', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NoSuchDomain', 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberItemAttributesExceeded', 'error' => [ 'code' => 'NumberItemAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberDomainAttributesExceeded', 'error' => [ 'code' => 'NumberDomainAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberDomainBytesExceeded', 'error' => [ 'code' => 'NumberDomainBytesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberSubmittedItemsExceeded', 'error' => [ 'code' => 'NumberSubmittedItemsExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberSubmittedAttributesExceeded', 'error' => [ 'code' => 'NumberSubmittedAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], ], ], 'CreateDomain' => [ 'name' => 'CreateDomain', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'CreateDomainRequest', ], 'errors' => [ [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberDomainsExceeded', 'error' => [ 'code' => 'NumberDomainsExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], ], ], 'DeleteAttributes' => [ 'name' => 'DeleteAttributes', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'DeleteAttributesRequest', ], 'errors' => [ [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NoSuchDomain', 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'AttributeDoesNotExist', 'error' => [ 'code' => 'AttributeDoesNotExist', 'httpStatusCode' => 404, 'senderFault' => true, ], 'exception' => true, ], ], ], 'DeleteDomain' => [ 'name' => 'DeleteDomain', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'DeleteDomainRequest', ], 'errors' => [ [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], ], ], 'DomainMetadata' => [ 'name' => 'DomainMetadata', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'DomainMetadataRequest', ], 'output' => [ 'shape' => 'DomainMetadataResult', 'resultWrapper' => 'DomainMetadataResult', ], 'errors' => [ [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NoSuchDomain', 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], ], ], 'GetAttributes' => [ 'name' => 'GetAttributes', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'GetAttributesRequest', ], 'output' => [ 'shape' => 'GetAttributesResult', 'resultWrapper' => 'GetAttributesResult', ], 'errors' => [ [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NoSuchDomain', 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], ], ], 'ListDomains' => [ 'name' => 'ListDomains', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'ListDomainsRequest', ], 'output' => [ 'shape' => 'ListDomainsResult', 'resultWrapper' => 'ListDomainsResult', ], 'errors' => [ [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'InvalidNextToken', 'error' => [ 'code' => 'InvalidNextToken', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], ], ], 'PutAttributes' => [ 'name' => 'PutAttributes', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'PutAttributesRequest', ], 'errors' => [ [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NoSuchDomain', 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberDomainAttributesExceeded', 'error' => [ 'code' => 'NumberDomainAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberDomainBytesExceeded', 'error' => [ 'code' => 'NumberDomainBytesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NumberItemAttributesExceeded', 'error' => [ 'code' => 'NumberItemAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'AttributeDoesNotExist', 'error' => [ 'code' => 'AttributeDoesNotExist', 'httpStatusCode' => 404, 'senderFault' => true, ], 'exception' => true, ], ], ], 'Select' => [ 'name' => 'Select', 'http' => [ 'method' => 'POST', 'requestUri' => '/', ], 'input' => [ 'shape' => 'SelectRequest', ], 'output' => [ 'shape' => 'SelectResult', 'resultWrapper' => 'SelectResult', ], 'errors' => [ [ 'shape' => 'InvalidParameterValue', 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'InvalidNextToken', 'error' => [ 'code' => 'InvalidNextToken', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'InvalidNumberPredicates', 'error' => [ 'code' => 'InvalidNumberPredicates', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'InvalidNumberValueTests', 'error' => [ 'code' => 'InvalidNumberValueTests', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'InvalidQueryExpression', 'error' => [ 'code' => 'InvalidQueryExpression', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'MissingParameter', 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'NoSuchDomain', 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'RequestTimeout', 'error' => [ 'code' => 'RequestTimeout', 'httpStatusCode' => 408, 'senderFault' => true, ], 'exception' => true, ], [ 'shape' => 'TooManyRequestedAttributes', 'error' => [ 'code' => 'TooManyRequestedAttributes', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], ], ], ], 'shapes' => [ 'Attribute' => [ 'type' => 'structure', 'required' => [ 'Name', 'Value', ], 'members' => [ 'Name' => [ 'shape' => 'String', ], 'AlternateNameEncoding' => [ 'shape' => 'String', ], 'Value' => [ 'shape' => 'String', ], 'AlternateValueEncoding' => [ 'shape' => 'String', ], ], ], 'AttributeDoesNotExist' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'AttributeDoesNotExist', 'httpStatusCode' => 404, 'senderFault' => true, ], 'exception' => true, ], 'AttributeList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Attribute', 'locationName' => 'Attribute', ], 'flattened' => true, ], 'AttributeNameList' => [ 'type' => 'list', 'member' => [ 'shape' => 'String', 'locationName' => 'AttributeName', ], 'flattened' => true, ], 'BatchDeleteAttributesRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'Items', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], 'Items' => [ 'shape' => 'DeletableItemList', ], ], ], 'BatchPutAttributesRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'Items', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], 'Items' => [ 'shape' => 'ReplaceableItemList', ], ], ], 'Boolean' => [ 'type' => 'boolean', ], 'CreateDomainRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], ], ], 'DeletableItem' => [ 'type' => 'structure', 'required' => [ 'Name', ], 'members' => [ 'Name' => [ 'shape' => 'String', 'locationName' => 'ItemName', ], 'Attributes' => [ 'shape' => 'AttributeList', ], ], ], 'DeletableItemList' => [ 'type' => 'list', 'member' => [ 'shape' => 'DeletableItem', 'locationName' => 'Item', ], 'flattened' => true, ], 'DeleteAttributesRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'ItemName', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], 'ItemName' => [ 'shape' => 'String', ], 'Attributes' => [ 'shape' => 'AttributeList', ], 'Expected' => [ 'shape' => 'UpdateCondition', ], ], ], 'DeleteDomainRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], ], ], 'DomainMetadataRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], ], ], 'DomainMetadataResult' => [ 'type' => 'structure', 'members' => [ 'ItemCount' => [ 'shape' => 'Integer', ], 'ItemNamesSizeBytes' => [ 'shape' => 'Long', ], 'AttributeNameCount' => [ 'shape' => 'Integer', ], 'AttributeNamesSizeBytes' => [ 'shape' => 'Long', ], 'AttributeValueCount' => [ 'shape' => 'Integer', ], 'AttributeValuesSizeBytes' => [ 'shape' => 'Long', ], 'Timestamp' => [ 'shape' => 'Integer', ], ], ], 'DomainNameList' => [ 'type' => 'list', 'member' => [ 'shape' => 'String', 'locationName' => 'DomainName', ], 'flattened' => true, ], 'DuplicateItemName' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'DuplicateItemName', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'Float' => [ 'type' => 'float', ], 'GetAttributesRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'ItemName', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], 'ItemName' => [ 'shape' => 'String', ], 'AttributeNames' => [ 'shape' => 'AttributeNameList', ], 'ConsistentRead' => [ 'shape' => 'Boolean', ], ], ], 'GetAttributesResult' => [ 'type' => 'structure', 'members' => [ 'Attributes' => [ 'shape' => 'AttributeList', ], ], ], 'Integer' => [ 'type' => 'integer', ], 'InvalidNextToken' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'InvalidNextToken', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'InvalidNumberPredicates' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'InvalidNumberPredicates', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'InvalidNumberValueTests' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'InvalidNumberValueTests', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'InvalidParameterValue' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'InvalidParameterValue', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'InvalidQueryExpression' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'InvalidQueryExpression', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'Item' => [ 'type' => 'structure', 'required' => [ 'Name', 'Attributes', ], 'members' => [ 'Name' => [ 'shape' => 'String', ], 'AlternateNameEncoding' => [ 'shape' => 'String', ], 'Attributes' => [ 'shape' => 'AttributeList', ], ], ], 'ItemList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Item', 'locationName' => 'Item', ], 'flattened' => true, ], 'ListDomainsRequest' => [ 'type' => 'structure', 'members' => [ 'MaxNumberOfDomains' => [ 'shape' => 'Integer', ], 'NextToken' => [ 'shape' => 'String', ], ], ], 'ListDomainsResult' => [ 'type' => 'structure', 'members' => [ 'DomainNames' => [ 'shape' => 'DomainNameList', ], 'NextToken' => [ 'shape' => 'String', ], ], ], 'Long' => [ 'type' => 'long', ], 'MissingParameter' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'MissingParameter', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'NoSuchDomain' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NoSuchDomain', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'NumberDomainAttributesExceeded' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NumberDomainAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], 'NumberDomainBytesExceeded' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NumberDomainBytesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], 'NumberDomainsExceeded' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NumberDomainsExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], 'NumberItemAttributesExceeded' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NumberItemAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], 'NumberSubmittedAttributesExceeded' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NumberSubmittedAttributesExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], 'NumberSubmittedItemsExceeded' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'NumberSubmittedItemsExceeded', 'httpStatusCode' => 409, 'senderFault' => true, ], 'exception' => true, ], 'PutAttributesRequest' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'ItemName', 'Attributes', ], 'members' => [ 'DomainName' => [ 'shape' => 'String', ], 'ItemName' => [ 'shape' => 'String', ], 'Attributes' => [ 'shape' => 'ReplaceableAttributeList', ], 'Expected' => [ 'shape' => 'UpdateCondition', ], ], ], 'ReplaceableAttribute' => [ 'type' => 'structure', 'required' => [ 'Name', 'Value', ], 'members' => [ 'Name' => [ 'shape' => 'String', ], 'Value' => [ 'shape' => 'String', ], 'Replace' => [ 'shape' => 'Boolean', ], ], ], 'ReplaceableAttributeList' => [ 'type' => 'list', 'member' => [ 'shape' => 'ReplaceableAttribute', 'locationName' => 'Attribute', ], 'flattened' => true, ], 'ReplaceableItem' => [ 'type' => 'structure', 'required' => [ 'Name', 'Attributes', ], 'members' => [ 'Name' => [ 'shape' => 'String', 'locationName' => 'ItemName', ], 'Attributes' => [ 'shape' => 'ReplaceableAttributeList', ], ], ], 'ReplaceableItemList' => [ 'type' => 'list', 'member' => [ 'shape' => 'ReplaceableItem', 'locationName' => 'Item', ], 'flattened' => true, ], 'RequestTimeout' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'RequestTimeout', 'httpStatusCode' => 408, 'senderFault' => true, ], 'exception' => true, ], 'SelectRequest' => [ 'type' => 'structure', 'required' => [ 'SelectExpression', ], 'members' => [ 'SelectExpression' => [ 'shape' => 'String', ], 'NextToken' => [ 'shape' => 'String', ], 'ConsistentRead' => [ 'shape' => 'Boolean', ], ], ], 'SelectResult' => [ 'type' => 'structure', 'members' => [ 'Items' => [ 'shape' => 'ItemList', ], 'NextToken' => [ 'shape' => 'String', ], ], ], 'String' => [ 'type' => 'string', ], 'TooManyRequestedAttributes' => [ 'type' => 'structure', 'members' => [ 'BoxUsage' => [ 'shape' => 'Float', ], ], 'error' => [ 'code' => 'TooManyRequestedAttributes', 'httpStatusCode' => 400, 'senderFault' => true, ], 'exception' => true, ], 'UpdateCondition' => [ 'type' => 'structure', 'members' => [ 'Name' => [ 'shape' => 'String', ], 'Value' => [ 'shape' => 'String', ], 'Exists' => [ 'shape' => 'Boolean', ], ], ], ],];