mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-24 18:13:00 +01:00
[Service] Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
This commit is contained in:
parent
06834146d7
commit
f48f686e7a
@ -63,7 +63,7 @@ class AsyncPlugin implements EventSubscriberInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Event emitted when a curl exception occurs. Ignore the exception and
|
||||
* Event emitted when a curl exception occurs. Ignore the exception and
|
||||
* set a mock response.
|
||||
*
|
||||
* @param Event $event
|
||||
@ -79,7 +79,7 @@ class AsyncPlugin implements EventSubscriberInterface
|
||||
|
||||
/**
|
||||
* Event emitted when a request completes because it took less than 1ms.
|
||||
* Add an X-Guzzle-Async header to notify the caller that there is no
|
||||
* Add an X-Guzzle-Async header to notify the caller that there is no
|
||||
* body in the message.
|
||||
*
|
||||
* @param Event $event
|
||||
|
@ -53,34 +53,14 @@ abstract class AbstractCommand extends Collection implements CommandInterface
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array|Collection $parameters Collection of parameters
|
||||
* to set on the command
|
||||
* @param ApiCommand $apiCommand Command definition from description
|
||||
* @param array|Collection $parameters Collection of parameters to set on the command
|
||||
* @param ApiCommand $apiCommand Command definition from description
|
||||
*/
|
||||
public function __construct($parameters = null, ApiCommand $apiCommand = null)
|
||||
{
|
||||
parent::__construct($parameters);
|
||||
|
||||
if ($apiCommand) {
|
||||
|
||||
$this->apiCommand = $apiCommand;
|
||||
|
||||
} else {
|
||||
|
||||
// If this is a concrete command, then build an ApiCommand object
|
||||
$className = get_class($this);
|
||||
|
||||
// Determine the name of the command based on the relation to the
|
||||
// client that executes the command
|
||||
$this->apiCommand = new ApiCommand(array(
|
||||
'name' => str_replace('\\_', '.', Inflector::snake(substr($className, strpos($className, 'Command') + 8))),
|
||||
'class' => $className,
|
||||
'params' => $this->getInspector()->getApiParamsForClass($className)
|
||||
));
|
||||
}
|
||||
|
||||
// Set default and static values on the command
|
||||
$this->getInspector()->initConfig($this->apiCommand->getParams(), $this);
|
||||
$this->apiCommand = $apiCommand ?: ApiCommand::fromCommand(get_class($this));
|
||||
$this->initConfig();
|
||||
|
||||
$headers = $this->get('headers');
|
||||
if (!$headers instanceof Collection) {
|
||||
@ -324,10 +304,7 @@ abstract class AbstractCommand extends Collection implements CommandInterface
|
||||
}
|
||||
|
||||
// Fail on missing required arguments, and change parameters via filters
|
||||
// Perform a non-idempotent validation on the parameters. Options that
|
||||
// change during validation will persist (e.g. injection, filters).
|
||||
$this->getInspector()->validateConfig($this->apiCommand->getParams(), $this, true);
|
||||
|
||||
$this->apiCommand->validate($this, $this->getInspector());
|
||||
$this->build();
|
||||
|
||||
// Add custom request headers set on the command
|
||||
@ -431,4 +408,20 @@ abstract class AbstractCommand extends Collection implements CommandInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the default and static settings of the command
|
||||
*/
|
||||
protected function initConfig()
|
||||
{
|
||||
foreach ($this->apiCommand->getParams() as $name => $arg) {
|
||||
$currentValue = $this->get($name);
|
||||
$configValue = $arg->getValue($currentValue);
|
||||
// If default or static values are set, then this should always be
|
||||
// updated on the config object
|
||||
if ($currentValue !== $configValue) {
|
||||
$this->set($name, $configValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,11 @@
|
||||
|
||||
namespace Guzzle\Service\Description;
|
||||
|
||||
use Guzzle\Common\Collection;
|
||||
use Guzzle\Common\Exception\InvalidArgumentException;
|
||||
use Guzzle\Service\Exception\ValidationException;
|
||||
use Guzzle\Service\Inflector;
|
||||
use Guzzle\Service\Inspector;
|
||||
|
||||
/**
|
||||
* Data object holding the information of an API command
|
||||
@ -13,6 +18,11 @@ class ApiCommand
|
||||
*/
|
||||
const DEFAULT_COMMAND_CLASS = 'Guzzle\\Service\\Command\\DynamicCommand';
|
||||
|
||||
/**
|
||||
* @var string Annotation used to specify Guzzle service description info
|
||||
*/
|
||||
const GUZZLE_ANNOTATION = '@guzzle';
|
||||
|
||||
/**
|
||||
* @var array Parameters
|
||||
*/
|
||||
@ -43,6 +53,11 @@ class ApiCommand
|
||||
*/
|
||||
protected $class;
|
||||
|
||||
/**
|
||||
* @var array Cache of parsed Command class ApiCommands
|
||||
*/
|
||||
protected static $apiCommandCache = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -89,6 +104,62 @@ class ApiCommand
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ApiCommand object from a class and its docblock
|
||||
*
|
||||
* The following is the format for @guzzle arguments:
|
||||
* @guzzle argument_name [default="default value"] [required="true|false"] [type="registered constraint name"] [type_args=""] [doc="Description of argument"]
|
||||
* Example: @guzzle my_argument default="hello" required="true" doc="Set the argument to control the widget..."
|
||||
*
|
||||
* @param string $className Name of the class
|
||||
*
|
||||
* @return ApiCommand
|
||||
*/
|
||||
public static function fromCommand($className)
|
||||
{
|
||||
if (!isset(self::$apiCommandCache[$className])) {
|
||||
|
||||
$reflection = new \ReflectionClass($className);
|
||||
|
||||
// Get all of the @guzzle annotations from the class
|
||||
$matches = array();
|
||||
$params = array();
|
||||
preg_match_all('/' . self::GUZZLE_ANNOTATION . '\s+([A-Za-z0-9_\-\.]+)\s*([A-Za-z0-9]+=".+")*/', $reflection->getDocComment(), $matches);
|
||||
|
||||
// Parse the docblock annotations
|
||||
if (!empty($matches[1])) {
|
||||
foreach ($matches[1] as $index => $match) {
|
||||
// Add the matched argument to the array keys
|
||||
$params[$match] = array();
|
||||
if (isset($matches[2])) {
|
||||
// Break up the argument attributes by closing quote
|
||||
foreach (explode('" ', $matches[2][$index]) as $part) {
|
||||
$attrs = array();
|
||||
// Find the attribute and attribute value
|
||||
preg_match('/([A-Za-z0-9]+)="(.+)"*/', $part, $attrs);
|
||||
if (isset($attrs[1]) && isset($attrs[0])) {
|
||||
// Sanitize the strings
|
||||
if ($attrs[2][strlen($attrs[2]) - 1] == '"') {
|
||||
$attrs[2] = substr($attrs[2], 0, strlen($attrs[2]) - 1);
|
||||
}
|
||||
$params[$match][$attrs[1]] = $attrs[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
$params[$match] = new ApiParam($params[$match]);
|
||||
}
|
||||
}
|
||||
|
||||
self::$apiCommandCache[$className] = new ApiCommand(array(
|
||||
'name' => str_replace('\\_', '.', Inflector::snake(substr($className, strpos($className, 'Command') + 8))),
|
||||
'class' => $className,
|
||||
'params' => $params
|
||||
));
|
||||
}
|
||||
|
||||
return self::$apiCommandCache[$className];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get as an array
|
||||
*
|
||||
@ -177,4 +248,72 @@ class ApiCommand
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all required args are included in a config object,
|
||||
* and if not, throws an InvalidArgumentException with a helpful error message. Adds
|
||||
* default args to the passed config object if the parameter was not
|
||||
* set in the config object.
|
||||
*
|
||||
* @param Collection $config Configuration settings
|
||||
*
|
||||
* @throws ValidationException when validation errors occur
|
||||
*/
|
||||
public function validate(Collection $config, Inspector $inspector = null)
|
||||
{
|
||||
$inspector = $inspector ?: Inspector::getInstance();
|
||||
$typeValidation = $inspector->getTypeValidation();
|
||||
$errors = array();
|
||||
|
||||
foreach ($this->params as $name => $arg) {
|
||||
|
||||
$currentValue = $config->get($name);
|
||||
$configValue = $arg->getValue($currentValue);
|
||||
|
||||
// Inject configuration information into the config value
|
||||
if ($configValue && is_string($configValue)) {
|
||||
$configValue = $config->inject($configValue);
|
||||
}
|
||||
|
||||
// Ensure that required arguments are set
|
||||
if ($arg->getRequired() && ($configValue === null || $configValue === '')) {
|
||||
$errors[] = 'Requires that the ' . $name . ' argument be supplied.' . ($arg->getDoc() ? ' (' . $arg->getDoc() . ').' : '');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure that the correct data type is being used
|
||||
if ($typeValidation && $configValue !== null && $argType = $arg->getType()) {
|
||||
$validation = $inspector->validateConstraint($argType, $configValue, $arg->getTypeArgs());
|
||||
if ($validation !== true) {
|
||||
$errors[] = $name . ': ' . $validation;
|
||||
$config->set($name, $configValue);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$configValue = $arg->filter($configValue);
|
||||
|
||||
// Update the config value if it changed
|
||||
if (!$configValue !== $currentValue) {
|
||||
$config->set($name, $configValue);
|
||||
}
|
||||
|
||||
// Check the length values if validating data
|
||||
$argMinLength = $arg->getMinLength();
|
||||
if ($argMinLength && strlen($configValue) < $argMinLength) {
|
||||
$errors[] = 'Requires that the ' . $name . ' argument be >= ' . $arg->getMinLength() . ' characters.';
|
||||
}
|
||||
|
||||
$argMaxLength = $arg->getMaxLength();
|
||||
if ($argMaxLength && strlen($configValue) > $argMaxLength) {
|
||||
$errors[] = 'Requires that the ' . $name . ' argument be <= ' . $arg->getMaxLength() . ' characters.';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
$e = new ValidationException('Validation errors: ' . implode("\n", $errors));
|
||||
$e->setErrors($errors);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,27 @@ namespace Guzzle\Service\Exception;
|
||||
|
||||
use Guzzle\Common\Exception\RuntimeException;
|
||||
|
||||
class ValidationException extends RuntimeException {}
|
||||
class ValidationException extends RuntimeException
|
||||
{
|
||||
protected $errors = array();
|
||||
|
||||
/**
|
||||
* Set the validation error messages
|
||||
*
|
||||
* @param array $errors Array of validation errors
|
||||
*/
|
||||
public function setErrors(array $errors)
|
||||
{
|
||||
$this->errors = $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any validation errors
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
|
@ -5,39 +5,19 @@ namespace Guzzle\Service;
|
||||
use Guzzle\Common\Collection;
|
||||
use Guzzle\Common\Exception\InvalidArgumentException;
|
||||
use Guzzle\Service\Exception\ValidationException;
|
||||
use Guzzle\Service\Description\ApiParam;
|
||||
|
||||
/**
|
||||
* Inspects configuration options versus defined parameters, adding default
|
||||
* values where appropriate, performing type validation on config settings, and
|
||||
* validating input vs output data.
|
||||
*
|
||||
* The Reflection class can parse @guzzle specific parameters in a class's
|
||||
* docblock comment and validate that a specified {@see Collection} object
|
||||
* has the default arguments specified, has the required arguments set, and
|
||||
* that the passed arguments that match the typehints specified in the
|
||||
* annotation.
|
||||
*
|
||||
* The following is the format for @guzzle arguments:
|
||||
* @guzzle argument_name [default="default value"] [required="true|false"] [type="registered constraint name"] [type_args=""] [doc="Description of argument"]
|
||||
*
|
||||
* Here's an example:
|
||||
* @guzzle my_argument default="hello" required="true" doc="Set the argument to control the widget..."
|
||||
* Prepares configuration settings with default values and ensures that required
|
||||
* values are set. Holds references to validation constraints and their default
|
||||
* values.
|
||||
*/
|
||||
class Inspector
|
||||
{
|
||||
const GUZZLE_ANNOTATION = '@guzzle';
|
||||
|
||||
/**
|
||||
* @var Inspector Cached Inspector instance
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var array Cache of parsed doc blocks
|
||||
*/
|
||||
protected $cache = array();
|
||||
|
||||
/**
|
||||
* @var array Array of aliased constraints
|
||||
*/
|
||||
@ -143,6 +123,16 @@ class Inspector
|
||||
$this->typeValidation = $typeValidation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if type validation is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getTypeValidation()
|
||||
{
|
||||
return $this->typeValidation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of the registered constraints by name
|
||||
*
|
||||
@ -209,152 +199,4 @@ class Inspector
|
||||
|
||||
return $this->getConstraint($name)->validate($value, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of ApiParam objects for a class using @guzzle annotations
|
||||
*
|
||||
* @param string $class Name of a class to parse
|
||||
*
|
||||
* @return array Returns an array of ApiParam objects
|
||||
*/
|
||||
public function getApiParamsForClass($class)
|
||||
{
|
||||
if (!isset($this->cache[$class])) {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$this->cache[$class] = $this->parseDocBlock($reflection->getDocComment());
|
||||
}
|
||||
|
||||
return $this->cache[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a configuration collection with static and default parameters
|
||||
*
|
||||
* @param array $params Array of ApiParam objects
|
||||
* @param Collection $config Collection of configuration options
|
||||
*/
|
||||
public function initConfig(array $params, Collection $config)
|
||||
{
|
||||
foreach ($params as $name => $arg) {
|
||||
$currentValue = $config->get($name);
|
||||
$configValue = $arg->getValue($currentValue);
|
||||
// If default or static values are set, then this should always be
|
||||
// updated on the config object
|
||||
if ($currentValue !== $configValue) {
|
||||
$config->set($name, $configValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all required args are included in a config object,
|
||||
* and if not, throws an InvalidArgumentException with a helpful error message. Adds
|
||||
* default args to the passed config object if the parameter was not
|
||||
* set in the config object.
|
||||
*
|
||||
* @param array $params Params to validate
|
||||
* @param Collection $config Configuration settings
|
||||
* @param bool $strict Set to false to allow missing required fields
|
||||
*
|
||||
* @return array|bool Returns an array of errors or TRUE on success
|
||||
*
|
||||
* @throws InvalidArgumentException if any args are missing and $strict is TRUE
|
||||
*/
|
||||
public function validateConfig(array $params, Collection $config, $strict = true)
|
||||
{
|
||||
$errors = array();
|
||||
|
||||
foreach ($params as $name => $arg) {
|
||||
|
||||
$currentValue = $config->get($name);
|
||||
$configValue = $arg->getValue($currentValue);
|
||||
|
||||
// Inject configuration information into the config value
|
||||
if ($configValue && is_string($configValue)) {
|
||||
$configValue = $config->inject($configValue);
|
||||
}
|
||||
|
||||
// Ensure that required arguments are set
|
||||
if ($arg->getRequired() && ($configValue === null || $configValue === '')) {
|
||||
$errors[] = 'Requires that the ' . $name . ' argument be supplied.' . ($arg->getDoc() ? ' (' . $arg->getDoc() . ').' : '');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure that the correct data type is being used
|
||||
if ($this->typeValidation && $configValue !== null && $argType = $arg->getType()) {
|
||||
$validation = $this->validateConstraint($argType, $configValue, $arg->getTypeArgs());
|
||||
if ($validation !== true) {
|
||||
$errors[] = $name . ': ' . $validation;
|
||||
$config->set($name, $configValue);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$configValue = $arg->filter($configValue);
|
||||
|
||||
// Update the config value if it changed
|
||||
if (!$configValue !== $currentValue) {
|
||||
$config->set($name, $configValue);
|
||||
}
|
||||
|
||||
// Check the length values if validating data
|
||||
$argMinLength = $arg->getMinLength();
|
||||
if ($argMinLength && strlen($configValue) < $argMinLength) {
|
||||
$errors[] = 'Requires that the ' . $name . ' argument be >= ' . $arg->getMinLength() . ' characters.';
|
||||
}
|
||||
|
||||
$argMaxLength = $arg->getMaxLength();
|
||||
if ($argMaxLength && strlen($configValue) > $argMaxLength) {
|
||||
$errors[] = 'Requires that the ' . $name . ' argument be <= ' . $arg->getMaxLength() . ' characters.';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
return true;
|
||||
} elseif ($strict) {
|
||||
throw new ValidationException('Validation errors: ' . implode("\n", $errors));
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Guzzle arguments from a DocBlock
|
||||
*
|
||||
* @param string $doc Docblock to parse
|
||||
*
|
||||
* @return array Returns an associative array of ApiParam objects
|
||||
*/
|
||||
protected function parseDocBlock($doc)
|
||||
{
|
||||
// Get all of the @guzzle annotations from the class
|
||||
$matches = array();
|
||||
preg_match_all('/' . self::GUZZLE_ANNOTATION . '\s+([A-Za-z0-9_\-\.]+)\s*([A-Za-z0-9]+=".+")*/', $doc, $matches);
|
||||
|
||||
$params = array();
|
||||
if (!empty($matches[1])) {
|
||||
foreach ($matches[1] as $index => $match) {
|
||||
// Add the matched argument to the array keys
|
||||
$params[$match] = array();
|
||||
if (isset($matches[2])) {
|
||||
// Break up the argument attributes by closing quote
|
||||
foreach (explode('" ', $matches[2][$index]) as $part) {
|
||||
$attrs = array();
|
||||
// Find the attribute and attribute value
|
||||
preg_match('/([A-Za-z0-9]+)="(.+)"*/', $part, $attrs);
|
||||
if (isset($attrs[1]) && isset($attrs[0])) {
|
||||
// Sanitize the strings
|
||||
if ($attrs[2][strlen($attrs[2]) - 1] == '"') {
|
||||
$attrs[2] = substr($attrs[2], 0, strlen($attrs[2]) - 1);
|
||||
}
|
||||
$params[$match][$attrs[1]] = $attrs[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
$params[$match] = new ApiParam($params[$match]);
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use Guzzle\Service\Client;
|
||||
use Guzzle\Service\Command\CommandInterface;
|
||||
use Guzzle\Service\Command\AbstractCommand;
|
||||
use Guzzle\Service\Description\ApiCommand;
|
||||
use Guzzle\Service\Description\ApiParam;
|
||||
use Guzzle\Service\Inspector;
|
||||
use Guzzle\Http\Plugin\MockPlugin;
|
||||
use Guzzle\Tests\Service\Mock\Command\MockCommand;
|
||||
@ -400,4 +401,25 @@ class CommandTest extends AbstractCommandTest
|
||||
$command->setResult('foo!');
|
||||
$this->assertEquals('foo!', $command->getResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Command\AbstractCommand::initConfig
|
||||
*/
|
||||
public function testCanInitConfig()
|
||||
{
|
||||
$command = $this->getMockBuilder('Guzzle\\Service\\Command\\AbstractCommand')
|
||||
->setConstructorArgs(array(array(
|
||||
'foo' => 'bar'
|
||||
), new ApiCommand(array(
|
||||
'params' => array(
|
||||
'baz' => new ApiParam(array(
|
||||
'default' => 'baaar'
|
||||
))
|
||||
)
|
||||
))))
|
||||
->getMockForAbstractClass();
|
||||
|
||||
$this->assertEquals('bar', $command['foo']);
|
||||
$this->assertEquals('baaar', $command['baz']);
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,32 @@
|
||||
namespace Guzzle\Tests\Service\Description;
|
||||
|
||||
use Guzzle\Common\Collection;
|
||||
use Guzzle\Service\Inspector;
|
||||
use Guzzle\Service\Description\ApiCommand;
|
||||
use Guzzle\Service\Description\ApiParam;
|
||||
use Guzzle\Service\Description\ServiceDescription;
|
||||
use Guzzle\Service\Exception\ValidationException;
|
||||
|
||||
/**
|
||||
* @guzzle test type="type:object"
|
||||
* @guzzle bool_1 default="true" type="boolean"
|
||||
* @guzzle bool_2 default="false"
|
||||
* @guzzle float type="float"
|
||||
* @guzzle int type="integer"
|
||||
* @guzzle date type="date"
|
||||
* @guzzle timestamp type="time"
|
||||
* @guzzle string type="string"
|
||||
* @guzzle username required="true" filters="strtolower"
|
||||
* @guzzle dynamic default="{username}_{ string }_{ does_not_exist }"
|
||||
* @guzzle test_function type="string" filters="Guzzle\Tests\Service\Description\ApiCommandTest::strtoupper"
|
||||
*/
|
||||
class ApiCommandTest extends \Guzzle\Tests\GuzzleTestCase
|
||||
{
|
||||
public static function strtoupper($string)
|
||||
{
|
||||
return strtoupper($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand
|
||||
*/
|
||||
@ -97,4 +117,201 @@ class ApiCommandTest extends \Guzzle\Tests\GuzzleTestCase
|
||||
$c = new ApiCommand($data);
|
||||
$this->assertEquals($data, $c->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the class cache of the ApiCommand static factory method
|
||||
*/
|
||||
protected function clearCommandCache()
|
||||
{
|
||||
$refObject = new \ReflectionClass('Guzzle\Service\Description\ApiCommand');
|
||||
$refProperty = $refObject->getProperty('apiCommandCache');
|
||||
$refProperty->setAccessible(true);
|
||||
$refProperty->setValue(null, array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::fromCommand
|
||||
*/
|
||||
public function testDoesNotErrorWhenNoAnnotationsArePresent()
|
||||
{
|
||||
$this->clearCommandCache();
|
||||
|
||||
$command = ApiCommand::fromCommand('Guzzle\\Tests\\Service\\Mock\\Command\\Sub\\Sub');
|
||||
$this->assertEquals(array(), $command->getParams());
|
||||
|
||||
// Ensure that the cache returns the same value
|
||||
$this->assertSame($command, ApiCommand::fromCommand('Guzzle\\Tests\\Service\\Mock\\Command\\Sub\\Sub'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::fromCommand
|
||||
*/
|
||||
public function testBuildsApiParamFromClassDocBlock()
|
||||
{
|
||||
$command = ApiCommand::fromCommand('Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand');
|
||||
$this->assertEquals(3, count($command->getParams()));
|
||||
|
||||
$this->assertTrue($command->getParam('test')->getRequired());
|
||||
$this->assertEquals('123', $command->getParam('test')->getDefault());
|
||||
$this->assertEquals('Test argument', $command->getParam('test')->getDoc());
|
||||
|
||||
$this->assertEquals('abc', $command->getParam('_internal')->getDefault());
|
||||
}
|
||||
|
||||
protected function getApiCommand()
|
||||
{
|
||||
return ApiCommand::fromCommand(get_class($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
*/
|
||||
public function testAddsDefaultAndInjectsConfigs()
|
||||
{
|
||||
$col = new Collection(array(
|
||||
'username' => 'user',
|
||||
'string' => 'test',
|
||||
'float' => 1.23
|
||||
));
|
||||
|
||||
$this->getApiCommand()->validate($col);
|
||||
$this->assertEquals(false, $col->get('bool_2'));
|
||||
$this->assertEquals('user_test_', $col->get('dynamic'));
|
||||
$this->assertEquals(1.23, $col->get('float'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
* @expectedException Guzzle\Service\Exception\ValidationException
|
||||
*/
|
||||
public function testValidatesTypeHints()
|
||||
{
|
||||
$this->getApiCommand()->validate(new Collection(array(
|
||||
'test' => 'uh oh',
|
||||
'username' => 'test'
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
*/
|
||||
public function testConvertsBooleanDefaults()
|
||||
{
|
||||
$c = new Collection(array(
|
||||
'test' => $this,
|
||||
'username' => 'test'
|
||||
));
|
||||
|
||||
$this->getApiCommand()->validate($c);
|
||||
$this->assertTrue($c->get('bool_1'));
|
||||
$this->assertFalse($c->get('bool_2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
*/
|
||||
public function testValidatesArgs()
|
||||
{
|
||||
$config = new Collection(array(
|
||||
'data' => 123,
|
||||
'min' => 'a',
|
||||
'max' => 'aaa'
|
||||
));
|
||||
|
||||
$command = new ApiCommand(array(
|
||||
'params' => array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'string'
|
||||
)),
|
||||
'min' => new ApiParam(array(
|
||||
'type' => 'string',
|
||||
'min_length' => 2
|
||||
)),
|
||||
'max' => new ApiParam(array(
|
||||
'type' => 'string',
|
||||
'max_length' => 2
|
||||
))
|
||||
)
|
||||
));
|
||||
|
||||
try {
|
||||
$command->validate($config);
|
||||
$this->fail('Did not throw expected exception');
|
||||
} catch (ValidationException $e) {
|
||||
$concat = implode("\n", $e->getErrors());
|
||||
$this->assertContains("Value must be of type string", $concat);
|
||||
$this->assertContains("Requires that the min argument be >= 2 characters", $concat);
|
||||
$this->assertContains("Requires that the max argument be <= 2 characters", $concat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
*/
|
||||
public function testRunsValuesThroughFilters()
|
||||
{
|
||||
$data = new Collection(array(
|
||||
'username' => 'TEST',
|
||||
'test_function' => 'foo'
|
||||
));
|
||||
|
||||
$this->getApiCommand()->validate($data);
|
||||
$this->assertEquals('test', $data->get('username'));
|
||||
$this->assertEquals('FOO', $data->get('test_function'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
*/
|
||||
public function testTypeValidationCanBeDisabled()
|
||||
{
|
||||
$i = Inspector::getInstance();
|
||||
$i->setTypeValidation(false);
|
||||
|
||||
$command = new ApiCommand(array(
|
||||
'params' => array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'string'
|
||||
))
|
||||
)
|
||||
));
|
||||
|
||||
$command->validate(new Collection(array(
|
||||
'data' => new \stdClass()
|
||||
)), $i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
*/
|
||||
public function testSkipsFurtherValidationIfNotSet()
|
||||
{
|
||||
$command = new ApiCommand(array(
|
||||
'params' => array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'string'
|
||||
))
|
||||
)
|
||||
));
|
||||
|
||||
$command->validate(new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Description\ApiCommand::validate
|
||||
* @expectedException Guzzle\Service\Exception\ValidationException
|
||||
* @expectedExceptionMessage Validation errors: Requires that the data argument be supplied.
|
||||
*/
|
||||
public function testValidatesRequiredFieldsAreSet()
|
||||
{
|
||||
$command = new ApiCommand(array(
|
||||
'params' => array(
|
||||
'data' => new ApiParam(array(
|
||||
'required' => true
|
||||
))
|
||||
)
|
||||
));
|
||||
|
||||
$command->validate(new Collection());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Guzzle\Tests\Service\Exception;
|
||||
|
||||
use Guzzle\Service\Exception\ValidationException;
|
||||
|
||||
class ValidationExceptionTest extends \Guzzle\Tests\GuzzleTestCase
|
||||
{
|
||||
public function testCanSetAndRetrieveErrors()
|
||||
{
|
||||
$errors = array('foo', 'bar');
|
||||
|
||||
$e = new ValidationException('Foo');
|
||||
$e->setErrors($errors);
|
||||
$this->assertEquals($errors, $e->getErrors());
|
||||
}
|
||||
}
|
@ -5,28 +5,24 @@ namespace Guzzle\Tests\Common;
|
||||
use Guzzle\Common\Collection;
|
||||
use Guzzle\Service\Inspector;
|
||||
use Guzzle\Service\Description\ApiParam;
|
||||
use Guzzle\Service\Description\ApiCommand;
|
||||
use Guzzle\Service\Exception\ValidationException;
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector
|
||||
*
|
||||
* @guzzle test type="type:object"
|
||||
* @guzzle bool_1 default="true" type="boolean"
|
||||
* @guzzle bool_2 default="false"
|
||||
* @guzzle float type="float"
|
||||
* @guzzle int type="integer"
|
||||
* @guzzle date type="date"
|
||||
* @guzzle timestamp type="time"
|
||||
* @guzzle string type="string"
|
||||
* @guzzle username required="true" filters="strtolower"
|
||||
* @guzzle dynamic default="{username}_{ string }_{ does_not_exist }"
|
||||
* @guzzle test_function type="string" filters="Guzzle\Tests\Common\InspectorTest::strtoupper"
|
||||
*/
|
||||
class InspectorTest extends \Guzzle\Tests\GuzzleTestCase
|
||||
{
|
||||
public static function strtoupper($string)
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::setTypeValidation
|
||||
* @covers Guzzle\Service\Inspector::getTypeValidation
|
||||
*/
|
||||
public function testTypeValidationCanBeToggled()
|
||||
{
|
||||
return strtoupper($string);
|
||||
$i = new Inspector();
|
||||
$this->assertTrue($i->getTypeValidation());
|
||||
$i->setTypeValidation(false);
|
||||
$this->assertFalse($i->getTypeValidation());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,6 +34,15 @@ class InspectorTest extends \Guzzle\Tests\GuzzleTestCase
|
||||
$this->assertNotEmpty($inspector->getRegisteredConstraints());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testChecksFilterValidity()
|
||||
{
|
||||
Inspector::getInstance()->getConstraint('foooo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::prepareConfig
|
||||
*/
|
||||
@ -65,152 +70,6 @@ class InspectorTest extends \Guzzle\Tests\GuzzleTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector
|
||||
*/
|
||||
public function testAddsDefaultAndInjectsConfigs()
|
||||
{
|
||||
$col = new Collection(array(
|
||||
'username' => 'user',
|
||||
'string' => 'test',
|
||||
'float' => 1.23
|
||||
));
|
||||
|
||||
$inspector = Inspector::getInstance();
|
||||
$inspector->validateConfig($inspector->getApiParamsForClass(__CLASS__), $col, true);
|
||||
$this->assertEquals(false, $col->get('bool_2'));
|
||||
$this->assertEquals('user_test_', $col->get('dynamic'));
|
||||
$this->assertEquals(1.23, $col->get('float'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::validateConfig
|
||||
* @covers Guzzle\Service\Inspector::getApiParamsForClass
|
||||
* @expectedException Guzzle\Service\Exception\ValidationException
|
||||
*/
|
||||
public function testValidatesTypeHints()
|
||||
{
|
||||
$inspector = Inspector::getInstance();
|
||||
$inspector->validateConfig($inspector->getApiParamsForClass(__CLASS__), new Collection(array(
|
||||
'test' => 'uh oh',
|
||||
'username' => 'test'
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::validateConfig
|
||||
*/
|
||||
public function testConvertsBooleanDefaults()
|
||||
{
|
||||
$c = new Collection(array(
|
||||
'test' => $this,
|
||||
'username' => 'test'
|
||||
));
|
||||
|
||||
$inspector = Inspector::getInstance();
|
||||
$inspector->validateConfig($inspector->getApiParamsForClass(__CLASS__), $c);
|
||||
|
||||
$this->assertTrue($c->get('bool_1'));
|
||||
$this->assertFalse($c->get('bool_2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector
|
||||
*/
|
||||
public function testInspectsClassArgs()
|
||||
{
|
||||
$doc = <<<EOT
|
||||
/**
|
||||
* Client for interacting with the Unfuddle webservice
|
||||
*
|
||||
* @guzzle username required="true" doc="API username" type="string"
|
||||
* @guzzle password required="true" doc="API password" type="string"
|
||||
* @guzzle subdomain required="true" doc="Unfuddle project subdomain" type="string"
|
||||
* @guzzle api_version required="true" default="v1" doc="API version" type="choice:'v1','v2',v3"
|
||||
* @guzzle protocol required="true" default="https" doc="HTTP protocol (http or https)" type="string"
|
||||
* @guzzle base_url required="true" default="{ protocol }://{ subdomain }.unfuddle.com/api/{ api_version }/" doc="Unfuddle API base URL" type="string"
|
||||
* @guzzle class type="type:object"
|
||||
*/
|
||||
EOT;
|
||||
|
||||
$inspector = new Inspector();
|
||||
$method = new \ReflectionMethod($inspector, 'parseDocBlock');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$params = $method->invoke($inspector, $doc);
|
||||
|
||||
$this->assertEquals(array(
|
||||
'required' => true,
|
||||
'doc' => 'API username',
|
||||
'type' => 'string'
|
||||
), array_filter($params['username']->toArray()));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'required' => true,
|
||||
'default' => 'v1',
|
||||
'doc' => 'API version',
|
||||
'type' => 'choice',
|
||||
'type_args' => array('v1', 'v2', 'v3')
|
||||
), array_filter($params['api_version']->toArray()));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'required' => true,
|
||||
'default' => 'https',
|
||||
'doc' => 'HTTP protocol (http or https)',
|
||||
'type' => 'string'
|
||||
), array_filter($params['protocol']->toArray()));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'required' => true,
|
||||
'default' => '{ protocol }://{ subdomain }.unfuddle.com/api/{ api_version }/',
|
||||
'doc' => 'Unfuddle API base URL',
|
||||
'type' => 'string'
|
||||
), array_filter($params['base_url']->toArray()));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'type' => 'type',
|
||||
'type_args' => array('object')
|
||||
), array_filter($params['class']->toArray()));
|
||||
|
||||
$config = new Collection(array(
|
||||
'username' => 'test',
|
||||
'password' => 'pass',
|
||||
'subdomain' => 'sub',
|
||||
'api_version' => 'v2'
|
||||
));
|
||||
|
||||
// Do an idempotent initialization
|
||||
Inspector::getInstance()->initConfig($params, $config);
|
||||
// make sure defaults and statics were added, but configs were not injected
|
||||
$this->assertEquals('{ protocol }://{ subdomain }.unfuddle.com/api/{ api_version }/', $config->get('base_url'));
|
||||
$this->assertEquals('https', $config->get('protocol'));
|
||||
|
||||
// Not do a non-idempotent updated
|
||||
Inspector::getInstance()->validateConfig($params, $config, true);
|
||||
|
||||
// make sure the configs were injected
|
||||
$this->assertEquals('https://sub.unfuddle.com/api/v2/', $config->get('base_url'));
|
||||
|
||||
try {
|
||||
Inspector::getInstance()->validateConfig($params, new Collection(array(
|
||||
'base_url' => '',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'class' => '123',
|
||||
'api_version' => 'v10'
|
||||
)));
|
||||
$this->fail('Expected exception not thrown when params are invalid');
|
||||
} catch (ValidationException $e) {
|
||||
|
||||
$concat = $e->getMessage();
|
||||
$this->assertContains("Validation errors: Requires that the username argument be supplied. (API username)", $concat);
|
||||
$this->assertContains("Requires that the password argument be supplied. (API password)", $concat);
|
||||
$this->assertContains("Requires that the subdomain argument be supplied. (Unfuddle project subdomain)", $concat);
|
||||
$this->assertContains("api_version: Value must be one of: v1, v2, v3", $concat);
|
||||
$this->assertContains("class: Value must be of type object", $concat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::registerConstraint
|
||||
* @covers Guzzle\Service\Inspector::getConstraint
|
||||
@ -231,157 +90,7 @@ EOT;
|
||||
$this->assertInstanceOf($constraintClass, Inspector::getInstance()->getConstraint('mock'));
|
||||
$this->assertInstanceOf($constraintClass, Inspector::getInstance()->getConstraint('mock_2'));
|
||||
|
||||
$validating = new Collection(array(
|
||||
'data' => '192.168.16.121',
|
||||
'test' => '10.1.1.0'
|
||||
));
|
||||
|
||||
$this->assertTrue(Inspector::getInstance()->validateConfig(array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'mock',
|
||||
'name' => 'data'
|
||||
)),
|
||||
'test' => new ApiParam(array(
|
||||
'type' => 'mock_2',
|
||||
'name' => 'test'
|
||||
))
|
||||
), $validating, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testChecksFilterValidity()
|
||||
{
|
||||
Inspector::getInstance()->validateConfig(array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'invalid'
|
||||
))
|
||||
), new Collection(array(
|
||||
'data' => 'false'
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector
|
||||
*/
|
||||
public function testValidatesArgs()
|
||||
{
|
||||
$config = new Collection(array(
|
||||
'data' => 123,
|
||||
'min' => 'a',
|
||||
'max' => 'aaa'
|
||||
));
|
||||
|
||||
$result = Inspector::getInstance()->validateConfig(array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'string'
|
||||
)),
|
||||
'min' => new ApiParam(array(
|
||||
'type' => 'string',
|
||||
'min_length' => 2
|
||||
)),
|
||||
'max' => new ApiParam(array(
|
||||
'type' => 'string',
|
||||
'max_length' => 2
|
||||
))
|
||||
), $config, false);
|
||||
|
||||
$concat = implode("\n", $result);
|
||||
$this->assertContains("Value must be of type string", $concat);
|
||||
$this->assertContains("Requires that the min argument be >= 2 characters", $concat);
|
||||
$this->assertContains("Requires that the max argument be <= 2 characters", $concat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::parseDocBlock
|
||||
*/
|
||||
public function testVerifiesGuzzleAnnotations()
|
||||
{
|
||||
$inspector = new Inspector();
|
||||
$method = new \ReflectionMethod($inspector, 'parseDocBlock');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($inspector, 'testing');
|
||||
$this->assertEquals(array(), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::validateConfig
|
||||
* @covers Guzzle\Service\Inspector::getApiParamsForClass
|
||||
*/
|
||||
public function testRunsValuesThroughFilters()
|
||||
{
|
||||
$data = new Collection(array(
|
||||
'username' => 'TEST',
|
||||
'test_function' => 'foo'
|
||||
));
|
||||
|
||||
$inspector = Inspector::getInstance();
|
||||
$inspector->validateConfig($inspector->getApiParamsForClass(__CLASS__), $data, true, true, false);
|
||||
|
||||
$this->assertEquals('test', $data->get('username'));
|
||||
$this->assertEquals('FOO', $data->get('test_function'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::setTypeValidation
|
||||
* @covers Guzzle\Service\Inspector::validateConfig
|
||||
*/
|
||||
public function testTypeValidationCanBeDisabled()
|
||||
{
|
||||
$i = Inspector::getInstance();
|
||||
$i->setTypeValidation(false);
|
||||
|
||||
// Ensure that the type is not validated
|
||||
$i->validateConfig(array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'string'
|
||||
))
|
||||
), new Collection(array(
|
||||
'data' => new \stdClass()
|
||||
)), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::validateConfig
|
||||
*/
|
||||
public function testSkipsFurtherValidationIfNotSet()
|
||||
{
|
||||
$i = Inspector::getInstance();
|
||||
|
||||
// Ensure that the type is not validated
|
||||
$this->assertEquals(true, $i->validateConfig(array(
|
||||
'data' => new ApiParam(array(
|
||||
'type' => 'string'
|
||||
))
|
||||
), new Collection(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Guzzle\Service\Inspector::initConfig
|
||||
*/
|
||||
public function testCanInitConfig()
|
||||
{
|
||||
$i = Inspector::getInstance();
|
||||
|
||||
$param = new ApiParam(array(
|
||||
'type' => 'array',
|
||||
'filters' => 'json_encode'
|
||||
));
|
||||
|
||||
$config = new Collection(array(
|
||||
'data' => array(
|
||||
'foo' => 'bar'
|
||||
)
|
||||
));
|
||||
|
||||
$i->initConfig(array(
|
||||
'data' => $param
|
||||
), $config);
|
||||
|
||||
// Ensure it's still an array
|
||||
$this->assertInternalType('array', $config->get('data'));
|
||||
$this->assertTrue(Inspector::getInstance()->validateConstraint('mock', '192.168.16.121'));
|
||||
$this->assertTrue(Inspector::getInstance()->validateConstraint('mock_2', '10.1.1.0'));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user