<?php
namespace Aws\Endpoint;

use JmesPath\Env;

class PartitionEndpointProvider
{
    /** @var Partition[] */
    private $partitions;
    /** @var string */
    private $defaultPartition;
    /** @var array  */
    private $options;

    /**
     * The 'options' parameter accepts the following arguments:
     *
     * - sts_regional_endpoints: For STS legacy regions, set to 'regional' to
     *   use regional endpoints, 'legacy' to use the legacy global endpoint.
     *   Defaults to 'legacy'.
     * - s3_us_east_1_regional_endpoint: For S3 us-east-1 region, set to 'regional'
     *   to use the regional endpoint, 'legacy' to use the legacy global endpoint.
     *   Defaults to 'legacy'.
     *
     * @param array $partitions
     * @param string $defaultPartition
     * @param array $options
     */
    public function __construct(
        array $partitions,
        $defaultPartition = 'aws',
        $options = []
    ) {
        $this->partitions = array_map(function (array $definition) {
            return new Partition($definition);
        }, array_values($partitions));
        $this->defaultPartition = $defaultPartition;
        $this->options = $options;
    }

    public function __invoke(array $args = [])
    {
        $partition = $this->getPartition(
            isset($args['region']) ? $args['region'] : '',
            isset($args['service']) ? $args['service'] : ''
        );
        $args['options'] = $this->options;

        return $partition($args);
    }

    /**
     * Returns the partition containing the provided region or the default
     * partition if no match is found.
     *
     * @param string $region
     * @param string $service
     *
     * @return Partition
     */
    public function getPartition($region, $service)
    {
        foreach ($this->partitions as $partition) {
            if ($partition->isRegionMatch($region, $service)) {
                return $partition;
            }
        }

        return $this->getPartitionByName($this->defaultPartition);
    }

    /**
     * Returns the partition with the provided name or null if no partition with
     * the provided name can be found.
     *
     * @param string $name
     *
     * @return Partition|null
     */
    public function getPartitionByName($name)
    {
        foreach ($this->partitions as $partition) {
            if ($name === $partition->getName()) {
                return $partition;
            }
        }
    }

    /**
     * Creates and returns the default SDK partition provider.
     *
     * @param array $options
     * @return PartitionEndpointProvider
     */
    public static function defaultProvider($options = [])
    {
        $data = \Aws\load_compiled_json(__DIR__ . '/../data/endpoints.json');
        $prefixData = \Aws\load_compiled_json(__DIR__ . '/../data/endpoints_prefix_history.json');
        $mergedData = self::mergePrefixData($data, $prefixData);

        return new self($mergedData['partitions'], 'aws', $options);
    }

    /**
     * Copy endpoint data for other prefixes used by a given service
     *
     * @param $data
     * @param $prefixData
     * @return array
     */
    public static function mergePrefixData($data, $prefixData)
    {
        $prefixGroups = $prefixData['prefix-groups'];

        foreach ($data["partitions"] as $index => $partition) {
            foreach ($prefixGroups as $current => $old) {
                $serviceData = Env::search("services.\"{$current}\"", $partition);
                if (!empty($serviceData)) {
                    foreach ($old as $prefix) {
                        if (empty(Env::search("services.\"{$prefix}\"", $partition))) {
                            $data["partitions"][$index]["services"][$prefix] = $serviceData;
                        }
                    }
                }
            }
        }

        return $data;
    }
}


<?php
namespace Aws\Endpoint;

/**
 * Represents a section of the AWS cloud.
 */
interface PartitionInterface
{
    /**
     * Returns the partition's short name, e.g., 'aws,' 'aws-cn,' or
     * 'aws-us-gov.'
     *
     * @return string
     */
    public function getName();

    /**
     * Determine if this partition contains the provided region. Include the
     * name of the service to inspect non-regional endpoints
     *
     * @param string $region
     * @param string $service
     *
     * @return bool
     */
    public function isRegionMatch($region, $service);

    /**
     * Return the endpoints supported by a given service.
     *
     * @param string    $service                    Identifier of the service
     *                                              whose endpoints should be
     *                                              listed (e.g., 's3' or 'ses')
     * @param bool      $allowNonRegionalEndpoints  Set to `true` to include
     *                                              endpoints that are not AWS
     *                                              regions (e.g., 'local' for
     *                                              DynamoDB or
     *                                              'fips-us-gov-west-1' for S3)
     *
     * @return string[]
     */
    public function getAvailableEndpoints(
        $service,
        $allowNonRegionalEndpoints = false
    );

    /**
     * A partition must be invokable as an endpoint provider.
     *
     * @see EndpointProvider
     *
     * @param array $args
     * @return array
     */
    public function __invoke(array $args = []);
}


<?php
namespace Aws\Endpoint;

/**
 * Provides endpoints based on an endpoint pattern configuration array.
 */
class PatternEndpointProvider
{
    /** @var array */
    private $patterns;

    /**
     * @param array $patterns Hash of endpoint patterns mapping to endpoint
     *                        configurations.
     */
    public function __construct(array $patterns)
    {
        $this->patterns = $patterns;
    }

    public function __invoke(array $args = [])
    {
        $service = isset($args['service']) ? $args['service'] : '';
        $region = isset($args['region']) ? $args['region'] : '';
        $keys = ["{$region}/{$service}", "{$region}/*", "*/{$service}", "*/*"];

        foreach ($keys as $key) {
            if (isset($this->patterns[$key])) {
                return $this->expand(
                    $this->patterns[$key],
                    isset($args['scheme']) ? $args['scheme'] : 'https',
                    $service,
                    $region
                );
            }
        }

        return null;
    }

    private function expand(array $config, $scheme, $service, $region)
    {
        $config['endpoint'] = $scheme . '://'
            . strtr($config['endpoint'], [
                '{service}' => $service,
                '{region}'  => $region
            ]);

        return $config;
    }
}


<?php
namespace Aws\Endpoint\UseDualstackEndpoint;

use Aws;
use Aws\Endpoint\UseDualstackEndpoint\Exception\ConfigurationException;

class Configuration implements ConfigurationInterface
{
    private $useDualstackEndpoint;

    public function __construct($useDualstackEndpoint, $region)
    {
        $this->useDualstackEndpoint = Aws\boolean_value($useDualstackEndpoint);
        if (is_null($this->useDualstackEndpoint)) {
            throw new ConfigurationException("'use_dual_stack_endpoint' config option"
                . " must be a boolean value.");
        }
        if ($this->useDualstackEndpoint == true
            && (strpos($region, "iso-") !== false || strpos($region, "-iso") !== false)
        ) {
            throw new ConfigurationException("Dual-stack is not supported in ISO regions");        }
    }

    /**
     * {@inheritdoc}
     */
    public function isUseDualstackEndpoint()
    {
        return $this->useDualstackEndpoint;
    }

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        return [
            'use_dual_stack_endpoint' => $this->isUseDualstackEndpoint(),
        ];
    }
}


<?php
namespace Aws\Endpoint\UseDualstackEndpoint;

interface ConfigurationInterface
{
    /**
     * Returns whether or not to use a DUALSTACK endpoint
     *
     * @return bool
     */
    public function isUseDualstackEndpoint();

    /**
     * Returns the configuration as an associative array
     *
     * @return array
     */
    public function toArray();
}


<?php
namespace Aws\Endpoint\UseDualstackEndpoint;

use Aws\AbstractConfigurationProvider;
use Aws\CacheInterface;
use Aws\ConfigurationProviderInterface;
use Aws\Endpoint\UseDualstackEndpoint\Exception\ConfigurationException;
use GuzzleHttp\Promise;

/**
 * A configuration provider is a function that returns a promise that is
 * fulfilled with a {@see \Aws\Endpoint\UseDualstackEndpoint\onfigurationInterface}
 * or rejected with an {@see \Aws\Endpoint\UseDualstackEndpoint\ConfigurationException}.
 *
 *
 * use Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider;
 * $provider = ConfigurationProvider::defaultProvider();
 * // Returns a ConfigurationInterface or throws.
 * $config = $provider()->wait();
 *
 *
 * Configuration providers can be composed to create configuration using
 * conditional logic that can create different configurations in different
 * environments. You can compose multiple providers into a single provider using
 * {@see Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider::chain}. This function
 * accepts providers as variadic arguments and returns a new function that will
 * invoke each provider until a successful configuration is returned.
 *
 *
 * // First try an INI file at this location.
 * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
 * // Then try an INI file at this location.
 * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
 * // Then try loading from environment variables.
 * $c = ConfigurationProvider::env();
 * // Combine the three providers together.
 * $composed = ConfigurationProvider::chain($a, $b, $c);
 * // Returns a promise that is fulfilled with a configuration or throws.
 * $promise = $composed();
 * // Wait on the configuration to resolve.
 * $config = $promise->wait();
 *
 */
