diff --git a/.gitignore b/.gitignore index f13bd84b..1e012b42 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ build vendor .php_cs.cache composer.lock -composer.phar diff --git a/src/NamedParameter.php b/src/NamedParameter.php index 1dcf2762..27a35247 100644 --- a/src/NamedParameter.php +++ b/src/NamedParameter.php @@ -17,6 +17,10 @@ class NamedParameter implements ArrayInstantiationInterface const TYPE_DATE = 'date'; const TYPE_BOOLEAN = 'boolean'; const TYPE_FILE = 'file'; + const TYPE_DATE_ONLY = 'date-only'; + const TYPE_TIME_ONLY = 'time-only'; + const TYPE_DATETIME_ONLY = 'datetime-only'; + const TYPE_DATETIME = 'datetime'; const TYPE_ARRAY = 'array'; // Validation exception codes @@ -46,6 +50,10 @@ class NamedParameter implements ArrayInstantiationInterface self::TYPE_DATE, self::TYPE_BOOLEAN, self::TYPE_FILE, + self::TYPE_DATETIME_ONLY, + self::TYPE_DATE_ONLY, + self::TYPE_TIME_ONLY, + self::TYPE_DATETIME, self::TYPE_ARRAY, ]; @@ -175,12 +183,19 @@ class NamedParameter implements ArrayInstantiationInterface */ private $default; + /** + * DateTime format (for datetime type only) + * + * @var string + */ + private $format; + // --- /** * Create a new Query Parameter * - * @param string $key + * @param string $key */ public function __construct($key) { @@ -207,7 +222,7 @@ public function getKey() * [ * displayName: ?string * description: ?string - * type: ?["string","number","integer","date","boolean","file"] + * type: ?["string","number","integer","date","boolean","file", ...] * enum: ?array * pattern: ?string * validationPattern: ?string @@ -220,6 +235,7 @@ public function getKey() * repeat: ?boolean * required: ?boolean * default: ?string + * format: ?string * ] * * @throws \Exception @@ -297,6 +313,10 @@ public static function createFromArray($key, array $data = []) } } + if (isset($data['format'])) { + $namedParameter->setFormat($data['format']); + } + return $namedParameter; } @@ -618,6 +638,22 @@ public function getDefault() return $this->default; } + /** + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * @param string $format + */ + public function setFormat($format) + { + $this->format = $format; + } + /** * Set the default * @@ -705,6 +741,7 @@ public function validate($param) } // no break + case static::TYPE_STRING: // Must be a string @@ -794,6 +831,24 @@ public function validate($param) case static::TYPE_FILE: // File type cannot be reliably validated based on its type alone. + break; + + case static::TYPE_TIME_ONLY: + case static::TYPE_DATE_ONLY: + case static::TYPE_DATETIME_ONLY: + case static::TYPE_DATETIME: + $typeObject = ApiDefinition::determineType( + $this->key, + [ + 'type' => $this->getType(), + 'format' => $this->getFormat(), + ] + ); + $typeObject->validate($param); + if (!$typeObject->isValid()) { + throw new ValidationException($typeObject->getErrors()[0]); + } + break; } diff --git a/src/Parser.php b/src/Parser.php index 40fc465e..87d51918 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -504,7 +504,19 @@ private function addNamespacePrefix($nameSpace, array $definition) } } else { if (!in_array($item, ApiDefinition::getStraightForwardTypes(), true)) { - $definition[$key] = $nameSpace . '.' . $item; + if (mb_strpos($item, '|') !== false) { + $definition[$key] = implode( + '|', + array_map( + function ($v) use ($nameSpace) { + return $nameSpace . '.' . trim($v); + }, + explode('|', $item) + ) + ); + } else { + $definition[$key] = $nameSpace . '.' . $item; + } } } } elseif (is_array($definition[$key])) { @@ -518,7 +530,7 @@ private function addNamespacePrefix($nameSpace, array $definition) /** * Parse the traits * - * @param mixed $ramlData + * @param array $ramlData * * @return array */ diff --git a/src/Types/ArrayType.php b/src/Types/ArrayType.php index a7f7ec40..c4933bba 100644 --- a/src/Types/ArrayType.php +++ b/src/Types/ArrayType.php @@ -2,6 +2,7 @@ namespace Raml\Types; +use Raml\ApiDefinition; use Raml\Type; use Raml\TypeCollection; use Raml\TypeInterface; @@ -21,6 +22,9 @@ class ArrayType extends Type 'string', 'boolean', 'number', + 'datetime-only', + 'date-only', + 'time-only', ]; /** @@ -175,48 +179,16 @@ public function validate($value) private function validateScalars($value) { - foreach ($value as $valueItem) { - switch ($this->items) { - case 'integer': - if (!is_int($valueItem)) { - $this->errors[] = TypeValidationError::unexpectedArrayValueType( - $this->getName(), - 'integer', - $valueItem - ); - } + $typeObject = ApiDefinition::determineType('item', ['type' => $this->items]); - break; - case 'string': - if (!is_string($valueItem)) { - $this->errors[] = TypeValidationError::unexpectedArrayValueType( - $this->getName(), - 'string', - $valueItem - ); - } - - break; - case 'boolean': - if (!is_bool($valueItem)) { - $this->errors[] = TypeValidationError::unexpectedArrayValueType( - $this->getName(), - 'boolean', - $valueItem - ); - } - - break; - case 'number': - if (!is_float($valueItem) && !is_int($valueItem)) { - $this->errors[] = TypeValidationError::unexpectedArrayValueType( - $this->getName(), - 'number', - $valueItem - ); - } - - break; + foreach ($value as $valueItem) { + $typeObject->validate($valueItem); + if (!$typeObject->isValid()) { + $this->errors[] = TypeValidationError::unexpectedArrayValueType( + $this->getName(), + $this->items, + $valueItem + ); } } } diff --git a/src/Types/DatetimeOnlyType.php b/src/Types/DatetimeOnlyType.php index 1f92795d..cad33238 100644 --- a/src/Types/DatetimeOnlyType.php +++ b/src/Types/DatetimeOnlyType.php @@ -15,8 +15,8 @@ class DatetimeOnlyType extends Type /** * Create a new DateTimeOnlyType from an array of data * - * @param string $name - * @param array $data + * @param string $name + * @param array $data * * @return self */ diff --git a/src/Types/DatetimeType.php b/src/Types/DatetimeType.php index c88fa295..dd31335f 100644 --- a/src/Types/DatetimeType.php +++ b/src/Types/DatetimeType.php @@ -10,6 +10,7 @@ */ class DatetimeType extends Type { + const DEFAULT_FORMAT = DATE_RFC3339; /** * DateTime format to use * @@ -20,8 +21,8 @@ class DatetimeType extends Type /** * Create a new DatetimeType from an array of data * - * @param string $name - * @param array $data + * @param string $name + * @param array $data * * @return DatetimeType */ @@ -70,11 +71,15 @@ public function validate($value) { parent::validate($value); - $format = $this->format ?: DATE_RFC3339; + $format = $this->format ?: self::DEFAULT_FORMAT; $d = DateTime::createFromFormat($format, $value); - if ($d && $d->format($format) !== $value) { - $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'datetime', $value); + if (!$d || $d->format($format) !== $value) { + $this->errors[] = TypeValidationError::unexpectedValueType( + $this->getName(), + 'datetime with format ' . $this->format, + $value + ); } } } diff --git a/src/Types/TimeOnlyType.php b/src/Types/TimeOnlyType.php index 46850119..5d006f51 100644 --- a/src/Types/TimeOnlyType.php +++ b/src/Types/TimeOnlyType.php @@ -34,8 +34,8 @@ public function validate($value) $d = DateTime::createFromFormat(self::FORMAT, $value); - if ($d && $d->format(self::FORMAT) !== $value) { - $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), self::FORMAT, $value); + if (!$d || $d->format(self::FORMAT) !== $value) { + $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'time-only', $value); } } } diff --git a/src/Types/UnionType.php b/src/Types/UnionType.php index a692ee3d..3fc1448d 100644 --- a/src/Types/UnionType.php +++ b/src/Types/UnionType.php @@ -2,8 +2,8 @@ namespace Raml\Types; -use Raml\Type; use Raml\ApiDefinition; +use Raml\Type; use Raml\TypeInterface; /** @@ -21,13 +21,23 @@ class UnionType extends Type private $possibleTypes = []; /** - * Create a new UnionType from an array of data - * - * @param string $name - * @param array $data - * - * @return UnionType - */ + * @var Type[] + */ + private $properties = []; + + /** + * @var Type[] + */ + private $additionalProperties; + + /** + * Create a new UnionType from an array of data + * + * @param string $name + * @param array $data + * + * @return UnionType + */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); @@ -35,6 +45,14 @@ public static function createFromArray($name, array $data = []) $type->setPossibleTypes(explode('|', $type->getType())); $type->setType('union'); + if (isset($data['properties'])) { + $type->setProperties($data['properties']); + } + + if (isset($data['additionalProperties'])) { + $type->setAdditionalProperties($data['additionalProperties']); + } + return $type; } @@ -64,23 +82,128 @@ public function setPossibleTypes(array $possibleTypes) return $this; } + /** + * Set the value of Properties + * + * @param array $properties + * @return self + */ + public function setProperties(array $properties) + { + foreach ($properties as $name => $property) { + if ($property instanceof Type === false) { + $property = ApiDefinition::determineType($name, $property); + } + $this->properties[] = $property; + } + + return $this; + } + + /** + * Get the value of Properties + * + * @return Type[] + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Returns a property by name + * + * @param string $name + * @return null|Type + */ + public function getPropertyByName($name) + { + foreach ($this->properties as $property) { + if ($property->getName() === $name) { + return $property; + } + } + + return null; + } + + public function getAdditionalProperties() + { + return $this->additionalProperties; + } + + /** + * Set the value of Additional Properties + * + * @param mixed $additionalProperties + * + * @return self + */ + public function setAdditionalProperties($additionalProperties) + { + $this->additionalProperties = $additionalProperties; + + return $this; + } + public function validate($value) { - $errors = []; + $selfProperties = []; + $selfValue = []; + $unionValue = $value; + foreach ($this->getProperties() as $property) { + $propName = $property->getName(); + $selfProperties[] = $propName; + if (isset($value[$propName])) { + $selfValue[$propName] = $value[$propName]; + unset($unionValue[$propName]); + } + } + $typeErrors = []; foreach ($this->getPossibleTypes() as $type) { if (!$type->discriminate($value)) { continue; } - $type->validate($value); - if ($type->isValid()) { + $errors = []; + + foreach ($this->getProperties() as $property) { + if ($property->getRequired() && !array_key_exists($property->getName(), $selfValue)) { + $errors[] = TypeValidationError::missingRequiredProperty($property->getName()); + } + } + + if (is_array($selfValue)) { + foreach ($selfValue as $name => $propertyValue) { + $property = $this->getPropertyByName($name); + if (!$property) { + if ($this->additionalProperties === false) { + $errors[] = TypeValidationError::unexpectedProperty($name); + } + + continue; + } + + $property->validate($propertyValue); + if (!$property->isValid()) { + $errors = array_merge($errors, $property->getErrors()); + } + } + } + + $type->validate($unionValue); + if (!$type->isValid()) { + $errors = array_merge($errors, $type->getErrors()); + } + + if (!$errors) { return; } - $errors[$type->getName()] = $type->getErrors(); + $typeErrors[$type->getName()] = $errors; } - $this->errors[] = TypeValidationError::unionTypeValidationFailed($this->getName(), $errors); + $this->errors[] = TypeValidationError::unionTypeValidationFailed($this->getName(), $typeErrors); } } diff --git a/tests/NamedParameters/ParameterTypesTest.php b/tests/NamedParameters/ParameterTypesTest.php index a42ecec6..373bf3fe 100644 --- a/tests/NamedParameters/ParameterTypesTest.php +++ b/tests/NamedParameters/ParameterTypesTest.php @@ -54,6 +54,17 @@ enum: ['laughing', 'my', 'butt', 'off'] type: boolean date: type: date + date-only: + type: date-only + datetime-only: + type: datetime-only + time-only: + type: time-only + datetime: + type: datetime + datetime_format: + type: datetime + format: Y-m-d H:i:s RAML; $apiDefinition = $this->parser->parseFromString($validateRaml, ''); $resource = $apiDefinition->getResourceByUri('/'); @@ -73,6 +84,12 @@ public function shouldValidateWithoutExceptions() // Valid date $namedParameter = $this->validateBody->getParameter('date'); $namedParameter->validate('Sun, 06 Nov 1994 08:49:37 GMT'); + + $this->validateBody->getParameter('date-only')->validate('2018-08-05'); + $this->validateBody->getParameter('datetime-only')->validate('2018-08-05T13:24:55'); + $this->validateBody->getParameter('time-only')->validate('12:30:00'); + $this->validateBody->getParameter('datetime')->validate('2018-08-05T13:24:55+12:00'); + $this->validateBody->getParameter('datetime_format')->validate('2018-08-05 13:24:55'); } /** @@ -231,6 +248,50 @@ public function shouldValidateDate() $namedParameter->validate('Sun, 06 Nov 1994 08:49:37 BUNS'); } + /** @test */ + public function shouldValidateDateOnly() + { + $this->expectException('\Raml\Exception\ValidationException', 'Expected date-only'); + $namedParameter = $this->validateBody->getParameter('date-only'); + $namedParameter->validate('2018-08-05T13:24:55'); + } + + /** @test */ + public function shouldValidateDateTimeOnly() + { + $this->expectException('\Raml\Exception\ValidationException', 'Expected datetime-only'); + $namedParameter = $this->validateBody->getParameter('datetime-only'); + $namedParameter->validate('2018-08-05T13:24:55+12:00'); + } + + /** @test */ + public function shouldValidateTimeOnly() + { + $this->expectException('\Raml\Exception\ValidationException', 'Expected time-only'); + $namedParameter = $this->validateBody->getParameter('time-only'); + $namedParameter->validate('2018-08-05T13:24:55'); + } + + /** @test */ + public function shouldValidateDateTime() + { + $this->expectException('\Raml\Exception\ValidationException', 'Expected datetime'); + $namedParameter = $this->validateBody->getParameter('datetime'); + $namedParameter->validate('2018-08-05 13:24:55'); + } + + /** @test */ + public function shouldValidateDateTimeFormat() + { + $this->expectException('\Raml\Exception\ValidationException', 'Expected datetime with format Y-m-d H:i:s'); + $namedParameter = $this->validateBody->getParameter('datetime_format'); + $namedParameter->validate('2018-08-05T13:24:55'); + } + + // --- + + /** @test */ + /** * @test */ diff --git a/tests/ParseTest.php b/tests/ParseTest.php index a57e45d6..86ccdd84 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -305,10 +305,13 @@ public function shouldReturnAnExampleForType() $schema = $body->getExample(); - $this->assertEquals([ - 'title' => 'Wish You Were Here', - 'artist' => 'Pink Floyd' - ], json_decode($schema, true)); + $this->assertEquals( + [ + 'title' => 'Wish You Were Here', + 'artist' => 'Pink Floyd', + ], + json_decode($schema, true) + ); } /** @@ -552,8 +555,14 @@ public function shouldApplyTraitVariables() $this->assertArrayHasKey('access_token', $queryParameters); $this->assertArrayHasKey('numPages', $queryParameters); - $this->assertEquals('Return books that have their title matching the given value for path /books', $queryParameters['title']->getDescription()); - $this->assertEquals('If no values match the value given for title, use digest_all_fields instead', $queryParameters['digest_all_fields']->getDescription()); + $this->assertEquals( + 'Return books that have their title matching the given value for path /books', + $queryParameters['title']->getDescription() + ); + $this->assertEquals( + 'If no values match the value given for title, use digest_all_fields instead', + $queryParameters['digest_all_fields']->getDescription() + ); $this->assertEquals('A valid access_token is required', $queryParameters['access_token']->getDescription()); $this->assertEquals('The number of pages to return', $queryParameters['numPages']->getDescription()); @@ -561,7 +570,10 @@ public function shouldApplyTraitVariables() $method = $resource->getMethod('get'); $queryParameters = $method->getQueryParameters(); - $this->assertEquals('Return DVD that have their title matching the given value for path /dvds', $queryParameters['title']->getDescription()); + $this->assertEquals( + 'Return DVD that have their title matching the given value for path /dvds', + $queryParameters['title']->getDescription() + ); } /** @@ -637,7 +649,10 @@ public function shouldParseHateoasExample() public function shouldParseMethodDescription() { $methodDescriptionRaml = $this->parser->parse(__DIR__ . '/fixture/methodDescription.raml'); - $this->assertEquals('Get a list of available songs', $methodDescriptionRaml->getResourceByUri('/songs')->getMethod('get')->getDescription()); + $this->assertEquals( + 'Get a list of available songs', + $methodDescriptionRaml->getResourceByUri('/songs')->getMethod('get')->getDescription() + ); } /** @@ -646,7 +661,10 @@ public function shouldParseMethodDescription() public function shouldParseResourceDescription() { $resourceDescriptionRaml = $this->parser->parse(__DIR__ . '/fixture/resourceDescription.raml'); - $this->assertEquals('Collection of available songs resource', $resourceDescriptionRaml->getResourceByUri('/songs')->getDescription()); + $this->assertEquals( + 'Collection of available songs resource', + $resourceDescriptionRaml->getResourceByUri('/songs')->getDescription() + ); } /** @@ -668,8 +686,15 @@ public function shouldParseMethodHeaders() $headersRaml = $this->parser->parse(__DIR__ . '/fixture/headers.raml'); $resource = $headersRaml->getResourceByUri('/jobs'); - $this->assertEquals(['Zencoder-Api-Key' => NamedParameter::createFromArray('Zencoder-Api-Key', ['displayName' => 'ZEncoder API Key']) - ], $resource->getMethod('post')->getHeaders()); + $this->assertEquals( + [ + 'Zencoder-Api-Key' => NamedParameter::createFromArray( + 'Zencoder-Api-Key', + ['displayName' => 'ZEncoder API Key'] + ), + ], + $resource->getMethod('post')->getHeaders() + ); } /** @@ -680,14 +705,22 @@ public function shouldParseResponseHeaders() $headersRaml = $this->parser->parse(__DIR__ . '/fixture/headers.raml'); $resource = $headersRaml->getResourceByUri('/jobs'); - $this->assertEquals(['X-waiting-period' => NamedParameter::createFromArray('X-waiting-period', [ - 'description' => 'The number of seconds to wait before you can attempt to make a request again.' . "\n", - 'type' => 'integer', - 'required' => 'yes', - 'minimum' => 1, - 'maximum' => 3600, - 'example' => 34 - ])], $resource->getMethod('post')->getResponse(503)->getHeaders()); + $this->assertEquals( + [ + 'X-waiting-period' => NamedParameter::createFromArray( + 'X-waiting-period', + [ + 'description' => 'The number of seconds to wait before you can attempt to make a request again.' . "\n", + 'type' => 'integer', + 'required' => 'yes', + 'minimum' => 1, + 'maximum' => 3600, + 'example' => 34, + ] + ), + ], + $resource->getMethod('post')->getResponse(503)->getHeaders() + ); } /** @@ -696,7 +729,10 @@ public function shouldParseResponseHeaders() public function shouldReplaceReservedParameter() { $def = $this->parser->parse(__DIR__ . '/fixture/reservedParameter.raml'); - $this->assertEquals('Get list of songs at /songs', $def->getResourceByUri('/songs')->getMethod('get')->getDescription()); + $this->assertEquals( + 'Get list of songs at /songs', + $def->getResourceByUri('/songs')->getMethod('get')->getDescription() + ); } /** @@ -705,8 +741,14 @@ public function shouldReplaceReservedParameter() public function shouldParameterTransformerWorks() { $def = $this->parser->parse(__DIR__ . '/fixture/parameterTransformer.raml'); - $this->assertEquals('songs /songs song /song', $def->getResourceByUri('/songs')->getMethod('post')->getDescription()); - $this->assertEquals('song /song songs /songs', $def->getResourceByUri('/song')->getMethod('get')->getDescription()); + $this->assertEquals( + 'songs /songs song /song', + $def->getResourceByUri('/songs')->getMethod('post')->getDescription() + ); + $this->assertEquals( + 'song /song songs /songs', + $def->getResourceByUri('/song')->getMethod('get')->getDescription() + ); } /** @@ -750,9 +792,22 @@ public function shouldThrowExceptionOnBadQueryParameter() $this->parser->parse(__DIR__ . '/fixture/invalid/queryParameters.raml'); } catch (InvalidQueryParameterTypeException $e) { $this->assertEquals('invalid', $e->getType()); - $this->assertEquals([ - 'string', 'number', 'integer', 'date', 'boolean', 'file', 'array' - ], $e->getValidTypes()); + $this->assertEquals( + [ + 'string', + 'number', + 'integer', + 'date', + 'boolean', + 'file', + 'datetime-only', + 'date-only', + 'time-only', + 'datetime', + 'array', + ], + $e->getValidTypes() + ); throw $e; } @@ -768,12 +823,17 @@ public function shouldReplaceParameterByJsonString() $body = $def->getResourceByUri('/songs')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); $example = $body->getExample(); - $this->assertEquals([ - 'items' => [[ - 'id' => 2, - 'title' => 'test' - ]] - ], json_decode($example, true)); + $this->assertEquals( + [ + 'items' => [ + [ + 'id' => 2, + 'title' => 'test', + ], + ], + ], + json_decode($example, true) + ); } // --- diff --git a/tests/TypeTest.php b/tests/TypeTest.php index 60a0196c..e0c3577c 100644 --- a/tests/TypeTest.php +++ b/tests/TypeTest.php @@ -184,7 +184,7 @@ public function shouldCorrectlyValidateWrongDateOnlyTypes() /** * @test */ - public function shouldCorrectlyValidateArrayIntegerRightTypes() + public function shouldCorrectlyValidateArrayScalarTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); @@ -193,119 +193,46 @@ public function shouldCorrectlyValidateArrayIntegerRightTypes() $body = $response->getBodyByType('application/json'); $type = $body->getType(); + // integer $type->validate(json_decode('{"intArray": [1,2,3]}', true)); $this->assertTrue($type->isValid()); - } - - /** - * @test - */ - public function shouldCorrectlyValidateArrayIntegerWrongTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); - $type->validate(json_decode('{"intArray": [1,2,"str"]}', true)); $this->assertFalse($type->isValid()); - } - - /** - * @test - */ - public function shouldCorrectlyValidateArrayStringRightTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); + // string $type->validate(json_decode('{"strArray": ["one", "two"]}', true)); $this->assertTrue($type->isValid()); - } - - /** - * @test - */ - public function shouldCorrectlyValidateArrayStringWrongTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); - $type->validate(json_decode('{"strArray": [1, "two"]}', true)); $this->assertFalse($type->isValid()); - } - - /** - * @test - */ - public function shouldCorrectlyValidateArrayBooleanRightTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); + // boolean $type->validate(json_decode('{"boolArray": [true, false]}', true)); $this->assertTrue($type->isValid()); - } - - /** - * @test - */ - public function shouldCorrectlyValidateArrayBooleanWrongTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); - $type->validate(json_decode('{"boolArray": [true, 0]}', true)); $this->assertFalse($type->isValid()); - } - - /** - * @test - */ - public function shouldCorrectlyValidateArrayNumberRightTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); + // number $type->validate(json_decode('{"numberArray": [12, 13.5, 0]}', true)); $this->assertTrue($type->isValid()); - } + $type->validate(json_decode('{"numberArray": ["12", 0]}', true)); + $this->assertFalse($type->isValid()); - /** - * @test - */ - public function shouldCorrectlyValidateArrayNumberWrongTypes() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(206); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); + // datetime-only + $type->validate(json_decode('{"datetime-onlyArray": ["2018-08-08T12:54:34", "2018-08-08T00:00:00"]}', true)); + $this->assertTrue($type->isValid()); + $type->validate(json_decode('{"datetime-onlyArray": ["2018-08-08T12:54:34", "not_date"]}', true)); + $this->assertFalse($type->isValid()); - $type->validate(json_decode('{"numberArray": ["12", 0]}', true)); + // date-only + $type->validate(json_decode('{"date-onlyArray": ["2018-08-08", "2018-01-01"]}', true)); + $this->assertTrue($type->isValid()); + $type->validate(json_decode('{"date-onlyArray": ["2018-08-08", "not_date"]}', true)); + $this->assertFalse($type->isValid()); + + // time-only + $type->validate(json_decode('{"time-onlyArray": ["12:54:34", "00:00:00"]}', true)); + $this->assertTrue($type->isValid()); + $type->validate(json_decode('{"time-onlyArray": ["2018-08-08T12:54:34", "not_date"]}', true)); $this->assertFalse($type->isValid()); } } diff --git a/tests/Types/UnionTypeTest.php b/tests/Types/UnionTypeTest.php index 9cc53fdd..fcbd33c4 100644 --- a/tests/Types/UnionTypeTest.php +++ b/tests/Types/UnionTypeTest.php @@ -50,8 +50,8 @@ public function shouldCorrectlyValidateIncorrectType() $this->assertFalse($type->isValid()); $this->assertEquals( 'name (Value did not pass validation against any type: ' - . 'integer (integer (Expected int, got (boolean) "")), ' - . 'string (string (Expected string, got (boolean) "")))', + . 'integer (integer (Expected int, got (boolean) "")), ' + . 'string (string (Expected string, got (boolean) "")))', (string) $type->getErrors()[0] ); } @@ -98,4 +98,48 @@ public function shouldCorrectlyValidateNullableStringTypes() $type->validate(1); $this->assertFalse($type->isValid()); } + + /** + * @test + */ + public function shouldCorrectlyValidateUnionInheritedTypes1() + { + $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); + $resource = $simpleRaml->getResourceByUri('/songs'); + $method = $resource->getMethod('get'); + $response = $method->getResponse(209); + $body = $response->getBodyByType('application/json'); + $type = $body->getType(); + + $type->validate(json_decode('{"title": "abc", "prop1": 1}', true)); + $this->assertTrue($type->isValid()); + + $type->validate(json_decode('{"id": "abc", "name": 2, "prop1": 1}', true)); + $this->assertTrue($type->isValid()); + + $type->validate(json_decode('{"title": "abc", "prop1": "qwe"}', true)); + $this->assertFalse($type->isValid()); + } + + /** + * @test + */ + public function shouldCorrectlyValidateUnionInheritedTypes2() + { + $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); + $resource = $simpleRaml->getResourceByUri('/songs'); + $method = $resource->getMethod('get'); + $response = $method->getResponse(210); + $body = $response->getBodyByType('application/json'); + $type = $body->getType(); + + $type->validate(json_decode('{"title": "abc", "prop1": 1, "prop3": 2}', true)); + $this->assertTrue($type->isValid()); + + $type->validate(json_decode('{"id": "abc", "name": 2, "prop1": 1, "prop3": 2}', true)); + $this->assertTrue($type->isValid()); + + $type->validate(json_decode('{"title": "abc", "prop1": 1, "prop3": "qwe"}', true)); + $this->assertFalse($type->isValid()); + } } diff --git a/tests/fixture/simple_types.raml b/tests/fixture/simple_types.raml index b564f253..ecc63ca0 100644 --- a/tests/fixture/simple_types.raml +++ b/tests/fixture/simple_types.raml @@ -49,6 +49,15 @@ mediaType: application/json type: array items: type: Sample + 209: + body: + application/json: + type: UnionInherited + + 210: + body: + application/json: + type: UnionInherited2 types: Song: @@ -92,4 +101,21 @@ types: type: boolean[] numberArray?: type: number[] + datetime-onlyArray?: + type: datetime-only[] + date-onlyArray?: + type: date-only[] + time-onlyArray?: + type: time-only[] + UnionInherited: + type: Song | Sample + properties: + prop1: integer + prop2: + type: integer + required: false + UnionInherited2: + type: UnionInherited + properties: + prop3: integer