diff options
| author | EoleDev <root@serverpc.home> | 2016-03-09 15:37:42 +0100 |
|---|---|---|
| committer | EoleDev <root@serverpc.home> | 2016-03-09 15:37:42 +0100 |
| commit | 1d3ed3af6d57316640c143002ddf80d61e6c098a (patch) | |
| tree | 97ac49bc7ff0f16150aefee821da3557dc0d0644 /server/vendor/php-opencloud/common/src/Common/Api | |
| parent | d69adc4f9d1f6019927de235ef84885c68e6e508 (diff) | |
| parent | 08ea5ef31abcc4e23a39a780cacb64fa27f19194 (diff) | |
Merge branch 'Evan' into develop
Diffstat (limited to 'server/vendor/php-opencloud/common/src/Common/Api')
7 files changed, 902 insertions, 0 deletions
diff --git a/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php b/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php new file mode 100644 index 0000000..beee5e8 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php @@ -0,0 +1,33 @@ +<?php + +namespace OpenCloud\Common\Api; + +abstract class AbstractApi implements ApiInterface +{ + protected $params; + + protected function isRequired(array $param) + { + return array_merge($param, ['required' => true]); + } + + protected function notRequired(array $param) + { + return array_merge($param, ['required' => false]); + } + + protected function query(array $param) + { + return array_merge($param, ['location' => AbstractParams::QUERY]); + } + + protected function url(array $param) + { + return array_merge($param, ['location' => AbstractParams::URL]); + } + + public function documented(array $param) + { + return array_merge($param, ['required' => true]); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php b/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php new file mode 100644 index 0000000..225e025 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php @@ -0,0 +1,100 @@ +<?php + +namespace OpenCloud\Common\Api; + +abstract class AbstractParams +{ + // locations + const QUERY = 'query'; + const HEADER = 'header'; + const URL = 'url'; + const JSON = 'json'; + const RAW = 'raw'; + + // types + const STRING_TYPE = "string"; + const BOOL_TYPE = "boolean"; + const BOOLEAN_TYPE = self::BOOL_TYPE; + const OBJECT_TYPE = "object"; + const ARRAY_TYPE = "array"; + const NULL_TYPE = "NULL"; + const INT_TYPE = 'integer'; + const INTEGER_TYPE = self::INT_TYPE; + + public static function isSupportedLocation($val) + { + return in_array($val, [self::QUERY, self::HEADER, self::URL, self::JSON, self::RAW]); + } + + public function limit() + { + return [ + 'type' => self::INT_TYPE, + 'location' => 'query', + 'description' => <<<DESC +This will limit the total amount of elements returned in a list up to the number specified. For example, specifying a +limit of 10 will return 10 elements, regardless of the actual count. +DESC + ]; + } + + public function marker() + { + return [ + 'type' => 'string', + 'location' => 'query', + 'description' => <<<DESC +Specifying a marker will begin the list from the value specified. Elements will have a particular attribute that +identifies them, such as a name or ID. The marker value will search for an element whose identifying attribute matches +the marker value, and begin the list from there. +DESC + ]; + } + + public function id($type) + { + return [ + 'description' => sprintf("The unique ID, or identifier, for the %s", $type), + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + ]; + } + + public function idPath() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::URL, + 'description' => 'The unique ID of the resource', + ]; + } + + public function name($resource) + { + return [ + 'description' => sprintf("The name of the %s", $resource), + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + ]; + } + + + public function sortDir() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::QUERY, + 'description' => "Sorts by one or more sets of attribute and sort direction combinations.", + 'enum' => ['asc', 'desc'] + ]; + } + + public function sortKey() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::QUERY, + 'description' => "Sorts by one or more sets of attribute and sort direction combinations.", + ]; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php b/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php new file mode 100644 index 0000000..d5d26a0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php @@ -0,0 +1,20 @@ +<?php + +namespace OpenCloud\Common\Api; + +/** + * All classes which implement this interface are a data representation of a remote OpenCloud API. + * They do not execute functionality, but instead return data for each API operation for other parts + * of the SDK to use. Usually, the data is injected into {@see OpenCloud\Common\Api\Operation} objects. + * The operation is then serialized into a {@see GuzzleHttp\Message\Request} and sent to the API. + * + * The reason for storing all the API-specific data is to decouple service information from client + * HTTP functionality. Too often it is mixed all across different layers, leading to duplication and + * no separation of concerns. The choice was made for storage in PHP classes, rather than YAML or JSON + * syntax, due to performance concerns. + * + * @package OpenCloud\Common\Api + */ +interface ApiInterface +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Operation.php b/server/vendor/php-opencloud/common/src/Common/Api/Operation.php new file mode 100644 index 0000000..3155ca4 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Operation.php @@ -0,0 +1,136 @@ +<?php + +namespace OpenCloud\Common\Api; + +use GuzzleHttp\Utils; + +/** + * This class represents an OpenCloud API operation. It encapsulates most aspects of the REST operation: its HTTP + * method, the URL path, its top-level JSON key, and all of its {@see Parameter} objects. + * + * An operation not only represents a remote operation, but it also provides the mechanism for executing it + * over HTTP. To do this, it uses a {@see ClientInterface} that allows a {@see GuzzleHttp\Message\Request} + * to be created from the user values provided. Once this request is assembled, it is then sent to the + * remote API and the response is returned to whoever first invoked the Operation class. + * + * @package OpenCloud\Common\Api + */ +class Operation +{ + /** @var string The HTTP method */ + private $method; + + /** @var string The URL path */ + private $path; + + /** @var string The top-level JSON key */ + private $jsonKey; + + /** @var []Parameter The parameters of this operation */ + private $params; + + /** + * @param array $definition The data definition (in array form) that will populate this + * operation. Usually this is retrieved from an {@see ApiInterface} + * object method. + */ + public function __construct(array $definition) + { + $this->method = $definition['method']; + $this->path = $definition['path']; + + if (isset($definition['jsonKey'])) { + $this->jsonKey = $definition['jsonKey']; + } + + $this->params = self::toParamArray($definition['params']); + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Indicates whether this operation supports a parameter. + * + * @param $key The name of a parameter + * + * @return bool + */ + public function hasParam($key) + { + return isset($this->params[$key]); + } + + /** + * @param $name + * + * @return Parameter + */ + public function getParam($name) + { + return isset($this->params[$name]) ? $this->params[$name] : null; + } + + /** + * @return string + */ + public function getJsonKey() + { + return $this->jsonKey; + } + + /** + * A convenience method that will take a generic array of data and convert it into an array of + * {@see Parameter} objects. + * + * @param array $data A generic data array + * + * @return array + */ + public static function toParamArray(array $data) + { + $params = []; + + foreach ($data as $name => $param) { + $params[$name] = new Parameter($param + ['name' => $name]); + } + + return $params; + } + + /** + * This method will validate all of the user-provided values and throw an exception if any + * failures are detected. This is useful for basic sanity-checking before a request is + * serialized and sent to the API. + * + * @param array $userValues The user-defined values + * + * @return bool TRUE if validation passes + * @throws \Exception If validate fails + */ + public function validate(array $userValues) + { + foreach ($this->params as $paramName => $param) { + if (array_key_exists($paramName, $userValues)) { + $param->validate($userValues[$paramName]); + } elseif ($param->isRequired()) { + throw new \Exception(sprintf('"%s" is a required option, but it was not provided', $paramName)); + } + } + + return true; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Operator.php b/server/vendor/php-opencloud/common/src/Common/Api/Operator.php new file mode 100644 index 0000000..4325b69 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Operator.php @@ -0,0 +1,173 @@ +<?php + +namespace OpenCloud\Common\Api; + +use function GuzzleHttp\uri_template; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Promise\Promise; +use OpenCloud\Common\Resource\ResourceInterface; +use OpenCloud\Common\Transport\RequestSerializer; +use Psr\Http\Message\ResponseInterface; + +/** + * {@inheritDoc} + */ +abstract class Operator implements OperatorInterface +{ + /** @var ClientInterface */ + protected $client; + + /** @var ApiInterface */ + protected $api; + + /** + * {@inheritDoc} + */ + public function __construct(ClientInterface $client, ApiInterface $api) + { + $this->client = $client; + $this->api = $api; + } + + /** + * Magic method for dictating how objects are rendered when var_dump is called. + * For the benefit of users, extremely verbose and heavy properties (such as HTTP clients) are + * removed to provide easier access to normal state, such as resource attributes. + * + * @codeCoverageIgnore + * @return array + */ + public function __debugInfo() + { + $excludedVars = ['client', 'errorBuilder', 'api']; + + $output = []; + + foreach (get_object_vars($this) as $key => $val) { + if (!in_array($key, $excludedVars)) { + $output[$key] = $val; + } + } + + return $output; + } + + /** + * Retrieves a populated Operation according to the definition and values provided. A + * HTTP client is also injected into the object to allow it to communicate with the remote API. + * + * @param array $definition The data that dictates how the operation works + * + * @return Operation + */ + public function getOperation(array $definition) + { + return new Operation($definition); + } + + protected function sendRequest(Operation $operation, array $userValues = [], $async = false) + { + $operation->validate($userValues); + + $options = (new RequestSerializer)->serializeOptions($operation, $userValues); + $method = $async ? 'requestAsync' : 'request'; + $uri = uri_template($operation->getPath(), $userValues); + + return $this->client->$method($operation->getMethod(), $uri, $options); + } + + /** + * {@inheritDoc} + */ + public function execute(array $definition, array $userValues = []) + { + return $this->sendRequest($this->getOperation($definition), $userValues); + } + + /** + * {@inheritDoc} + */ + public function executeAsync(array $definition, array $userValues = []) + { + return $this->sendRequest($this->getOperation($definition), $userValues, true); + } + + /** + * {@inheritDoc} + */ + public function model($class, $data = null) + { + $model = new $class($this->client, $this->api); + + // @codeCoverageIgnoreStart + if (!$model instanceof ResourceInterface) { + throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class)); + } + // @codeCoverageIgnoreEnd + + if ($data instanceof ResponseInterface) { + $model->populateFromResponse($data); + } elseif (is_array($data)) { + $model->populateFromArray($data); + } + + return $model; + } + + /** + * Will create a new instance of this class with the current HTTP client and API injected in. This + * is useful when enumerating over a collection since multiple copies of the same resource class + * are needed. + * + * @return static + */ + public function newInstance() + { + return new static($this->client, $this->api); + } + + /** + * @return \GuzzleHttp\Psr7\Uri + */ + protected function getHttpBaseUrl() + { + return $this->client->getConfig('base_uri'); + } + + /** + * Magic method which intercepts async calls, finds the sequential version, and wraps it in a + * {@see Promise} object. In order for this to happen, the called methods need to be in the + * following format: `createAsync`, where `create` is the sequential method being wrapped. + * + * @param $methodName The name of the method being invoked. + * @param $args The arguments to be passed to the sequential method. + * + * @throws \RuntimeException If method does not exist + * + * @return Promise + */ + public function __call($methodName, $args) + { + $e = function ($name) { + return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name)); + }; + + if (substr($methodName, -5) === 'Async') { + $realMethod = substr($methodName, 0, -5); + if (!method_exists($this, $realMethod)) { + throw $e($realMethod); + } + + $promise = new Promise( + function () use (&$promise, $realMethod, $args) { + $value = call_user_func_array([$this, $realMethod], $args); + $promise->resolve($value); + } + ); + + return $promise; + } + + throw $e($methodName); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php b/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php new file mode 100644 index 0000000..43c6ce2 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php @@ -0,0 +1,51 @@ +<?php + +namespace OpenCloud\Common\Api; + +use GuzzleHttp\ClientInterface; + +/** + * An operator is any resource or service that can invoke and send REST operations. In other words, it + * is any class that can send requests and receive responses with a HTTP client. To do this + * it needs two things: a {@see ClientInterface} for handling HTTP transactions and an {@see ApiInterface} + * for handling how operations are created. + * + * @package OpenCloud\Common\Api + */ +interface OperatorInterface +{ + /** + * @param ClientInterface $client The HTTP client responsible for handling HTTP transactions + * @param ApiInterface $api The data API class that dictates how REST operations are structured + */ + public function __construct(ClientInterface $client, ApiInterface $api); + + /** + * A convenience method that assembles an operation and sends it to the remote API + * + * @param array $definition The data that dictates how the operation works + * @param array $userValues The user-defined values that populate the request + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function execute(array $definition, array $userValues = []); + + /** + * A convenience method that assembles an operation and asynchronously sends it to the remote API + * + * @param array $definition The data that dictates how the operation works + * @param array $userValues The user-defined values that populate the request + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function executeAsync(array $definition, array $userValues = []); + + /** + * @param string $name The name of the model class. + * @param mixed $data Either a {@see ResponseInterface} or data array that will populate the newly + * created model class. + * + * @return \OpenCloud\Common\Resource\ResourceInterface + */ + public function model($name, $data = null); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Parameter.php b/server/vendor/php-opencloud/common/src/Common/Api/Parameter.php new file mode 100644 index 0000000..97330a4 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Parameter.php @@ -0,0 +1,389 @@ +<?php + +namespace OpenCloud\Common\Api; + +use OpenCloud\Common\HydratorStrategyTrait; + +/** + * Represents an individual request parameter in a RESTful operation. A parameter can take on many forms: + * in a URL path, in a URL query, in a JSON body, and in a HTTP header. It is worth documenting brifly each + * variety of parameter: + * + * * Header parameters are those which populate a HTTP header in a request. Header parameters can have + * aliases; for example, a user-facing name of "Foo" can be sent over the wire as "X-Foo_Bar", as defined + * by ``sentAs``. Prefixes can also be used. + * + * * Query parameters are those which populate a URL query parameter. The value is therefore usually + * confined to a string. + * + * * JSON parameters are those which populate a JSON request body. These are the most complex variety + * of Parameter, since there are so many different ways a JSON document can be constructed. The SDK + * supports deep-nesting according to a XPath syntax; for more information, see {@see \OpenCloud\Common\JsonPath}. + * Nested object and array properties are also supported since JSON is a recursive data type. What + * this means is that a Parameter can have an assortment of child Parameters, one for each object + * property or array element. + * + * * Raw parameters are those which populate a non-JSON request body. This is typically used for + * uploading payloads (such as Swift object data) to a remote API. + * + * * Path parameters are those which populate a URL path. They are serialized according to URL + * placeholders. + * + * @package OpenCloud\Common\Api + */ +class Parameter +{ + use HydratorStrategyTrait; + + const DEFAULT_LOCATION = 'json'; + + /** + * The human-friendly name of the parameter. This is what the user will input. + * + * @var string + */ + private $name; + + /** + * The alias for this parameter. Although the user will always interact with the human-friendly $name property, + * the $sentAs is what's used over the wire. + * + * @var string + */ + private $sentAs; + + /** + * For array parameters (for example, an array of security group names when creating a server), each array element + * will need to adhere to a common schema. For the aforementioned example, each element will need to be a string. + * For more complicated parameters, you might be validated an array of complicated objects. + * + * @var Parameter + */ + private $itemSchema; + + /** + * For object parameters, each property will need to adhere to a specific schema. For every property in the + * object, it has its own schema - meaning that this property is a hash of name/schema pairs. + * + * The *only* exception to this rule is for metadata parameters, which are arbitrary key/value pairs. Since it does + * not make sense to have a schema for each metadata key, a common schema is use for every one. So instead of this + * property being a hash of schemas, it is a single Parameter object instead. This single Parameter schema will + * then be applied to each metadata key provided. + * + * @var []Parameter|Parameter + */ + private $properties; + + /** + * The value's PHP type which this parameter represents; either "string", "bool", "object", "array", "NULL". + * + * @var string + */ + private $type; + + /** + * Indicates whether this parameter requires a value from the user. + * + * @var bool + */ + private $required; + + /** + * The location in the HTTP request where this parameter will populate; either "header", "url", "query", "raw" or + * "json". + * + * @var string + */ + private $location; + + /** + * Relevant to "json" location parameters only. This property allows for deep nesting through the use of + * {@see OpenCloud\Common\JsonPath}. + * + * @var string + */ + private $path; + + /** + * Allows for the prefixing of parameter names. + * + * @var string + */ + private $prefix; + + /** + * The enum values for which this param is restricted. + * + * @var array + */ + private $enum; + + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->hydrate($data); + + $this->required = (bool)$this->required; + + $this->stockLocation($data); + $this->stockItemSchema($data); + $this->stockProperties($data); + } + + private function stockLocation(array $data) + { + $this->location = isset($data['location']) ? $data['location'] : self::DEFAULT_LOCATION; + + if (!AbstractParams::isSupportedLocation($this->location)) { + throw new \RuntimeException(sprintf("%s is not a permitted location", $this->location)); + } + } + + private function stockItemSchema(array $data) + { + if (isset($data['items'])) { + $this->itemSchema = new Parameter($data['items']); + } + } + + private function stockProperties(array $data) + { + if (isset($data['properties'])) { + if (stripos($this->name, 'metadata') !== false) { + $this->properties = new Parameter($data['properties']); + } else { + foreach ($data['properties'] as $name => $property) { + $this->properties[$name] = new Parameter($property + ['name' => $name]); + } + } + } + } + + /** + * Retrieve the name that will be used over the wire. + * + * @return string + */ + public function getName() + { + return $this->sentAs ?: $this->name; + } + + /** + * Indicates whether the user must provide a value for this parameter. + * + * @return bool + */ + public function isRequired() + { + return $this->required === true; + } + + /** + * Validates a given user value and checks whether it passes basic sanity checking, such as types. + * + * @param $userValues The value provided by the user + * + * @return bool TRUE if the validation passes + * @throws \Exception If validation fails + */ + public function validate($userValues) + { + $this->validateEnums($userValues); + $this->validateType($userValues); + + if ($this->isArray()) { + $this->validateArray($userValues); + } elseif ($this->isObject()) { + $this->validateObject($userValues); + } + + return true; + } + + private function validateEnums($userValues) + { + if (!empty($this->enum) && $this->type == 'string' && !in_array($userValues, $this->enum)) { + throw new \Exception(sprintf( + 'The only permitted values are %s. You provided %s', implode(', ', $this->enum), print_r($userValues, true) + )); + } + } + + private function validateType($userValues) + { + if (!$this->hasCorrectType($userValues)) { + throw new \Exception(sprintf( + 'The key provided "%s" has the wrong value type. You provided %s (%s) but was expecting %s', + $this->name, print_r($userValues, true), gettype($userValues), $this->type + )); + } + } + + private function validateArray($userValues) + { + foreach ($userValues as $userValue) { + $this->itemSchema->validate($userValue); + } + } + + private function validateObject($userValues) + { + foreach ($userValues as $key => $userValue) { + $property = $this->getNestedProperty($key); + $property->validate($userValue); + } + } + + /** + * Internal method which retrieves a nested property for object parameters. + * + * @param $key The name of the child parameter + * + * @returns Parameter + * @throws \Exception + */ + private function getNestedProperty($key) + { + if (stripos($this->name, 'metadata') !== false && $this->properties instanceof Parameter) { + return $this->properties; + } elseif (isset($this->properties[$key])) { + return $this->properties[$key]; + } else { + throw new \Exception(sprintf('The key provided "%s" is not defined', $key)); + } + } + + /** + * Internal method which indicates whether the user value is of the same type as the one expected + * by this parameter. + * + * @param $userValue The value being checked + * + * @return bool + */ + private function hasCorrectType($userValue) + { + // Helper fn to see whether an array is associative (i.e. a JSON object) + $isAssociative = function ($value) { + return is_array($value) && array_keys($value) !== range(0, count($value) - 1); + }; + + // For params defined as objects, we'll let the user get away with + // passing in an associative array - since it's effectively a hash + if ($this->type == 'object' && $isAssociative($userValue)) { + return true; + } + + if (class_exists($this->type) || interface_exists($this->type)) { + return is_a($userValue, $this->type); + } + + if (!$this->type) { + return true; + } + + return gettype($userValue) == $this->type; + } + + /** + * Indicates whether this parameter represents an array type + * + * @return bool + */ + public function isArray() + { + return $this->type == 'array' && $this->itemSchema instanceof Parameter; + } + + /** + * Indicates whether this parameter represents an object type + * + * @return bool + */ + public function isObject() + { + return $this->type == 'object' && !empty($this->properties); + } + + public function getLocation() + { + return $this->location; + } + + /** + * Verifies whether the given location matches the parameter's location. + * + * @param $value + * + * @return bool + */ + public function hasLocation($value) + { + return $this->location == $value; + } + + /** + * Retrieves the parameter's path. + * + * @return string|null + */ + public function getPath() + { + return $this->path; + } + + /** + * Retrieves the common schema that an array parameter applies to all its child elements. + * + * @return Parameter + */ + public function getItemSchema() + { + return $this->itemSchema; + } + + /** + * Sets the name of the parameter to a new value + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Retrieves the child parameter for an object parameter. + * + * @param string $name The name of the child property + * + * @return null|Parameter + */ + public function getProperty($name) + { + if ($this->properties instanceof Parameter) { + $this->properties->setName($name); + return $this->properties; + } + + return isset($this->properties[$name]) ? $this->properties[$name] : null; + } + + /** + * Retrieves the prefix for a parameter, if any. + * + * @return string|null + */ + public function getPrefix() + { + return $this->prefix; + } + + public function getPrefixedName() + { + return $this->prefix . $this->getName(); + } +} |