class ConfigurationProvider extends AbstractConfigurationProvider
    implements ConfigurationProviderInterface
{
    const ENV_USE_DUAL_STACK_ENDPOINT = 'AWS_USE_DUALSTACK_ENDPOINT';
    const INI_USE_DUAL_STACK_ENDPOINT = 'use_dualstack_endpoint';

    public static $cacheKey = 'aws_cached_use_dualstack_endpoint_config';

    protected static $interfaceClass = ConfigurationInterface::class;
    protected static $exceptionClass = ConfigurationException::class;

    /**
     * Create a default config provider that first checks for environment
     * variables, then checks for a specified profile in the environment-defined
     * config file location (env variable is 'AWS_CONFIG_FILE', file location
     * defaults to ~/.aws/config), then checks for the "default" profile in the
     * environment-defined config file location, and failing those uses a default
     * fallback set of configuration options.
     *
     * This provider is automatically wrapped in a memoize function that caches
     * previously provided config options.
     *
     * @param array $config
     *
     * @return callable
     */
    public static function defaultProvider(array $config = [])
    {
        $region = $config['region'];
        $configProviders = [self::env($region)];
        if (
            !isset($config['use_aws_shared_config_files'])
            || $config['use_aws_shared_config_files'] != false
        ) {
            $configProviders[] = self::ini($region);
        }
        $configProviders[] = self::fallback($region);

        $memo = self::memoize(
            call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)
        );

        if (isset($config['use_dual_stack_endpoint'])
            && $config['use_dual_stack_endpoint'] instanceof CacheInterface
        ) {
            return self::cache($memo, $config['use_dual_stack_endpoint'], self::$cacheKey);
        }

        return $memo;
    }

    /**
     * Provider that creates config from environment variables.
     *
     * @return callable
     */
    public static function env($region)
    {
        return function () use ($region) {
            // Use config from environment variables, if available
            $useDualstackEndpoint = getenv(self::ENV_USE_DUAL_STACK_ENDPOINT);
            if (!empty($useDualstackEndpoint)) {
                return Promise\Create::promiseFor(
                    new Configuration($useDualstackEndpoint, $region)
                );
            }

            return self::reject('Could not find environment variable config'
                . ' in ' . self::ENV_USE_DUAL_STACK_ENDPOINT);
        };
    }

    /**
     * Config provider that creates config using a config file whose location
     * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to
     * ~/.aws/config if not specified
     *
     * @param string|null $profile  Profile to use. If not specified will use
     *                              the "default" profile.
     * @param string|null $filename If provided, uses a custom filename rather
     *                              than looking in the default directory.
     *
     * @return callable
     */
    public static function ini($region, $profile = null, $filename = null)
    {
        $filename = $filename ?: (self::getDefaultConfigFilename());
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

        return function () use ($region, $profile, $filename) {
            if (!@is_readable($filename)) {
                return self::reject("Cannot read configuration from $filename");
            }

            // Use INI_SCANNER_NORMAL instead of INI_SCANNER_TYPED for PHP 5.5 compatibility
            $data = \Aws\parse_ini_file($filename, true, INI_SCANNER_NORMAL);
            if ($data === false) {
                return self::reject("Invalid config file: $filename");
            }
            if (!isset($data[$profile])) {
                return self::reject("'$profile' not found in config file");
            }
            if (!isset($data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT])) {
                return self::reject("Required use dualstack endpoint config values
                    not present in INI profile '{$profile}' ({$filename})");
            }

            // INI_SCANNER_NORMAL parses false-y values as an empty string
            if ($data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT] === "") {
                $data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT] = false;
            }

            return Promise\Create::promiseFor(
                new Configuration($data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT], $region)
            );
        };
    }

    /**
     * Fallback config options when other sources are not set.
     *
     * @return callable
     */
    public static function fallback($region)
    {
        return function () use ($region) {
            return Promise\Create::promiseFor(new Configuration(false, $region));
        };
    }
}


<?php
namespace Aws\Endpoint\UseDualstackEndpoint\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

/**
 * Represents an error interacting with configuration for useDualstackRegion
 */
class ConfigurationException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}


<?php
namespace Aws\Endpoint\UseFipsEndpoint;

use Aws;
use Aws\Endpoint\UseFipsEndpoint\Exception\ConfigurationException;

class Configuration implements ConfigurationInterface
{
    private $useFipsEndpoint;

    public function __construct($useFipsEndpoint)
    {
        $this->useFipsEndpoint = Aws\boolean_value($useFipsEndpoint);
        if (is_null($this->useFipsEndpoint)) {
            throw new ConfigurationException("'use_fips_endpoint' config option"
                . " must be a boolean value.");
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isUseFipsEndpoint()
    {
        return $this->useFipsEndpoint;
    }

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        return [
            'use_fips_endpoint' => $this->isUseFipsEndpoint(),
        ];
    }
}


<?php
namespace Aws\Endpoint\UseFipsEndpoint;

interface ConfigurationInterface
{
    /**
     * Returns whether or not to use a FIPS endpoint
     *
     * @return bool
     */
    public function isUseFipsEndpoint();

    /**
     * Returns the configuration as an associative array
     *
     * @return array
     */
    public function toArray();
}


<?php
namespace Aws\Endpoint\UseFipsEndpoint;

use Aws\AbstractConfigurationProvider;
use Aws\CacheInterface;
use Aws\ConfigurationProviderInterface;
use Aws\Endpoint\UseFipsEndpoint\Exception\ConfigurationException;
use GuzzleHttp\Promise;

/**
 * A configuration provider is a function that returns a promise that is
 * fulfilled with a {@see \Aws\Endpoint\UseFipsEndpoint\onfigurationInterface}
 * or rejected with an {@see \Aws\Endpoint\UseFipsEndpoint\ConfigurationException}.
 *
 *
 * use Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider;
 * $provider = ConfigurationProvider::defaultProvider();
 * // Returns a ConfigurationInterface or throws.
 * $config = $provider()->wait();
 *
 *
 * Configuration providers can be composed to create configuration using
 * conditional logic that can create different configurations in different
 * environments. You can compose multiple providers into a single provider using
 * {@see Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider::chain}. This function
 * accepts providers as variadic arguments and returns a new function that will
 * invoke each provider until a successful configuration is returned.
 *
 *
 * // First try an INI file at this location.
 * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
 * // Then try an INI file at this location.
 * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
 * // Then try loading from environment variables.
 * $c = ConfigurationProvider::env();
 * // Combine the three providers together.
 * $composed = ConfigurationProvider::chain($a, $b, $c);
 * // Returns a promise that is fulfilled with a configuration or throws.
 * $promise = $composed();
 * // Wait on the configuration to resolve.
 * $config = $promise->wait();
 *
 */
class ConfigurationProvider extends AbstractConfigurationProvider
    implements ConfigurationProviderInterface
{
    const ENV_USE_FIPS_ENDPOINT = 'AWS_USE_FIPS_ENDPOINT';
    const INI_USE_FIPS_ENDPOINT = 'use_fips_endpoint';

    public static $cacheKey = 'aws_cached_use_fips_endpoint_config';

    protected static $interfaceClass = ConfigurationInterface::class;
    protected static $exceptionClass = ConfigurationException::class;

    /**
     * Create a default config provider that first checks for environment
     * variables, then checks for a specified profile in the environment-defined
     * config file location (env variable is 'AWS_CONFIG_FILE', file location
     * defaults to ~/.aws/config), then checks for the "default" profile in the
     * environment-defined config file location, and failing those uses a default
     * fallback set of configuration options.
     *
     * This provider is automatically wrapped in a memoize function that caches
     * previously provided config options.
     *
     * @param array $config
     *
     * @return callable
     */
    public static function defaultProvider(array $config = [])
    {
        $configProviders = [self::env()];
        if (
            !isset($config['use_aws_shared_config_files'])
            || $config['use_aws_shared_config_files'] != false
        ) {
            $configProviders[] = self::ini();
        }
        $configProviders[] = self::fallback($config['region']);

        $memo = self::memoize(
            call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)
        );

        if (isset($config['use_fips_endpoint'])
            && $config['use_fips_endpoint'] instanceof CacheInterface
        ) {
            return self::cache($memo, $config['use_fips_endpoint'], self::$cacheKey);
        }

        return $memo;
    }

    /**
     * Provider that creates config from environment variables.
     *
     * @return callable
     */
    public static function env()
    {
        return function () {
            // Use config from environment variables, if available
            $useFipsEndpoint = getenv(self::ENV_USE_FIPS_ENDPOINT);
            if (!empty($useFipsEndpoint)) {
                return Promise\Create::promiseFor(
                    new Configuration($useFipsEndpoint)
                );
            }

            return self::reject('Could not find environment variable config'
                . ' in ' . self::ENV_USE_FIPS_ENDPOINT);
        };
    }

    /**
     * Config provider that creates config using a config file whose location
     * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to
     * ~/.aws/config if not specified
     *
     * @param string|null $profile  Profile to use. If not specified will use
     *                              the "default" profile.
     * @param string|null $filename If provided, uses a custom filename rather
     *                              than looking in the default directory.
     *
     * @return callable
     */
    public static function ini($profile = null, $filename = null)
    {
        $filename = $filename ?: (self::getDefaultConfigFilename());
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

        return function () use ($profile, $filename) {
            if (!@is_readable($filename)) {
                return self::reject("Cannot read configuration from $filename");
            }

            // Use INI_SCANNER_NORMAL instead of INI_SCANNER_TYPED for PHP 5.5 compatibility
            $data = \Aws\parse_ini_file($filename, true, INI_SCANNER_NORMAL);
            if ($data === false) {
                return self::reject("Invalid config file: $filename");
            }
            if (!isset($data[$profile])) {
                return self::reject("'$profile' not found in config file");
            }
            if (!isset($data[$profile][self::INI_USE_FIPS_ENDPOINT])) {
                return self::reject("Required use fips endpoint config values
                    not present in INI profile '{$profile}' ({$filename})");
            }

            // INI_SCANNER_NORMAL parses false-y values as an empty string
            if ($data[$profile][self::INI_USE_FIPS_ENDPOINT] === "") {
                $data[$profile][self::INI_USE_FIPS_ENDPOINT] = false;
            }

            return Promise\Create::promiseFor(
                new Configuration($data[$profile][self::INI_USE_FIPS_ENDPOINT])
            );
        };
    }

    /**
     * Fallback config options when other sources are not set.
     *
     * @return callable
     */
    public static function fallback($region)
    {
        return function () use ($region) {
            $isFipsPseudoRegion = strpos($region, 'fips-') !== false
                || strpos($region, '-fips') !== false;
            if ($isFipsPseudoRegion){
                $configuration = new Configuration(true);
            } else {
                $configuration = new Configuration(false);
            }
            return Promise\Create::promiseFor($configuration);
        };
    }
}


<?php
namespace Aws\Endpoint\UseFipsEndpoint\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

/**
 * Represents an error interacting with configuration for useFipsRegion
 */
class ConfigurationException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}


<?php
namespace Aws\EndpointDiscovery;

class Configuration implements ConfigurationInterface
{
    private $cacheLimit;
    private $enabled;

    public function __construct($enabled, $cacheLimit = 1000)
    {
        $this->cacheLimit = filter_var($cacheLimit, FILTER_VALIDATE_INT);
        if ($this->cacheLimit == false || $this->cacheLimit < 1) {
            throw new \InvalidArgumentException(
                "'cache_limit' value must be a positive integer."
            );
        }

        // Unparsable $enabled flag errs on the side of disabling endpoint discovery
        $this->enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * {@inheritdoc}
     */
    public function isEnabled()
    {
        return $this->enabled;
    }

    /**
     * {@inheritdoc}
     */
    public function getCacheLimit()
    {
        return $this->cacheLimit;
    }

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        return [
            'enabled' => $this->isEnabled(),
            'cache_limit' => $this->getCacheLimit()
        ];
    }
}


<?php
namespace Aws\EndpointDiscovery;

/**
 * Provides access to endpoint discovery configuration options:
 * 'enabled', 'cache_limit'
 */
interface ConfigurationInterface
{
    /**
     * Checks whether or not endpoint discovery is enabled.
     *
     * @return bool
     */
    public function isEnabled();

    /**
     * Returns the cache limit, if available.
     *
     * @return string|null
     */
    public function getCacheLimit();

    /**
     * Returns the configuration as an associative array
     *
     * @return array
     */
    public function toArray();
}


<?php
namespace Aws\EndpointDiscovery;

use Aws\AbstractConfigurationProvider;
use Aws\CacheInterface;
use Aws\ConfigurationProviderInterface;
use Aws\EndpointDiscovery\Exception\ConfigurationException;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromiseInterface;

/**
 * A configuration provider is a function that returns a promise that is
 * fulfilled with a {@see \Aws\EndpointDiscovery\ConfigurationInterface}
 * or rejected with an {@see \Aws\EndpointDiscovery\Exception\ConfigurationException}.
 *
 *
 * use Aws\EndpointDiscovery\ConfigurationProvider;
 * $provider = ConfigurationProvider::defaultProvider();
 * // Returns a ConfigurationInterface or throws.
 * $config = $provider()->wait();
 *
 *
 * Configuration providers can be composed to create configuration using
 * conditional logic that can create different configurations in different
 * environments. You can compose multiple providers into a single provider using
 * {@see Aws\EndpointDiscovery\ConfigurationProvider::chain}. This function
 * accepts providers as variadic arguments and returns a new function that will
 * invoke each provider until a successful configuration is returned.
 *
 *
 * // First try an INI file at this location.
 * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
 * // Then try an INI file at this location.
 * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
 * // Then try loading from environment variables.
 * $c = ConfigurationProvider::env();
 * // Combine the three providers together.
 * $composed = ConfigurationProvider::chain($a, $b, $c);
 * // Returns a promise that is fulfilled with a configuration or throws.
 * $promise = $composed();
 * // Wait on the configuration to resolve.
 * $config = $promise->wait();
 *
 */
class ConfigurationProvider extends AbstractConfigurationProvider
    implements ConfigurationProviderInterface
{
    const DEFAULT_ENABLED = false;
    const DEFAULT_CACHE_LIMIT = 1000;
    const ENV_ENABLED = 'AWS_ENDPOINT_DISCOVERY_ENABLED';
    const ENV_ENABLED_ALT = 'AWS_ENABLE_ENDPOINT_DISCOVERY';
    const ENV_PROFILE = 'AWS_PROFILE';

    public static $cacheKey = 'aws_cached_endpoint_discovery_config';

    protected static $interfaceClass = ConfigurationInterface::class;
    protected static $exceptionClass = ConfigurationException::class;

    /**
     * Create a default config provider that first checks for environment
     * variables, then checks for a specified profile in the environment-defined
     * config file location (env variable is 'AWS_CONFIG_FILE', file location
     * defaults to ~/.aws/config), then checks for the "default" profile in the
     * environment-defined config file location, and failing those uses a default
     * fallback set of configuration options.
     *
     * This provider is automatically wrapped in a memoize function that caches
     * previously provided config options.
     *
     * @param array $config
     *
     * @return callable
     */
    public static function defaultProvider(array $config = [])
    {
        $configProviders = [self::env()];
        if (
            !isset($config['use_aws_shared_config_files'])
            || $config['use_aws_shared_config_files'] != false
        ) {
            $configProviders[] = self::ini();
        }
        $configProviders[] = self::fallback($config);

        $memo = self::memoize(
            call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)
        );

        if (isset($config['endpoint_discovery'])
            && $config['endpoint_discovery'] instanceof CacheInterface
        ) {
            return self::cache($memo, $config['endpoint_discovery'], self::$cacheKey);
        }

        return $memo;
    }

    /**
     * Provider that creates config from environment variables.
     *
     * @param $cacheLimit
     * @return callable
     */
    public static function env($cacheLimit = self::DEFAULT_CACHE_LIMIT)
    {
        return function () use ($cacheLimit) {
            // Use config from environment variables, if available
            $enabled = getenv(self::ENV_ENABLED);
            if ($enabled === false || $enabled === '') {
                $enabled = getenv(self::ENV_ENABLED_ALT);
            }
            if ($enabled !== false && $enabled !== '') {
                return Promise\Create::promiseFor(
                    new Configuration($enabled, $cacheLimit)
                );
            }

            return self::reject('Could not find environment variable config'
                . ' in ' . self::ENV_ENABLED);
        };
    }

    /**
     * Fallback config options when other sources are not set. Will check the
     * service model for any endpoint discovery required operations, and enable
     * endpoint discovery in that case. If no required operations found, will use
     * the class default values.
     *
     * @param array $config
     * @return callable
     */
    public static function fallback($config = [])
    {
        $enabled = self::DEFAULT_ENABLED;
        if (!empty($config['api_provider'])
            && !empty($config['service'])
            && !empty($config['version'])
        ) {
            $provider = $config['api_provider'];
            $apiData = $provider('api', $config['service'], $config['version']);
            if (!empty($apiData['operations'])) {
                foreach ($apiData['operations'] as $operation) {
                    if (!empty($operation['endpointdiscovery']['required'])) {
                        $enabled = true;
                    }
                }
            }
        }

        return function () use ($enabled) {
            return Promise\Create::promiseFor(
                new Configuration(
                    $enabled,
                    self::DEFAULT_CACHE_LIMIT
                )
            );
        };
    }

    /**
     * Config provider that creates config using a config file whose location
     * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to
     * ~/.aws/config if not specified
     *
     * @param string|null $profile  Profile to use. If not specified will use
     *                              the "default" profile.
     * @param string|null $filename If provided, uses a custom filename rather
     *                              than looking in the default directory.
     * @param int         $cacheLimit
     *
     * @return callable
     */
    public static function ini(
        $profile = null,
        $filename = null,
        $cacheLimit = self::DEFAULT_CACHE_LIMIT
    ) {
        $filename = $filename ?: (self::getDefaultConfigFilename());
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

        return function () use ($profile, $filename, $cacheLimit) {
            if (!@is_readable($filename)) {
                return self::reject("Cannot read configuration from $filename");
            }
            $data = \Aws\parse_ini_file($filename, true);
            if ($data === false) {
                return self::reject("Invalid config file: $filename");
            }
            if (!isset($data[$profile])) {
                return self::reject("'$profile' not found in config file");
            }
            if (!isset($data[$profile]['endpoint_discovery_enabled'])) {
                return self::reject("Required endpoint discovery config values
                    not present in INI profile '{$profile}' ({$filename})");
            }

            return Promise\Create::promiseFor(
                new Configuration(
                    $data[$profile]['endpoint_discovery_enabled'],
                    $cacheLimit
                )
            );
        };
    }

    /**
     * Unwraps a configuration object in whatever valid form it is in,
     * always returning a ConfigurationInterface object.
     *
     * @param  mixed $config
     * @return ConfigurationInterface
     * @throws \InvalidArgumentException
     */
    public static function unwrap($config)
    {
        if (is_callable($config)) {
            $config = $config();
        }
        if ($config instanceof PromiseInterface) {
            $config = $config->wait();
        }
        if ($config instanceof ConfigurationInterface) {
            return $config;
        } elseif (is_array($config) && isset($config['enabled'])) {
            if (isset($config['cache_limit'])) {
                return new Configuration(
                    $config['enabled'],
                    $config['cache_limit']
                );
            }
            return new Configuration(
                $config['enabled'],
                self::DEFAULT_CACHE_LIMIT
            );
        }

        throw new \InvalidArgumentException('Not a valid endpoint_discovery '
            . 'configuration argument.');
    }
}


<?php
namespace Aws\EndpointDiscovery;

use Aws\AwsClient;
use Aws\CacheInterface;
use Aws\CommandInterface;
use Aws\Credentials\CredentialsInterface;
use Aws\Exception\AwsException;
use Aws\Exception\UnresolvedEndpointException;
use Aws\LruArrayCache;
use Aws\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;

class EndpointDiscoveryMiddleware
{
    /**
     * @var CacheInterface
     */
    private static $cache;
    private static $discoveryCooldown = 60;

    private $args;
    private $client;
    private $config;
    private $discoveryTimes = [];
    private $nextHandler;
    private $service;

    public static function wrap(
        $client,
        $args,
        $config
    ) {
        return function (callable $handler) use (
            $client,
            $args,
            $config
        ) {
            return new static(
                $handler,
                $client,
                $args,
                $config
            );
        };
    }

    public function __construct(
        callable $handler,
        AwsClient $client,
        array $args,
        $config
    ) {
        $this->nextHandler = $handler;
        $this->client = $client;
        $this->args = $args;
        $this->service = $client->getApi();
        $this->config = $config;
    }

    public function __invoke(CommandInterface $cmd, RequestInterface $request)
    {
        $nextHandler = $this->nextHandler;
        $op = $this->service->getOperation($cmd->getName())->toArray();

        // Continue only if endpointdiscovery trait is set
        if (isset($op['endpointdiscovery'])) {
            $config = ConfigurationProvider::unwrap($this->config);
            $isRequired = !empty($op['endpointdiscovery']['required']);

            if ($isRequired && !($config->isEnabled())) {
                throw new UnresolvedEndpointException('This operation '
                    . 'requires the use of endpoint discovery, but this has '
                    . 'been disabled in the configuration. Enable endpoint '
                    . 'discovery or use a different operation.');
            }

            // Continue only if enabled by config
            if ($config->isEnabled()) {
                if (isset($op['endpointoperation'])) {
                    throw new UnresolvedEndpointException('This operation is '
                        . 'contradictorily marked both as using endpoint discovery '
                        . 'and being the endpoint discovery operation. Please '
                        . 'verify the accuracy of your model files.');
                }

                // Original endpoint may be used if discovery optional
                $originalUri = $request->getUri();

                $identifiers = $this->getIdentifiers($op);

                $cacheKey = $this->getCacheKey(
                    $this->client->getCredentials()->wait(),
                    $cmd,
                    $identifiers
                );

                // Check/create cache
                if (!isset(self::$cache)) {
                    self::$cache = new LruArrayCache($config->getCacheLimit());
                }

                if (empty($endpointList = self::$cache->get($cacheKey))) {
                    $endpointList = new EndpointList([]);
                }
                $endpoint = $endpointList->getActive();

                // Retrieve endpoints if there is no active endpoint
                if (empty($endpoint)) {
                    try {
                        $endpoint = $this->discoverEndpoint(
                            $cacheKey,
                            $cmd,
                            $identifiers
                        );
                    } catch (\Exception $e) {
                        // Use cached endpoint, expired or active, if any remain
                        $endpoint = $endpointList->getEndpoint();

                        if (empty($endpoint)) {
                            return $this->handleDiscoveryException(
                                $isRequired,
                                $originalUri,
                                $e,
                                $cmd,
                                $request
                            );
                        }
                    }
                }

                $request = $this->modifyRequest($request, $endpoint);

                $g = function ($value) use (
                    $cacheKey,
                    $cmd,
                    $identifiers,
                    $isRequired,
                    $originalUri,
                    $request,
                    &$endpoint,
                    &$g
                ) {
                    if ($value instanceof AwsException
                        && (
                            $value->getAwsErrorCode() == 'InvalidEndpointException'
                            || $value->getStatusCode() == 421
                        )
                    ) {
                        return $this->handleInvalidEndpoint(
                            $cacheKey,
                            $cmd,
                            $identifiers,
                            $isRequired,
                            $originalUri,
                            $request,
                            $value,
                            $endpoint,
                            $g
                        );
                    }

                    return $value;
                };

                return $nextHandler($cmd, $request)->otherwise($g);
            }
        }

        return $nextHandler($cmd, $request);
    }

    private function discoverEndpoint(
        $cacheKey,
        CommandInterface $cmd,
        array $identifiers
    ) {
        $discCmd = $this->getDiscoveryCommand($cmd, $identifiers);
        $this->discoveryTimes[$cacheKey] = time();
        $result = $this->client->execute($discCmd);

        if (isset($result['Endpoints'])) {
            $endpointData = [];
            foreach ($result['Endpoints'] as $datum) {
                $endpointData[$datum['Address']] = time()
                    + ($datum['CachePeriodInMinutes'] * 60);
            }
            $endpointList = new EndpointList($endpointData);
            self::$cache->set($cacheKey, $endpointList);
            return $endpointList->getEndpoint();
        }

        throw new UnresolvedEndpointException('The endpoint discovery operation '
            . 'yielded a response that did not contain properly formatted '
            . 'endpoint data.');
    }

    private function getCacheKey(
        CredentialsInterface $creds,
        CommandInterface $cmd,
        array $identifiers
    ) {
        $key = $this->service->getServiceName() . '_' . $creds->getAccessKeyId();
        if (!empty($identifiers)) {
            $key .= '_' . $cmd->getName();
            foreach ($identifiers as $identifier) {
                $key .= "_{$cmd[$identifier]}";
            }
        }

        return $key;
    }

    private function getDiscoveryCommand(
        CommandInterface $cmd,
        array $identifiers
    ) {
        foreach ($this->service->getOperations() as $op) {
            if (isset($op['endpointoperation'])) {
                $endpointOperation = $op->toArray()['name'];
                break;
            }
        }

        if (!isset($endpointOperation)) {
            throw new UnresolvedEndpointException('This command is set to use '
                . 'endpoint discovery, but no endpoint discovery operation was '
                . 'found. Please verify the accuracy of your model files.');
        }

        $params = [];
        if (!empty($identifiers)) {
            $params['Operation'] = $cmd->getName();
            $params['Identifiers'] = [];
            foreach ($identifiers as $identifier) {
                $params['Identifiers'][$identifier] = $cmd[$identifier];
            }
        }
        $command = $this->client->getCommand($endpointOperation, $params);
        $command->getHandlerList()->appendBuild(
            Middleware::mapRequest(function (RequestInterface $r) {
                return $r->withHeader(
                    'x-amz-api-version',
                    $this->service->getApiVersion()
                );
            }),
            'x-amz-api-version-header'
        );

        return $command;
    }

    private function getIdentifiers(array $operation)
    {
        $inputShape = $this->service->getShapeMap()
            ->resolve($operation['input'])
            ->toArray();
        $identifiers = [];
        foreach ($inputShape['members'] as $key => $member) {
            if (!empty($member['endpointdiscoveryid'])) {
                $identifiers[] = $key;
            }
        }
        return $identifiers;
    }

    private function handleDiscoveryException(
        $isRequired,
        $originalUri,
        \Exception $e,
        CommandInterface $cmd,
        RequestInterface $request
    ) {
        // If no cached endpoints and discovery required,
        // throw exception
        if ($isRequired) {
            $message = 'The endpoint required for this service is currently '
                . 'unable to be retrieved, and your request can not be fulfilled '
                . 'unless you manually specify an endpoint.';
            throw new AwsException(
                $message,
                $cmd,
                [
                    'code' => 'EndpointDiscoveryException',
                    'message' => $message
                ],
                $e
            );
        }

        // If discovery isn't required, use original endpoint
        return $this->useOriginalUri(
            $originalUri,
            $cmd,
            $request
        );
    }

    private function handleInvalidEndpoint(
        $cacheKey,
        $cmd,
        $identifiers,
        $isRequired,
        $originalUri,
        $request,
        $value,
        &$endpoint,
        &$g
    ) {
        $nextHandler = $this->nextHandler;
        $endpointList = self::$cache->get($cacheKey);
        if ($endpointList instanceof EndpointList) {

            // Remove invalid endpoint from cached list
            $endpointList->remove($endpoint);

            // If possible, get another cached endpoint
            $newEndpoint = $endpointList->getEndpoint();
        }
        if (empty($newEndpoint)) {

            // If no more cached endpoints, make discovery call
            // if none made within cooldown for given key
            if (time() - $this->discoveryTimes[$cacheKey]
                < self::$discoveryCooldown
            ) {

                // If no more cached endpoints and it's required,
                // fail with original exception
                if ($isRequired) {
                    return $value;
                }

                // Use original endpoint if not required
                return $this->useOriginalUri(
                    $originalUri,
                    $cmd,
                    $request
                );
            }

            $newEndpoint = $this->discoverEndpoint(
                $cacheKey,
                $cmd,
                $identifiers
            );
        }
        $endpoint = $newEndpoint;
        $request = $this->modifyRequest($request, $endpoint);
        return $nextHandler($cmd, $request)->otherwise($g);
    }

    private function modifyRequest(RequestInterface $request, $endpoint)
    {
        $parsed = $this->parseEndpoint($endpoint);
        if (!empty($request->getHeader('User-Agent'))) {
            $userAgent = $request->getHeader('User-Agent')[0];
            if (strpos($userAgent, 'endpoint-discovery') === false) {
                $userAgent = $userAgent . ' endpoint-discovery';
            }
        } else {
            $userAgent = 'endpoint-discovery';
        }

        return $request
            ->withUri(
                $request->getUri()
                    ->withHost($parsed['host'])
                    ->withPath($parsed['path'])
            )
            ->withHeader('User-Agent', $userAgent);
    }

    /**
     * Parses an endpoint returned from the discovery API into an array with
     * 'host' and 'path' keys.
     *
     * @param $endpoint
     * @return array
     */
    private function parseEndpoint($endpoint)
    {
        $parsed = parse_url($endpoint);

        // parse_url() will correctly parse full URIs with schemes
        if (isset($parsed['host'])) {
            return $parsed;
        }

        // parse_url() will put host & path in 'path' if scheme is not provided
        if (isset($parsed['path'])) {
            $split = explode('/', $parsed['path'], 2);
            $parsed['host'] = $split[0];
            if (isset($split[1])) {
                if (substr($split[1], 0 , 1) !== '/') {
                    $split[1] = '/' . $split[1];
                }
                $parsed['path'] = $split[1];
            } else {
                $parsed['path'] = '';
            }
            return $parsed;
        }

        throw new UnresolvedEndpointException("The supplied endpoint '"
            . "{$endpoint}' is invalid.");
    }

    private function useOriginalUri(
        UriInterface $uri,
        CommandInterface $cmd,
        RequestInterface $request
    ) {
        $nextHandler = $this->nextHandler;
        $endpoint = $uri->getHost() . $uri->getPath();
        $request = $this->modifyRequest(
            $request,
            $endpoint
        );
        return $nextHandler($cmd, $request);
    }
}


<?php
namespace Aws\EndpointDiscovery;

class EndpointList
{
    private $active;
    private $expired = [];

    public function __construct(array $endpoints)
    {
        $this->active = $endpoints;
        reset($this->active);
    }

    /**
     * Gets an active (unexpired) endpoint. Returns null if none found.
     *
     * @return null|string
     */
    public function getActive()
    {
        if (count($this->active) < 1) {
            return null;
        }
        while (time() > current($this->active)) {
            $key = key($this->active);
            $this->expired[$key] = current($this->active);
            $this->increment($this->active);
            unset($this->active[$key]);
            if (count($this->active) < 1) {
                return null;
            }
        }
        $active = key($this->active);
        $this->increment($this->active);
        return $active;
    }

    /**
     * Gets an active endpoint if possible, then an expired endpoint if possible.
     * Returns null if no endpoints found.
     *
     * @return null|string
     */
    public function getEndpoint()
    {
        if (!empty($active = $this->getActive())) {
            return $active;
        }
        return $this->getExpired();
    }

    /**
     * Removes an endpoint from both lists.
     *
     * @param string $key
     */
    public function remove($key)
    {
        unset($this->active[$key]);
        unset($this->expired[$key]);
    }

    /**
     * Get an expired endpoint. Returns null if none found.
     *
     * @return null|string
     */
    private function getExpired()
    {
        if (count($this->expired) < 1) {
            return null;
        }
        $expired = key($this->expired);
        $this->increment($this->expired);
        return $expired;
    }

    private function increment(&$array)
    {
        if (next($array) === false) {
            reset($array);
        }
    }
}


<?php
namespace Aws\EndpointDiscovery\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

/**
 * Represents an error interacting with configuration for endpoint discovery
 */
class ConfigurationException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}


<?php

namespace Aws\EndpointV2;

/**
 * Provides Endpoint-related artifacts used for endpoint resolution
 * and testing.
 */
class EndpointDefinitionProvider
{
    public static function getEndpointRuleset($service, $apiVersion)
    {
        return self::getData($service, $apiVersion, 'ruleset');
    }

    public static function getEndpointTests($service, $apiVersion)
    {
        return self::getData($service, $apiVersion, 'tests');
    }

    public static function getPartitions()
    {
        $basePath = __DIR__ . '/../data';
        $file = '/partitions.json';

        if (file_exists($basePath . $file . '.php')) {
           return require($basePath . $file . '.php');
        } else {
            return json_decode(file_get_contents($basePath . $file));
        }
    }

    private static function getData($service, $apiVersion, $type)
    {
        $basePath = __DIR__ . '/../data';
        $serviceDir = $basePath . "/{$service}";
        if (!is_dir($serviceDir)) {
            throw new \InvalidArgumentException(
                'Invalid service name.'
            );
        }

        if ($apiVersion === 'latest') {
            $apiVersion = self::getLatest($service);
        }

        $rulesetPath = $serviceDir . '/' . $apiVersion;
        if (!is_dir($rulesetPath)) {
            throw new \InvalidArgumentException(
                'Invalid api version.'
            );
        }
        $fileName = $type === 'tests' ? '/endpoint-tests-1' : '/endpoint-rule-set-1';

        if (file_exists($rulesetPath . $fileName . '.json.php')) {
            return require($rulesetPath . $fileName . '.json.php');
        } else {
            return json_decode(file_get_contents($rulesetPath . $fileName . '.json'), true);
        }
    }

    private static function getLatest($service)
    {
        $manifest = \Aws\manifest();
        return $manifest[$service]['versions']['latest'];
    }
}


<?php

namespace Aws\EndpointV2;

use Aws\EndpointV2\Ruleset\Ruleset;
use Aws\Exception\UnresolvedEndpointException;
use Aws\LruArrayCache;

/**
 * Given a service's Ruleset and client-provided input parameters, provides
 * either an object reflecting the properties of a resolved endpoint,
 * or throws an error.
 */
class EndpointProviderV2
{
    /** @var Ruleset */
    private $ruleset;

    /** @var LruArrayCache */
    private $cache;

    public function __construct(array $ruleset, array $partitions)
    {
        $this->ruleset = new Ruleset($ruleset, $partitions);
        $this->cache = new LruArrayCache(100);
    }

    /**
     * @return Ruleset
     */
    public function getRuleset()
    {
        return $this->ruleset;
    }

    /**
     * Given a Ruleset and input parameters, determines the correct endpoint
     * or an error to be thrown for a given request.
     *
     * @return RulesetEndpoint
     * @throws UnresolvedEndpointException
     */
    public function resolveEndpoint(array $inputParameters)
    {
        $hashedParams = $this->hashInputParameters($inputParameters);
        $match = $this->cache->get($hashedParams);

        if (!is_null($match)) {
            return $match;
        }

        $endpoint = $this->ruleset->evaluate($inputParameters);
        if ($endpoint === false) {
            throw new UnresolvedEndpointException(
                'Unable to resolve an endpoint using the provider arguments: '
                . json_encode($inputParameters)
            );
        }
        $this->cache->set($hashedParams, $endpoint);

        return $endpoint;
    }

    private function hashInputParameters($inputParameters)
    {
        return md5(serialize($inputParameters));
    }
}


<?php

namespace Aws\EndpointV2;

use Aws\Api\Serializer\RestSerializer;
use GuzzleHttp\Psr7\Uri;

/**
 * Set of helper functions used to set endpoints and endpoint
 * properties derived from dynamic endpoint resolution.
 *
 * @internal
 */
trait EndpointV2SerializerTrait
{
    /**
     * Merges endpoint resolution arguments passed from the client
     * and command and attempts to resolve an endpoint. Headers and
     * auth schemes may be returned in a resolved endpoint object.
     * A resolved endpoint uri and headers will be applied to the request.
     * Auth schemes are applied to the command and compared against the default
     * auth scheme at signing.
     *
     * @internal
     */
    private function setRequestOptions(
        $endpointProvider,
        $command,
        $operation,
        $commandArgs,
        $clientArgs,
        &$headers
    )
    {
        $providerArgs = $this->resolveProviderArgs(
            $endpointProvider,
            $operation,
            $commandArgs,
            $clientArgs
        );
        $endpoint = $endpointProvider->resolveEndpoint($providerArgs);
        $resolvedUrl = $endpoint->getUrl();

        $this->applyScheme($resolvedUrl);
        $this->endpoint = $resolvedUrl;
        $this->applyAuthSchemeToCommand($endpoint, $command);
        $this->applyHeaders($endpoint, $headers);
    }

    private function resolveProviderArgs(
        $endpointProvider,
        $operation,
        $commandArgs,
        $clientArgs
    )
    {
        $rulesetParams = $endpointProvider->getRuleset()->getParameters();
        $endpointCommandArgs = $this->filterEndpointCommandArgs(
            $rulesetParams,
            $commandArgs
        );
        $staticContextParams = $this->bindStaticContextParams(
            $operation->getStaticContextParams()
        );
        $contextParams = $this->bindContextParams(
            $commandArgs, $operation->getContextParams()
        );
        $providerArgs = $this->normalizeEndpointProviderArgs(
            $endpointCommandArgs,
            $clientArgs,
            $contextParams,
            $staticContextParams
        );

        return $providerArgs;
    }

    /**
     * Merges endpoint provider arguments from different sources.
     * Client built-ins are superseded by client context params.
     * Client context params are superseded by context params on
     * an input member's shape.  Context params are superseded by
     * static context params. The result of this combination is then
     * merged with any appropriate arguments from the command.
     */
    private function normalizeEndpointProviderArgs(
        $endpointCommandArgs,
        $clientArgs,
        $contextParams,
        $staticContextParams
    )
    {
        $commandContextParams = array_merge($contextParams, $staticContextParams);
        $combinedEndpointArgs = array_merge($clientArgs, $commandContextParams);

        return array_merge($combinedEndpointArgs, $endpointCommandArgs);
    }

    private function bindContextParams($commandArgs, $contextParams)
    {
        $scopedParams = [];

        foreach($contextParams as $name => $spec) {
            if (isset($commandArgs[$spec['shape']])) {
                $scopedParams[$name] = $commandArgs[$spec['shape']];
            }
        }
        return $scopedParams;
    }

    private function bindStaticContextParams($staticContextParams)
    {
        $scopedParams = [];

        forEach($staticContextParams as $paramName => $paramValue) {
            $scopedParams[$paramName] = $paramValue['value'];
        }
        return $scopedParams;
    }

    private function filterEndpointCommandArgs(
        $rulesetParams,
        $commandArgs
    )
    {
        $endpointMiddlewareOpts = [
            '@use_dual_stack_endpoint' => 'UseDualStack',
            '@use_accelerate_endpoint' => 'Accelerate',
            '@use_path_style_endpoint' => 'ForcePathStyle'
        ];

        $filteredArgs = [];

        foreach($rulesetParams as $name => $value) {
            if (isset($commandArgs[$name])) {
                if (!empty($value->getBuiltIn())) {
                    continue;
                }
                $filteredArgs[$name] = $commandArgs[$name];
            }
        }

        if ($this->api->getServiceName() === 's3') {
            foreach($endpointMiddlewareOpts as $optionName => $newValue) {
                if (isset($commandArgs[$optionName])) {
                    $filteredArgs[$newValue] = $commandArgs[$optionName];
                }
            }
        }

        return $filteredArgs;
    }

    private function applyHeaders($endpoint, &$headers)
    {
        if (!is_null($endpoint->getHeaders())) {
           $headers = array_merge(
               $headers,
               $endpoint->getHeaders()
           );
        }
    }

    private function applyAuthSchemeToCommand($endpoint, $command)
    {
        if (isset($endpoint->getProperties()['authSchemes'])) {
            $authScheme = $this->selectAuthScheme(
                $endpoint->getProperties()['authSchemes']
            );
            $command->setAuthSchemes($authScheme);
        }
    }

    private function selectAuthScheme($authSchemes)
    {
        $validAuthSchemes = ['sigv4', 'sigv4a', 'none', 'bearer'];
        $invalidAuthSchemes = [];

        foreach($authSchemes as $authScheme) {
            if (in_array($authScheme['name'], $validAuthSchemes)) {
                return $this->normalizeAuthScheme($authScheme);
            } else {
                $invalidAuthSchemes[] = "`{$authScheme['name']}`";
            }
        }

        $invalidAuthSchemesString = implode(', ', $invalidAuthSchemes);
        $validAuthSchemesString = '`' . implode('`, `', $validAuthSchemes) . '`';
        throw new \InvalidArgumentException(
            "This operation requests {$invalidAuthSchemesString}"
            . " auth schemes, but the client only supports {$validAuthSchemesString}."
        );
    }

    private function normalizeAuthScheme($authScheme)
    {
       /*
            sigv4a will contain a regionSet property. which is guaranteed to be `*`
            for now.  The SigV4 class handles this automatically for now. It seems
            complexity will be added here in the future.
       */
        $normalizedAuthScheme = [];

        if (isset($authScheme['disableDoubleEncoding'])
            && $authScheme['disableDoubleEncoding'] === true
            && $authScheme['name'] !== 'sigv4a'
        ) {
            $normalizedAuthScheme['version'] = 's3v4';
        } elseif ($authScheme['name'] === 'none') {
            $normalizedAuthScheme['version'] = 'anonymous';
        }
        else {
            $normalizedAuthScheme['version'] = str_replace(
                'sig', '', $authScheme['name']
            );
        }

        $normalizedAuthScheme['name'] = isset($authScheme['signingName']) ?
            $authScheme['signingName'] : null;
        $normalizedAuthScheme['region'] = isset($authScheme['signingRegion']) ?
            $authScheme['signingRegion'] : null;
        $normalizedAuthScheme['signingRegionSet'] = isset($authScheme['signingRegionSet']) ?
            $authScheme['signingRegionSet'] : null;

        return $normalizedAuthScheme;
    }

    private function applyScheme(&$resolvedUrl)
    {
        $resolvedEndpointScheme = parse_url($resolvedUrl, PHP_URL_SCHEME);
        $scheme = $this->endpoint instanceof Uri
            ? $this->endpoint->getScheme()
            : parse_url($this->endpoint, PHP_URL_SCHEME);

        if (!empty($scheme) && $scheme !== $resolvedEndpointScheme) {
            $resolvedUrl = str_replace(
                $resolvedEndpointScheme,
                $scheme,
                $resolvedUrl
            );
        }
    }
}


<?php

namespace Aws\EndpointV2\Rule;

use Aws\EndpointV2\Ruleset\RulesetStandardLibrary;

/**
 *  A rule within a rule set. All rules contain a conditions property,
 * which can be empty, and documentation about the rule.
 */
abstract Class AbstractRule
{
    private $conditions;
    private $documentation;

    public function __construct(array $definition)
    {
        $this->conditions = $definition['conditions'];
        $this->documentation = isset($definition['documentation']) ?
            $definition['documentation'] : null;
    }

    /**
     * @return array
     */
    public function getConditions()
    {
        return $this->conditions;
    }

    /**
     * @return mixed
     */
    public function getDocumentation()
    {
        return $this->documentation;
    }

    /**
     * Determines if all conditions for a given rule are met.
     *
     * @return boolean
     */
    protected function evaluateConditions(
        array &$inputParameters,
        RulesetStandardLibrary $standardLibrary
    )
    {
        foreach($this->getConditions() as $condition) {
            $result = $standardLibrary->callFunction($condition, $inputParameters);
            if (is_null($result) || $result === false) {
                return false;
            }
        }
        return true;
    }

    abstract public function evaluate(
        array $inputParameters,
        RulesetStandardLibrary $standardLibrary
    );
}


<?php

namespace Aws\EndpointV2\Rule;

use Aws\EndpointV2\Ruleset\RulesetStandardLibrary;
use Aws\EndpointV2\Ruleset\RulesetEndpoint;

Class EndpointRule extends AbstractRule
{
    /** @var array */
    private $endpoint;

    public function __construct(array $definition)
    {
        parent::__construct($definition);
        $this->endpoint = $definition['endpoint'];
    }

    /**
     * @return array
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * If all the rule's conditions are met, return the resolved
     * endpoint object.
     *
     * @return RulesetEndpoint | null
     */
    public function evaluate(array $inputParameters, RulesetStandardLibrary $standardLibrary)
    {
        if ($this->evaluateConditions($inputParameters, $standardLibrary)) {
            return $this->resolve($inputParameters, $standardLibrary);
        }
        return false;
    }

    /**
     * Given input parameters, resolve an endpoint in its entirety.
     *
     * @return RulesetEndpoint
     */
    private function resolve(
        array $inputParameters,
        RulesetStandardLibrary $standardLibrary
    )
    {
        $uri = $standardLibrary->resolveValue($this->endpoint['url'], $inputParameters);
        $properties = isset($this->endpoint['properties'])
                ? $this->resolveProperties($this->endpoint['properties'], $inputParameters, $standardLibrary)
                : null;
        $headers = $this->resolveHeaders($inputParameters, $standardLibrary);

        return new RulesetEndpoint($uri, $properties, $headers);
    }

    /**
     * Recurse through an endpoint's `properties` attribute, resolving template
     * strings when found. Return the fully resolved attribute.
     *
     * @return array
     */
    private function resolveProperties(
        $properties,
        array $inputParameters,
        RulesetStandardLibrary $standardLibrary
    )
    {
        if (is_array($properties)) {
           $propertiesArr = [];
           foreach($properties as $key => $val) {
               $propertiesArr[$key] = $this->resolveProperties($val, $inputParameters, $standardLibrary);
           }
           return $propertiesArr;
        } elseif ($standardLibrary->isTemplate($properties)) {
            return $standardLibrary->resolveTemplateString($properties, $inputParameters);
        }
        return $properties;
    }

    /**
     * If present, iterate through an endpoint's headers attribute resolving
     * values along the way. Return the fully resolved attribute.
     *
     * @return array
     */
    private function resolveHeaders(
        array $inputParameters,
        RulesetStandardLibrary $standardLibrary
    )
    {
        $headers = isset($this->endpoint['headers']) ? $this->endpoint['headers'] : null;
        if (is_null($headers)) {
            return null;
        }
        $resolvedHeaders = [];

        foreach($headers as $headerName => $headerValues) {
            $resolvedValues = [];
            foreach($headerValues as $value) {
                $resolvedValue = $standardLibrary->resolveValue($value, $inputParameters, $standardLibrary);
                $resolvedValues[] = $resolvedValue;
            }
            $resolvedHeaders[$headerName] = $resolvedValues;
        }
        return $resolvedHeaders;
    }
}


<?php

namespace Aws\EndpointV2\Rule;

use Aws\EndpointV2\Ruleset\RulesetStandardLibrary;
use Aws\Exception\UnresolvedEndpointException;

class ErrorRule extends AbstractRule
{
    /** @var array */
    private $error;

    public function __construct($definition)
    {
        parent::__construct($definition);
        $this->error = $definition['error'];
    }

    /**
     * @return array
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * If an error rule's conditions are met, raise an
     * UnresolvedEndpointError containing the fully resolved error string.
     *
     * @return null
     * @throws UnresolvedEndpointException
     */
    public function evaluate(
        array $inputParameters,
        RulesetStandardLibrary $standardLibrary
    )
    {
        if ($this->evaluateConditions($inputParameters, $standardLibrary)) {
            $message = $standardLibrary->resolveValue($this->error, $inputParameters);
            throw new UnresolvedEndpointException($message);
        }
        return false;
    }
}


<?php

namespace Aws\EndpointV2\Rule;

use Aws\Exception\UnresolvedEndpointException;

class RuleCreator
{
    public static function create($type, $definition)
    {
        switch ($type) {
            case 'endpoint':
                return new EndpointRule($definition);
            case 'error':
                return new ErrorRule($definition);
            case 'tree':
                return new TreeRule($definition);
            default:
                throw new UnresolvedEndpointException(
                    'Unknown rule type ' . $type .
                    ' must be of type `endpoint`, `tree` or `error`'
                );
        }
    }
}


<?php

namespace Aws\EndpointV2\Rule;

use Aws\EndpointV2\Ruleset\RulesetStandardLibrary;

class TreeRule extends AbstractRule
{
    /** @var array */
    private $rules;

    public function __construct(array $definition)
    {
        parent::__construct($definition);
        $this->rules = $this->createRules($definition['rules']);
    }

    /**
     * @return array
     */
    public function getRules()
    {
        return $this->rules;
    }

    /**
     * If a tree rule's conditions evaluate successfully, iterate over its
     * subordinate rules and return a result if there is one. If any of the
     * subsequent rules are trees, the function will recurse until it reaches
     * an error or an endpoint rule
     *
     * @return mixed
     */
    public function evaluate(
        array $inputParameters,
        RulesetStandardLibrary $standardLibrary
    )
    {
        if ($this->evaluateConditions($inputParameters, $standardLibrary)) {
            foreach($this->rules as $rule) {
                $inputParametersCopy = $inputParameters;
                $evaluation = $rule->evaluate($inputParametersCopy, $standardLibrary);
                if ($evaluation !== false) {
                    return $evaluation;
                }
            }
        }
        return false;
    }

    private function createRules(array $rules)
    {
        $rulesList = [];

        forEach($rules as $rule) {
            $ruleType = RuleCreator::create($rule['type'], $rule);
            $rulesList[] = $ruleType;
        }
        return $rulesList;
    }
}


<?php

namespace Aws\EndpointV2\Ruleset;

use Aws\EndpointV2\Rule\RuleCreator;

/**
 * A collection of rules, parameter definitions and a class of helper functions
 * used to resolve either an endpoint or an error.
 */
Class Ruleset
{
    /** @var string */
    private $version;

    /** @var array */
    private $parameters;

    /** @var array */
    private $rules;

    /** @var RulesetStandardLibrary */
    public $standardLibrary;

    public function __construct(array $ruleset, array $partitions)
    {
        $this->version = $ruleset['version'];
        $this->parameters = $this->createParameters($ruleset['parameters']);
        $this->rules = $this->createRules($ruleset['rules']);
        $this->standardLibrary = new RulesetStandardLibrary($partitions);
    }

    /**
     * @return mixed
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * @return array
     */
    public function getParameters()
    {
        return $this->parameters;
    }

    /**
     * @return array
     */
    public function getRules()
    {
        return $this->rules;
    }

    /**
     * Evaluate the ruleset against the input parameters.
     * Return the first rule the parameters match against.
     *
     * @return mixed
     */
    public function evaluate(array $inputParameters)
    {
        $this->validateInputParameters($inputParameters);

        foreach($this->rules as $rule) {
            $evaluation = $rule->evaluate($inputParameters, $this->standardLibrary);
            if ($evaluation !== false) {
                return $evaluation;
            }
        }
        return false;
    }

    /**
     * Ensures all corresponding client-provided parameters match
     * the Ruleset parameter's specified type.
     *
     * @return void
     */
    private function validateInputParameters(array &$inputParameters)
    {
        foreach($this->parameters as $paramName => $param) {
            $inputParam = isset($inputParameters[$paramName]) ? $inputParameters[$paramName] : null;

            if (is_null($inputParam) && !is_null($param->getDefault())) {
                $inputParameters[$paramName] = $param->getDefault();
            } elseif (!is_null($inputParam)) {
                $param->validateInputParam($inputParam);
            }
        }
    }

    private function createParameters(array $parameters)
    {
        $parameterList = [];

        foreach($parameters as $name => $definition) {
            $parameterList[$name] = new RulesetParameter($name, $definition);
        }

        return $parameterList;
    }

    private function createRules(array $rules)
    {
        $rulesList = [];

        forEach($rules as $rule) {
            $ruleObj = RuleCreator::create($rule['type'], $rule);
            $rulesList[] = $ruleObj;
        }
        return $rulesList;
    }
}


<?php

namespace Aws\EndpointV2\Ruleset;

/**
 * Represents a fully resolved endpoint that a
 * rule returns if input parameters meet its requirements.
 */
class RulesetEndpoint
{
    /** @var string */
    private $url;

    /** @var array */
    private $properties;

    /** @var array */
    private $headers;

    public function __construct($url, $properties = null, $headers = null)
    {
        $this->url = $url;
        $this->properties = $properties;
        $this->headers = $headers;
    }

    /**
     * @return mixed
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * @return mixed
     */
    public function getProperties()
    {
        return $this->properties;
    }

    /**
     * @return mixed
     */
    public function getHeaders()
    {
        return $this->headers;
    }
}


<?php

namespace Aws\EndpointV2\Ruleset;

use Aws\Exception\UnresolvedEndpointException;

/**
 * Houses properties of an individual parameter definition.
 */
class RulesetParameter
{
    /** @var string */
    private $name;

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

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

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

    /** @var array */
    private $required;

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

    /** @var boolean */
    private $deprecated;

    public function __construct($name, array $definition)
    {
        $type = ucfirst($definition['type']);
        if ($this->isValidType($type)) {
            $this->type = $type;
        } else {
            throw new UnresolvedEndpointException(
                'Unknown parameter type ' . "`{$type}`" .
                '. Parameters must be of type `String` or `Boolean`.'
            );
        }
        $this->name = $name;
        $this->builtIn = isset($definition['builtIn']) ? $definition['builtIn'] : null;
        $this->default = isset($definition['default']) ? $definition['default'] : null;
        $this->required =  isset($definition['required']) ?
            $definition['required'] : false;
        $this->documentation =  isset($definition['documentation']) ?
            $definition['documentation'] : null;
        $this->deprecated =  isset($definition['deprecated']) ?
            $definition['deprecated'] : false;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @return mixed
     */
    public function getBuiltIn()
    {
        return $this->builtIn;
    }

    /**
     * @return mixed
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * @return boolean
     */
    public function getRequired()
    {
        return $this->required;
    }

    /**
     * @return string
     */
    public function getDocumentation()
    {
        return $this->documentation;
    }

    /**
     * @return boolean
     */
    public function getDeprecated()
    {
        return $this->deprecated;
    }

    /**
     * Validates that an input parameter matches the type provided in its definition.
     *
     * @return void
     * @throws InvalidArgumentException
     */
    public function validateInputParam($inputParam)
    {
        $typeMap = [
            'String' => 'is_string',
            'Boolean' => 'is_bool'
        ];

        if ($typeMap[$this->type]($inputParam) === false) {
            throw new UnresolvedEndpointException(
                "Input parameter `{$this->name}` is the wrong type. Must be a {$this->type}."
            );
        }

        if ($this->deprecated) {
            $deprecated = $this->deprecated;
            $deprecationString = "{$this->name} has been deprecated ";
            $msg = isset($deprecated['message']) ? $deprecated['message'] : null;
            $since = isset($deprecated['since']) ? $deprecated['since'] : null;

            if (!is_null($since)) $deprecationString = $deprecationString
                . 'since '. $since . '. ';
            if (!is_null($msg)) $deprecationString = $deprecationString . $msg;

            trigger_error($deprecationString, E_USER_WARNING);
        }
    }

    private function isValidType($type)
    {
        return in_array($type, ['String', 'Boolean']);
    }
}


<?php
namespace Aws\EventBridge;

use Aws\AwsClient;

/**
 * This client is used to interact with the **Amazon EventBridge** service.
 * @method \Aws\Result activateEventSource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise activateEventSourceAsync(array $args = [])
 * @method \Aws\Result cancelReplay(array $args = [])
 * @method \GuzzleHttp\Promise\Promise cancelReplayAsync(array $args = [])
 * @method \Aws\Result createApiDestination(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createApiDestinationAsync(array $args = [])
 * @method \Aws\Result createArchive(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createArchiveAsync(array $args = [])
 * @method \Aws\Result createConnection(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createConnectionAsync(array $args = [])
 * @method \Aws\Result createEndpoint(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createEndpointAsync(array $args = [])
 * @method \Aws\Result createEventBus(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createEventBusAsync(array $args = [])
 * @method \Aws\Result createPartnerEventSource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createPartnerEventSourceAsync(array $args = [])
 * @method \Aws\Result deactivateEventSource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deactivateEventSourceAsync(array $args = [])
 * @method \Aws\Result deauthorizeConnection(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deauthorizeConnectionAsync(array $args = [])
 * @method \Aws\Result deleteApiDestination(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteApiDestinationAsync(array $args = [])
 * @method \Aws\Result deleteArchive(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteArchiveAsync(array $args = [])
 * @method \Aws\Result deleteConnection(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteConnectionAsync(array $args = [])
 * @method \Aws\Result deleteEndpoint(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteEndpointAsync(array $args = [])
 * @method \Aws\Result deleteEventBus(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteEventBusAsync(array $args = [])
 * @method \Aws\Result deletePartnerEventSource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deletePartnerEventSourceAsync(array $args = [])
 * @method \Aws\Result deleteRule(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteRuleAsync(array $args = [])
 * @method \Aws\Result describeApiDestination(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeApiDestinationAsync(array $args = [])
 * @method \Aws\Result describeArchive(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeArchiveAsync(array $args = [])
 * @method \Aws\Result describeConnection(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeConnectionAsync(array $args = [])
 * @method \Aws\Result describeEndpoint(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeEndpointAsync(array $args = [])
 * @method \Aws\Result describeEventBus(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeEventBusAsync(array $args = [])
 * @method \Aws\Result describeEventSource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeEventSourceAsync(array $args = [])
 * @method \Aws\Result describePartnerEventSource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describePartnerEventSourceAsync(array $args = [])
 * @method \Aws\Result describeReplay(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeReplayAsync(array $args = [])
 * @method \Aws\Result describeRule(array $args = [])
 * @method \GuzzleHttp\Promise\Promise describeRuleAsync(array $args = [])
 * @method \Aws\Result disableRule(array $args = [])
 * @method \GuzzleHttp\Promise\Promise disableRuleAsync(array $args = [])
 * @method \Aws\Result enableRule(array $args = [])
 * @method \GuzzleHttp\Promise\Promise enableRuleAsync(array $args = [])
 * @method \Aws\Result listApiDestinations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listApiDestinationsAsync(array $args = [])
 * @method \Aws\Result listArchives(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listArchivesAsync(array $args = [])
 * @method \Aws\Result listConnections(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listConnectionsAsync(array $args = [])
 * @method \Aws\Result listEndpoints(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listEndpointsAsync(array $args = [])
 * @method \Aws\Result listEventBuses(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listEventBusesAsync(array $args = [])
 * @method \Aws\Result listEventSources(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listEventSourcesAsync(array $args = [])
 * @method \Aws\Result listPartnerEventSourceAccounts(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listPartnerEventSourceAccountsAsync(array $args = [])
 * @method \Aws\Result listPartnerEventSources(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listPartnerEventSourcesAsync(array $args = [])
 * @method \Aws\Result listReplays(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listReplaysAsync(array $args = [])
 * @method \Aws\Result listRuleNamesByTarget(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listRuleNamesByTargetAsync(array $args = [])
 * @method \Aws\Result listRules(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listRulesAsync(array $args = [])
 * @method \Aws\Result listTagsForResource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listTagsForResourceAsync(array $args = [])
 * @method \Aws\Result listTargetsByRule(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listTargetsByRuleAsync(array $args = [])
 * @method \Aws\Result putEvents(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putEventsAsync(array $args = [])
 * @method \Aws\Result putPartnerEvents(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putPartnerEventsAsync(array $args = [])
 * @method \Aws\Result putPermission(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putPermissionAsync(array $args = [])
 * @method \Aws\Result putRule(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putRuleAsync(array $args = [])
 * @method \Aws\Result putTargets(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putTargetsAsync(array $args = [])
 * @method \Aws\Result removePermission(array $args = [])
 * @method \GuzzleHttp\Promise\Promise removePermissionAsync(array $args = [])
 * @method \Aws\Result removeTargets(array $args = [])
 * @method \GuzzleHttp\Promise\Promise removeTargetsAsync(array $args = [])
 * @method \Aws\Result startReplay(array $args = [])
 * @method \GuzzleHttp\Promise\Promise startReplayAsync(array $args = [])
 * @method \Aws\Result tagResource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise tagResourceAsync(array $args = [])
 * @method \Aws\Result testEventPattern(array $args = [])
 * @method \GuzzleHttp\Promise\Promise testEventPatternAsync(array $args = [])
 * @method \Aws\Result untagResource(array $args = [])
 * @method \GuzzleHttp\Promise\Promise untagResourceAsync(array $args = [])
 * @method \Aws\Result updateApiDestination(array $args = [])
 * @method \GuzzleHttp\Promise\Promise updateApiDestinationAsync(array $args = [])
 * @method \Aws\Result updateArchive(array $args = [])
 * @method \GuzzleHttp\Promise\Promise updateArchiveAsync(array $args = [])
 * @method \Aws\Result updateConnection(array $args = [])
 * @method \GuzzleHttp\Promise\Promise updateConnectionAsync(array $args = [])
 * @method \Aws\Result updateEndpoint(array $args = [])
 * @method \GuzzleHttp\Promise\Promise updateEndpointAsync(array $args = [])
 */
class EventBridgeClient extends AwsClient {
    public function __construct(array $args)
    {
        parent::__construct($args);

        if ($this->isUseEndpointV2()) {
            $stack = $this->getHandlerList();
            $isCustomEndpoint = isset($args['endpoint']);
            $stack->appendBuild(
                EventBridgeEndpointMiddleware::wrap(
                    $this->getRegion(),
                    [
                        'use_fips_endpoint' =>
                            $this->getConfig('use_fips_endpoint')->isUseFipsEndpoint(),
                        'dual_stack' =>
                            $this->getConfig('use_dual_stack_endpoint')->isUseDualStackEndpoint(),
                    ],
                    $this->getConfig('endpoint_provider'),
                    $isCustomEndpoint
                ),
                'eventbridge.endpoint_middleware'
            );
        }
    }
}


<?php
namespace Aws\EventBridge;

use Aws\CommandInterface;
use Aws\Endpoint\EndpointProvider;
use Aws\Endpoint\PartitionEndpointProvider;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;

/**
 * Reroutes an eventbridge request to the proper endpoint
 * @internal
 */
class EventBridgeEndpointMiddleware
{
    private $nextHandler;
    private $region;
    private $config;
    private $endpointProvider;
    private $isCustomEndpoint;

    /**
     * Provide the URI scheme of the client sending requests.
     * @param EndpointProvider $endpointProvider
     * @return callable
     */
    public static function wrap($region, $config, $endpointProvider, $isCustomEndpoint)
    {
        return function (callable $handler) use (
            $region,
            $config,
            $endpointProvider,
            $isCustomEndpoint
        ) {
            return new self(
                $handler,
                $region,
                $config,
                $endpointProvider,
                $isCustomEndpoint
            );
        };
    }

    public function __construct(
        callable $nextHandler,
        $region,
        $config,
        $endpointProvider,
        $isCustomEndpoint
    ) {
        $this->nextHandler = $nextHandler;
        $this->region = $region;
        $this->config = $config;
        $this->endpointProvider = is_null($endpointProvider)
            ? PartitionEndpointProvider::defaultProvider()
            : $endpointProvider;
        $this->isCustomEndpoint = $isCustomEndpoint;
    }

    public function __invoke(CommandInterface $cmd, RequestInterface $req) {
        $sigV4aCommands = ['PutEvents'];
        if (in_array($cmd->getName(), $sigV4aCommands)) {
            if (isset($cmd['EndpointId'])) {
                $endpointID = $cmd['EndpointId'];
                $this->validateEndpointId($endpointID);
                if (!$this->isCustomEndpoint) {
                    $dnsSuffix = $this->endpointProvider
                        ->getPartition($this->region, 'eventbridge')
                        ->getDnsSuffix();
                    $newUri = "{$endpointID}.endpoint.events.{$dnsSuffix}";
                    $oldUri = $req->getUri();
                    $req = $req->withUri($oldUri->withHost($newUri));
                }
                $cmd['@context']['signature_version'] = 'v4a';
            }
        }
        $f = $this->nextHandler;
        return $f($cmd, $req);
    }

    protected static function isValidHostLabel($string)
    {
        if (empty($string) || strlen($string) > 63) {
            return false;
        }
        if ($value = preg_match("/^[a-zA-Z0-9-.]+$/", $string)) {
            return true;
        }
        return false;
    }

    /**
     * @param $endpointID
     * @param CommandInterface $cmd
     */
    private function validateEndpointId($endpointID)
    {
        if (empty($endpointID)) {
            throw new \InvalidArgumentException("EventId must be a non-empty string");
        }
        if (!self::isValidHostLabel($endpointID)) {
            throw new InvalidArgumentException("EventId must be a valid host");
        }
        if ($this->config['use_fips_endpoint']) {
            throw new InvalidArgumentException(
                "EventId is currently not compatible with FIPS pseudo regions"
            );
        }
        if ($this->config['dual_stack']) {
            throw new InvalidArgumentException(
                "EventId is currently not compatible with dualstack"
            );
        }
    }
}


<?php
namespace Aws\EventBridge\Exception;

use Aws\Exception\AwsException;

/**
 * Represents an error interacting with the **Amazon EventBridge** service.
 */
class EventBridgeException extends AwsException {}


<?php
namespace Aws\Exception;

use Aws\Api\Shape;
use Aws\CommandInterface;
use Aws\HasDataTrait;
use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;
use Aws\ResponseContainerInterface;
use Aws\ResultInterface;
use JmesPath\Env as JmesPath;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Represents an AWS exception that is thrown when a command fails.
 */
class AwsException extends \RuntimeException implements
    MonitoringEventsInterface,
    ResponseContainerInterface,
    \ArrayAccess
{
    use HasDataTrait;
    use HasMonitoringEventsTrait;

    /** @var ResponseInterface */
    private $response;
    private $request;
    private $result;
    private $command;
    private $requestId;
    private $errorType;
    private $errorCode;
    private $errorShape;
    private $connectionError;
    private $transferInfo;
    private $errorMessage;
    private $maxRetriesExceeded;

    /**
     * @param string           $message Exception message
     * @param CommandInterface $command
     * @param array            $context Exception context
     * @param \Exception       $previous  Previous exception (if any)
     */
    public function __construct(
        $message,
        CommandInterface $command,
        array $context = [],
        \Exception $previous = null
    ) {
        $this->data = isset($context['body']) ? $context['body'] : [];
        $this->command = $command;
        $this->response = isset($context['response']) ? $context['response'] : null;
        $this->request = isset($context['request']) ? $context['request'] : null;
        $this->requestId = isset($context['request_id'])
            ? $context['request_id']
            : null;
        $this->errorType = isset($context['type']) ? $context['type'] : null;
        $this->errorCode = isset($context['code']) ? $context['code'] : null;
        $this->errorShape = isset($context['error_shape']) ? $context['error_shape'] : null;
        $this->connectionError = !empty($context['connection_error']);
        $this->result = isset($context['result']) ? $context['result'] : null;
        $this->transferInfo = isset($context['transfer_stats'])
            ? $context['transfer_stats']
            : [];
        $this->errorMessage = isset($context['message'])
            ? $context['message']
            : null;
        $this->monitoringEvents = [];
        $this->maxRetriesExceeded = false;
        parent::__construct($message, 0, $previous);
    }

    public function __toString()
    {
        if (!$this->getPrevious()) {
            return parent::__toString();
        }

        // PHP strangely shows the innermost exception first before the outer
        // exception message. It also has a default character limit for
        // exception message strings such that the "next" exception (this one)
        // might not even get shown, causing developers to attempt to catch
        // the inner exception instead of the actual exception because they
        // can't see the outer exception's __toString output.
        return sprintf(
            "exception '%s' with message '%s'\n\n%s",
            get_class($this),
            $this->getMessage(),
            parent::__toString()
        );
    }

    /**
     * Get the command that was executed.
     *
     * @return CommandInterface
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * Get the concise error message if any.
     *
     * @return string|null
     */
    public function getAwsErrorMessage()
    {
        return $this->errorMessage;
    }

    /**
     * Get the sent HTTP request if any.
     *
     * @return RequestInterface|null
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Get the received HTTP response if any.
     *
     * @return ResponseInterface|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Get the result of the exception if available
     *
     * @return ResultInterface|null
     */
    public function getResult()
    {
        return $this->result;
    }

    /**
     * Returns true if this is a connection error.
     *
     * @return bool
     */
    public function isConnectionError()
    {
        return $this->connectionError;
    }

    /**
     * If available, gets the HTTP status code of the corresponding response
     *
     * @return int|null
     */
    public function getStatusCode()
    {
        return $this->response ? $this->response->getStatusCode() : null;
    }

    /**
     * Get the request ID of the error. This value is only present if a
     * response was received and is not present in the event of a networking
     * error.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsRequestId()
    {
        return $this->requestId;
    }

    /**
     * Get the AWS error type.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsErrorType()
    {
        return $this->errorType;
    }

    /**
     * Get the AWS error code.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsErrorCode()
    {
        return $this->errorCode;
    }

    /**
     * Get the AWS error shape.
     *
     * @return Shape|null Returns null if no response was received
     */
    public function getAwsErrorShape()
    {
        return $this->errorShape;
    }

    /**
     * Get all transfer information as an associative array if no $name
     * argument is supplied, or gets a specific transfer statistic if
     * a $name attribute is supplied (e.g., 'retries_attempted').
     *
     * @param string $name Name of the transfer stat to retrieve
     *
     * @return mixed|null|array
     */
    public function getTransferInfo($name = null)
    {
        if (!$name) {
            return $this->transferInfo;
        }

        return isset($this->transferInfo[$name])
            ? $this->transferInfo[$name]
            : null;
    }

    /**
     * Replace the transfer information associated with an exception.
     *
     * @param array $info
     */
    public function setTransferInfo(array $info)
    {
        $this->transferInfo = $info;
    }

    /**
     * Returns whether the max number of retries is exceeded.
     *
     * @return bool
     */
    public function isMaxRetriesExceeded()
    {
        return $this->maxRetriesExceeded;
    }

    /**
     * Sets the flag for max number of retries exceeded.
     */
    public function setMaxRetriesExceeded()
    {
        $this->maxRetriesExceeded = true;
    }

    public function hasKey($name)
    {
        return isset($this->data[$name]);
    }

    public function get($key)
    {
        return $this[$key];
    }

    public function search($expression)
    {
        return JmesPath::search($expression, $this->toArray());
    }
}


<?php
namespace Aws\Exception;

class CommonRuntimeException extends \RuntimeException
{

}


<?php
namespace Aws\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

class CouldNotCreateChecksumException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;

    public function __construct($algorithm, \Exception $previous = null)
    {
        $prefix = $algorithm === 'md5' ? "An" : "A";
        parent::__construct("{$prefix} {$algorithm} checksum could not be "
            . "calculated for the provided upload body, because it was not "
            . "seekable. To prevent this error you can either 1) include the "
            . "ContentMD5 or ContentSHA256 parameters with your request, 2) "
            . "use a seekable stream for the body, or 3) wrap the non-seekable "
            . "stream in a GuzzleHttp\\Psr7\\CachingStream object. You "
            . "should be careful though and remember that the CachingStream "
            . "utilizes PHP temp streams. This means that the stream will be "
            . "temporarily stored on the local disk.", 0, $previous);
    }
}


<?php
namespace Aws\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

class CredentialsException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}


<?php
namespace Aws\Exception;

/**
 * This class represents exceptions related to logic surrounding client-side
 * encryption usage.
 */
class CryptoException extends \RuntimeException
{

}


<?php
namespace Aws\Exception;

/**
 * Class CryptoPolyfillException
 * @package Aws\Exception
 */
class CryptoPolyfillException extends \RuntimeException
{

}


<?php
namespace Aws\Exception;

/**
 * Represents an exception that was supplied via an EventStream.
 */
class EventStreamDataException extends \RuntimeException
{
    private $errorCode;
    private $errorMessage;

    public function __construct($code, $message)
    {
        $this->errorCode = $code;
        $this->errorMessage = $message;
        parent::__construct($message);
    }

    /**
     * Get the AWS error code.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsErrorCode()
    {
        return $this->errorCode;
    }

    /**
     * Get the concise error message if any.
     *
     * @return string|null
     */
    public function getAwsErrorMessage()
    {
        return $this->errorMessage;
    }
}


<?php
namespace Aws\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

class IncalculablePayloadException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}


<?php
namespace Aws\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

class InvalidJsonException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}


<?php
namespace Aws\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;

class InvalidRegionException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}