From 8f10f7b7e5d9b0bd195c0565e3420bc931eb256b Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 6 Aug 2018 14:13:47 +0300 Subject: [PATCH 01/11] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BF=D0=B0=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D1=84=D0=BE=D1=80=D0=BC=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B8=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D0=B1=20=D0=BE=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Types/TypeValidationError.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Types/TypeValidationError.php b/src/Types/TypeValidationError.php index 4bdfea60..d00d1c3f 100644 --- a/src/Types/TypeValidationError.php +++ b/src/Types/TypeValidationError.php @@ -89,11 +89,13 @@ public static function unexpectedValue($property, $possibleValues, $actualValue) */ public static function unexpectedValueType($property, $constraint, $actualValue) { + $value = is_array($actualValue) ? json_encode($actualValue) : (string)$actualValue; + return new self($property, sprintf( 'Expected %s, got (%s) "%s"', $constraint, gettype($actualValue), - $actualValue + $value )); } From befe7a48905dec803826eda2ab9545639144ec83 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Thu, 13 Sep 2018 17:45:06 +0300 Subject: [PATCH 02/11] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D1=83?= =?UTF-8?q?=20=D0=B4=D0=B0=D1=82=20=D0=B2=20queryParameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/NamedParameter.php | 169 ++++++++++++------ src/Types/DatetimeOnlyType.php | 1 + .../{DateTimeType.php => DatetimeType.php} | 19 +- src/Types/TimeOnlyType.php | 7 +- test/NamedParameters/ParameterTypesTest.php | 57 ++++++ test/ParseTest.php | 3 - 6 files changed, 184 insertions(+), 72 deletions(-) rename src/Types/{DateTimeType.php => DatetimeType.php} (75%) diff --git a/src/NamedParameter.php b/src/NamedParameter.php index 9a4de633..8c37b0cb 100644 --- a/src/NamedParameter.php +++ b/src/NamedParameter.php @@ -1,8 +1,9 @@ setFormat($data['format']); + } + return $namedParameter; } @@ -446,7 +467,7 @@ public function setMinLength($minLength) throw new \Exception('minLength can only be set on type "string"'); } - $this->minLength = (int) $minLength; + $this->minLength = (int)$minLength; } // -- @@ -474,7 +495,7 @@ public function setMaxLength($maxLength) throw new \Exception('maxLength can only be set on type "string"'); } - $this->maxLength = (int) $maxLength; + $this->maxLength = (int)$maxLength; } // -- @@ -502,7 +523,7 @@ public function setMinimum($minimum) throw new \Exception('minimum can only be set on type "integer" or "number'); } - $this->minimum = (int) $minimum; + $this->minimum = (int)$minimum; } // -- @@ -530,7 +551,7 @@ public function setMaximum($maximum) throw new \Exception('maximum can only be set on type "integer" or "number'); } - $this->maximum = (int) $maximum; + $this->maximum = (int)$maximum; } // -- @@ -584,7 +605,7 @@ public function canBeRepeated() */ public function setRepeat($repeated) { - $this->repeated = (bool) $repeated; + $this->repeated = (bool)$repeated; } // -- @@ -606,7 +627,7 @@ public function isRequired() */ public function setRequired($required) { - $this->required = (bool) $required; + $this->required = (bool)$required; } // -- @@ -621,6 +642,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 * @@ -663,7 +700,7 @@ public function setDefault($default) $this->default = $default; } - + /** * Validate a paramater via RAML specifications * @@ -681,21 +718,21 @@ public function validate($param) if ($this->isRequired()) { throw new ValidationException($this->getKey().' is required', static::VAL_ISREQUIRED); } - + return; - + } - + switch ($this->getType()) { case static::TYPE_BOOLEAN: - + // Must be boolean if (!is_bool($param)) { throw new ValidationException($this->getKey().' is not boolean', static::VAL_NOTBOOLEAN); } - + break; - + case static::TYPE_DATE: // Must be a valid date @@ -703,14 +740,14 @@ public function validate($param) throw new ValidationException($this->getKey().' is not a valid date', static::VAL_NOTDATE); } - // DATES are also strings + // DATES are also strings case static::TYPE_STRING: - + // Must be a string if (!is_string($param)) { throw new ValidationException($this->getKey().' is not a string', static::VAL_NOTSTRING); } - + /** * Check the length of a string. * @@ -723,7 +760,7 @@ public function validate($param) static::VAL_TOOSHORT ); } - + /** * Check the length of a string. * @@ -736,13 +773,12 @@ public function validate($param) static::VAL_TOOLONG ); } - + break; - case static::TYPE_INTEGER: - + /** * Integers must be of type integer. * @@ -751,10 +787,10 @@ public function validate($param) if (!is_int($param)) { throw new ValidationException($this->getKey().' is not an integer', static::VAL_NOTINT); } - // No break - + // No break + case static::TYPE_NUMBER: - + /** * Number types must be numeric. ;) * @@ -763,7 +799,7 @@ public function validate($param) if (!is_numeric($param)) { throw new ValidationException($this->getKey().' is not a number', static::VAL_NOTNUMBER); } - + /** * Check the value constraints if specified. * @@ -776,7 +812,7 @@ public function validate($param) static::VAL_NUMLESSTHAN ); } - + /** * Check the value constraints if specified. * @@ -789,17 +825,32 @@ public function validate($param) static::VAL_GREATERTHAN ); } - + break; - + 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; - + } - + + /** * Validate against the RAML specified pattern if it exists. * @@ -814,7 +865,7 @@ public function validate($param) static::VAL_PATTERNFAIL ); } - + /** * If we have an enum (array), then it must be a specified value. * @@ -844,7 +895,7 @@ public function getMatchPattern() if ($this->validationPattern) { $pattern = $this->validationPattern; } elseif ($enum = $this->getEnum()) { - $pattern = '^(' . implode('|', array_map('preg_quote', $enum)) . ')$'; + $pattern = '^('.implode('|', array_map('preg_quote', $enum)).')$'; } else { switch ($this->getType()) { case self::TYPE_NUMBER: @@ -874,7 +925,7 @@ public function getMatchPattern() break; case self::TYPE_STRING: if ($this->getMinLength() || $this->getMaxLength()) { - $pattern = '((?!\/).){' . $this->getMinLength() . ',' . $this->getMaxLength() . '}'; + $pattern = '((?!\/).){'.$this->getMinLength().','.$this->getMaxLength().'}'; } else { $pattern = '([^/]+)'; } diff --git a/src/Types/DatetimeOnlyType.php b/src/Types/DatetimeOnlyType.php index 26210bbf..768da7b5 100644 --- a/src/Types/DatetimeOnlyType.php +++ b/src/Types/DatetimeOnlyType.php @@ -13,6 +13,7 @@ class DatetimeOnlyType extends Type { const FORMAT = "Y-m-d\TH:i:s"; + /** * Create a new DateTimeOnlyType from an array of data * diff --git a/src/Types/DateTimeType.php b/src/Types/DatetimeType.php similarity index 75% rename from src/Types/DateTimeType.php rename to src/Types/DatetimeType.php index 45c9895e..ba3b173a 100644 --- a/src/Types/DateTimeType.php +++ b/src/Types/DatetimeType.php @@ -8,8 +8,9 @@ /** * DateTimeType type class */ -class DateTimeType extends Type +class DatetimeType extends Type { + const DEFAULT_FORMAT = DATE_RFC3339; /** * DateTime format to use * @@ -20,10 +21,10 @@ 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 + * @return DatetimeType */ public static function createFromArray($name, array $data = []) { @@ -68,11 +69,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 d671895c..47da0e9a 100644 --- a/src/Types/TimeOnlyType.php +++ b/src/Types/TimeOnlyType.php @@ -10,6 +10,7 @@ */ class TimeOnlyType extends Type { + const FORMAT = 'H:i:s'; /** * Create a new TimeOnlyType from an array of data * @@ -29,10 +30,10 @@ public function validate($value) { parent::validate($value); - $d = DateTime::createFromFormat('HH:II:SS', $value); + $d = DateTime::createFromFormat(self::FORMAT, $value); - if ($d && $d->format('HH:II:SS') !== $value) { - $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'HH:II:SS', $value); + if (!$d || $d->format(self::FORMAT) !== $value) { + $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'time-only', $value); } } } diff --git a/test/NamedParameters/ParameterTypesTest.php b/test/NamedParameters/ParameterTypesTest.php index a9f7f104..7940bf3c 100644 --- a/test/NamedParameters/ParameterTypesTest.php +++ b/test/NamedParameters/ParameterTypesTest.php @@ -49,6 +49,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('/'); @@ -68,6 +79,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'); } /** @test */ @@ -178,6 +195,46 @@ public function shouldValidateDate() $namedParameter->validate('Sun, 06 Nov 1994 08:49:37 BUNS'); } + /** @test */ + public function shouldValidateDateOnly() + { + $this->setExpectedException('\Raml\Exception\ValidationException', 'Expected date-only'); + $namedParameter = $this->validateBody->getParameter('date-only'); + $namedParameter->validate('2018-08-05T13:24:55'); + } + + /** @test */ + public function shouldValidateDateTimeOnly() + { + $this->setExpectedException('\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->setExpectedException('\Raml\Exception\ValidationException', 'Expected time-only'); + $namedParameter = $this->validateBody->getParameter('time-only'); + $namedParameter->validate('2018-08-05T13:24:55'); + } + + /** @test */ + public function shouldValidateDateTime() + { + $this->setExpectedException('\Raml\Exception\ValidationException', 'Expected datetime'); + $namedParameter = $this->validateBody->getParameter('datetime'); + $namedParameter->validate('2018-08-05 13:24:55'); + } + + /** @test */ + public function shouldValidateDateTimeFormat() + { + $this->setExpectedException('\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 */ diff --git a/test/ParseTest.php b/test/ParseTest.php index f30dcc41..90b643c8 100644 --- a/test/ParseTest.php +++ b/test/ParseTest.php @@ -642,9 +642,6 @@ public function shouldThrowExceptionOnBadQueryParameter() $this->parser->parse(__DIR__ . '/fixture/invalid/queryParameters.raml'); } catch (\Raml\Exception\InvalidQueryParameterTypeException $e) { $this->assertEquals('invalid', $e->getType()); - $this->assertEquals([ - 'string', 'number', 'integer', 'date', 'boolean', 'file' - ], $e->getValidTypes()); throw $e; } } From 9ab22e79acdab685a1e0a0de63348b19044e3612 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 17 Sep 2018 14:13:11 +0300 Subject: [PATCH 03/11] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7=20=D0=BE=D1=80=D0=B8=D0=B3?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 16 +- .php_cs | 107 ++++ .travis.yml | 68 +- README.md | 83 ++- composer.json | 85 ++- phpunit.xml | 4 +- src/ApiDefinition.php | 198 +++--- src/BaseUriParameter.php | 4 +- src/Body.php | 13 +- .../BadParameter/InvalidProtocolException.php | 6 +- src/Exception/EmptyBodyException.php | 11 + src/FileLoader/JsonSchemaFileLoader.php | 2 +- src/Method.php | 103 +-- src/NamedParameter.php | 253 ++++---- src/ParseConfiguration.php | 37 +- src/Parser.php | 306 +++++---- src/QueryParameter.php | 15 - src/Resource.php | 122 +++- src/Response.php | 22 +- src/RouteFormatter/BasicRoute.php | 2 +- src/RouteFormatter/NoRouteFormatter.php | 2 +- src/RouteFormatter/SymfonyRouteFormatter.php | 6 +- .../Definition/JsonSchemaDefinition.php | 10 +- src/Schema/Definition/XmlSchemaDefinition.php | 6 +- src/Schema/Parser/JsonSchemaParser.php | 21 +- src/Schema/Parser/XmlSchemaParser.php | 9 +- src/Schema/SchemaParserAbstract.php | 21 +- src/Schema/SchemaParserInterface.php | 7 +- src/SecurityScheme.php | 12 +- .../SecuritySchemeDescribedBy.php | 20 +- .../DefaultSecuritySettings.php | 24 +- .../OAuth1SecuritySettings.php | 17 +- .../OAuth2SecuritySettings.php | 5 +- .../SecuritySettingsInterface.php | 3 +- .../DefaultSecuritySettingsParser.php | 1 - .../OAuth2SecuritySettingsParser.php | 1 - .../SecuritySettingsParserInterface.php | 1 + src/Trait.php | 15 - src/TraitCollection.php | 181 ++++++ src/TraitDefinition.php | 257 ++++++++ src/Type.php | 60 +- src/TypeCollection.php | 108 ++-- src/TypeInterface.php | 2 +- src/Types/ArrayType.php | 26 +- src/Types/BooleanType.php | 1 + src/Types/DateOnlyType.php | 6 +- src/Types/DatetimeOnlyType.php | 6 +- .../{DateTimeType.php => DatetimeType.php} | 12 +- src/Types/FileType.php | 10 +- src/Types/IntegerType.php | 3 +- src/Types/JsonType.php | 19 +- src/Types/LazyProxyType.php | 158 +++-- src/Types/NilType.php | 1 - src/Types/NullType.php | 5 +- src/Types/NumberType.php | 41 +- src/Types/ObjectType.php | 85 +-- src/Types/StringType.php | 33 +- src/Types/TimeOnlyType.php | 9 +- src/Types/TypeValidationError.php | 110 ++-- src/Types/UnionType.php | 9 +- src/Types/XmlType.php | 20 +- src/Utility/StringTransformer.php | 47 +- src/Utility/TraitParserHelper.php | 117 ++++ src/Validator/ContentConverter.php | 8 +- src/Validator/RequestValidator.php | 7 +- src/Validator/ResponseValidator.php | 6 +- src/Validator/ValidatorException.php | 1 + src/Validator/ValidatorRequestException.php | 1 + src/Validator/ValidatorResponseException.php | 1 + src/Validator/ValidatorSchemaException.php | 1 + src/Validator/ValidatorSchemaHelper.php | 31 +- src/ValidatorInterface.php | 4 +- src/WebFormBody.php | 29 +- test/ApiDefinitionTest.php | 391 ------------ test/MethodTest.php | 91 --- test/Types/ArrayTest.php | 72 --- test/fixture/hateoas/traits.raml | 9 - test/fixture/raml-1.0/types.raml | 25 - tests/ApiDefinitionTest.php | 487 ++++++++++++++ {test => tests}/JsonSchemaTest.php | 37 +- tests/MethodTest.php | 160 +++++ .../NamedParameters/BaseUriParameterTest.php | 39 +- .../NamedParameters/MultipleTypesTest.php | 28 +- .../NamedParameters/ParameterTypesTest.php | 213 ++++--- .../NamedParameters/UriParameterTest.php | 70 ++- .../NamedParameters/WebFormBodyTest.php | 62 +- .../fixture/multipleTypes.raml | 0 {test => tests}/ParseTest.php | 595 ++++++++++++------ {test => tests}/TypeTest.php | 128 ++-- tests/Types/ArrayTest.php | 98 +++ {test => tests}/Types/UnionTypeTest.php | 51 +- .../Validator/RequestValidatorTest.php | 68 +- .../Validator/ResponseValidatorTest.php | 74 ++- .../Validator/ValidatorSchemaHelperTest.php | 98 +-- {test => tests}/XmlSchemaTest.php | 56 +- {test => tests}/fixture/child.raml | 0 {test => tests}/fixture/child.yaml | 0 {test => tests}/fixture/hateoas/example.raml | 0 .../fixture/hateoas/posts.schema.json | 0 .../fixture/hateoas/resourceTypes.raml | 0 tests/fixture/hateoas/traits.raml | 9 + {test => tests}/fixture/headers.raml | 0 {test => tests}/fixture/includeRaml.raml | 0 {test => tests}/fixture/includeSchema.raml | 0 {test => tests}/fixture/includeTreeRaml.raml | 0 .../fixture/includeUnknownSchema.raml | 0 {test => tests}/fixture/includeUrlPrefix.raml | 0 {test => tests}/fixture/includeYaml.raml | 0 {test => tests}/fixture/includedTraits.raml | 0 .../fixture/infoq/eventExample.json | 0 .../fixture/infoq/eventListExample.json | 0 .../fixture/infoq/eventListSchema.json | 0 .../fixture/infoq/eventSchema.json | 0 {test => tests}/fixture/infoq/eventlog.raml | 56 +- {test => tests}/fixture/invalid/bad.json | 0 {test => tests}/fixture/invalid/bad.raml | 12 +- {test => tests}/fixture/invalid/badJson.raml | 0 {test => tests}/fixture/invalid/empty.raml | 0 {test => tests}/fixture/invalid/noTitle.raml | 0 .../fixture/invalid/queryParameters.raml | 0 .../fixture/jsonStringExample.raml | 0 .../fixture/methodDescription.raml | 0 .../fixture/parameterTransformer.raml | 0 .../fixture/parentAndChildSchema.raml | 0 .../protocols/invalidProtocolsSpecified.raml | 0 .../protocols/noProtocolSpecified.raml | 1 - .../fixture/protocols/protocolsSpecified.raml | 0 {test => tests}/fixture/queryParameters.raml | 0 .../fixture/raml-1.0/complexTypes.raml | 0 .../fixture/raml-1.0/example/test.json | 0 tests/fixture/raml-1.0/inheritanceTypes.raml | 69 ++ tests/fixture/raml-1.0/library/library.raml | 31 + .../fixture/raml-1.0/library/sub_library.raml | 11 + {test => tests}/fixture/raml-1.0/traits.raml | 0 .../fixture/raml-1.0/traitsAndTypes.raml | 0 tests/fixture/raml-1.0/types.raml | 57 ++ .../fixture/replaceSchemaByRootSchema.raml | 0 .../fixture/reservedParameter.raml | 0 .../fixture/resourceDescription.raml | 0 {test => tests}/fixture/resourcePathName.raml | 0 .../fixture/resourceSecuritySchemes.raml | 8 +- {test => tests}/fixture/rootSchemas.raml | 0 {test => tests}/fixture/schema/child.json | 0 {test => tests}/fixture/schema/parent.json | 0 {test => tests}/fixture/schemaInRoot.raml | 0 {test => tests}/fixture/schemaInTypes.raml | 0 .../fixture/securedByCustomProps.raml | 6 +- {test => tests}/fixture/securitySchemes.raml | 8 +- {test => tests}/fixture/simple.raml | 10 +- {test => tests}/fixture/simple_types.raml | 74 ++- {test => tests}/fixture/song.json | 0 {test => tests}/fixture/songs.json | 0 {test => tests}/fixture/traits/category.raml | 0 {test => tests}/fixture/traitsAndTypes.raml | 18 +- {test => tests}/fixture/tree/child.raml | 0 {test => tests}/fixture/tree/schema.json | 0 .../fixture/treeTraversal/bad.raml | 0 {test => tests}/fixture/types.raml | 0 .../fixture/validator/queryParameters.raml | 0 .../validator/requestAcceptHeader.raml | 0 .../fixture/validator/requestBody.raml | 5 + .../fixture/validator/responseBody.raml | 0 .../fixture/validator/responseEmptyBody.raml | 0 .../fixture/validator/responseHeaders.raml | 0 .../fixture/validator/webFormBody.raml | 0 {test => tests}/fixture/xmlSchema.raml | 0 166 files changed, 4020 insertions(+), 2260 deletions(-) create mode 100644 .php_cs create mode 100644 src/Exception/EmptyBodyException.php delete mode 100644 src/QueryParameter.php delete mode 100644 src/Trait.php create mode 100644 src/TraitCollection.php create mode 100644 src/TraitDefinition.php rename src/Types/{DateTimeType.php => DatetimeType.php} (87%) create mode 100644 src/Utility/TraitParserHelper.php delete mode 100644 test/ApiDefinitionTest.php delete mode 100644 test/MethodTest.php delete mode 100644 test/Types/ArrayTest.php delete mode 100644 test/fixture/hateoas/traits.raml delete mode 100644 test/fixture/raml-1.0/types.raml create mode 100644 tests/ApiDefinitionTest.php rename {test => tests}/JsonSchemaTest.php (85%) create mode 100644 tests/MethodTest.php rename {test => tests}/NamedParameters/BaseUriParameterTest.php (87%) rename {test => tests}/NamedParameters/MultipleTypesTest.php (59%) rename {test => tests}/NamedParameters/ParameterTypesTest.php (59%) rename {test => tests}/NamedParameters/UriParameterTest.php (51%) rename {test => tests}/NamedParameters/WebFormBodyTest.php (73%) rename {test => tests}/NamedParameters/fixture/multipleTypes.raml (100%) rename {test => tests}/ParseTest.php (67%) rename {test => tests}/TypeTest.php (85%) create mode 100644 tests/Types/ArrayTest.php rename {test => tests}/Types/UnionTypeTest.php (75%) rename {test => tests}/Validator/RequestValidatorTest.php (68%) rename {test => tests}/Validator/ResponseValidatorTest.php (70%) rename {test => tests}/Validator/ValidatorSchemaHelperTest.php (72%) rename {test => tests}/XmlSchemaTest.php (80%) rename {test => tests}/fixture/child.raml (100%) rename {test => tests}/fixture/child.yaml (100%) rename {test => tests}/fixture/hateoas/example.raml (100%) rename {test => tests}/fixture/hateoas/posts.schema.json (100%) rename {test => tests}/fixture/hateoas/resourceTypes.raml (100%) create mode 100644 tests/fixture/hateoas/traits.raml rename {test => tests}/fixture/headers.raml (100%) rename {test => tests}/fixture/includeRaml.raml (100%) rename {test => tests}/fixture/includeSchema.raml (100%) rename {test => tests}/fixture/includeTreeRaml.raml (100%) rename {test => tests}/fixture/includeUnknownSchema.raml (100%) rename {test => tests}/fixture/includeUrlPrefix.raml (100%) rename {test => tests}/fixture/includeYaml.raml (100%) rename {test => tests}/fixture/includedTraits.raml (100%) rename {test => tests}/fixture/infoq/eventExample.json (100%) rename {test => tests}/fixture/infoq/eventListExample.json (100%) rename {test => tests}/fixture/infoq/eventListSchema.json (100%) rename {test => tests}/fixture/infoq/eventSchema.json (100%) rename {test => tests}/fixture/infoq/eventlog.raml (69%) rename {test => tests}/fixture/invalid/bad.json (100%) rename {test => tests}/fixture/invalid/bad.raml (70%) rename {test => tests}/fixture/invalid/badJson.raml (100%) rename {test => tests}/fixture/invalid/empty.raml (100%) rename {test => tests}/fixture/invalid/noTitle.raml (100%) rename {test => tests}/fixture/invalid/queryParameters.raml (100%) rename {test => tests}/fixture/jsonStringExample.raml (100%) rename {test => tests}/fixture/methodDescription.raml (100%) rename {test => tests}/fixture/parameterTransformer.raml (100%) rename {test => tests}/fixture/parentAndChildSchema.raml (100%) rename {test => tests}/fixture/protocols/invalidProtocolsSpecified.raml (100%) rename {test => tests}/fixture/protocols/noProtocolSpecified.raml (65%) rename {test => tests}/fixture/protocols/protocolsSpecified.raml (100%) rename {test => tests}/fixture/queryParameters.raml (100%) rename {test => tests}/fixture/raml-1.0/complexTypes.raml (100%) rename {test => tests}/fixture/raml-1.0/example/test.json (100%) create mode 100644 tests/fixture/raml-1.0/inheritanceTypes.raml create mode 100644 tests/fixture/raml-1.0/library/library.raml create mode 100644 tests/fixture/raml-1.0/library/sub_library.raml rename {test => tests}/fixture/raml-1.0/traits.raml (100%) rename {test => tests}/fixture/raml-1.0/traitsAndTypes.raml (100%) create mode 100644 tests/fixture/raml-1.0/types.raml rename {test => tests}/fixture/replaceSchemaByRootSchema.raml (100%) rename {test => tests}/fixture/reservedParameter.raml (100%) rename {test => tests}/fixture/resourceDescription.raml (100%) rename {test => tests}/fixture/resourcePathName.raml (100%) rename {test => tests}/fixture/resourceSecuritySchemes.raml (96%) rename {test => tests}/fixture/rootSchemas.raml (100%) rename {test => tests}/fixture/schema/child.json (100%) rename {test => tests}/fixture/schema/parent.json (100%) rename {test => tests}/fixture/schemaInRoot.raml (100%) rename {test => tests}/fixture/schemaInTypes.raml (100%) rename {test => tests}/fixture/securedByCustomProps.raml (98%) rename {test => tests}/fixture/securitySchemes.raml (96%) rename {test => tests}/fixture/simple.raml (93%) rename {test => tests}/fixture/simple_types.raml (54%) rename {test => tests}/fixture/song.json (100%) rename {test => tests}/fixture/songs.json (100%) rename {test => tests}/fixture/traits/category.raml (100%) rename {test => tests}/fixture/traitsAndTypes.raml (84%) rename {test => tests}/fixture/tree/child.raml (100%) rename {test => tests}/fixture/tree/schema.json (100%) rename {test => tests}/fixture/treeTraversal/bad.raml (100%) rename {test => tests}/fixture/types.raml (100%) rename {test => tests}/fixture/validator/queryParameters.raml (100%) rename {test => tests}/fixture/validator/requestAcceptHeader.raml (100%) rename {test => tests}/fixture/validator/requestBody.raml (94%) rename {test => tests}/fixture/validator/responseBody.raml (100%) rename {test => tests}/fixture/validator/responseEmptyBody.raml (100%) rename {test => tests}/fixture/validator/responseHeaders.raml (100%) rename {test => tests}/fixture/validator/webFormBody.raml (100%) rename {test => tests}/fixture/xmlSchema.raml (100%) diff --git a/.gitignore b/.gitignore index dacf05b2..f13bd84b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,6 @@ -# IDE and OS files -.idea -.DS_Store - -# Test and composer files -/vendor -/build -/composer.lock -/composer.phar - +bin +build +vendor +.php_cs.cache +composer.lock +composer.phar diff --git a/.php_cs b/.php_cs new file mode 100644 index 00000000..e564dc03 --- /dev/null +++ b/.php_cs @@ -0,0 +1,107 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/tests'); + +return PhpCsFixer\Config::create() + ->setRules( + [ + '@PSR2' => true, + '@PHP56Migration' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_return' => true, + 'blank_line_before_statement' => true, + 'cast_spaces' => ['space' => 'single'], + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'dir_constant' => true, + 'ereg_to_preg' => true, + 'final_internal_class' => false, + 'function_to_constant' => true, + 'function_typehint_space' => true, + 'include' => true, + 'is_null' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'long'], + 'lowercase_cast' => true, + 'magic_constant_casing' => true, + 'mb_str_functions' => false, + 'method_chaining_indentation' => true, + 'method_separation' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], + 'modernize_types_casting' => true, + 'native_function_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiline_whitespace_before_semicolons' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => false, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => false, + 'object_operator_without_whitespace' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_types' => false, + 'phpdoc_var_without_name' => true, + 'increment_style' => ['style' => 'post'], + 'return_type_declaration' => true, + 'self_accessor' => true, + 'semicolon_after_instruction' => true, + 'short_scalar_cast' => true, + 'silenced_deprecation_error' => false, + 'single_blank_line_before_namespace' => true, + 'single_line_comment_style' => false, + 'single_quote' => true, + 'space_after_semicolon' => true, + 'standardize_not_equals' => true, + 'static_lambda' => false, + 'strict_comparison' => false, + 'strict_param' => false, + 'ternary_operator_spaces' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'void_return' => false, + 'whitespace_after_comma_in_array' => true, + ] + ) + ->setRiskyAllowed(true) + ->setUsingCache(true) + ->setIndent(" ") + ->setLineEnding("\n") + ->setFinder($finder); diff --git a/.travis.yml b/.travis.yml index a9113db6..5e006b97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,20 +2,74 @@ language: php sudo: false +cache: + directories: + - $HOME/.composer/cache + php: - - 5.5 - 5.6 - 7.0 - 7.1 - 7.2 + - nightly -before_script: - - composer install --dev --no-interaction +env: + global: + - LATEST_PHP_VERSION="7.2" + matrix: + - DEPENDENCIES="beta" + - DEPENDENCIES="low" + - DEPENDENCIES="stable" -script: +matrix: + fast_finish: true + allow_failures: + - php: nightly + - env: DEPENDENCIES="beta" + - env: DEPENDENCIES="low" + +before_install: + - echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - if [ -n "$GH_TOKEN" ]; then + composer config github-oauth.github.com ${GH_TOKEN}; + fi; - mkdir -p build/logs - - ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml + +install: + - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then + composer remove friendsofphp/php-cs-fixer phpstan/phpstan --dev; + fi + - if [ "$DEPENDENCIES" = "beta" ]; then + composer config minimum-stability beta; + composer update -n --prefer-dist; + elif [ "$DEPENDENCIES" = "low" ]; then + composer update -n --prefer-dist --prefer-lowest; + else + composer update -n --prefer-dist; + fi; + +script: + - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then + echo "File validation is skipped for older PHP versions"; + else + composer validate-files; + fi; + - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then + echo "Static analysis is skipped for older PHP versions"; + else + composer run-static-analysis; + fi; + - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then + echo "Code style check is skipped for older PHP versions"; + else + composer check-code-style; + fi; + - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then + echo "Security check is skipped for older PHP versions"; + else + composer check-security; + fi; + - composer run-tests-with-clover after_script: - - ./vendor/bin/coveralls -v - - ./vendor/bin/phpcs --report=summary --standard=PSR1,PSR2 src + - bin/php-coveralls -v diff --git a/README.md b/README.md index 2b6ead89..a2e8c381 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,34 @@ -# PHP RAML Parser +[![Build Status](https://api.travis-ci.org/raml-org/raml-php-parser.svg?branch=master)](https://www.travis-ci.org/raml-org/raml-php-parser) +[![Coverage Status](https://coveralls.io/repos/github/raml-org/raml-php-parser/badge.svg?branch=master)](https://coveralls.io/github/raml-org/raml-php-parser?branch=master) +[![Latest Stable Version](https://poser.pugx.org/raml-org/raml-php-parser/version)](https://packagist.org/packages/raml-org/raml-php-parser) +[![Total Downloads](https://poser.pugx.org/raml-org/raml-php-parser/downloads)](https://packagist.org/packages/raml-org/raml-php-parser) -## **!!Attention!!** this is a work-in-progress of the RAML 1.0 specification, for RAML 0.8 see the [master branch](https://github.com/alecsammon/php-raml-parser/tree/master) +See the RAML spec here: https://github.com/raml-org/raml-spec + +## RAML 0.8 Support +For RAML 0.8 support follow version 2. + +## RAML 1.0 Support +For RAML 1.0 support follow version 3 or above. RAML 1.0 support is still work in progress. -### What is done and should work +_What is done and should work:_ - Part of RAML 1.0 [type expressions](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#type-expressions) - Enums - Union type expression (the "or" `|` operator) - Array of types - `discriminator` and `discriminatorValue` facets + - Traits inheritance -### Still TODO: -- [Libraries](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#libraries) -- [User defined facets](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#user-defined-facets) -- Full implementation of [type expressions](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#type-expressions) +_To be implemented:_ + - [Libraries](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#libraries) + - [User defined facets](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#user-defined-facets) + - Full implementation of [type expressions](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#type-expressions) - The shorthand array and the union type have been implemented - Bi-dimensional array and the array-union combination have **NOT** been implemented yet. -- [Multiple inheritance](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#multiple-inheritance) -- [Annotations](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#annotations) -- [Overlays and Extensions](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#overlays-and-extensions) -- [Improved Security Schemes](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#security-schemes) - -## Original documentation - -Parses a RAML file into a PHP object. - -[![Build Status](https://travis-ci.org/eLama/php-raml-parser.svg?branch=master)](https://travis-ci.org/eLama/php-raml-parser) -[![Coverage Status](https://coveralls.io/repos/github/eLama/php-raml-parser/badge.svg?branch=master)](https://coveralls.io/github/eLama/php-raml-parser?branch=master) - -See the RAML spec here: https://github.com/raml-org/raml-spec + - [Multiple inheritance](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#multiple-inheritance) + - [Annotations](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#annotations) + - [Overlays and Extensions](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#overlays-and-extensions) + - [Improved Security Schemes](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#security-schemes) ### Get started Requires: @@ -35,7 +36,7 @@ Requires: - composer (see [https://getcomposer.org](https://getcomposer.org)) ```bash -composer require elama/php-raml-parser +composer require raml-org/raml-php-parser ``` ```php @@ -47,14 +48,14 @@ $title = $apiDef->getTitle(); ### Parsing schemas The library can convert schemas into an validation object. There is a default list, or they can be configured manually. -Each schema parser needs to conform to `\Raml\Schema\SchemaParserInterface` and will return a instance of -`\Raml\Schema\SchemaDefinitionInterface`. +Each schema parser needs to conform to `Raml\Schema\SchemaParserInterface` and will return a instance of +`Raml\Schema\SchemaDefinitionInterface`. -Additional parsers and schema definitions can be created and passed into the `\Raml\Parser` constructor +Additional parsers and schema definitions can be created and passed into the `Raml\Parser` constructor ### Exporting routes It is also possible to export the entire RAML file to an array of the full endpoints. For example, considering -a [basic RAML](https://github.com/alecsammon/php-raml-parser/blob/master/test/fixture/simple.raml), this can be +a [basic RAML](https://github.com/raml-org/raml-php-parser/blob/master/test/fixture/simple.raml), this can be returned using: @@ -68,39 +69,25 @@ $routes = $api->getResourcesAsUri(); To return: ```php [ - GET /songs => ... - POST /songs => ... - GET /songs/{songId} => ... - DELETE /songs/{songId} => ... + GET /songs => ... + POST /songs => ... + GET /songs/{songId} => ... + DELETE /songs/{songId} => ... ] $routes = $api->getResourcesAsUri(new Raml\RouteFormatter\NoRouteFormatter()); ``` #### Route Formatters -There are two Route Formatters included in this package: +There are two Route Formatters included in the package: - `NoRouteFormatter` which does nothing and simply echoes the result - `SymfonyRouteFormatter` which adds the routes to a Symfony `RouteCollection` ### Contributing ```bash -./vendor/bin/phpunit -./vendor/bin/phpunit --coverage-text -./vendor/bin/phpcs --standard=PSR1,PSR2 src +composer validate-files +composer run-static-analysis +composer check-code-style +composer run-tests ``` - -### TODO -- Documentation/Markdown parser -- Date Representations? -- Parse RAML at provided URL - -### Supported (I Believe) -- Includes - - .yml/.yaml - - .raml/.rml - - .json (parsed using json-schema) -- Display Name -- Traits -- Resource Types - diff --git a/composer.json b/composer.json index cbf945ea..e16dcb57 100644 --- a/composer.json +++ b/composer.json @@ -1,40 +1,95 @@ { - "name": "elama/php-raml-parser", + "name": "raml-org/raml-php-parser", "type": "library", "description": "A RAML parser built in php", - "homepage": "https://github.com/alecsammon/php-raml-parser", + "homepage": "https://github.com/raml-org/raml-php-parser", "license": "MIT", "authors": [ + { + "name": "Alec Sammon", + "email": "alec.sammon@googlemail.com", + "role": "Original Author" + }, { "name": "eLama Team", - "email": "dev@elama.ru" + "email": "dev@elama.ru", + "role": "Main Contributor" }, { - "name": "Alec Sammon", - "email": "alec.sammon@googlemail.com" + "name": "Martin Georgiev", + "email": "martin.georgiev@gmail.com", + "role": "Maintainer" } ], + + "replace": { + "alecsammon/php-raml-parser": "*" + }, "autoload": { "psr-4": { "Raml\\": "src/" } }, - - "require-dev": { - "satooshi/php-coveralls": "~0.6", - "phpunit/phpunit": "4.*", - "squizlabs/php_codesniffer": "@dev" + "autoload-dev": { + "psr-4": { + "Raml\\Tests\\": "tests/" + } }, "require": { - "php": ">=5.5.9", - "justinrainbow/json-schema": "5.1.*", + "php": "^5.6|^7.0", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "justinrainbow/json-schema": "^5.0", "symfony/yaml": "^3.0|^4.0", "symfony/routing": "^3.0|^4.0", - "oodle/inflect": "~0.2", + "oodle/inflect": "^0.2", "psr/http-message": "^1.0", - "willdurand/negotiation": "^2.2" - } + "willdurand/negotiation": "^2.2.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "jakub-onderka/php-parallel-lint": "^1.0", + "php-coveralls/php-coveralls": "^2.1", + "phpstan/phpstan": "^0.10.2", + "phpunit/phpunit": "^5.7", + "sensiolabs/security-checker": "^4.1" + }, + + "scripts": { + "check-code-style": [ + "bin/php-cs-fixer fix --config='./.php_cs' --show-progress=none --dry-run --no-interaction --diff -v" + ], + "check-security": [ + "bin/security-checker security:check" + ], + "fix-code-style": [ + "bin/php-cs-fixer fix --config='./.php_cs' --show-progress=none --no-interaction --diff -v" + ], + "run-static-analysis": [ + "bin/phpstan analyse --level=1 src/" + ], + "run-static-analysis-including-tests": [ + "@run-static-analysis", + "bin/phpstan analyse --level=1 tests/" + ], + "run-tests": [ + "bin/phpunit" + ], + "run-tests-with-clover": [ + "bin/phpunit --coverage-clover build/logs/clover.xml" + ], + "validate-files": [ + "bin/parallel-lint --exclude vendor --exclude bin ." + ] + }, + + "config": { + "bin-dir": "bin", + "sort-packages": true + }, + "prefer-stable": true } diff --git a/phpunit.xml b/phpunit.xml index 4a11b3e3..d858c800 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,9 +1,9 @@ - ./test + ./tests diff --git a/src/ApiDefinition.php b/src/ApiDefinition.php index 1bc23084..9c93217d 100644 --- a/src/ApiDefinition.php +++ b/src/ApiDefinition.php @@ -1,20 +1,16 @@ types = TypeCollection::getInstance(); // since the TypeCollection is a singleton, we need to clear it for every parse $this->types->clear(); + + $this->traits = TraitCollection::getInstance(); + // since the TraitCollection is a singleton, we need to clear it for every parse + $this->traits->clear(); } /** * Create a new API Definition from an array * * @param string $title - * @param array $data + * @param array $data * [ * title: string * version: ?string @@ -185,7 +190,6 @@ public function __construct($title) * schemas: ?array * securitySchemes: ?array * documentation: ?array - * /* * ] * * @return ApiDefinition @@ -195,7 +199,7 @@ public static function createFromArray($title, array $data = []) $apiDefinition = new static($title); if (isset($data['version'])) { - $apiDefinition->version = $data['version']; + $apiDefinition->version = $data['version']; } if (isset($data['baseUrl'])) { @@ -237,13 +241,13 @@ public static function createFromArray($title, array $data = []) $apiDefinition->setDefaultMediaType($data['defaultMediaType']); } - if (isset($data['schemas']) && isset($data['types'])) { + if (isset($data['schemas'], $data['types'])) { throw new MutuallyExclusiveElementsException(); } if (isset($data['schemas'])) { foreach ($data['schemas'] as $name => $schema) { - $apiDefinition->addType(ApiDefinition::determineType($name, $schema)); + $apiDefinition->addType(self::determineType($name, $schema)); } } @@ -271,7 +275,13 @@ public static function createFromArray($title, array $data = []) if (isset($data['types'])) { foreach ($data['types'] as $name => $definition) { - $apiDefinition->addType(ApiDefinition::determineType($name, $definition)); + $apiDefinition->addType(self::determineType($name, $definition)); + } + } + + if (isset($data['traits'])) { + foreach ($data['traits'] as $name => $definition) { + $apiDefinition->addTrait(self::determineTrait($name, $definition)); } } @@ -285,7 +295,7 @@ public static function createFromArray($title, array $data = []) if (strpos($resourceName, '/') === 0) { $apiDefinition->addResource( Resource::createFromArray( - $apiDefinition->getUrlPrefix().$resourceName, + $apiDefinition->getUrlPrefix() . $resourceName, $resource, $apiDefinition ) @@ -296,16 +306,13 @@ public static function createFromArray($title, array $data = []) return $apiDefinition; } - // --- - /** * Get a resource by a uri * * @param string $uri + * @return Resource * - * @throws InvalidKeyException - * - * @return \Raml\Resource + * @throws ResourceNotFoundException */ public function getResourceByUri($uri) { @@ -314,11 +321,12 @@ public function getResourceByUri($uri) $resources = $this->getResourcesAsArray($this->resources); foreach ($resources as $resource) { + assert($resource instanceof Resource); if ($resource->matchesUri($uri)) { return $resource; } } - // we never returned so throw exception + throw new ResourceNotFoundException($uri); } @@ -326,10 +334,9 @@ public function getResourceByUri($uri) * Get a resource by a path * * @param string $path + * @return Resource * - * @throws InvalidKeyException - * - * @return \Raml\Resource + * @throws ResourceNotFoundException */ public function getResourceByPath($path) { @@ -346,7 +353,6 @@ public function getResourceByPath($path) throw new ResourceNotFoundException($path); } - /** * Returns all the resources as a URI, essentially documenting the entire API Definition. * This will output, by default, an array that looks like: @@ -355,7 +361,6 @@ public function getResourceByPath($path) * GET /songs/{songId} => [/songs/{songId}, GET, Raml\Method] * * @param RouteFormatterInterface $formatter - * * @return RouteFormatterInterface */ public function getResourcesAsUri(RouteFormatterInterface $formatter = null) @@ -370,9 +375,8 @@ public function getResourcesAsUri(RouteFormatterInterface $formatter = null) } /** - * @param \Raml\Resource[] $resources - * - * @return \Raml\Resource[] + * @param Resource[] $resources + * @return Resource[] */ private function getResourcesAsArray($resources) { @@ -452,10 +456,8 @@ public function getUrlPrefix() return $this->urlPrefix; } - // -- - /** - * @return boolean + * @return bool */ public function supportsHttp() { @@ -463,7 +465,7 @@ public function supportsHttp() } /** - * @return boolean + * @return bool */ public function supportsHttps() { @@ -484,7 +486,7 @@ public function getProtocols() */ private function addProtocol($protocol) { - if (!in_array($protocol, [self::PROTOCOL_HTTP, self::PROTOCOL_HTTPS])) { + if (!in_array($protocol, [self::PROTOCOL_HTTP, self::PROTOCOL_HTTPS], true)) { throw new InvalidProtocolException(sprintf('"%s" is not a valid protocol', $protocol)); } @@ -493,8 +495,6 @@ private function addProtocol($protocol) } } - // --- - /** * Get the default media type * @@ -508,20 +508,20 @@ public function getDefaultMediaTypes() /** * Set a default media type * - * @param $defaultMediaType + * @param string $defaultMediaType */ public function setDefaultMediaType($defaultMediaType) { - // @todo - Should this validate? - $this->defaultMediaType = $defaultMediaType; + if (!in_array($defaultMediaType, $this->defaultMediaTypes, true)) { + return; + } + $this->defaultMediaTypes[] = $defaultMediaType; } - // -- - /** * Get the schemas defined in the root of the API * - * @return array[] + * @return TypeCollection */ public function getSchemaCollections() { @@ -546,9 +546,9 @@ public function addSchemaCollection($collectionName, $schemas) /** * Add a new schema to a collection * - * @param string $collectionName - * @param string $schemaName - * @param string|SchemaDefinitionInterface $schema + * @param string $collectionName + * @param string $schemaName + * @param string|SchemaDefinitionInterface $schema * * @throws InvalidSchemaDefinitionException */ @@ -561,8 +561,6 @@ private function addSchema($collectionName, $schemaName, $schema) $this->schemaCollections[$collectionName][$schemaName] = $schema; } - // -- - /** * Get the documentation of the API * @@ -584,19 +582,9 @@ public function addDocumentation($title, $documentation) $this->documentationList[$title] = $documentation; } - /** - * Determines the right Type and returns an instance - * - * @param string $name Name of type. - * @param array $definition Definition of type. - * @param \Raml\TypeCollection|null $typeCollection Type collection object. - * - * @return \Raml\TypeInterface Returns a (best) matched type object. - **/ - public static function determineType($name, $definition) + public static function getStraightForwardTypes() { - // check if we can find a more appropriate Type subclass - $straightForwardTypes = [ + return [ 'time-only', 'datetime', 'datetime-only', @@ -609,8 +597,22 @@ public static function determineType($name, $definition) 'nil', 'file', 'array', - 'object' + 'object', ]; + } + + /** + * Determines the right Type and returns an instance + * + * @param string $name Name of type. + * @param array $definition Definition of type. + * @return Type Returns a (best) matched type object. + * + * @throws \InvalidArgumentException + */ + public static function determineType($name, $definition) + { + // check if we can find a more appropriate Type subclass if (is_string($definition)) { $definition = ['type' => $definition]; } elseif (is_array($definition)) { @@ -618,20 +620,22 @@ public static function determineType($name, $definition) $definition['type'] = isset($definition['properties']) ? 'object' : 'string'; } } else { - throw new \Exception('Invalid datatype for $definition parameter.'); + throw new \InvalidArgumentException('Invalid datatype for $definition parameter.'); } $type = $definition['type'] ?: 'null'; - if (!in_array($type, ['','any'])) { - if (in_array($type, $straightForwardTypes)) { + if (!in_array($type, ['', 'any'], true)) { + if (in_array($type, static::getStraightForwardTypes(), true)) { $className = sprintf( 'Raml\Types\%sType', StringTransformer::convertString($type, StringTransformer::UPPER_CAMEL_CASE) ); - return forward_static_call_array([$className,'createFromArray'], [$name, $definition]); + assert(class_exists($className)); + + return forward_static_call_array([$className, 'createFromArray'], [$name, $definition]); } - // if $type contains a '|' we can savely assume it's a combination of types (union) + // if $type contains a '|' we can safely assume it's a combination of types (union) if (strpos($type, '|') !== false) { return UnionType::createFromArray($name, $definition); } @@ -649,10 +653,20 @@ public static function determineType($name, $definition) return Type::createFromArray($name, $definition); } + /** + * @param string $name + * @param array $definition + * @return TraitDefinition + */ + public static function determineTrait($name, $definition) + { + return TraitDefinition::createFromArray($name, $definition); + } + /** * Add data type * - * @param \Raml\TypeInterface $type + * @param TypeInterface $type */ public function addType(TypeInterface $type) { @@ -662,19 +676,37 @@ public function addType(TypeInterface $type) /** * Get data types * - * @return \Raml\TypeCollection + * @return TypeCollection */ public function getTypes() { return $this->types; } - // -- + /** + * Add trait + * + * @param TraitDefinition $trait + */ + public function addTrait(TraitDefinition $trait) + { + $this->traits->add($trait); + } + + /** + * Get data types + * + * @return TraitCollection + */ + public function getTraits() + { + return $this->traits; + } /** * Get the resources tree * - * @return \Raml\Resource[] + * @return Resource[] */ public function getResources() { @@ -684,14 +716,25 @@ public function getResources() /** * Add an additional resource * - * @param \Raml\Resource $resource + * @param Resource $resource */ public function addResource(Resource $resource) { $this->resources[$resource->getUri()] = $resource; } - // -- + /** + * Removes Resource from ApiDefinition + * + * @param Resource $resource + */ + public function removeResource(Resource $resource) + { + if (!isset($this->resources[$resource->getUri()])) { + return; + } + unset($this->resources[$resource->getUri()]); + } /** * Get a security scheme by it's name @@ -735,17 +778,14 @@ public function addSecuredBy(SecurityScheme $securityScheme) $this->securedBy[$securityScheme->getKey()] = $securityScheme; } - // --- - /** * Recursive function that generates a flat array of the entire API Definition * * GET /songs => [api.example.org, /songs, GET, [https], Raml\Method] * GET /songs/{songId} => [api.example.org, /songs/{songId}, GET, [https], Raml\Method] * - * @param \Raml\Resource[] $resources - * - * @return array[BasicRoute] + * @param Resource[] $resources + * @return BasicRoute[] */ private function getMethodsAsArray(array $resources) { diff --git a/src/BaseUriParameter.php b/src/BaseUriParameter.php index 4b88d7cc..727b1fc1 100644 --- a/src/BaseUriParameter.php +++ b/src/BaseUriParameter.php @@ -1,4 +1,5 @@ mediaType = $mediaType; @@ -90,10 +90,7 @@ public function __construct($mediaType) * example: ?string * examples: ?array * ] - * - * @throws \Exception - * - * @return Body + * @return self */ public static function createFromArray($mediaType, array $data = []) { @@ -215,7 +212,7 @@ public function getType() /** * Set the type * - * @param \Raml\TypeInterface $type + * @param TypeInterface $type * * @throws \Exception Throws exception when type does not parse */ diff --git a/src/Exception/BadParameter/InvalidProtocolException.php b/src/Exception/BadParameter/InvalidProtocolException.php index b581cfe8..5dbe7e46 100644 --- a/src/Exception/BadParameter/InvalidProtocolException.php +++ b/src/Exception/BadParameter/InvalidProtocolException.php @@ -2,12 +2,14 @@ namespace Raml\Exception\BadParameter; -use RuntimeException; use Raml\Exception\ExceptionInterface; use Raml\Exception\BadParameterExceptionInterface; -class InvalidProtocolException extends RuntimeException implements ExceptionInterface, BadParameterExceptionInterface +class InvalidProtocolException extends \RuntimeException implements ExceptionInterface, BadParameterExceptionInterface { + /** + * @param string $protocol + */ public function __construct($protocol) { parent::__construct(sprintf('"%s" is not a valid protocol.', $protocol)); diff --git a/src/Exception/EmptyBodyException.php b/src/Exception/EmptyBodyException.php new file mode 100644 index 00000000..ac1e72d4 --- /dev/null +++ b/src/Exception/EmptyBodyException.php @@ -0,0 +1,11 @@ +retrieve('file://' . $filePath)); diff --git a/src/Method.php b/src/Method.php index 11e4d7c3..e9e16ab1 100644 --- a/src/Method.php +++ b/src/Method.php @@ -1,6 +1,9 @@ $bodyData) { if (is_array($bodyData)) { - if (in_array($key, WebFormBody::$validMediaTypes)) { + if (in_array($key, WebFormBody::$validMediaTypes, true)) { $body = WebFormBody::createFromArray($key, $bodyData); } else { $body = Body::createFromArray($key, $bodyData); @@ -201,11 +200,17 @@ public static function createFromArray($method, array $data = [], ApiDefinition $method->addSecurityScheme($apiDefinition->getSecurityScheme($securedBy)); } } else { - $method->addSecurityScheme(SecurityScheme::createFromArray('null', array(), $apiDefinition)); + $method->addSecurityScheme(SecurityScheme::createFromArray('null', [], $apiDefinition)); } } } + if (isset($data['is'])) { + foreach ((array) $data['is'] as $traitName) { + $method->addTrait(TraitCollection::getInstance()->getTraitByName($traitName)); + } + } + return $method; } @@ -290,21 +295,21 @@ public function addHeader(NamedParameter $header) /** * Does the API support HTTP (non SSL) requests? * - * @return boolean + * @return bool */ public function supportsHttp() { - return in_array(ApiDefinition::PROTOCOL_HTTP, $this->protocols); + return in_array(ApiDefinition::PROTOCOL_HTTP, $this->protocols, true); } /** * Does the API support HTTPS (SSL enabled) requests? * - * @return boolean + * @return bool */ public function supportsHttps() { - return in_array(ApiDefinition::PROTOCOL_HTTPS, $this->protocols); + return in_array(ApiDefinition::PROTOCOL_HTTPS, $this->protocols, true); } /** @@ -321,7 +326,7 @@ public function getProtocols() * Get example by type (application/json, text/plain, ...) * * @param string $type - * @return array + * @return string[] */ public function getExampleByType($type) { @@ -337,11 +342,11 @@ public function getExampleByType($type) */ public function addProtocol($protocol) { - if (!in_array($protocol, [ApiDefinition::PROTOCOL_HTTP, ApiDefinition::PROTOCOL_HTTPS])) { + if (!in_array($protocol, [ApiDefinition::PROTOCOL_HTTP, ApiDefinition::PROTOCOL_HTTPS], true)) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid protocol', $protocol)); } - if (in_array($protocol, $this->protocols)) { + if (in_array($protocol, $this->protocols, true) === false) { $this->protocols[] = $protocol; } } @@ -374,13 +379,17 @@ public function addQueryParameter(NamedParameter $queryParameter) * Get the body by type * * @param string $type - * - * @throws \Exception - * * @return BodyInterface + * + * @throws EmptyBodyException + * @throws \InvalidArgumentException */ public function getBodyByType($type) { + if (empty($this->getBodies())) { + throw new EmptyBodyException(); + } + if (isset($this->bodyList[$type])) { return $this->bodyList[$type]; } @@ -392,13 +401,13 @@ public function getBodyByType($type) } } - throw new \Exception('No body of type "' . $type . '"'); + throw new \InvalidArgumentException(sprintf('No body of type "%s"', $type)); } /** * Get an array of all bodies * - * @return array The array of bodies + * @return BodyInterface[] */ public function getBodies() { @@ -430,7 +439,7 @@ public function getResponses() /** * Get a response by the response code (200, 404,....) * - * @param integer $responseCode + * @param int $responseCode * * @return Response */ @@ -462,7 +471,6 @@ public function getSecuritySchemes() } /** - * @param SecurityScheme $securityScheme * @param bool $merge Set to true to merge the security scheme data with the method, or false to not merge it. */ public function addSecurityScheme(SecurityScheme $securityScheme, $merge = true) @@ -475,31 +483,50 @@ public function addSecurityScheme(SecurityScheme $securityScheme, $merge = true) foreach ($describedBy->getHeaders() as $header) { $this->addHeader($header); } - + foreach ($describedBy->getResponses() as $response) { $this->addResponse($response); } - + foreach ($describedBy->getQueryParameters() as $queryParameter) { $this->addQueryParameter($queryParameter); } - + foreach ($this->getBodies() as $bodyType => $body) { - if (in_array($bodyType, array_keys($describedBy->getBodies())) && - in_array($bodyType, WebFormBody::$validMediaTypes) + if (in_array($bodyType, array_keys($describedBy->getBodies(), true)) && + in_array($bodyType, WebFormBody::$validMediaTypes, true) ) { - $params = $describedBy->getBodyByType($bodyType)->getParameters(); - + $body = $describedBy->getBodyByType($bodyType); + assert($body instanceof WebFormBody); + $params = $body->getParameters(); + foreach ($params as $parameterName => $namedParameter) { $body->addParameter($namedParameter); } } - + $this->addBody($body); } - } - } } + + /** + * @return TraitDefinition[] + */ + public function getTraits() + { + return $this->traits; + } + + /** + * @param TraitDefinition $trait + * @return self + */ + public function addTrait($trait) + { + $this->traits[] = $trait; + + return $this; + } } diff --git a/src/NamedParameter.php b/src/NamedParameter.php index 9a4de633..1dcf2762 100644 --- a/src/NamedParameter.php +++ b/src/NamedParameter.php @@ -1,47 +1,43 @@ validTypes)) { + if (!in_array($type, $this->validTypes, true)) { throw new InvalidQueryParameterTypeException($type, $this->validTypes); } @@ -426,7 +421,7 @@ public function setValidationPattern($validationPattern) /** * Get the minLength * - * @return integer + * @return int */ public function getMinLength() { @@ -436,7 +431,7 @@ public function getMinLength() /** * Set minLength * - * @param integer $minLength + * @param int $minLength * * @throws \Exception */ @@ -454,7 +449,7 @@ public function setMinLength($minLength) /** * Get the maxLength * - * @return integer + * @return int */ public function getMaxLength() { @@ -464,7 +459,7 @@ public function getMaxLength() /** * Set maxLength * - * @param integer $minLength + * @param int $maxLength * * @throws \Exception */ @@ -482,7 +477,7 @@ public function setMaxLength($maxLength) /** * Get the minimum * - * @return integer + * @return int */ public function getMinimum() { @@ -492,13 +487,13 @@ public function getMinimum() /** * Set minimum * - * @param integer $minimum + * @param int $minimum * * @throws \Exception */ public function setMinimum($minimum) { - if (!in_array($this->type, [self::TYPE_INTEGER, self::TYPE_NUMBER])) { + if (!in_array($this->type, [self::TYPE_INTEGER, self::TYPE_NUMBER], true)) { throw new \Exception('minimum can only be set on type "integer" or "number'); } @@ -510,7 +505,7 @@ public function setMinimum($minimum) /** * Get the maximum * - * @return integer + * @return int */ public function getMaximum() { @@ -520,13 +515,13 @@ public function getMaximum() /** * Set maximum * - * @param integer $maximum + * @param int $maximum * * @throws \Exception */ public function setMaximum($maximum) { - if (!in_array($this->type, [self::TYPE_INTEGER, self::TYPE_NUMBER])) { + if (!in_array($this->type, [self::TYPE_INTEGER, self::TYPE_NUMBER], true)) { throw new \Exception('maximum can only be set on type "integer" or "number'); } @@ -538,11 +533,13 @@ public function setMaximum($maximum) /** * Get the example * + * @param int $position + * * @return string */ - public function getExample() + public function getExample($position = 0) { - return $this->examples[0]; + return $this->examples[$position]; } /** @@ -570,9 +567,9 @@ public function addExample($example) /** * Can the parameter be repeated * - * @return boolean + * @return bool */ - public function canBeRepeated() + public function canRepeat() { return $this->repeat; } @@ -580,11 +577,11 @@ public function canBeRepeated() /** * Set if the parameter can be repeated * - * @param boolean $repeated + * @param bool $repeat */ - public function setRepeat($repeated) + public function setRepeat($repeat) { - $this->repeated = (bool) $repeated; + $this->repeat = (bool) $repeat; } // -- @@ -592,7 +589,7 @@ public function setRepeat($repeated) /** * Is the parameter required * - * @return boolean + * @return bool */ public function isRequired() { @@ -602,7 +599,7 @@ public function isRequired() /** * Set if the parameter is required * - * @param boolean $required + * @param bool $required */ public function setRequired($required) { @@ -626,91 +623,95 @@ public function getDefault() * * @param mixed $default * - * @throws \Exception + * @throws \InvalidArgumentException */ public function setDefault($default) { switch ($this->type) { case self::TYPE_STRING: if (!is_string($default)) { - throw new \Exception('Default parameter is not a string'); + throw new \InvalidArgumentException('Default parameter is not a string'); } + break; case self::TYPE_NUMBER: if (!is_numeric($default)) { - throw new \Exception('Default parameter is not a number'); + throw new \InvalidArgumentException('Default parameter is not a number'); } + break; case self::TYPE_INTEGER: - if (!is_integer($default)) { - throw new \Exception('Default parameter is not an integer'); + if (!is_numeric($default) || (int) $default != $default) { + throw new \InvalidArgumentException('Default parameter is not an integer'); } + break; case self::TYPE_DATE: if (!$default instanceof \DateTime) { - throw new \Exception('Default parameter is not a dateTime object'); + throw new \InvalidArgumentException('Default parameter is not a dateTime object'); } + break; case self::TYPE_BOOLEAN: if (!is_bool($default)) { - throw new \Exception('Default parameter is not a boolean'); + throw new \InvalidArgumentException('Default parameter is not a boolean'); } + break; case self::TYPE_FILE: - throw new \Exception('A default value cannot be set for a file'); + throw new \InvalidArgumentException('A default value cannot be set for a file'); + break; } $this->default = $default; } - + /** - * Validate a paramater via RAML specifications + * Validate a parameter via RAML specifications * - * @param mixed $param The value of the paramater to validate - * @throws \Exception The code corresponds to the error that occured. + * @param mixed $param The value of the parameter to validate + * @throws ValidationException The code corresponds to the error that occurred. */ public function validate($param) { - /** + /* * If we don't have a value to validate, check if it's required. - * * @link http://raml.org/spec.html#required */ - if (in_array($param, array(null, ''), true)) { + if (in_array($param, [null, ''], true)) { if ($this->isRequired()) { - throw new ValidationException($this->getKey().' is required', static::VAL_ISREQUIRED); + throw new ValidationException($this->getKey() . ' is required', static::VAL_ISREQUIRED); } - + return; - } - + switch ($this->getType()) { case static::TYPE_BOOLEAN: - + // Must be boolean if (!is_bool($param)) { - throw new ValidationException($this->getKey().' is not boolean', static::VAL_NOTBOOLEAN); + throw new ValidationException($this->getKey() . ' is not boolean', static::VAL_NOTBOOLEAN); } - + break; - + case static::TYPE_DATE: // Must be a valid date if (\DateTime::createFromFormat('D, d M Y H:i:s T', $param) === false) { - throw new ValidationException($this->getKey().' is not a valid date', static::VAL_NOTDATE); + throw new ValidationException($this->getKey() . ' is not a valid date', static::VAL_NOTDATE); } - // DATES are also strings + // no break case static::TYPE_STRING: - + // Must be a string if (!is_string($param)) { - throw new ValidationException($this->getKey().' is not a string', static::VAL_NOTSTRING); + throw new ValidationException($this->getKey() . ' is not a string', static::VAL_NOTSTRING); } - + /** * Check the length of a string. * @@ -719,11 +720,11 @@ public function validate($param) $minLength = $this->getMinLength(); if (!empty($minLength) && strlen($param) < $minLength) { throw new ValidationException( - $this->getKey().' must be at least '.$minLength.' characters long', + $this->getKey() . ' must be at least ' . $minLength . ' characters long', static::VAL_TOOSHORT ); } - + /** * Check the length of a string. * @@ -732,38 +733,36 @@ public function validate($param) $maxLength = $this->getMaxLength(); if (!empty($maxLength) && strlen($param) > $maxLength) { throw new ValidationException( - $this->getKey().' must be no more than '.$maxLength.' characters long', + $this->getKey() . ' must be no more than ' . $maxLength . ' characters long', static::VAL_TOOLONG ); } - - break; - + break; case static::TYPE_INTEGER: - - /** + + /* * Integers must be of type integer. * * @link http://raml.org/spec.html#type */ if (!is_int($param)) { - throw new ValidationException($this->getKey().' is not an integer', static::VAL_NOTINT); + throw new ValidationException($this->getKey() . ' is not an integer', static::VAL_NOTINT); } - // No break - + // no break + case static::TYPE_NUMBER: - - /** + + /* * Number types must be numeric. ;) * * @link http://raml.org/spec.html#type */ if (!is_numeric($param)) { - throw new ValidationException($this->getKey().' is not a number', static::VAL_NOTNUMBER); + throw new ValidationException($this->getKey() . ' is not a number', static::VAL_NOTNUMBER); } - + /** * Check the value constraints if specified. * @@ -772,11 +771,11 @@ public function validate($param) $min = $this->getMinimum(); if (!empty($min) && $param < $min) { throw new ValidationException( - $this->getKey().' must be greater than or equal to '.$min, + $this->getKey() . ' must be greater than or equal to ' . $min, static::VAL_NUMLESSTHAN ); } - + /** * Check the value constraints if specified. * @@ -785,21 +784,19 @@ public function validate($param) $max = $this->getMaximum(); if (!empty($max) && $param > $max) { throw new ValidationException( - $this->getKey().' must be less than or equal to '.$max, + $this->getKey() . ' must be less than or equal to ' . $max, static::VAL_GREATERTHAN ); } - + break; - + case static::TYPE_FILE: - // File type cannot be reliably validated based on its type alone. - + break; - } - + /** * Validate against the RAML specified pattern if it exists. * @@ -807,15 +804,15 @@ public function validate($param) */ $validationPattern = $this->getValidationPattern(); if (!empty($validationPattern) && - preg_match('|'.$validationPattern.'|', $param) !== 1 + preg_match('|' . $validationPattern . '|', $param) !== 1 ) { throw new ValidationException( - $this->getKey().' does not match the specified pattern', + $this->getKey() . ' does not match the specified pattern', static::VAL_PATTERNFAIL ); } - - /** + + /* * If we have an enum (array), then it must be a specified value. * * NOTE: The RAML spec states that "enum" only applies to strings. However, it @@ -824,11 +821,9 @@ public function validate($param) * * @link http://raml.org/spec.html#enum */ - if (is_array($enum = $this->getEnum()) && - !in_array($param, $enum) - ) { + if (is_array($enum = $this->getEnum()) && !in_array($param, $enum, true)) { throw new ValidationException( - $this->getKey().' must be one of the following: '.implode(', ', $enum), + $this->getKey() . ' must be one of the following: ' . implode(', ', $enum), static::VAL_NOTENUMVALUE ); } @@ -850,27 +845,32 @@ public function getMatchPattern() case self::TYPE_NUMBER: // @see http://www.regular-expressions.info/floatingpoint.html $pattern = '[-+]?[0-9]*\.?[0-9]+'; + break; case self::TYPE_INTEGER: $pattern = '[-+]?[0-9]+'; + break; case self::TYPE_DATE: // @see https://snipt.net/DamienGarrido/ // http-date-regular-expression-validation-rfc-1123rfc-850asctime-f64e6aa3/ - $pattern = '^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?:[0-2][0-9]|3[01]) '. - '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} '. - '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] '. - 'GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), '. - '(?:[0-2][0-9]|3[01])-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} '. - '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) '. - '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:[ 1-2][0-9]|3[01]) '. + $pattern = '^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?:[0-2][0-9]|3[01]) ' . + '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} ' . + '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] ' . + 'GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), ' . + '(?:[0-2][0-9]|3[01])-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} ' . + '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) ' . + '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:[ 1-2][0-9]|3[01]) ' . '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] \d{4})$'; + break; case self::TYPE_BOOLEAN: $pattern = '(true|false)'; + break; case self::TYPE_FILE: $pattern = '([^/]+)'; + break; case self::TYPE_STRING: if ($this->getMinLength() || $this->getMaxLength()) { @@ -878,6 +878,7 @@ public function getMatchPattern() } else { $pattern = '([^/]+)'; } + break; default: $pattern = '([^/]+)'; diff --git a/src/ParseConfiguration.php b/src/ParseConfiguration.php index 7363d65b..fd094aa1 100644 --- a/src/ParseConfiguration.php +++ b/src/ParseConfiguration.php @@ -8,7 +8,7 @@ class ParseConfiguration * If directory tree traversal is allowed * Enabling this may be a security risk! * - * @var boolean + * @var bool */ private $allowDirectoryTraversal = false; @@ -16,17 +16,24 @@ class ParseConfiguration * Should schemas be parsed * This is most likely wanted, but does increase time * - * @var boolean + * @var bool */ private $parseSchemas = true; /** * Should security schemes be merged * - * @var boolean + * @var bool */ private $parseSecuritySchemes = true; + /** + * Enable inclusion of Remote resources, i.e. RAML files from web + * + * @var bool + */ + private $remoteFileInclusionEnabled = false; + // ---- /** @@ -48,7 +55,7 @@ public function disableDirectoryTraversal() /** * If directory tree traversal is allowed * - * @return boolean + * @return bool */ public function isDirectoryTraversalAllowed() { @@ -76,7 +83,7 @@ public function disableSchemaParsing() /** * Is schema parsing enabled * - * @return boolean + * @return bool */ public function isSchemaParsingEnabled() { @@ -104,10 +111,28 @@ public function disableSecuritySchemeParsing() /** * Is security scheme parsing enabled * - * @return boolean + * @return bool */ public function isSchemaSecuritySchemeParsingEnabled() { return $this->parseSecuritySchemes; } + + /** + * @return bool + */ + public function isRemoteFileInclusionEnabled() + { + return $this->remoteFileInclusionEnabled; + } + + public function allowRemoteFileInclusion() + { + $this->remoteFileInclusionEnabled = true; + } + + public function forbidRemoteFileInclusion() + { + $this->remoteFileInclusionEnabled = false; + } } diff --git a/src/Parser.php b/src/Parser.php index 985a2f6a..40fc465e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2,7 +2,6 @@ namespace Raml; -use Inflect\Inflect; use Raml\Exception\BadParameter\FileNotFoundException; use Raml\Exception\InvalidSchemaFormatException; use Raml\Exception\RamlParserException; @@ -16,6 +15,7 @@ use Raml\SecurityScheme\SecuritySettingsParser\OAuth1SecuritySettingsParser; use Raml\SecurityScheme\SecuritySettingsParser\OAuth2SecuritySettingsParser; use Raml\SecurityScheme\SecuritySettingsParserInterface; +use Raml\Utility\TraitParserHelper; use Symfony\Component\Yaml\Tag\TaggedValue; use Symfony\Component\Yaml\Yaml; @@ -24,7 +24,6 @@ */ class Parser { - /** * Array of cached files * No point in fetching them twice @@ -53,8 +52,8 @@ class Parser /** * List of types * - * @var string - **/ + * @var TypeInterface[] + */ private $types = []; /** @@ -71,6 +70,11 @@ class Parser */ private $fileLoaders = []; + /** + * @var ParseConfiguration + */ + private $configuration; + /** * Create a new parser object * - Optionally pass a list of parsers to use @@ -79,17 +83,17 @@ class Parser * @param SchemaParserInterface[] $schemaParsers * @param SecuritySettingsParserInterface[] $securitySettingsParsers * @param FileLoaderInterface[] $fileLoaders - * @param ParseConfiguration $config + * @param ParseConfiguration $configuration */ public function __construct( array $schemaParsers = null, array $securitySettingsParsers = null, array $fileLoaders = null, - ParseConfiguration $config = null + ParseConfiguration $configuration = null ) { // --- // parse settings - $this->configuration = $config ?: new ParseConfiguration(); + $this->configuration = $configuration ?: new ParseConfiguration(); // --- // add schema parsers @@ -165,8 +169,8 @@ public function addSchemaParser(SchemaParserInterface $schemaParser) /** * Add a new type * - * @param TypeInterface $type Type to add. - **/ + * @param TypeInterface $type + */ public function addType(TypeInterface $type) { $this->types[$type->getName()] = $type; @@ -202,11 +206,10 @@ public function addFileLoader(FileLoaderInterface $fileLoader) * Parse a RAML spec from a file * * @param string $rawFileName + * @return ApiDefinition * * @throws FileNotFoundException * @throws RamlParserException - * - * @return \Raml\ApiDefinition */ public function parse($rawFileName) { @@ -229,10 +232,7 @@ public function parse($rawFileName) * * @param string $ramlString * @param string $rootDir - * - * @throws RamlParserException - * - * @return \Raml\ApiDefinition + * @return ApiDefinition */ public function parseFromString($ramlString, $rootDir) { @@ -246,12 +246,11 @@ public function parseFromString($ramlString, $rootDir) /** * Parse RAML data * - * @param string $ramlData + * @param array $ramlData * @param string $rootDir + * @return ApiDefinition * * @throws RamlParserException - * - * @return \Raml\ApiDefinition */ private function parseRamlData($ramlData, $rootDir) { @@ -259,6 +258,8 @@ private function parseRamlData($ramlData, $rootDir) throw new RamlParserException(); } + $ramlData = $this->parseLibraries($ramlData, $rootDir); + $ramlData = $this->parseTraits($ramlData); $ramlData = $this->parseResourceTypes($ramlData); @@ -338,7 +339,7 @@ private function recurseAndParseSchemas(array $array, $rootDir) foreach ($this->schemaParsers as $schemaParser) { try { $schemaParser->setSourceUri( - 'file://'.($fileDir ? $fileDir : $rootDir.DIRECTORY_SEPARATOR) + 'file://' . ($fileDir ? $fileDir : $rootDir . DIRECTORY_SEPARATOR) ); $schema = $schemaParser->createSchemaDefinition($value['schema']); @@ -383,33 +384,29 @@ private function parseSecuritySettings($schemesArray) { $securitySchemes = []; - foreach ($schemesArray as $securitySchemeData) { + foreach ($schemesArray as $key => $securitySchemeData) { // Create the default parser. if (isset($this->securitySettingsParsers['*'])) { $parser = $this->securitySettingsParsers['*']; } else { $parser = false; } - // RAML spec defines a list of one security type per scheme - if (count($securitySchemeData) == 1) { - $key = key($securitySchemeData); - $securitySchemes[$key] = $securitySchemeData[$key]; - $securityScheme = $securitySchemes[$key]; - - // If we're using protocol specific parsers, see if we have one to use. - if ($this->configuration->isSchemaSecuritySchemeParsingEnabled()) { - if (isset($securityScheme['type']) && - isset($this->securitySettingsParsers[$securityScheme['type']]) + + $securitySchemes[$key] = $securitySchemeData; + $securityScheme = $securitySchemes[$key]; + + // If we're using protocol specific parsers, see if we have one to use. + if ($this->configuration->isSchemaSecuritySchemeParsingEnabled()) { + if (isset($securityScheme['type'], $this->securitySettingsParsers[$securityScheme['type']]) ) { - $parser = $this->securitySettingsParsers[$securityScheme['type']]; - } + $parser = $this->securitySettingsParsers[$securityScheme['type']]; } + } - // If we found a parser, create it's settings object. - if ($parser) { - $settings = isset($securityScheme['settings']) ? $securityScheme['settings'] : []; - $securitySchemes[$key]['settings'] = $parser->createSecuritySettings($settings); - } + // If we found a parser, create it's settings object. + if ($parser) { + $settings = isset($securityScheme['settings']) ? $securityScheme['settings'] : []; + $securitySchemes[$key]['settings'] = $parser->createSecuritySettings($settings); } } @@ -444,6 +441,80 @@ private function parseResourceTypes($ramlData) return $ramlData; } + /** + * @param array $ramlData + * @param string $rootDir + * @return array + */ + private function parseLibraries(array $ramlData, $rootDir) + { + if (!isset($ramlData['uses'])) { + return $ramlData; + } + + foreach ($ramlData['uses'] as $nameSpace => $import) { + $fileName = $import; + $dir = $rootDir; + + if (filter_var($import, FILTER_VALIDATE_URL) !== false) { + $fileName = basename($import); + $dir = dirname($import); + } + $library = $this->loadAndParseFile($fileName, $dir); + $library = $this->parseLibraries($library, $dir . '/' . dirname($fileName)); + foreach ($library as $key => $item) { + if ( + in_array( + $key, + [ + 'types', + 'traits', + 'annotationTypes', + 'securitySchemes', + 'resourceTypes', + 'schemas', + ], + true + )) { + foreach ($item as $itemName => $itemData) { + $itemData = $this->addNamespacePrefix($nameSpace, $itemData); + $ramlData[$key][$nameSpace . '.' . $itemName] = $itemData; + } + } + } + } + + return $ramlData; + } + + /** + * @param string $nameSpace + * @param array $definition + * @return array + */ + private function addNamespacePrefix($nameSpace, array $definition) + { + foreach ($definition as $key => $item) { + if (in_array($key, ['type', 'is'], true)) { + if (is_array($item)) { + foreach ($item as $itemKey => $itemValue) { + if (!in_array($itemValue, ApiDefinition::getStraightForwardTypes(), true)) { + $definition[$key][$itemKey] = $nameSpace . '.' . $itemValue; + } + } + } else { + if (!in_array($item, ApiDefinition::getStraightForwardTypes(), true)) { + $definition[$key] = $nameSpace . '.' . $item; + } + } + } elseif (is_array($definition[$key])) { + $definition[$key] = $this->addNamespacePrefix($nameSpace, $definition[$key]); + } + } + + return $definition; + } + /** * Parse the traits * @@ -485,10 +556,9 @@ private function parseTraits($ramlData) * * @param string $ramlString * @param string $rootDir - * - * @throws \Exception - * * @return array + * + * @throws \RuntimeException */ private function parseRamlString($ramlString, $rootDir) { @@ -497,12 +567,12 @@ private function parseRamlString($ramlString, $rootDir) $data = $this->parseYaml($ramlString); - if (!$data) { - throw new \Exception('RAML file appears to be empty'); + if (empty($data)) { + throw new \RuntimeException('RAML file appears to be empty'); } if (strpos($header, '#%RAML') === 0) { - // @todo extract the vesion of the raml and do something with it + // @todo extract the raml version and do something with it $data = $this->includeAndParseFiles( $data, @@ -513,13 +583,10 @@ private function parseRamlString($ramlString, $rootDir) return $data; } - // --- - /** * Convert a yaml string into an array * * @param string $fileData - * * @return array */ private function parseYaml($fileData) @@ -535,18 +602,24 @@ private function parseYaml($fileData) * * @param string $fileName * @param string $rootDir - * - * @throws \Exception - * * @return array + * + * @throws FileNotFoundException */ private function loadAndParseFile($fileName, $rootDir) { - $rootDir = realpath($rootDir); - $fullPath = realpath($rootDir.'/'.$fileName); + if (!$this->configuration->isRemoteFileInclusionEnabled()) { + $rootDir = realpath($rootDir); + $fullPath = realpath($rootDir . '/' . $fileName); - if (is_readable($fullPath) === false) { - throw new FileNotFoundException($fileName); + if (is_readable($fullPath) === false) { + throw new FileNotFoundException($fileName); + } + } else { + $fullPath = $rootDir . '/' . $fileName; + if (filter_var($fullPath, FILTER_VALIDATE_URL) === false && is_readable($fullPath) === false) { + throw new FileNotFoundException($fileName); + } } // Prevent LFI directory traversal attacks @@ -565,8 +638,8 @@ private function loadAndParseFile($fileName, $rootDir) $fileExtension = (pathinfo($fileName, PATHINFO_EXTENSION)); - if (in_array($fileExtension, ['yaml', 'yml', 'raml'])) { - $rootDir = dirname($rootDir.'/'.$fileName); + if (in_array($fileExtension, ['yaml', 'yml', 'raml'], true)) { + $rootDir = dirname($rootDir . '/' . $fileName); // RAML and YAML files are always parsed $fileData = $this->parseRamlString( @@ -575,7 +648,7 @@ private function loadAndParseFile($fileName, $rootDir) ); $fileData = $this->includeAndParseFiles($fileData, $rootDir); } else { - if (in_array($fileExtension, array_keys($this->fileLoaders))) { + if (in_array($fileExtension, array_keys($this->fileLoaders), true)) { $loader = $this->fileLoaders[$fileExtension]; } else { $loader = $this->fileLoaders['*']; @@ -596,15 +669,14 @@ private function loadAndParseFile($fileName, $rootDir) /** * Recurse through the structure and load includes * - * @param array|string $structure + * @param array|string|TaggedValue $structure * @param string $rootDir - * - * @return array + * @return array|string|TaggedValue */ private function includeAndParseFiles($structure, $rootDir) { if (is_array($structure)) { - $result = array(); + $result = []; foreach ($structure as $key => $structureElement) { $result[$key] = $this->includeAndParseFiles($structureElement, $rootDir); } @@ -612,7 +684,7 @@ private function includeAndParseFiles($structure, $rootDir) return $result; } - if ($structure instanceof TaggedValue && $structure->getTag() == 'include') { + if ($structure instanceof TaggedValue && $structure->getTag() === 'include') { return $this->loadAndParseFile($structure->getValue(), $rootDir); } @@ -630,8 +702,7 @@ private function includeAndParseFiles($structure, $rootDir) * @param array $traits * @param string $path * @param string $name - * - * @return array + * @return array|string */ private function replaceTraits($raml, array $traits, $path, $name) { @@ -656,18 +727,22 @@ private function replaceTraits($raml, array $traits, $path, $name) } elseif (isset($traits[$traitName])) { $trait = $traits[$traitName]; } + // @todo Refactor as can be resource greedy + // @see https://github.com/kalessil/phpinspectionsea/blob/master/docs/performance.md#slow-array-function-used-in-loop $newArray = array_replace_recursive($newArray, $this->replaceTraits($trait, $traits, $path, $name)); } + $newArray['is'] = $value; } else { $newValue = $this->replaceTraits($value, $traits, $path, $name); if (isset($newArray[$key]) && is_array($newArray[$key])) { + // @todo Refactor as can be resource greedy + // @see https://github.com/kalessil/phpinspectionsea/blob/master/docs/performance.md#slow-array-function-used-in-loop $newArray[$key] = array_replace_recursive($newArray[$key], $newValue); } else { $newArray[$key] = $newValue; } } - } return $newArray; @@ -676,12 +751,11 @@ private function replaceTraits($raml, array $traits, $path, $name) /** * Insert the types into the RAML file * - * @param array $raml + * @param string|array $raml * @param array $types * @param string $path * @param string $name - * @param string $parentKey - * + * @param string|null $parentKey * @return array */ private function replaceTypes($raml, $types, $path, $name, $parentKey = null) @@ -720,7 +794,6 @@ private function replaceTypes($raml, $types, $path, $name, $parentKey = null) $newArray[$key] = $newValue; } } - } return $newArray; @@ -731,104 +804,11 @@ private function replaceTypes($raml, $types, $path, $name, $parentKey = null) * * @param array $values * @param array $trait - * - * @return mixed + * @return array */ private function applyVariables(array $values, array $trait) { - $newTrait = []; - - foreach ($trait as $key => &$value) { - $newKey = $this->applyFunctions($key, $values); - - if (is_array($value)) { - $value = $this->applyVariables($values, $value); - } else { - $value = $this->applyFunctions($value, $values); - } - $newTrait[$newKey] = $value; - } - - return $newTrait; - } - - /** - * Applies functions on variable if they are defined - * - * @param mixed $trait - * @param array $values - * - * @return mixed Return input $trait after applying functions (if any) - */ - private function applyFunctions($trait, array $values) - { - $variables = implode('|', array_keys($values)); - - return preg_replace_callback( - '/<<('.$variables.')'. - '('. - '[\s]*\|[\s]*!'. - '('. - 'singularize|pluralize|uppercase|lowercase|lowercamelcase|uppercamelcase|lowerunderscorecase|upperunderscorecase|lowerhyphencase|upperhyphencase'. - ')'. - ')?>>/', - function ($matches) use ($values) { - $transformer = isset($matches[3]) ? $matches[3] : ''; - switch ($transformer) { - case 'singularize': - return Inflect::singularize($values[$matches[1]]); - break; - case 'pluralize': - return Inflect::pluralize($values[$matches[1]]); - break; - case 'uppercase': - return strtoupper($values[$matches[1]]); - break; - case 'lowercase': - return strtolower($values[$matches[1]]); - break; - case 'lowercamelcase': - return StringTransformer::convertString( - $values[$matches[1]], - StringTransformer::LOWER_CAMEL_CASE - ); - break; - case 'uppercamelcase': - return StringTransformer::convertString( - $values[$matches[1]], - StringTransformer::UPPER_CAMEL_CASE - ); - break; - case 'lowerunderscorecase': - return StringTransformer::convertString( - $values[$matches[1]], - StringTransformer::LOWER_UNDERSCORE_CASE - ); - break; - case 'upperunderscorecase': - return StringTransformer::convertString( - $values[$matches[1]], - StringTransformer::UPPER_UNDERSCORE_CASE - ); - break; - case 'lowerhyphencase': - return StringTransformer::convertString( - $values[$matches[1]], - StringTransformer::LOWER_HYPHEN_CASE - ); - break; - case 'upperhyphencase': - return StringTransformer::convertString( - $values[$matches[1]], - StringTransformer::UPPER_HYPHEN_CASE - ); - break; - default: - return $values[$matches[1]]; - } - }, - $trait - ); + return TraitParserHelper::applyVariables($values, $trait); } public function getIncludedFiles() diff --git a/src/QueryParameter.php b/src/QueryParameter.php deleted file mode 100644 index 5f5a8043..00000000 --- a/src/QueryParameter.php +++ /dev/null @@ -1,15 +0,0 @@ -uri = $uri; @@ -102,16 +105,15 @@ public function __construct($uri, ApiDefinition $apiDefinition) /** * Create a Resource from an array * - * @param string $uri + * @param string $uri * @param ApiDefinition $apiDefinition - * @param array $data + * @param array $data * [ - * uri: string - * displayName: ?string - * description: ?string + * uri: string + * displayName: ?string + * description: ?string * baseUriParameters: ?array * ] - * * @return self */ public static function createFromArray($uri, array $data = [], ApiDefinition $apiDefinition = null) @@ -156,21 +158,32 @@ public static function createFromArray($uri, array $data = [], ApiDefinition $ap $resource->addSecurityScheme($apiDefinition->getSecurityScheme($securedBy)); } } else { - $resource->addSecurityScheme(SecurityScheme::createFromArray('null', array(), $apiDefinition)); + $resource->addSecurityScheme(SecurityScheme::createFromArray('null', [], $apiDefinition)); } } } + if (isset($data['is'])) { + foreach ((array) $data['is'] as $traitName) { + $resource->addTrait(TraitCollection::getInstance()->getTraitByName($traitName)); + } + } + foreach ($data as $key => $value) { if (strpos($key, '/') === 0) { + $value = $value ?: []; + if (isset($data['uriParameters'])) { + $currentParameters = isset($value['uriParameters']) ? $value['uriParameters'] : []; + $value['uriParameters'] = array_merge($currentParameters, $data['uriParameters']); + } $resource->addResource( - Resource::createFromArray( - $uri.$key, - $value ?: [], + self::createFromArray( + $uri . $key, + $value, $apiDefinition ) ); - } elseif (in_array(strtoupper($key), Method::$validMethods)) { + } elseif (in_array(strtoupper($key), Method::$validMethods, true)) { $resource->addMethod( Method::createFromArray( $key, @@ -181,15 +194,21 @@ public static function createFromArray($uri, array $data = [], ApiDefinition $ap } } + foreach ($resource->getMethods() as $method) { + foreach ($resource->getTraits() as $trait) { + $method->addTrait($trait); + } + } + return $resource; } /** * Does a uri match this resource * - * @param $uri + * @param string $uri * - * @return boolean + * @return bool */ public function matchesUri($uri) { @@ -206,14 +225,14 @@ public function matchesUri($uri) } $regexUri = str_replace( - '/{'.$uriParameter->getKey().'}', - '/'.$matchPattern, + '/{' . $uriParameter->getKey() . '}', + '/' . $matchPattern, $regexUri ); $regexUri = str_replace( - '/~{'.$uriParameter->getKey().'}', - '/(('.$matchPattern.')|())', + '/~{' . $uriParameter->getKey() . '}', + '/((' . $matchPattern . ')|())', $regexUri ); } @@ -222,7 +241,7 @@ public function matchesUri($uri) $regexUri = preg_replace('/\/{.*}/U', '\/([^/]+)', $regexUri); $regexUri = preg_replace('/\/~{.*}/U', '\/([^/]*)', $regexUri); // начало и конец регулярки - символ, который гарантированно не встретится - $regexUri = chr(128).'^'.$regexUri.'$'.chr(128); + $regexUri = chr(128) . '^' . $regexUri . '$' . chr(128); return (bool) preg_match($regexUri, $uri); } @@ -344,9 +363,10 @@ public function getResources() * * @param self $resource */ - public function addResource(Resource $resource) + public function addResource(self $resource) { $this->subResources[$resource->getUri()] = $resource; + $resource->setParentResource($this); } // -- @@ -363,7 +383,6 @@ public function addMethod(Method $method) foreach ($this->getSecuritySchemes() as $securityScheme) { $method->addSecurityScheme($securityScheme); } - } /** @@ -414,4 +433,43 @@ public function addSecurityScheme(SecurityScheme $securityScheme) { $this->securitySchemes[$securityScheme->getKey()] = $securityScheme; } + + /** + * @return TraitDefinition[] + */ + public function getTraits() + { + return $this->traits; + } + + /** + * @param TraitDefinition $trait + * @return $this + */ + public function addTrait($trait) + { + $this->traits[] = $trait; + + return $this; + } + + /** + * @return Resource|null + */ + public function getParentResource() + { + return $this->parentResource; + } + + /** + * @param Resource $parentResource + * + * @return $this + */ + public function setParentResource($parentResource) + { + $this->parentResource = $parentResource; + + return $this; + } } diff --git a/src/Response.php b/src/Response.php index edcc4974..2f94e9a1 100644 --- a/src/Response.php +++ b/src/Response.php @@ -1,4 +1,5 @@ bodyList[$type])) { return $this->bodyList[$type]; - } elseif (isset($this->bodyList['*/*'])) { + } + if (isset($this->bodyList['*/*'])) { return $this->bodyList['*/*']; } - throw new \Exception('No body found for type "'.$type.'"'); + throw new \InvalidArgumentException(sprintf('No body found for type "%s"', $type)); } /** @@ -137,7 +137,7 @@ public function getBodies() /** * Returns all supported types in response * - * @return array + * @return string[] */ public function getTypes() { diff --git a/src/RouteFormatter/BasicRoute.php b/src/RouteFormatter/BasicRoute.php index 67234946..53f0ab29 100644 --- a/src/RouteFormatter/BasicRoute.php +++ b/src/RouteFormatter/BasicRoute.php @@ -2,7 +2,7 @@ namespace Raml\RouteFormatter; -use \Raml\Method; +use Raml\Method; class BasicRoute { diff --git a/src/RouteFormatter/NoRouteFormatter.php b/src/RouteFormatter/NoRouteFormatter.php index 930dfeb5..1828891b 100644 --- a/src/RouteFormatter/NoRouteFormatter.php +++ b/src/RouteFormatter/NoRouteFormatter.php @@ -24,7 +24,7 @@ public function format(array $resources) $this->routes = []; foreach ($resources as $resource) { - $this->routes[$resource->getType() .' ' . $resource->getUri()] = [ + $this->routes[$resource->getType() . ' ' . $resource->getUri()] = [ 'path' => $resource->getUri(), 'type' => $resource->getType(), 'method' => $resource->getMethod() diff --git a/src/RouteFormatter/SymfonyRouteFormatter.php b/src/RouteFormatter/SymfonyRouteFormatter.php index 8003f533..76130d11 100644 --- a/src/RouteFormatter/SymfonyRouteFormatter.php +++ b/src/RouteFormatter/SymfonyRouteFormatter.php @@ -13,7 +13,7 @@ class SymfonyRouteFormatter implements RouteFormatterInterface private $routes; /** - * @var boolean + * @var bool */ private $addTrailingSlash = true; @@ -24,7 +24,7 @@ class SymfonyRouteFormatter implements RouteFormatterInterface * append a trailing slash to the final routes or not. * * @param RouteCollection $routes - * @param boolean $addTrailingSlash + * @param bool $addTrailingSlash * By default this is true */ public function __construct(RouteCollection $routes, $addTrailingSlash = true) @@ -40,7 +40,7 @@ public function __construct(RouteCollection $routes, $addTrailingSlash = true) * Given an array of RAML\Resources, this function will add each Resource * into the Symfony Route Collection, and set the corresponding method. * - * @param BasicRoute[Resource] $resources + * @param BasicRoute[] $resources * Associative array where the key is the method and full path, and the value contains * the path, method type (GET/POST etc.) and then the Raml\Method object * diff --git a/src/Schema/Definition/JsonSchemaDefinition.php b/src/Schema/Definition/JsonSchemaDefinition.php index 57c43bfb..0e76778f 100644 --- a/src/Schema/Definition/JsonSchemaDefinition.php +++ b/src/Schema/Definition/JsonSchemaDefinition.php @@ -3,8 +3,8 @@ namespace Raml\Schema\Definition; use JsonSchema\Constraints\Constraint; -use \Raml\Schema\SchemaDefinitionInterface; -use \JsonSchema\Validator; +use Raml\Schema\SchemaDefinitionInterface; +use JsonSchema\Validator; use Raml\Types\TypeValidationError; final class JsonSchemaDefinition implements SchemaDefinitionInterface @@ -32,9 +32,7 @@ public function __construct(\stdClass $json) * Validate a JSON string against the schema * - Converts the string into a JSON object then uses the JsonSchema Validator to validate * - * @param $value - * - * @throws \Exception + * @param mixed $value */ public function validate($value) { @@ -91,7 +89,7 @@ public function getErrors() } /** - * @return boolean + * @return bool */ public function isValid() { diff --git a/src/Schema/Definition/XmlSchemaDefinition.php b/src/Schema/Definition/XmlSchemaDefinition.php index ec80fe39..ee6694e3 100644 --- a/src/Schema/Definition/XmlSchemaDefinition.php +++ b/src/Schema/Definition/XmlSchemaDefinition.php @@ -3,7 +3,7 @@ namespace Raml\Schema\Definition; use DOMDocument; -use \Raml\Schema\SchemaDefinitionInterface; +use Raml\Schema\SchemaDefinitionInterface; use Raml\Types\TypeValidationError; class XmlSchemaDefinition implements SchemaDefinitionInterface @@ -35,7 +35,7 @@ public function __construct($xml) /** * Validate an XML string against the schema * - * @param $value + * @param mixed $value * * @throws \Exception */ @@ -81,7 +81,7 @@ public function getErrors() } /** - * @return boolean + * @return bool */ public function isValid() { diff --git a/src/Schema/Parser/JsonSchemaParser.php b/src/Schema/Parser/JsonSchemaParser.php index 04ea816a..04cd2646 100644 --- a/src/Schema/Parser/JsonSchemaParser.php +++ b/src/Schema/Parser/JsonSchemaParser.php @@ -9,27 +9,23 @@ class JsonSchemaParser extends SchemaParserAbstract { - /** * List of known JSON content types * - * @var array + * @var string[] */ protected $compatibleContentTypes = [ 'application/json', - 'text/json' + 'text/json', ]; - // --- - /** * Create a new JSON Schema definition from a string * - * @param $schemaString + * @param string $schemaString + * @return JsonSchemaDefinition * * @throws InvalidJsonException - * - * @return \Raml\Schema\Definition\JsonSchemaDefinition */ public function createSchemaDefinition($schemaString) { @@ -43,15 +39,14 @@ public function createSchemaDefinition($schemaString) } /** - * @param \stdClass $data + * @param \stdClass|string $data * @param SchemaStorage $schemaStorage * @return mixed */ - private function resolveRefSchemaRecursively($data, $schemaStorage) + private function resolveRefSchemaRecursively($data, SchemaStorage $schemaStorage) { $data = $schemaStorage->resolveRefSchema($data); - - if (!is_object($data) && !is_array($data)) { + if (!is_object($data) || (is_object($data) && !$data instanceof \stdClass)) { return $data; } @@ -61,7 +56,7 @@ private function resolveRefSchemaRecursively($data, $schemaStorage) } if (is_array($value)) { - $data->{$key} = array_map(function($val) use ($schemaStorage) { + $data->{$key} = array_map(function ($val) use ($schemaStorage) { return $this->resolveRefSchemaRecursively($val, $schemaStorage); }, $value); } diff --git a/src/Schema/Parser/XmlSchemaParser.php b/src/Schema/Parser/XmlSchemaParser.php index 2ff966ff..2df87d38 100644 --- a/src/Schema/Parser/XmlSchemaParser.php +++ b/src/Schema/Parser/XmlSchemaParser.php @@ -7,7 +7,6 @@ class XmlSchemaParser extends SchemaParserAbstract { - /** * List of known XML content types * @@ -16,17 +15,15 @@ class XmlSchemaParser extends SchemaParserAbstract protected $compatibleContentTypes = [ 'application/xml', 'text/xml', - 'application/soap+xml' + 'application/soap+xml', ]; - // --- - /** * Create a new XML Schema definition from a string * - * @param $schemaString + * @param string $schemaString * - * @return \Raml\Schema\Definition\XmlSchemaDefinition + * @return XmlSchemaDefinition */ public function createSchemaDefinition($schemaString) { diff --git a/src/Schema/SchemaParserAbstract.php b/src/Schema/SchemaParserAbstract.php index a1793a61..ef9fbe4d 100644 --- a/src/Schema/SchemaParserAbstract.php +++ b/src/Schema/SchemaParserAbstract.php @@ -4,7 +4,6 @@ /** * Adapter between third party parser and RAML parser - * */ abstract class SchemaParserAbstract implements SchemaParserInterface { @@ -13,23 +12,20 @@ abstract class SchemaParserAbstract implements SchemaParserInterface * * @var string */ - private $sourceUri = null; + private $sourceUri; /** * List of compatible content types for this parser * - Should be populated with any content types that this parser will support * - * @var array + * @var string[] */ protected $compatibleContentTypes = []; - // --- - // SchemaParserInterface - /** * Set the sourceUri for the RAML file in order to fetch relative paths * - * @param $sourceUri + * @param string $sourceUri */ public function setSourceUri($sourceUri) { @@ -40,8 +36,7 @@ public function setSourceUri($sourceUri) * Create a new schema definition from a string * * @param string $schema - * - * @return \Raml\Schema\SchemaDefinitionInterface + * @return SchemaDefinitionInterface */ abstract public function createSchemaDefinition($schema); @@ -55,8 +50,6 @@ public function getCompatibleContentTypes() return $this->compatibleContentTypes; } - // -- - /** * Get the source uri; * @@ -68,13 +61,13 @@ public function getSourceUri() } /** - * Add an aditional supported content type + * Add an additional supported content type * - * @param $contentType + * @param string $contentType */ public function addCompatibleContentType($contentType) { - if (!in_array($contentType, $this->compatibleContentTypes)) { + if (!in_array($contentType, $this->compatibleContentTypes, true)) { $this->compatibleContentTypes[] = $contentType; } } diff --git a/src/Schema/SchemaParserInterface.php b/src/Schema/SchemaParserInterface.php index a125e98d..1d72384e 100644 --- a/src/Schema/SchemaParserInterface.php +++ b/src/Schema/SchemaParserInterface.php @@ -4,14 +4,13 @@ /** * Adapter between third party parser and RAML parser - * */ interface SchemaParserInterface { /** * Set the sourceUri for the RAML file in order to fetch relative paths * - * @param $sourceUri + * @param string $sourceUri */ public function setSourceUri($sourceUri); @@ -20,12 +19,10 @@ public function setSourceUri($sourceUri); * * @param string $schema * - * @return \Raml\Schema\SchemaDefinitionInterface + * @return SchemaDefinitionInterface */ public function createSchemaDefinition($schema); - // -- - /** * Returns a list of the compatible content types * diff --git a/src/SecurityScheme.php b/src/SecurityScheme.php index 98fa7446..eaf96208 100644 --- a/src/SecurityScheme.php +++ b/src/SecurityScheme.php @@ -11,7 +11,6 @@ */ class SecurityScheme implements ArrayInstantiationInterface { - /** * The key of the security scheme * @@ -62,7 +61,7 @@ class SecurityScheme implements ArrayInstantiationInterface /** * Create a new security scheme * - * @param $key + * @param string $key */ public function __construct($key) { @@ -215,8 +214,13 @@ public function setSettings($settings) */ public function mergeSettings($newSettings) { - $settingsClass = get_class($this->getSettings()); - $settings = $settingsClass::createFromArray($newSettings, $this->getSettings()); + if (is_object($this->getSettings())) { + $settingsClass = get_class($this->getSettings()); + $settings = $settingsClass::createFromArray($newSettings, $this->getSettings()); + } else { + $settings = array_replace($this->getSettings(), $newSettings); + } + $this->setSettings($settings); } } diff --git a/src/SecurityScheme/SecuritySchemeDescribedBy.php b/src/SecurityScheme/SecuritySchemeDescribedBy.php index b1924c65..761f780d 100644 --- a/src/SecurityScheme/SecuritySchemeDescribedBy.php +++ b/src/SecurityScheme/SecuritySchemeDescribedBy.php @@ -2,9 +2,10 @@ namespace Raml\SecurityScheme; -use \Raml\ArrayInstantiationInterface; -use \Raml\NamedParameter; -use \Raml\Response; +use Raml\ArrayInstantiationInterface; +use Raml\BodyInterface; +use Raml\NamedParameter; +use Raml\Response; /** * A description of a security scheme @@ -13,7 +14,6 @@ */ class SecuritySchemeDescribedBy implements ArrayInstantiationInterface { - /** * The key of the security scheme * @@ -21,8 +21,6 @@ class SecuritySchemeDescribedBy implements ArrayInstantiationInterface */ private $key; - // -- - /** * A list of non default headers (optional) * @@ -55,9 +53,6 @@ class SecuritySchemeDescribedBy implements ArrayInstantiationInterface */ private $bodyList = []; - - // --- - /** * Create a new security scheme description * @@ -87,7 +82,7 @@ public static function createFromArray($key, array $data = []) if (isset($data['body'])) { foreach ($data['body'] as $key => $bodyData) { - if (in_array($key, \Raml\WebFormBody::$validMediaTypes)) { + if (in_array($key, \Raml\WebFormBody::$validMediaTypes, true)) { $body = \Raml\WebFormBody::createFromArray($key, $bodyData); } else { $body = \Raml\Body::createFromArray($key, $bodyData); @@ -157,12 +152,11 @@ public function getBodies() * * @param BodyInterface $body */ - public function addBody(\Raml\BodyInterface $body) + public function addBody(BodyInterface $body) { $this->bodyList[$body->getMediaType()] = $body; } - /** * Returns the headers * @@ -220,7 +214,7 @@ public function getResponses() /** * Get a response by the response code (200, 404,....) * - * @param integer $responseCode + * @param int $responseCode * * @return Response */ diff --git a/src/SecurityScheme/SecuritySettings/DefaultSecuritySettings.php b/src/SecurityScheme/SecuritySettings/DefaultSecuritySettings.php index 1980b407..e7f9104a 100644 --- a/src/SecurityScheme/SecuritySettings/DefaultSecuritySettings.php +++ b/src/SecurityScheme/SecuritySettings/DefaultSecuritySettings.php @@ -11,8 +11,6 @@ class DefaultSecuritySettings implements SecuritySettingsInterface, \ArrayAccess */ const TYPE = '*'; - // -- - /** * The security settings * @@ -20,9 +18,6 @@ class DefaultSecuritySettings implements SecuritySettingsInterface, \ArrayAccess */ private $settings = []; - // --- - // SecuritySettingsInterface - /** * Flesh out the settings * @@ -35,32 +30,27 @@ class DefaultSecuritySettings implements SecuritySettingsInterface, \ArrayAccess */ public static function createFromArray(array $data, SecuritySettingsInterface $sourceSettings = null) { - if ($sourceSettings && !$sourceSettings instanceof DefaultSecuritySettings) { - throw new \Exception(); + if ($sourceSettings && !$sourceSettings instanceof self) { + throw new \InvalidArgumentException('Provide an instance of DefaultSecuritySettings for $sourceSettings'); } $settings = $sourceSettings ? clone $sourceSettings : new static(); - + assert($settings instanceof self); $settings->mergeSettings($data); return $settings; } - // --- - // \ArrayAccess - /** * Merge new settings into the current settings * - * @param $newSettings + * @param array $newSettings */ - public function mergeSettings($newSettings) + public function mergeSettings(array $newSettings) { $this->settings = array_replace($this->settings, $newSettings); } - // --- - /** * Sets a settings value * @link http://php.net/manual/en/arrayaccess.offsetset.php @@ -70,7 +60,7 @@ public function mergeSettings($newSettings) */ public function offsetSet($offset, $value) { - if (is_null($offset)) { + if (null === $offset) { $this->settings[] = $value; } else { $this->settings[$offset] = $value; @@ -113,7 +103,7 @@ public function offsetGet($offset) { return isset($this->settings[$offset]) ? $this->settings[$offset] : null; } - + /** * Get the array of settings data * diff --git a/src/SecurityScheme/SecuritySettings/OAuth1SecuritySettings.php b/src/SecurityScheme/SecuritySettings/OAuth1SecuritySettings.php index 58ce8a21..78255e3f 100644 --- a/src/SecurityScheme/SecuritySettings/OAuth1SecuritySettings.php +++ b/src/SecurityScheme/SecuritySettings/OAuth1SecuritySettings.php @@ -8,8 +8,6 @@ class OAuth1SecuritySettings implements SecuritySettingsInterface { const TYPE = 'OAuth 1.0'; - // -- - /** * The URI of the Temporary Credential Request endpoint as defined in RFC5849 Section 2.1 * @@ -31,9 +29,6 @@ class OAuth1SecuritySettings implements SecuritySettingsInterface */ private $requestTokenUri; - // --- - // SecuritySettingsInterface - /** * Flesh out the settings * @@ -46,11 +41,12 @@ class OAuth1SecuritySettings implements SecuritySettingsInterface */ public static function createFromArray(array $data, SecuritySettingsInterface $sourceSettings = null) { - if ($sourceSettings && !$sourceSettings instanceof OAuth1SecuritySettings) { - throw new \Exception(); + if ($sourceSettings && !$sourceSettings instanceof self) { + throw new \InvalidArgumentException('Provide an instance of OAuth1SecuritySettings for $sourceSettings'); } $settings = $sourceSettings ? clone $sourceSettings : new static(); + assert($settings instanceof self); if (isset($data['tokenCredentialsUri'])) { $settings->setTokenCredentialsUri($data['tokenCredentialsUri']); @@ -64,12 +60,9 @@ public static function createFromArray(array $data, SecuritySettingsInterface $s $settings->setAuthorizationUri($data['authorizationUri']); } - return $settings; } - // --- - /** * Get the Token Credentials URI * @@ -90,8 +83,6 @@ public function setTokenCredentialsUri($tokenCredentialsUri) $this->tokenCredentialsUri = $tokenCredentialsUri; } - // -- - /** * Get the Request Token URI * @@ -112,8 +103,6 @@ public function setRequestTokenUri($requestTokenUri) $this->requestTokenUri = $requestTokenUri; } - // -- - /** * Get the Authorization URI * diff --git a/src/SecurityScheme/SecuritySettings/OAuth2SecuritySettings.php b/src/SecurityScheme/SecuritySettings/OAuth2SecuritySettings.php index a8ea146d..4e11bb55 100644 --- a/src/SecurityScheme/SecuritySettings/OAuth2SecuritySettings.php +++ b/src/SecurityScheme/SecuritySettings/OAuth2SecuritySettings.php @@ -54,11 +54,12 @@ class OAuth2SecuritySettings implements SecuritySettingsInterface */ public static function createFromArray(array $data, SecuritySettingsInterface $sourceSettings = null) { - if ($sourceSettings && !$sourceSettings instanceof OAuth2SecuritySettings) { - throw new \Exception(); + if ($sourceSettings && !$sourceSettings instanceof self) { + throw new \InvalidArgumentException('Provide an instance of OAuth2SecuritySettings for $sourceSettings'); } $settings = $sourceSettings ? clone $sourceSettings : new static(); + assert($settings instanceof self); if (isset($data['authorizationUri'])) { $settings->setAuthorizationUri($data['authorizationUri']); diff --git a/src/SecurityScheme/SecuritySettingsInterface.php b/src/SecurityScheme/SecuritySettingsInterface.php index 6686f181..f6bf5a8a 100644 --- a/src/SecurityScheme/SecuritySettingsInterface.php +++ b/src/SecurityScheme/SecuritySettingsInterface.php @@ -1,4 +1,5 @@ collection = []; + $this->position = 0; + } + + /** + * The object is created from within the class itself + * only if the class has no instance. + * + * @return TraitCollection + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @return TraitDefinition + */ + public function current() + { + if ($this->valid()) { + return $this->collection[$this->position]; + } + + throw new InvalidKeyException($this->position); + } + + /** + * @return int + */ + public function key() + { + return $this->position; + } + + public function next() + { + $this->position++; + } + + public function rewind() + { + $this->position = 0; + } + + /** + * @return bool + */ + public function valid() + { + return isset($this->collection[$this->position]); + } + + /** + * Adds a Type to the collection + * + * @param TraitDefinition $traitToAdd Type to add. + */ + public function add(TraitDefinition $traitToAdd) + { + foreach ($this->collection as $key => $trait) { + if ($trait === $traitToAdd) { + throw new Exception(sprintf('Trait already exists %s', var_export($traitToAdd, true))); + } + } + $this->collection[] = $traitToAdd; + } + + /** + * Remove given Type from the collection + * + * @param TraitDefinition $traitToRemove Type to remove. + * + * @throws Exception + */ + public function remove(TraitDefinition $traitToRemove) + { + foreach ($this->collection as $key => $trait) { + if ($trait === $traitToRemove) { + unset($this->collection[$key]); + + return; + } + } + + throw new Exception(sprintf('Cannot remove given trait %s', var_export($traitToRemove, true))); + } + + /** + * Retrieves a trait by name + * + * @param string $name Name of the Trait to retrieve. + * + * @return TraitDefinition Returns Trait matching given name if found. + * @throws Exception When no type is found. + */ + public function getTraitByName($name) + { + $variables = []; + foreach ($this->collection as $trait) { + if (is_array($name)) { + $variables = reset($name); + $name = key($name); + } + /** @var $trait TraitDefinition */ + if ($trait->getName() === $name) { + return $trait->parseVariables($variables); + } + } + + throw new Exception(sprintf('No trait found for name %s, list: %s', var_export($name, true), var_export($this->collection, true))); + } + + /** + * Returns types in a plain multidimensional array + * + * @return array Returns plain array. + */ + public function toArray() + { + $types = []; + foreach ($this->collection as $trait) { + $types[$trait->getName()] = $trait->toArray(); + } + + return $types; + } + + /** + * Clears the TraitCollection of any registered types + * + */ + public function clear() + { + $this->collection = []; + $this->position = 0; + } +} diff --git a/src/TraitDefinition.php b/src/TraitDefinition.php new file mode 100644 index 00000000..4e7ee964 --- /dev/null +++ b/src/TraitDefinition.php @@ -0,0 +1,257 @@ +name = $name; + $this->queryParameters = []; + $this->headers = []; + $this->traits = []; + } + + /** + * @param string $name + * @param array $data + * @return TraitDefinition + */ + public static function createFromArray($name, array $data = []) + { + $class = new static($name); + + if (isset($data['queryParameters'])) { + $queryParameters = []; + foreach ($data['queryParameters'] as $key => $parameter) { + $queryParameters[] = NamedParameter::createFromArray($key, $parameter); + } + $class->setQueryParameters($queryParameters); + } + if (isset($data['usage'])) { + $class->setUsage($data['usage']); + } + if (isset($data['description'])) { + $class->setDescription($data['description']); + } + if (isset($data['headers'])) { + $headers = []; + foreach ($data['headers'] as $key => $header) { + $headers[] = NamedParameter::createFromArray($key, $header); + } + $class->setHeaders($headers); + } + if (isset($data['is'])) { + $class->traits = (array) $data['is']; + } + + $class->setDefinition($data); + + return $class; + } + + /** + * @return array + */ + public function toArray() + { + return $this->definition; + } + + /** + * @param array $variables + * @return TraitDefinition + */ + public function parseVariables(array $variables) + { + $definition = TraitParserHelper::applyVariables($variables, $this->getDefinition()); + + return static::createFromArray($this->getName(), $definition); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * @return NamedParameter[] + */ + public function getQueryParameters() + { + return $this->queryParameters; + } + + /** + * @param NamedParameter[] $queryParameters + * + * @return $this + */ + public function setQueryParameters($queryParameters) + { + $this->queryParameters = $queryParameters; + + return $this; + } + + /** + * @return string + */ + public function getUsage() + { + return $this->usage; + } + + /** + * @param string $usage + * + * @return $this + */ + public function setUsage($usage) + { + $this->usage = $usage; + + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + * + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * @return NamedParameter[] + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * @param NamedParameter[] $headers + * + * @return $this + */ + public function setHeaders($headers) + { + $this->headers = $headers; + + return $this; + } + + /** + * @return TraitDefinition[] + */ + public function getTraits() + { + foreach ($this->traits as $key => $item) { + if (!$item instanceof self) { + $this->traits[$key] = TraitCollection::getInstance()->getTraitByName($item); + } + } + + return $this->traits; + } + + /** + * @param TraitDefinition[] $traits + * + * @return $this + */ + public function setTraits($traits) + { + $this->traits = $traits; + + return $this; + } + + /** + * @return array + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * @param array $definition + * + * @return $this + */ + public function setDefinition($definition) + { + $this->definition = $definition; + + return $this; + } +} diff --git a/src/Type.php b/src/Type.php index e9310c30..6e6fd0af 100644 --- a/src/Type.php +++ b/src/Type.php @@ -2,7 +2,6 @@ namespace Raml; -use Raml\Types; use Raml\Types\ObjectType; use Raml\Types\TypeValidationError; @@ -19,38 +18,35 @@ class Type implements ArrayInstantiationInterface, TypeInterface protected $errors = []; /** - * Parent object - * * @var ObjectType|string - **/ - private $parent = null; + */ + private $parent; /** * Key used for type * * @var string - **/ + */ private $name; /** - * Type * * @var string - **/ - private $type; + */ + protected $type; /** * Required * * @var bool - **/ + */ private $required = true; /** * Raml definition * * @var array - **/ + */ private $definition; /** @@ -59,9 +55,7 @@ class Type implements ArrayInstantiationInterface, TypeInterface private $enum = []; /** - * Create new type - * - * @param string $name + * @param string $name */ public function __construct($name) { @@ -71,12 +65,9 @@ public function __construct($name) /** * Create a new Type from an array of data * - * @param string $name - * @param array $data - * - * @return Type - * - * @throws \Exception Thrown when input is incorrect. + * @param string $name + * @param array $data + * @return self */ public static function createFromArray($name, array $data = []) { @@ -84,6 +75,7 @@ public static function createFromArray($name, array $data = []) $class->setType($data['type']); if (isset($data['usage'])) { + assert($class instanceof TraitDefinition); $class->setUsage($data['usage']); } if (isset($data['required'])) { @@ -168,7 +160,7 @@ public function getType() * Set definition * * @param array $data Definition data of type. - **/ + */ public function setDefinition(array $data = []) { $this->definition = $data; @@ -177,7 +169,7 @@ public function setDefinition(array $data = []) /** * Get definition * - * @return string Returns definition property. + * @return array Returns definition property. */ public function getDefinition() { @@ -232,8 +224,12 @@ public function setEnum(array $enum) public function getParent() { if (is_string($this->parent)) { - $this->parent = TypeCollection::getInstance()->getTypeByName($this->parent); + $parent = TypeCollection::getInstance()->getTypeByName($this->parent); + assert($parent instanceof ObjectType); + + return $parent; } + return $this->parent; } @@ -244,7 +240,7 @@ public function getParent() */ public function hasParent() { - return ($this->parent !== null); + return $this->parent !== null; } /** @@ -265,14 +261,15 @@ public function setParent($parent) * Inherit properties from parent (recursively) * * @return self Returns the new object with inherited properties. - **/ + * @throws \LogicException + */ public function inheritFromParent() { if (!$this->hasParent()) { return $this; } $parent = $this->getParent(); - // recurse if + // recurse if if ($parent instanceof $this && $parent->hasParent()) { $this->parent = $parent->inheritFromParent(); unset($parent); @@ -281,7 +278,7 @@ public function inheritFromParent() return $this->getParent(); } if (!($this->getParent() instanceof $this)) { - throw new \Exception(sprintf( + throw new \LogicException(sprintf( 'Inheritance not possible because of incompatible Types, child is instance of %s and parent is instance of %s', get_class($this), get_class($this->getParent()) @@ -303,10 +300,9 @@ public function inheritFromParent() } } $properties = array_keys(array_merge($getters, $setters)); - + foreach ($properties as $prop) { - if (!isset($getters[$prop]) || !isset($setters[$prop])) - { + if (!isset($getters[$prop]) || !isset($setters[$prop])) { continue; } $getter = $getters[$prop]; @@ -320,9 +316,11 @@ public function inheritFromParent() if (is_array($currentValue)) { $newValue = array_merge($this->getParent()->$getter(), $currentValue); $this->$setter($newValue); + continue; } } + return $this; } @@ -350,7 +348,7 @@ public function getErrors() } /** - * @return boolean + * @return bool */ public function isValid() { diff --git a/src/TypeCollection.php b/src/TypeCollection.php index 4fc73611..82ef9062 100644 --- a/src/TypeCollection.php +++ b/src/TypeCollection.php @@ -5,66 +5,60 @@ use Raml\Types\ObjectType; /** - * Singleton class used to register all types in one place - **/ + * Singleton class used to register all types in one place + */ class TypeCollection implements \Iterator { /** * Hold the class instance. * * @var self - **/ - private static $instance = null; + */ + private static $instance; /** * Collection * - * @var array - **/ + * @var TypeInterface[] + */ private $collection = []; /** * Current position * * @var string - **/ + */ private $position = 0; /** * Types which need to inherit properties from their parent * * @var ObjectType[] - **/ + */ private $typesWithInheritance = []; /** - * prevent initiation from outside, there can be only one! - * - **/ + * Singleton, prevent initiation from outside, there can be only one! + */ private function __construct() { - $this->collection = []; - $this->position = 0; } - + /** - * The object is created from within the class itself - * only if the class has no instance. - * - * @return TypeCollection - **/ + * @return self + */ public static function getInstance() { - if (self::$instance == null) { - self::$instance = new TypeCollection(); + if (self::$instance === null) { + self::$instance = new self(); } - + return self::$instance; } /** * {@inheritDoc} - **/ + */ public function current() { return $this->collection[$this->position]; @@ -72,7 +66,7 @@ public function current() /** * {@inheritDoc} - **/ + */ public function key() { return $this->position; @@ -80,15 +74,15 @@ public function key() /** * {@inheritDoc} - **/ + */ public function next() { - ++$this->position; + $this->position++; } /** * {@inheritDoc} - **/ + */ public function rewind() { $this->position = 0; @@ -96,7 +90,7 @@ public function rewind() /** * {@inheritDoc} - **/ + */ public function valid() { return isset($this->collection[$this->position]); @@ -105,9 +99,9 @@ public function valid() /** * Adds a Type to the collection * - * @param \Raml\TypeInterface $type Type to add. - **/ - public function add(\Raml\TypeInterface $type) + * @param TypeInterface $type Type to add. + */ + public function add(TypeInterface $type) { $this->collection[] = $type; } @@ -115,41 +109,58 @@ public function add(\Raml\TypeInterface $type) /** * Remove given Type from the collection * - * @param \Raml\TypeInterface $typeToRemove Type to remove. - **/ - public function remove(\Raml\TypeInterface $typeToRemove) + * @param TypeInterface $typeToRemove Type to remove. + * @throws \RuntimeException When no type is found. + */ + public function remove(TypeInterface $typeToRemove) { foreach ($this->collection as $key => $type) { if ($type === $typeToRemove) { unset($this->collection[$key]); + return; } } - throw new \Exception(sprintf('Cannot remove given type %s', var_export($type, true))); + + throw new \RuntimeException(sprintf('Cannot remove given type %s', var_export($typeToRemove, true))); } /** * Retrieves a type by name * * @param string $name Name of the Type to retrieve. + * @return TypeInterface Returns Type matching given name if found. * - * @return \Raml\TypeInterface Returns Type matching given name if found. - * @throws \Exception When no type is found. - **/ + * @throws \RuntimeException When no type is found. + */ public function getTypeByName($name) { foreach ($this->collection as $type) { - /** @var $type \Raml\TypeInterface */ + /** @var $type TypeInterface */ if ($type->getName() === $name) { return $type; } } - throw new \Exception(sprintf('No type found for name %s, list: %s', var_export($name, true), var_export($this->collection, true))); + + throw new \RuntimeException(sprintf('No type found for name %s, list: %s', var_export($name, true), var_export($this->collection, true))); + } + + /** + * @param string $name + * @return bool + */ + public function hasTypeByName($name) + { + try { + return $this->getTypeByName($name) instanceof TypeInterface; + } catch (\Exception $exception) { + return false; + } } /** * Applies inheritance on all types that have a parent - **/ + */ public function applyInheritance() { foreach ($this->typesWithInheritance as $key => $type) { @@ -163,12 +174,12 @@ public function applyInheritance() * Adds a Type to the list of typesWithInheritance * * @param ObjectType $type Type to add. - * - * @return self Returns self for chaining. - **/ + * @return self + */ public function addTypeWithInheritance(ObjectType $type) { $this->typesWithInheritance[] = $type; + return $this; } @@ -176,21 +187,20 @@ public function addTypeWithInheritance(ObjectType $type) * Returns types in a plain multidimensional array * * @return array Returns plain array. - **/ + */ public function toArray() { $types = []; - foreach ($this->collection as $type) - { + foreach ($this->collection as $type) { $types[$type->getName()] = $type->toArray(); } + return $types; } /** * Clears the TypeCollection of any registered types - * - **/ + */ public function clear() { $this->collection = []; diff --git a/src/TypeInterface.php b/src/TypeInterface.php index 425d981d..bef08782 100644 --- a/src/TypeInterface.php +++ b/src/TypeInterface.php @@ -11,7 +11,7 @@ interface TypeInterface extends ValidatorInterface { /** * Returns the name of the Type. - **/ + */ public function getName(); /** diff --git a/src/Types/ArrayType.php b/src/Types/ArrayType.php index 7c34d560..a7f7ec40 100644 --- a/src/Types/ArrayType.php +++ b/src/Types/ArrayType.php @@ -13,7 +13,6 @@ */ class ArrayType extends Type { - /** * Scalar types which we can validate */ @@ -28,14 +27,14 @@ class ArrayType extends Type * Boolean value that indicates if items in the array MUST be unique. * * @var bool - **/ + */ private $uniqueItems; /** * Indicates the type all items in the array are inherited from. Can be a reference to an existing type or an inline type declaration. * * @var string|TypeInterface - **/ + */ private $items; /** @@ -43,7 +42,7 @@ class ArrayType extends Type * Default: 0. * * @var int - **/ + */ private $minItems = 0; /** @@ -51,7 +50,7 @@ class ArrayType extends Type * Default: 2147483647. * * @var int - **/ + */ private $maxItems = 2147483647; /** @@ -64,8 +63,8 @@ class ArrayType extends Type */ public static function createFromArray($name, array $data = []) { - /** @var ArrayType $type */ $type = parent::createFromArray($name, $data); + assert($type instanceof self); $pos = strpos($type->getType(), '[]'); if ($pos !== false) { $type->setItems(substr($type->getType(), 0, $pos)); @@ -76,15 +75,24 @@ public static function createFromArray($name, array $data = []) switch ($key) { case 'uniqueItems': $type->setUniqueItems($value); + break; case 'items': + if (is_array($value) && isset($value['type'])) { + $type->setItems($value['type']); + + break; + } $type->setItems($value); + break; case 'minItems': $type->setMinItems($value); + break; case 'maxItems': $type->setMaxItems($value); + break; } } @@ -158,7 +166,7 @@ public function validate($value) ); } - if (in_array($this->items, self::$SCALAR_TYPES)) { + if (in_array($this->items, self::$SCALAR_TYPES, true)) { $this->validateScalars($value); } else { $this->validateObjects($value); @@ -177,6 +185,7 @@ private function validateScalars($value) $valueItem ); } + break; case 'string': if (!is_string($valueItem)) { @@ -186,6 +195,7 @@ private function validateScalars($value) $valueItem ); } + break; case 'boolean': if (!is_bool($valueItem)) { @@ -195,6 +205,7 @@ private function validateScalars($value) $valueItem ); } + break; case 'number': if (!is_float($valueItem) && !is_int($valueItem)) { @@ -204,6 +215,7 @@ private function validateScalars($value) $valueItem ); } + break; } } diff --git a/src/Types/BooleanType.php b/src/Types/BooleanType.php index 07f5049e..050803a6 100644 --- a/src/Types/BooleanType.php +++ b/src/Types/BooleanType.php @@ -22,6 +22,7 @@ class BooleanType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); return $type; } diff --git a/src/Types/DateOnlyType.php b/src/Types/DateOnlyType.php index c75c0cc4..4494349e 100644 --- a/src/Types/DateOnlyType.php +++ b/src/Types/DateOnlyType.php @@ -7,12 +7,13 @@ /** * DateOnlyType class - * + * * @author Melvin Loos */ class DateOnlyType extends Type { - const FORMAT = "Y-m-d"; + const FORMAT = 'Y-m-d'; + /** * Create a new DateOnlyType from an array of data * @@ -24,6 +25,7 @@ class DateOnlyType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); return $type; } diff --git a/src/Types/DatetimeOnlyType.php b/src/Types/DatetimeOnlyType.php index 26210bbf..1f92795d 100644 --- a/src/Types/DatetimeOnlyType.php +++ b/src/Types/DatetimeOnlyType.php @@ -6,24 +6,24 @@ use Raml\Type; /** - * DateTimeOnlyType class - * * @author Melvin Loos */ class DatetimeOnlyType extends Type { const FORMAT = "Y-m-d\TH:i:s"; + /** * Create a new DateTimeOnlyType from an array of data * * @param string $name * @param array $data * - * @return DateTimeOnlyType + * @return self */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); return $type; } diff --git a/src/Types/DateTimeType.php b/src/Types/DatetimeType.php similarity index 87% rename from src/Types/DateTimeType.php rename to src/Types/DatetimeType.php index 45c9895e..c88fa295 100644 --- a/src/Types/DateTimeType.php +++ b/src/Types/DatetimeType.php @@ -6,33 +6,35 @@ use Raml\Type; /** - * DateTimeType type class + * DatetimeType type class */ -class DateTimeType extends Type +class DatetimeType extends Type { /** * DateTime format to use * * @var string - **/ + */ private $format; /** - * Create a new DateTimeType from an array of data + * Create a new DatetimeType from an array of data * * @param string $name * @param array $data * - * @return DateTimeType + * @return DatetimeType */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); foreach ($data as $key => $value) { switch ($key) { case 'format': $type->setFormat($value); + break; } } diff --git a/src/Types/FileType.php b/src/Types/FileType.php index edefff12..34df49c5 100644 --- a/src/Types/FileType.php +++ b/src/Types/FileType.php @@ -15,7 +15,7 @@ class FileType extends Type * A list of valid content-type strings for the file. The file type * / * MUST be a valid value. * * @var array - **/ + */ private $fileTypes; /** @@ -23,7 +23,7 @@ class FileType extends Type * Default: 0 * * @var int - **/ + */ private $minLength; /** @@ -31,7 +31,7 @@ class FileType extends Type * Default: 2147483647 * * @var int - **/ + */ private $maxLength; /** @@ -45,17 +45,21 @@ class FileType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); foreach ($data as $key => $value) { switch ($key) { case 'fileTypes': $type->setFileTypes($value); + break; case 'minLength': $type->setMinLength($value); + break; case 'maxLength': $type->setMaxLength($value); + break; } } diff --git a/src/Types/IntegerType.php b/src/Types/IntegerType.php index 79e7b9a8..575c04e2 100644 --- a/src/Types/IntegerType.php +++ b/src/Types/IntegerType.php @@ -4,7 +4,7 @@ /** * IntegerType class - * + * * @author Melvin Loos */ class IntegerType extends NumberType @@ -20,6 +20,7 @@ class IntegerType extends NumberType public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); if (!$type->getFormat()) { $type->setFormat('int'); } diff --git a/src/Types/JsonType.php b/src/Types/JsonType.php index c457db88..8b6f589d 100644 --- a/src/Types/JsonType.php +++ b/src/Types/JsonType.php @@ -15,25 +15,24 @@ class JsonType extends Type /** * Json schema * - * @var string - **/ + * @var array + */ private $json; /** * Create a new JsonType from an array of data * - * @param string $name - * @param array $data - * - * @return StringType + * @param string $name + * @param array $data + * @return self */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); - /* @var $type StringType */ + assert($type instanceof self); $type->json = $data; - + return $type; } @@ -41,9 +40,7 @@ public static function createFromArray($name, array $data = []) * Validate a JSON string against the schema * - Converts the string into a JSON object then uses the JsonSchema Validator to validate * - * @param $string - * - * @return bool + * @param mixed $value */ public function validate($value) { diff --git a/src/Types/LazyProxyType.php b/src/Types/LazyProxyType.php index 79886c17..783ca923 100644 --- a/src/Types/LazyProxyType.php +++ b/src/Types/LazyProxyType.php @@ -2,136 +2,146 @@ namespace Raml\Types; -use Raml\ArrayInstantiationInterface; -use Raml\TypeInterface; +use Raml\Type; use Raml\TypeCollection; use Raml\ApiDefinition; /** * LazyProxyType class for lazy loading datatype objects */ -class LazyProxyType implements TypeInterface, ArrayInstantiationInterface +class LazyProxyType extends Type { /** - * name/id of type - * - * @var string - **/ - private $name; - - /** - * original type name, used for resolving - * - * @var string - **/ - private $type; + * @var Type + */ + private $wrappedObject; /** - * original type - * - * @var \Raml\TypeInterface - **/ - private $wrappedObject = null; + * @var Type[] + */ + private $properties; /** - * raml definition - * - * @var array - **/ - private $definition = []; + * @param string $name + */ + public function __construct($name) + { + parent::__construct($name); - private $errors = []; + $this->properties = []; + } /** * Create a new LazyProxyType from an array of data * - * @param string $name Type name. - * @param array $data Type data. - * + * @param string $name Type name. + * @param array $data Type data. * @return LazyProxyType + * + * @throws \InvalidArgumentException */ public static function createFromArray($name, array $data = []) { - $proxy = new static(); - $proxy->name = $name; - $proxy->definition = $data; + $proxy = new static($name); + $proxy->setDefinition($data); if (!isset($data['type'])) { - throw new \Exception('Missing "type" key in $data param to determine datatype!'); + throw new \InvalidArgumentException('Missing "type" key in $data param to determine datatype!'); + } + if (isset($data['properties'])) { + $proxy->properties = $data['properties']; } - $proxy->type = $data['type']; + $proxy->setType($data['type']); + if ($name !== $data['type']) { + $proxy->setParent($data['type']); + } return $proxy; } /** - * Dumps object to array - * - * @return array Object dumped to array. + * @param string $value + * @return bool */ - public function toArray() + public function discriminate($value) { - return $this->definition; + if (!$this->getWrappedObject()->discriminate($value)) { + if (isset($value[$this->getDiscriminator()])) { + $discriminatorValue = $this->getDiscriminatorValue() ?: $this->getName(); + + return $value[$this->getDiscriminator()] === $discriminatorValue; + } + + return true; + } + + return true; } /** - * Returns type definition - * - * @return array Definition of object. + * @return string */ - public function getDefinition() + public function getDiscriminator() { - return $this->definition; + return $this->getResolvedObject()->getDiscriminator(); } /** - * Get the value of name - * - * @return string Returns name property. + * @return string */ - public function getName() + public function getDiscriminatorValue() { - return $this->name; + return $this->getResolvedObject()->getDiscriminatorValue(); } - public function discriminate($value) + /** + * @return Type[] + */ + public function getProperties() { - if (!$this->getWrappedObject()->discriminate($value)) { - if (isset($value[$this->getDiscriminator()])) { - $discriminatorValue = $this->getDiscriminatorValue() ?: $this->getName(); - - return $value[$this->getDiscriminator()] === $discriminatorValue; + foreach ($this->properties as $name => $property) { + if (!$property instanceof Type) { + $property = ApiDefinition::determineType($name, $property); } - - return true; + $this->properties[$name] = $property; } - return true; + return $this->properties; } /** * Magic method to proxy all method calls to original object - * @param string $name Name of called method. - * @param mixed $params Parameteres of called method. - * + * @param string $name Name of called method. + * @param array $params Parameters of called method. * @return mixed Returns whatever the actual method returns. */ public function __call($name, $params) { $original = $this->getResolvedObject(); - return call_user_func_array(array($original, $name), $params); + return call_user_func_array([$original, $name], $params); + } + + /** + * @return string + */ + public function getOriginalType() + { + return $this->type; } public function getRequired() { - if (isset($this->definition['required'])) { - return $this->definition['required']; + if (isset($this->getDefinition()['required'])) { + return $this->getDefinition()['required']; } return $this->getResolvedObject()->getRequired(); } + /** + * @param mixed $value + */ public function validate($value) { $this->errors = []; @@ -154,24 +164,31 @@ public function getErrors() } /** - * @return boolean + * @return bool */ public function isValid() { return empty($this->errors); } + /** + * @return self|Type + */ public function getResolvedObject() { $object = $this->getWrappedObject(); - if ($object instanceof self) { - $definition = $object->getDefinitionRecursive(); - return ApiDefinition::determineType($this->name, $definition); + if (!$object instanceof self) { + return $object; } - return $object; + $definition = $object->getDefinitionRecursive(); + + return ApiDefinition::determineType($this->getName(), $definition); } + /** + * @return Type + */ public function getWrappedObject() { if ($this->wrappedObject === null) { @@ -182,6 +199,9 @@ public function getWrappedObject() return $this->wrappedObject; } + /** + * @return array + */ public function getDefinitionRecursive() { $type = $this->getWrappedObject(); diff --git a/src/Types/NilType.php b/src/Types/NilType.php index feb4db20..ba9c603a 100644 --- a/src/Types/NilType.php +++ b/src/Types/NilType.php @@ -9,5 +9,4 @@ */ class NilType extends NullType { - } diff --git a/src/Types/NullType.php b/src/Types/NullType.php index c0385ff2..ce83bcda 100644 --- a/src/Types/NullType.php +++ b/src/Types/NullType.php @@ -6,7 +6,7 @@ /** * NullType class - * + * * @author Melvin Loos */ class NullType extends Type @@ -22,13 +22,14 @@ class NullType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); return $type; } public function validate($value) { - if (!is_null($value)) { + if (null !== $value) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'null', $value); } } diff --git a/src/Types/NumberType.php b/src/Types/NumberType.php index 119a6112..b94b7e43 100644 --- a/src/Types/NumberType.php +++ b/src/Types/NumberType.php @@ -15,29 +15,29 @@ class NumberType extends Type * The minimum value of the parameter. Applicable only to parameters of type number or integer. * * @var int - **/ - private $minimum = null; + */ + private $minimum; /** * The maximum value of the parameter. Applicable only to parameters of type number or integer. * * @var int - **/ - private $maximum = null; + */ + private $maximum; /** * The format of the value. The value MUST be one of the following: int32, int64, int, long, float, double, int16, int8 * * @var string - **/ - private $format = null; + */ + private $format; /** * A numeric instance is valid against "multipleOf" if the result of dividing the instance by this keyword's value is an integer. * * @var int - **/ - private $multipleOf = null; + */ + private $multipleOf; /** * Create a new NumberType from an array of data @@ -50,20 +50,25 @@ class NumberType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); foreach ($data as $key => $value) { switch ($key) { case 'minimum': $type->setMinimum($value); + break; case 'maximum': $type->setMaximum($value); + break; case 'format': $type->setFormat($value); + break; case 'multipleOf': $type->setMultipleOf($value); + break; } } @@ -135,12 +140,12 @@ public function getFormat() * @param string $format * * @return self - * @throws Exception Thrown when given format is not any of allowed types. + * @throws \Exception Thrown when given format is not any of allowed types. */ public function setFormat($format) { - if (!in_array($format, ['int32', 'int64', 'int', 'long', 'float', 'double', 'int16', 'int8'])) { - throw new \Exception(sprinf('Incorrect format given: "%s"', $format)); + if (!in_array($format, ['int32', 'int64', 'int', 'long', 'float', 'double', 'int16', 'int8'], true)) { + throw new \Exception(sprintf('Incorrect format given: "%s"', $format)); } $this->format = $format; @@ -175,12 +180,12 @@ public function validate($value) { parent::validate($value); - if (!is_null($this->maximum)) { + if (null !== $this->maximum) { if ($value > $this->maximum) { $this->errors[] = TypeValidationError::valueExceedsMaximum($this->getName(), $this->maximum, $value); } } - if (!is_null($this->minimum)) { + if (null !== $this->minimum) { if ($value < $this->minimum) { $this->errors[] = TypeValidationError::valueExceedsMinimum($this->getName(), $this->minimum, $value); } @@ -190,31 +195,37 @@ public function validate($value) if (filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -128, 'max_range' => 127]]) === false) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'int8', $value); } + break; case 'int16': if (filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -32768, 'max_range' => 32767]]) === false) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'int16', $value); } + break; case 'int32': if (filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -2147483648, 'max_range' => 2147483647]]) === false) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'int32', $value); } + break; case 'int64': if (filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -9223372036854775808, 'max_range' => 9223372036854775807]]) === false) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'int64', $value); } + break; case 'int': if (!is_int($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'int', $value); } + break; case 'long': if (!is_int($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'int or long', $value); } + break; case 'float': // float === double @@ -222,15 +233,17 @@ public function validate($value) if (!is_float($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'double or float', $value); } + break; // if no format is given we check only if it is a number default: if (!is_float($value) && !is_int($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'number', $value); } + break; } - if (!is_null($this->multipleOf)) { + if (null !== $this->multipleOf) { if ($value % $this->multipleOf != 0) { $this->errors[] = TypeValidationError::isNotMultipleOf($this->getName(), $this->multipleOf, $value); } diff --git a/src/Types/ObjectType.php b/src/Types/ObjectType.php index 9f5d2e1f..91e1cfcc 100644 --- a/src/Types/ObjectType.php +++ b/src/Types/ObjectType.php @@ -15,30 +15,30 @@ class ObjectType extends Type /** * The properties that instances of this type can or must have. * - * @var \Raml\Type[] - **/ - private $properties = null; + * @var Type[] + */ + private $properties; /** * The minimum number of properties allowed for instances of this type. * * @var int - **/ - private $minProperties = null; + */ + private $minProperties; /** * The maximum number of properties allowed for instances of this type. * * @var int - **/ - private $maxProperties = null; + */ + private $maxProperties; /** * A Boolean that indicates if an object instance has additional properties. * Default: true * * @var bool - **/ + */ private $additionalProperties = true; /** @@ -48,8 +48,8 @@ class ObjectType extends Type * Unsupported practices are inline type declarations and using discriminator with non-scalar properties. * * @var string - **/ - protected $discriminator = null; + */ + protected $discriminator; /** * Identifies the declaring type. @@ -59,41 +59,45 @@ class ObjectType extends Type * Default: The name of the type * * @var string - **/ - private $discriminatorValue = null; + */ + private $discriminatorValue; /** - * Create a new ObjectType from an array of data - * - * @param string $name Type name. - * @param array $data Type data. - * - * @return ObjectType + * @param string $name Type name. + * @param array $data Type data. + * @return self */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); $type->setType('object'); foreach ($data as $key => $value) { switch ($key) { case 'properties': $type->setProperties($value); + break; case 'minProperties': $type->setMinProperties($value); + break; case 'maxProperties': $type->setMinProperties($value); + break; case 'additionalProperties': $type->setAdditionalProperties($value); + break; case 'discriminator': $type->setDiscriminator($value); + break; case 'discriminatorValue': $type->setDiscriminatorValue($value); + break; } } @@ -101,6 +105,10 @@ public static function createFromArray($name, array $data = []) return $type; } + /** + * @param string $value + * @return bool + */ public function discriminate($value) { if (isset($value[$this->getDiscriminator()])) { @@ -132,13 +140,12 @@ public function getProperties() * Set the value of Properties * * @param array $properties - * * @return self */ public function setProperties(array $properties) { foreach ($properties as $name => $property) { - if ($property instanceof \Raml\TypeInterface === false) { + if ($property instanceof Type === false) { $property = ApiDefinition::determineType($name, $property); } $this->properties[] = $property; @@ -164,12 +171,10 @@ public function getPropertyByName($name) return null; } - - /** * Get the value of Min Properties * - * @return mixed + * @return int */ public function getMinProperties() { @@ -179,13 +184,12 @@ public function getMinProperties() /** * Set the value of Min Properties * - * @param mixed $minProperties - * + * @param int $minProperties * @return self */ public function setMinProperties($minProperties) { - $this->minProperties = $minProperties; + $this->minProperties = (int) $minProperties; return $this; } @@ -193,7 +197,7 @@ public function setMinProperties($minProperties) /** * Get the value of Max Properties * - * @return mixed + * @return int */ public function getMaxProperties() { @@ -203,13 +207,12 @@ public function getMaxProperties() /** * Set the value of Max Properties * - * @param mixed $maxProperties - * + * @param int $maxProperties * @return self */ public function setMaxProperties($maxProperties) { - $this->maxProperties = $maxProperties; + $this->maxProperties = (int) $maxProperties; return $this; } @@ -241,7 +244,7 @@ public function setAdditionalProperties($additionalProperties) /** * Get the value of Discriminator * - * @return mixed + * @return string */ public function getDiscriminator() { @@ -251,8 +254,7 @@ public function getDiscriminator() /** * Set the value of Discriminator * - * @param mixed $discriminator - * + * @param string $discriminator * @return self */ public function setDiscriminator($discriminator) @@ -265,7 +267,7 @@ public function setDiscriminator($discriminator) /** * Get the value of Discriminator Value * - * @return mixed + * @return string */ public function getDiscriminatorValue() { @@ -275,8 +277,7 @@ public function getDiscriminatorValue() /** * Set the value of Discriminator Value * - * @param mixed $discriminatorValue - * + * @param string $discriminatorValue * @return self */ public function setDiscriminatorValue($discriminatorValue) @@ -294,13 +295,14 @@ public function validate($value) if (!is_array($value)) { if (!is_object($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'object', $value); + return; } // in case of stdClass - convert it to array for convenience $value = get_object_vars($value); } foreach ($this->getProperties() as $property) { - if ($property->getRequired()&& !array_key_exists($property->getName(), $value)) { + if ($property->getRequired() && !array_key_exists($property->getName(), $value)) { $this->errors[] = TypeValidationError::missingRequiredProperty($property->getName()); } } @@ -310,11 +312,16 @@ public function validate($value) if ($this->additionalProperties === false) { $this->errors[] = TypeValidationError::unexpectedProperty($name); } + continue; } + $property->validate($propertyValue); - if (!$property->isValid()) { - $this->errors = array_merge($this->errors, $property->getErrors()); + if ($property->isValid()) { + continue; + } + foreach ($property->getErrors() as $error) { + $this->errors[] = $error; } } } diff --git a/src/Types/StringType.php b/src/Types/StringType.php index e700d045..114687fa 100644 --- a/src/Types/StringType.php +++ b/src/Types/StringType.php @@ -15,24 +15,24 @@ class StringType extends Type * Regular expression that this string should match. * * @var string - **/ - private $pattern = null; + */ + private $pattern; /** * Minimum length of the string. Value MUST be equal to or greater than 0. * Default: 0 * * @var int - **/ - private $minLength = null; + */ + private $minLength; /** * Maximum length of the string. Value MUST be equal to or greater than 0. * Default: 2147483647 * * @var int - **/ - private $maxLength = null; + */ + private $maxLength; /** * Create a new StringType from an array of data @@ -45,22 +45,25 @@ class StringType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); - /* @var $type StringType */ + assert($type instanceof self); foreach ($data as $key => $value) { switch ($key) { case 'pattern': $type->setPattern($value); + break; case 'minLength': $type->setMinLength($value); + break; case 'maxLength': $type->setMaxLength($value); + break; } } - + return $type; } @@ -143,17 +146,23 @@ public function validate($value) if (!is_string($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'string', $value); } - if (!is_null($this->pattern)) { - if (preg_match('/'.$this->pattern.'/', $value) == false) { + if (null !== $this->pattern) { + $pregMatchResult = preg_match('/' . $this->pattern . '/', $value); + $failed = $pregMatchResult === false; + if ($failed) { + throw new \RuntimeException(sprintf('Failed to look up for "%s" with regex "%s"', var_export($value, true), $this->pattern)); + } + $foundNothing = $pregMatchResult === 0; + if ($foundNothing) { $this->errors[] = TypeValidationError::stringPatternMismatch($this->getName(), $this->pattern, $value); } } - if (!is_null($this->minLength)) { + if (null !== $this->minLength) { if (strlen($value) < $this->minLength) { $this->errors[] = TypeValidationError::stringLengthExceedsMinimum($this->getName(), $this->minLength, $value); } } - if (!is_null($this->maxLength)) { + if (null !== $this->maxLength) { if (strlen($value) > $this->maxLength) { $this->errors[] = TypeValidationError::stringLengthExceedsMaximum($this->getName(), $this->maxLength, $value); } diff --git a/src/Types/TimeOnlyType.php b/src/Types/TimeOnlyType.php index d671895c..46850119 100644 --- a/src/Types/TimeOnlyType.php +++ b/src/Types/TimeOnlyType.php @@ -10,6 +10,8 @@ */ class TimeOnlyType extends Type { + const FORMAT = 'H:i:s'; + /** * Create a new TimeOnlyType from an array of data * @@ -21,6 +23,7 @@ class TimeOnlyType extends Type public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); return $type; } @@ -29,10 +32,10 @@ public function validate($value) { parent::validate($value); - $d = DateTime::createFromFormat('HH:II:SS', $value); + $d = DateTime::createFromFormat(self::FORMAT, $value); - if ($d && $d->format('HH:II:SS') !== $value) { - $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'HH:II:SS', $value); + if ($d && $d->format(self::FORMAT) !== $value) { + $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), self::FORMAT, $value); } } } diff --git a/src/Types/TypeValidationError.php b/src/Types/TypeValidationError.php index d00d1c3f..1774866a 100644 --- a/src/Types/TypeValidationError.php +++ b/src/Types/TypeValidationError.php @@ -28,6 +28,12 @@ public function __toString() return sprintf('%s (%s)', $this->property, $this->constraint); } + /** + * @param string $property + * @param string $pattern + * @param string $value + * @return self + */ public static function stringPatternMismatch($property, $pattern, $value) { return new self($property, sprintf( @@ -37,40 +43,62 @@ public static function stringPatternMismatch($property, $pattern, $value) )); } + /** + * @param string $message + * @return self + */ public static function xmlValidationFailed($message) { return new self($message, 'xml validation'); } + /** + * @param string $message + * @return self + */ public static function jsonValidationFailed($message) { return new self($message, 'json validation'); } + /** + * @param string $property + * @return self + */ public static function missingRequiredProperty($property) { return new self($property, sprintf('Missing required property')); } + /** + * @param string $property + * @return self + */ public static function unexpectedProperty($property) { return new self($property, sprintf('Unexpected property')); } - public static function isNotMultipleOf($property, $multiplicator, $actualValue) + /** + * @param string $property + * @param int|float $multiplier + * @param int|float $actualValue + * @return self + */ + public static function isNotMultipleOf($property, $multiplier, $actualValue) { return new self($property, sprintf( - 'Minimum allowed value: %s, got %s', - $multiplicator, - $actualValue + '%s is not multiple of %ss', + $actualValue, + $multiplier )); } /** - * @param $property - * @param $possibleValues - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param string[] $possibleValues + * @param mixed $actualValue + * @return self */ public static function unexpectedValue($property, $possibleValues, $actualValue) { @@ -83,13 +111,14 @@ public static function unexpectedValue($property, $possibleValues, $actualValue) } /** - * @param $constraint - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param mixed $constraint + * @param mixed $actualValue + * @return self */ public static function unexpectedValueType($property, $constraint, $actualValue) { - $value = is_array($actualValue) ? json_encode($actualValue) : (string)$actualValue; + $value = is_array($actualValue) ? json_encode($actualValue) : (string) $actualValue; return new self($property, sprintf( 'Expected %s, got (%s) "%s"', @@ -100,9 +129,10 @@ public static function unexpectedValueType($property, $constraint, $actualValue) } /** - * @param $constraint - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param mixed $constraint + * @param mixed $actualValue + * @return self */ public static function unexpectedArrayValueType($property, $constraint, $actualValue) { @@ -115,40 +145,40 @@ public static function unexpectedArrayValueType($property, $constraint, $actualV } /** - * @param $property - * @param $minLength - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param int $minLength + * @param string $actualValue + * @return self */ public static function stringLengthExceedsMinimum($property, $minLength, $actualValue) { return new self($property, sprintf( 'Minimum allowed length: %d, got %d', $minLength, - strlen($actualValue) + mb_strlen($actualValue) )); } /** - * @param $property - * @param $maxLength - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param int $maxLength + * @param string $actualValue + * @return self */ public static function stringLengthExceedsMaximum($property, $maxLength, $actualValue) { return new self($property, sprintf( 'Maximum allowed length: %d, got %d', $maxLength, - strlen($actualValue) + mb_strlen($actualValue) )); } /** - * @param $property - * @param $minValue - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param mixed $minValue + * @param mixed $actualValue + * @return self */ public static function valueExceedsMinimum($property, $minValue, $actualValue) { @@ -160,10 +190,10 @@ public static function valueExceedsMinimum($property, $minValue, $actualValue) } /** - * @param $property - * @param $maxValue - * @param $actualValue - * @return TypeValidationError + * @param string $property + * @param mixed $maxValue + * @param mixed $actualValue + * @return self */ public static function valueExceedsMaximum($property, $maxValue, $actualValue) { @@ -176,20 +206,20 @@ public static function valueExceedsMaximum($property, $maxValue, $actualValue) /** * @param string $property - * @param $min $expected - * @param $max $expected - * @param integer $actual - * @return TypeValidationError + * @param int $min + * @param int $max + * @param int $actualSize + * @return self */ - public static function arraySizeValidationFailed($property, $min, $max, $actual) + public static function arraySizeValidationFailed($property, $min, $max, $actualSize) { - return new self($property, sprintf('Allowed array size: between %s and %s, got %s', $min, $max, $actual)); + return new self($property, sprintf('Allowed array size: between %s and %s, got %s', $min, $max, $actualSize)); } /** * @param string $property * @param array $errorsGroupedByTypes - * @return TypeValidationError + * @return self */ public static function unionTypeValidationFailed($property, array $errorsGroupedByTypes) { diff --git a/src/Types/UnionType.php b/src/Types/UnionType.php index dd443f7a..a692ee3d 100644 --- a/src/Types/UnionType.php +++ b/src/Types/UnionType.php @@ -17,20 +17,21 @@ class UnionType extends Type * Possible Types * * @var array - **/ + */ private $possibleTypes = []; /** * Create a new UnionType from an array of data * - * @param string $name - * @param array $data + * @param string $name + * @param array $data * * @return UnionType */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); + assert($type instanceof self); $type->setPossibleTypes(explode('|', $type->getType())); $type->setType('union'); @@ -65,6 +66,8 @@ public function setPossibleTypes(array $possibleTypes) public function validate($value) { + $errors = []; + foreach ($this->getPossibleTypes() as $type) { if (!$type->discriminate($value)) { continue; diff --git a/src/Types/XmlType.php b/src/Types/XmlType.php index 8c48cbc3..d21e177b 100644 --- a/src/Types/XmlType.php +++ b/src/Types/XmlType.php @@ -15,32 +15,30 @@ class XmlType extends Type /** * XML schema * - * @var string - **/ + * @var array + */ private $xml; /** * Create a new JsonType from an array of data * - * @param string $name - * @param array $data - * - * @return StringType + * @param string $name + * @param array $data + * @return self */ public static function createFromArray($name, array $data = []) { $type = parent::createFromArray($name, $data); - /* @var $type StringType */ - + assert($type instanceof self); $type->xml = $data; - + return $type; } /** * Validate an XML string against the schema * - * @param $string + * @param mixed $value */ public function validate($value) { @@ -61,7 +59,7 @@ public function validate($value) return; } - + libxml_use_internal_errors($originalErrorLevel); } } diff --git a/src/Utility/StringTransformer.php b/src/Utility/StringTransformer.php index 9f9097b1..68c5a147 100644 --- a/src/Utility/StringTransformer.php +++ b/src/Utility/StringTransformer.php @@ -4,12 +4,12 @@ class StringTransformer { - const LOWER_CAMEL_CASE = 1, - LOWER_HYPHEN_CASE = 2, - LOWER_UNDERSCORE_CASE = 4, - UPPER_CAMEL_CASE = 8, - UPPER_HYPHEN_CASE = 16, - UPPER_UNDERSCORE_CASE = 32; + const LOWER_CAMEL_CASE = 1; + const LOWER_HYPHEN_CASE = 2; + const LOWER_UNDERSCORE_CASE = 4; + const UPPER_CAMEL_CASE = 8; + const UPPER_HYPHEN_CASE = 16; + const UPPER_UNDERSCORE_CASE = 32; private static $possibleTransformations = [ self::LOWER_CAMEL_CASE, @@ -17,7 +17,7 @@ class StringTransformer self::LOWER_UNDERSCORE_CASE, self::UPPER_CAMEL_CASE, self::UPPER_HYPHEN_CASE, - self::UPPER_UNDERSCORE_CASE + self::UPPER_UNDERSCORE_CASE, ]; /** @@ -27,54 +27,55 @@ class StringTransformer * @param int $convertTo Type of conversion to apply (use constants). * * @return string Returns the converted string - **/ + */ public static function convertString($string, $convertTo) { - if (!in_array($convertTo, self::$possibleTransformations)) - { - throw new \Exception('Invalid parameter "'.$convertTo.'" given for '.__CLASS__.__METHOD__); + if (!in_array($convertTo, self::$possibleTransformations, true)) { + throw new \Exception('Invalid parameter "' . $convertTo . '" given for ' . __CLASS__ . __METHOD__); } // make a best possible guess about input type and split string into parts preg_match_all('/((?:^|[A-Z])[A-Z]|[a-z]+|(?:_)[A-Z]|[a-z]+|(?:-)[A-Z]|[a-z]+)/', $string, $matches); $split = $matches[0]; $newString = ''; - for ($i=0, $size = count($split); $i < $size; $i++) { - if ($i === 0) { - $delimiter = ''; - } else { - if ($convertTo === self::LOWER_HYPHEN_CASE || $convertTo === self::UPPER_HYPHEN_CASE) - { + $delimiter = ''; + for ($i = 0, $size = count($split); $i < $size; $i++) { + if ($i > 0) { + if ($convertTo === self::LOWER_HYPHEN_CASE || $convertTo === self::UPPER_HYPHEN_CASE) { $delimiter = '-'; } - if ($convertTo === self::LOWER_UNDERSCORE_CASE || $convertTo === self::UPPER_UNDERSCORE_CASE) - { + if ($convertTo === self::LOWER_UNDERSCORE_CASE || $convertTo === self::UPPER_UNDERSCORE_CASE) { $delimiter = '_'; } } switch ($convertTo) { case self::LOWER_CAMEL_CASE: - if ($i === 0) - { + if ($i === 0) { $newString .= lcfirst($split[$i]); + break; } // LOWER_CAMEL_CASE only applies to first part, rest follows same logic as UPPER_CAMEL_CASE + // no break case self::UPPER_CAMEL_CASE: $newString .= ucfirst($split[$i]); + break; // lowercase functions case self::LOWER_HYPHEN_CASE: case self::LOWER_UNDERSCORE_CASE: - $newString .= $delimiter.strtolower($split[$i]); + $newString .= $delimiter . strtolower($split[$i]); + break; // uppercase functions case self::UPPER_UNDERSCORE_CASE: case self::UPPER_HYPHEN_CASE: - $newString .= $delimiter.strtoupper($split[$i]); + $newString .= $delimiter . strtoupper($split[$i]); + break; } } + return $newString; } } diff --git a/src/Utility/TraitParserHelper.php b/src/Utility/TraitParserHelper.php new file mode 100644 index 00000000..13dd7ee1 --- /dev/null +++ b/src/Utility/TraitParserHelper.php @@ -0,0 +1,117 @@ + &$value) { + $newKey = static::applyFunctions($key, $values); + + if (is_array($value)) { + $value = static::applyVariables($values, $value); + } else { + $value = static::applyFunctions($value, $values); + } + $newTrait[$newKey] = $value; + } + + return $newTrait; + } + + /** + * @param string $trait + * @param array $values + * @return string + */ + private static function applyFunctions($trait, array $values) + { + $variables = implode('|', array_keys($values)); + + return preg_replace_callback( + '/<<(' . $variables . ')' . + '(' . + '[\s]*\|[\s]*!' . + '(' . + 'singularize|pluralize|uppercase|lowercase|lowercamelcase|uppercamelcase|lowerunderscorecase|upperunderscorecase|lowerhyphencase|upperhyphencase' . + ')' . + ')?>>/', + function ($matches) use ($values) { + $transformer = isset($matches[3]) ? $matches[3] : ''; + switch ($transformer) { + case 'singularize': + return Inflect::singularize($values[$matches[1]]); + + break; + case 'pluralize': + return Inflect::pluralize($values[$matches[1]]); + + break; + case 'uppercase': + return strtoupper($values[$matches[1]]); + + break; + case 'lowercase': + return strtolower($values[$matches[1]]); + + break; + case 'lowercamelcase': + return StringTransformer::convertString( + $values[$matches[1]], + StringTransformer::LOWER_CAMEL_CASE + ); + + break; + case 'uppercamelcase': + return StringTransformer::convertString( + $values[$matches[1]], + StringTransformer::UPPER_CAMEL_CASE + ); + + break; + case 'lowerunderscorecase': + return StringTransformer::convertString( + $values[$matches[1]], + StringTransformer::LOWER_UNDERSCORE_CASE + ); + + break; + case 'upperunderscorecase': + return StringTransformer::convertString( + $values[$matches[1]], + StringTransformer::UPPER_UNDERSCORE_CASE + ); + + break; + case 'lowerhyphencase': + return StringTransformer::convertString( + $values[$matches[1]], + StringTransformer::LOWER_HYPHEN_CASE + ); + + break; + case 'upperhyphencase': + return StringTransformer::convertString( + $values[$matches[1]], + StringTransformer::UPPER_HYPHEN_CASE + ); + + break; + default: + return $values[$matches[1]]; + } + }, + $trait + ); + } +} diff --git a/src/Validator/ContentConverter.php b/src/Validator/ContentConverter.php index 5d78a693..4f8126dd 100644 --- a/src/Validator/ContentConverter.php +++ b/src/Validator/ContentConverter.php @@ -26,7 +26,7 @@ public static function convertStringByContentType($string, $contentType) throw new InvalidJsonException(json_last_error_msg()); } } elseif (in_array($generalContentType, self::$xmlTypes, true)) { - $value = new \DOMDocument; + $value = new \DOMDocument(); $originalErrorLevel = libxml_use_internal_errors(true); @@ -49,7 +49,7 @@ public static function convertStringByContentType($string, $contentType) private static function parseMediaRange($mediaRange) { $parts = explode(';', $mediaRange); - $params = array(); + $params = []; foreach ($parts as $i => $param) { if (strpos($param, '=') !== false) { list($k, $v) = explode('=', trim($param)); @@ -57,12 +57,12 @@ private static function parseMediaRange($mediaRange) } } $fullType = trim($parts[0]); - if ($fullType == '*') { + if ($fullType === '*') { return '*/*'; } list($type, $subtype) = explode('/', $fullType); if (!$subtype) { - throw new \UnexpectedValueException('Malformed media-range: '.$mediaRange); + throw new \UnexpectedValueException('Malformed media-range: ' . $mediaRange); } $plusPos = strpos($subtype, '+'); if (false !== $plusPos) { diff --git a/src/Validator/RequestValidator.php b/src/Validator/RequestValidator.php index dced2fe2..5cbbc293 100644 --- a/src/Validator/RequestValidator.php +++ b/src/Validator/RequestValidator.php @@ -1,4 +1,5 @@ __toString(); }, $errors)); } diff --git a/src/Validator/ResponseValidator.php b/src/Validator/ResponseValidator.php index b18e4905..6e3a865f 100644 --- a/src/Validator/ResponseValidator.php +++ b/src/Validator/ResponseValidator.php @@ -85,7 +85,7 @@ private function assertValidHeaders(RequestInterface $request, ResponseInterface $schemaHeader->validate($headerValue); } catch (ValidationException $exception) { $message = sprintf( - 'Response header %s with value "%s" for %s %s '. + 'Response header %s with value "%s" for %s %s ' . 'with status code %s does not match schema: %s', $key, $headerValue, @@ -139,14 +139,14 @@ private function assertValidBody(RequestInterface $request, ResponseInterface $r */ private function getNamedParametersAsString(array $errors) { - return join(', ', array_map(function (NamedParameter $parameter) { + return implode(', ', array_map(function (NamedParameter $parameter) { return $parameter->getDisplayName(); }, $errors)); } private function getTypeValidationErrorsAsString(array $errors) { - return join(', ', array_map(function (TypeValidationError $error) { + return implode(', ', array_map(function (TypeValidationError $error) { return $error->__toString(); }, $errors)); } diff --git a/src/Validator/ValidatorException.php b/src/Validator/ValidatorException.php index 80502ac6..58c23b1f 100644 --- a/src/Validator/ValidatorException.php +++ b/src/Validator/ValidatorException.php @@ -1,4 +1,5 @@ apiDefinition = $api; } + /** + * @return string[] + */ public function getDefaultMediaTypes() { return $this->apiDefinition->getDefaultMediaTypes(); @@ -66,9 +73,9 @@ public function getRequestBody($method, $path, $contentType) } /** - * @param $method - * @param $path - * @return \Raml\Response[] + * @param string $method + * @param string $path + * @return Response[] */ public function getResponses($method, $path) { @@ -79,7 +86,7 @@ public function getResponses($method, $path) * @param string $method * @param string $path * @param int $statusCode - * @return \Raml\Response + * @return Response * @throws ValidatorSchemaException */ public function getResponse($method, $path, $statusCode) @@ -142,12 +149,15 @@ public function getResponseHeaders($method, $path, $statusCode, $requiredOnly = * @param string $path * @param string $contentType * @return Body + * @throws EmptyBodyException * @throws ValidatorSchemaException */ private function getBody(MessageSchemaInterface $schema, $method, $path, $contentType) { try { $body = $schema->getBodyByType($contentType); + } catch (EmptyBodyException $e) { + throw new EmptyBodyException($e->getMessage()); } catch (Exception $exception) { $message = sprintf( 'Schema for %s %s with content type %s was not found in API definition', @@ -155,7 +165,7 @@ private function getBody(MessageSchemaInterface $schema, $method, $path, $conten $path, $contentType ); - + throw new ValidatorSchemaException($message, 0, $exception); } @@ -174,7 +184,7 @@ private function getBody(MessageSchemaInterface $schema, $method, $path, $conten /** * @param string $path - * @return \Raml\Resource + * @return Resource * @throws ValidatorSchemaException */ private function getResource($path) @@ -195,15 +205,16 @@ private function getResource($path) * @param string $method * @param string $path * @return Method - * @throws Exception + * @throws ValidatorSchemaException */ private function getMethod($method, $path) { - $resource = $this->getResource($path); - try { + $resource = $this->getResource($path); + assert($resource instanceof Resource); + return $resource->getMethod($method); - } catch (Exception $exception) { + } catch (\Exception $exception) { throw new ValidatorSchemaException(sprintf( 'Schema for %s %s was not found in API definition', strtoupper($method), diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index 4fe5ae2b..f5ee624c 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -7,7 +7,7 @@ interface ValidatorInterface { /** - * @param $value + * @param mixed $value */ public function validate($value); @@ -17,7 +17,7 @@ public function validate($value); public function getErrors(); /** - * @return boolean + * @return bool */ public function isValid(); } diff --git a/src/WebFormBody.php b/src/WebFormBody.php index 8b214f25..59ac70da 100644 --- a/src/WebFormBody.php +++ b/src/WebFormBody.php @@ -7,21 +7,21 @@ /** * @see http://raml.org/spec.html#web-forms */ -class WebFormBody extends NamedParameter implements BodyInterface, ArrayInstantiationInterface +class WebFormBody extends NamedParameter implements BodyInterface { /** - * var array $namedParameters An array of \NamedParameter objects + * @var NamedParameter[] */ - private $namedParameters = array(); + private $namedParameters = []; /** * List of valid media types * - * @var array + * @var string[] */ public static $validMediaTypes = [ 'application/x-www-form-urlencoded', - 'multipart/form-data' + 'multipart/form-data', ]; /** @@ -29,12 +29,12 @@ class WebFormBody extends NamedParameter implements BodyInterface, ArrayInstanti * * @param string $mediaType * - * @throws \Exception + * @throws \InvalidArgumentException */ public function __construct($mediaType) { - if (!in_array($mediaType, self::$validMediaTypes)) { - throw new \Exception('Invalid type'); + if (!in_array($mediaType, self::$validMediaTypes, true)) { + throw new \InvalidArgumentException(sprintf('%s is invalid type', $mediaType)); } parent::__construct($mediaType); @@ -54,9 +54,8 @@ public function getMediaType() * Create a new WebFormObject from an array * * @param string $key The valid media type to use as the key - * @param array $data The array of data to create \NamedParameter objects from - * - * @return \Raml\WebFormBody + * @param array $data The array of data to create NamedParameter objects from + * @return WebFormBody */ public static function createFromArray($key, array $data = []) { @@ -76,9 +75,9 @@ public static function createFromArray($key, array $data = []) /** * Add a NamedParameter object to the list * - * @param namedParameter $namedParameter + * @param NamedParameter $namedParameter */ - public function addParameter(namedParameter $namedParameter) + public function addParameter(NamedParameter $namedParameter) { $this->namedParameters[$namedParameter->getKey()] = $namedParameter; } @@ -87,9 +86,9 @@ public function addParameter(namedParameter $namedParameter) * Get a named parameter object by key name * * @param string $key The name of the key for the named parameter + * @return NamedParameter * * @throws InvalidKeyException - * @return NamedParameter */ public function getParameter($key) { @@ -103,7 +102,7 @@ public function getParameter($key) /** * Get all NamedParameter objects * - * @return NamedParameter[] An array of NamedParameter objects + * @return NamedParameter[] */ public function getParameters() { diff --git a/test/ApiDefinitionTest.php b/test/ApiDefinitionTest.php deleted file mode 100644 index 16a096d3..00000000 --- a/test/ApiDefinitionTest.php +++ /dev/null @@ -1,391 +0,0 @@ -parser = new \Raml\Parser(); - setlocale(LC_NUMERIC, 'C'); - } - - /** @test */ - public function shouldReturnFullResourcesForRamlFileWithDefaultFormatter() - { - $api = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); - $routes = $api->getResourcesAsUri(); - - $this->assertCount(4, $routes->getRoutes()); - $this->assertEquals([ - 'GET /songs', - 'POST /songs/{songId}', - 'GET /songs/{songId}', - 'DELETE /songs/{songId}' - ], array_keys($routes->getRoutes())); - } - - /** @test */ - public function shouldReturnFullResourcesForRamlFileWithNoFormatter() - { - $api = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); - - $noFormatter = new Raml\RouteFormatter\NoRouteFormatter(); - $routes = $api->getResourcesAsUri($noFormatter, $api->getResources()); - - $this->assertCount(4, $routes->getRoutes()); - $this->assertEquals([ - 'GET /songs', - 'POST /songs/{songId}', - 'GET /songs/{songId}', - 'DELETE /songs/{songId}' - ], array_keys($routes->getRoutes())); - } - - /** @test */ - public function shouldReturnFullResourcesForRamlFileWithSymfonyFormatter() - { - $api = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); - - $routeCollection= new Symfony\Component\Routing\RouteCollection(); - $routeFormatter = new Raml\RouteFormatter\SymfonyRouteFormatter($routeCollection); - $routes = $api->getResourcesAsUri($routeFormatter, $api->getResources()); - - $this->assertEquals($routeFormatter, $routes); - - $this->assertCount(4, $routeFormatter->getRoutes()); - $this->assertInstanceOf('\Symfony\Component\Routing\RouteCollection', $routeFormatter->getRoutes()); - $this->assertInstanceOf('\Symfony\Component\Routing\Route', $routeFormatter->getRoutes()->get('GET /songs/')); - $this->assertEquals(['http'], $routeFormatter->getRoutes()->get('GET /songs/')->getSchemes()); - } - - /** @test */ - public function shouldReturnURIProtocol() - { - $api = $this->parser->parse(__DIR__.'/fixture/protocols/noProtocolSpecified.raml'); - $this->assertCount(1, $api->getProtocols()); - $this->assertSame(array( - 'HTTP', - ), $api->getProtocols()); - } - - /** @test */ - public function shouldProcessTypes() - { - $api = $this->parser->parse(__DIR__.'/fixture/types.raml'); - $this->assertCount(1, $api->getTypes()); - $this->assertSame(array( - 'User' => array( - 'type' => 'object', - 'properties' => array( - 'firstname' => 'string', - 'lastname' => 'string', - 'age' => 'number', - ) - ) - ), $api->getTypes()->toArray()); - } - - /** @test */ - public function shouldParseTypesToSubTypes() - { - $api = $this->parser->parse(__DIR__.'/fixture/raml-1.0/types.raml'); - $types = $api->getTypes(); - $object = $types->current(); - $this->assertInstanceOf('\Raml\Types\ObjectType', $object); - $this->assertInstanceOf('\Raml\Types\IntegerType', $object->getPropertyByName('id')); - $this->assertInstanceOf('\Raml\Types\StringType', $object->getPropertyByName('name')); - } - - /** @test */ - public function shouldParseComplexTypes() - { - $api = $this->parser->parse(__DIR__.'/fixture/raml-1.0/complexTypes.raml'); - // check types - $org = $api->getTypes()->getTypeByName('Org'); - $this->assertInstanceOf('\Raml\Types\ObjectType', $org); - // property will return a proxy object so to compare to actual type we will need to ask for the resolved object - $this->assertInstanceOf('\Raml\Types\UnionType', $org->getPropertyByName('onCall')->getResolvedObject()); - $head = $org->getPropertyByName('Head'); - $this->assertInstanceOf('\Raml\Types\ObjectType', $head->getResolvedObject()); - $this->assertInstanceOf('\Raml\Types\StringType', $head->getPropertyByName('firstname')); - $this->assertInstanceOf('\Raml\Types\StringType', $head->getPropertyByName('lastname')); - $this->assertInstanceOf('\Raml\Types\StringType', $head->getPropertyByName('title')); - $this->assertInstanceOf('\Raml\Types\StringType', $head->getPropertyByName('kind')); - $reports = $head->getPropertyByName('reports'); - $this->assertInstanceOf('\Raml\Types\ArrayType', $reports); - $phone = $head->getPropertyByName('phone')->getResolvedObject(); - $this->assertInstanceOf('\Raml\Types\StringType', $phone); - // check resources - $type = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json')->getType(); - $this->assertInstanceOf('\Raml\Types\ObjectType', $type->getResolvedObject()); - } - - /** @test */ - public function shouldPassValidResponse() - { - $api = $this->parser->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); - $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType( - 'application/json' - ); - /* @var $body \Raml\Body */ - - $validResponse = '{ - "onCall": { - "firstname": "John", - "lastname": "Flare", - "age": 41, - "kind": "AlertableAdmin", - "clearanceLevel": "low", - "phone": "12321" - }, - "Head": { - "firstname": "Nico", - "lastname": "Ark", - "age": 41, - "kind": "Manager", - "reports": [ - { - "firstname": "Archie", - "lastname": "Ark", - "age": 40, - "kind": "Admin" - } - ], - "phone": "123-23" - } - }'; - $type = $body->getType(); - $type->validate(json_decode($validResponse, true)); - self::assertTrue($type->isValid(), sprintf("Validation failed with following errors: %s", implode(', ', array_map('strval', $type->getErrors())))); - } - - /** @test */ - public function shouldRejectMissingParameterResponse() - { - $api = $this->parser->parse(__DIR__.'/fixture/raml-1.0/complexTypes.raml'); - $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); - /* @var $body \Raml\Body */ - $type = $body->getType(); - - $invalidResponse = [ - 'onCall' => 'this is not an object', - 'Head' => 'this is not a Head object' - ]; - - $type->validate($invalidResponse); - $this->assertValidationFailedWithErrors( - $type, - [ - new TypeValidationError( - 'Alertable', - 'Value did not pass validation against any type: ' - . 'Manager (Manager (Expected object, got (string) "this is not an object")), ' - . 'AlertableAdmin (AlertableAdmin (Expected object, got (string) "this is not an object"))' - ), - new TypeValidationError('Head', 'Expected object, got (string) "this is not a Head object"'), - ] - ); - } - - /** @test */ - public function shouldRejectInvalidIntegerParameterResponse() - { - $api = $this->parser->parse(__DIR__.'/fixture/raml-1.0/complexTypes.raml'); - $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); - /* @var $body \Raml\Body */ - $type = $body->getType(); - - $invalidResponse = [ - 'onCall' => [ - "firstname" => "John", - "lastname" => "Flare", - "age" => 18.5, - "kind" => "AlertableAdmin", - "clearanceLevel" => "low", - "phone" => "12321", - ], - 'Head' => [ - "firstname" => "Nico", - "lastname" => "Ark", - "age" => 71, - "kind" => "Manager", - "reports" => [ - [ - "firstname" => "Archie", - "lastname" => "Ark", - "kind" => "Admin", - "age" => 17, - "clearanceLevel" => "low", - ], - ], - "phone" => "123-23" - ] - ]; - - $type->validate($invalidResponse); - $this->assertValidationFailedWithErrors( - $type, - [ - new TypeValidationError('age', 'Maximum allowed value: 70, got 71'), - new TypeValidationError('age', 'Minimum allowed value: 18, got 17'), - new TypeValidationError( - 'Alertable', - 'Value did not pass validation against any type: ' - . 'AlertableAdmin (age (Expected int, got (double) "18.5"))' - ), - ] - ); - } - - /** @test */ - public function shouldRejectInvalidStringParameterResponse() - { - $api = $this->parser->parse(__DIR__.'/fixture/raml-1.0/complexTypes.raml'); - $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); - /* @var $body \Raml\Body */ - $type = $body->getType(); - - $invalidResponse = [ - 'onCall' => [ - "firstname" => "John", - "lastname" => "F", - "age" => 30, - "kind" => "AlertableAdmin", - "clearanceLevel" => "low", - "phone" => "12321", - ], - 'Head' => [ - "firstname" => "Nico von Teufelspieler, the true duke of northern Blasphomores", - "lastname" => "Ark", - "age" => 30, - "kind" => "Manager", - "reports" => [ - [ - "firstname" => "Archie", - "lastname" => "Ark", - "kind" => "Admin", - "age" => 30, - "clearanceLevel" => "low", - ], - ], - "phone" => "123-23 33 22" - ] - ]; - - $type->validate($invalidResponse); - $this->assertValidationFailedWithErrors( - $type, - [ - new TypeValidationError('firstname', 'Maximum allowed length: 50, got 62'), - new TypeValidationError('Phone', 'String "123-23 33 22" did not match pattern /^[0-9|-]+$/'), - new TypeValidationError( - 'Alertable', - 'Value did not pass validation against any type: ' - . 'AlertableAdmin (lastname (Minimum allowed length: 2, got 1))' - ), - ] - ); - } - - /** @test */ - public function shouldRejectInvalidEnumParameterResponse() - { - $api = $this->parser->parse(__DIR__.'/fixture/raml-1.0/complexTypes.raml'); - $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); - /* @var $body \Raml\Body */ - $type = $body->getType(); - - $invalidResponse = [ - 'onCall' => [ - "firstname" => "John", - "lastname" => "Flare", - "age" => 30, - "kind" => "AlertableAdmin", - "clearanceLevel" => "average", - "phone" => "12321", - ], - 'Head' => [ - "firstname" => "Nico", - "lastname" => "Ark", - "age" => 30, - "kind" => "Manager", - "reports" => [ - [ - "firstname" => "Archie", - "lastname" => "Ark", - "kind" => "Admin", - "age" => 30, - ], - ], - "phone" => "123-23" - ] - ]; - - $type->validate($invalidResponse); - $this->assertValidationFailedWithErrors( - $type, - [ - new TypeValidationError( - 'Alertable', - 'Value did not pass validation against any type: ' - . 'AlertableAdmin (ClearanceLevels (Expected any of [low, high], got (string) "average"))' - ), - ] - ); - } - - /** @test */ - public function shouldReturnProtocolsIfSpecified() - { - $api = $this->parser->parse(__DIR__.'/fixture/protocols/protocolsSpecified.raml'); - $this->assertCount(2, $api->getProtocols()); - $this->assertSame(array( - 'HTTP', - 'HTTPS' - ), $api->getProtocols()); - } - - /** @test */ - public function shouldThrowInvalidProtocolExceptionIfWrongProtocol() - { - $this->setExpectedException('Raml\Exception\BadParameter\InvalidProtocolException'); - - $this->parser->parse(__DIR__.'/fixture/protocols/invalidProtocolsSpecified.raml'); - } - - /** - * @param ValidatorInterface $validator - * @param TypeValidationError[] $errors - */ - private function assertValidationFailedWithErrors(ValidatorInterface $validator, $errors) - { - self::assertFalse($validator->isValid(), 'Validator expected to fail'); - foreach ($errors as $error) { - self::assertContains( - $error, - $validator->getErrors(), - $message = sprintf('Validator expected to contain error: %s', $error->__toString()), - $ignoreCase = false, - $checkObjectidentity = false - ); - } - } - - - /** @test */ - public function shouldReturnFullResourcesNameForRamlFileWithUrlPrefix() - { - $api = $this->parser->parse(__DIR__.'/fixture/includeUrlPrefix.raml'); - $this->assertEquals([ - '/prefix/songs', - ], array_keys($api->getResources())); - } -} diff --git a/test/MethodTest.php b/test/MethodTest.php deleted file mode 100644 index 6d83962b..00000000 --- a/test/MethodTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertSame('GET', $method->getType()); - - $method = \Raml\Method::createFromArray('Post', [], $apiDefinition); - $this->assertSame('POST', $method->getType()); - } - - /** @test */ - public function shouldGetTheDescriptionIfPassedInTheDataArray() - { - $apiDefinition = new \Raml\ApiDefinition('The title'); - - $method = \Raml\Method::createFromArray('get', ['description' => 'A dummy description'], $apiDefinition); - $this->assertSame('A dummy description', $method->getDescription()); - - $method = \Raml\Method::createFromArray('get', [], $apiDefinition); - $this->assertNull($method->getDescription()); - } - - /** @test */ - public function shouldGetNullForResponseIfNoneIsExists() - { - $apiDefinition = new \Raml\ApiDefinition('The title'); - - $method = \Raml\Method::createFromArray('get', [], $apiDefinition); - $this->assertSame([], $method->getResponses()); - $this->assertSame(null, $method->getResponse(200)); - } - - /** @test */ - public function shouldGetNullForResponseIfNotAnArrayIsPassed() - { - $apiDefinition = new \Raml\ApiDefinition('The title'); - - $method = \Raml\Method::createFromArray('get', ['responses' => 'invalid'], $apiDefinition); - $this->assertSame([], $method->getResponses()); - $this->assertSame(null, $method->getResponse(200)); - } - - /** @test */ - public function shouldGetValidResponsesIfPassedExpectedValues() - { - $apiDefinition = new \Raml\ApiDefinition('The title'); - - $method = \Raml\Method::createFromArray( - 'get', - [ - 'description' => 'A dummy method', - 'responses' => [ - 200 => [ - 'body' => [ - 'text/xml' => ['description' => 'xml body'], - 'text/txt' => ['description' => 'plain text'] - ], - 'description' => 'A dummy response', - 'headers' => [] - ] - ] - ], - $apiDefinition - ); - - $this->assertInternalType('array', $method->getResponses()); - $this->assertCount(1, $method->getResponses()); - - $responses = $method->getResponses(); - $this->assertInstanceOf('\Raml\Response', array_values($responses)[0]); - $this->assertInstanceOf('\Raml\Response', $method->getResponse(200)); - $this->assertSame(null, $method->getResponse(400)); - - $this->assertSame('A dummy method', $method->getDescription()); - $this->assertSame('A dummy response', array_values($responses)[0]->getDescription()); - } - - /** @test */ - public function shouldGetEmptyArrayForQueryParametersIfNoneIsExists() - { - $apiDefinition = new \Raml\ApiDefinition('The title'); - $method = \Raml\Method::createFromArray('get', [], $apiDefinition); - $this->assertEquals([], $method->getQueryParameters()); - } -} diff --git a/test/Types/ArrayTest.php b/test/Types/ArrayTest.php deleted file mode 100644 index 9b16503d..00000000 --- a/test/Types/ArrayTest.php +++ /dev/null @@ -1,72 +0,0 @@ -parser = new \Raml\Parser(); - } - - /** @test */ - public function shouldCorrectlyValidateCorrectType() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(202); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); - - $type->validate( - [ - ['id' => 1, 'name' => 'Sample 1'] - ] - ); - - self::assertTrue($type->isValid()); - } - - /** @test */ - public function shouldCorrectlyValidateIncorrectArraySizeLessThanMinimum() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(202); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); - - $type->validate([]); - - self::assertFalse($type->isValid()); - self::assertEquals('Sample[] (Allowed array size: between 1 and 2, got 0)', (string) $type->getErrors()[0]); - } - - /** @test */ - public function shouldCorrectlyValidateIncorrectTypeWhenArraySizeExceedsMaximum() - { - $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); - $resource = $simpleRaml->getResourceByUri('/songs'); - $method = $resource->getMethod('get'); - $response = $method->getResponse(202); - $body = $response->getBodyByType('application/json'); - $type = $body->getType(); - - $type->validate( - [ - ['id' => 1, 'name' => 'Sample 1'], - ['id' => 2, 'name' => 'Sample 2'], - ['id' => 3, 'name' => 'Sample 3'] - ] - ); - - self::assertFalse($type->isValid()); - self::assertEquals('Sample[] (Allowed array size: between 1 and 2, got 3)', (string) $type->getErrors()[0]); - } -} diff --git a/test/fixture/hateoas/traits.raml b/test/fixture/hateoas/traits.raml deleted file mode 100644 index 38bac8ba..00000000 --- a/test/fixture/hateoas/traits.raml +++ /dev/null @@ -1,9 +0,0 @@ -# traits.raml -- paginated: - queryParameters: - pages: - description: La página a retornar. - type: number - size: - description: El tamaño de la página. - type: number \ No newline at end of file diff --git a/test/fixture/raml-1.0/types.raml b/test/fixture/raml-1.0/types.raml deleted file mode 100644 index 8af65b6d..00000000 --- a/test/fixture/raml-1.0/types.raml +++ /dev/null @@ -1,25 +0,0 @@ -#%RAML 1.0 -title: Example API -version: v1 -types: - TestType: - type: object - properties: - id: integer - name: string - example: !include example/test.json -/test: - get: - responses: - 200: - body: - application/json: - type: TestType - post: - body: - type: TestType - responses: - 200: - body: - application/json: - type: TestType \ No newline at end of file diff --git a/tests/ApiDefinitionTest.php b/tests/ApiDefinitionTest.php new file mode 100644 index 00000000..0903e8d2 --- /dev/null +++ b/tests/ApiDefinitionTest.php @@ -0,0 +1,487 @@ +buildParser()->parse(__DIR__ . '/fixture/includeSchema.raml'); + $routes = $api->getResourcesAsUri(); + + $this->assertCount(4, $routes->getRoutes()); + $this->assertEquals([ + 'GET /songs', + 'POST /songs/{songId}', + 'GET /songs/{songId}', + 'DELETE /songs/{songId}', + ], array_keys($routes->getRoutes())); + } + + /** + * @test + */ + public function shouldReturnFullResourcesForRamlFileWithNoFormatter() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/includeSchema.raml'); + + $noFormatter = new NoRouteFormatter(); + $routes = $api->getResourcesAsUri($noFormatter, $api->getResources()); + + $this->assertCount(4, $routes->getRoutes()); + $this->assertEquals([ + 'GET /songs', + 'POST /songs/{songId}', + 'GET /songs/{songId}', + 'DELETE /songs/{songId}', + ], array_keys($routes->getRoutes())); + } + + /** + * @test + */ + public function shouldReturnFullResourcesForRamlFileWithSymfonyFormatter() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/includeSchema.raml'); + + $routeCollection = new RouteCollection(); + $routeFormatter = new SymfonyRouteFormatter($routeCollection); + $routes = $api->getResourcesAsUri($routeFormatter, $api->getResources()); + + $this->assertEquals($routeFormatter, $routes); + + $this->assertCount(4, $routeFormatter->getRoutes()); + $this->assertInstanceOf('\Symfony\Component\Routing\RouteCollection', $routeFormatter->getRoutes()); + $this->assertInstanceOf('\Symfony\Component\Routing\Route', $routeFormatter->getRoutes()->get('GET /songs/')); + $this->assertEquals(['http'], $routeFormatter->getRoutes()->get('GET /songs/')->getSchemes()); + } + + /** + * @test + */ + public function shouldReturnURIProtocol() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/protocols/noProtocolSpecified.raml'); + $this->assertCount(1, $api->getProtocols()); + $this->assertSame([ + 'HTTP', + ], $api->getProtocols()); + } + + /** + * @test + */ + public function shouldProcessTypes() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/types.raml'); + $this->assertCount(1, $api->getTypes()); + $this->assertSame([ + 'User' => [ + 'type' => 'object', + 'properties' => [ + 'firstname' => 'string', + 'lastname' => 'string', + 'age' => 'number', + ], + ], + ], $api->getTypes()->toArray()); + } + + /** + * @test + */ + public function shouldBeAbleToAccessOriginalInheritanceTypes() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/inheritanceTypes.raml'); + /** @var LazyProxyType $adminType */ + $adminType = $api->getTypes()->getTypeByName('Admin'); + $this->assertInstanceOf(LazyProxyType::class, $adminType); + $this->assertCount(1, $adminType->getProperties()); + foreach ($adminType->getProperties() as $property) { + if ($property->getName() === 'clearanceLevel') { + $this->assertTrue($property->getRequired()); + } + } + $parent = $adminType->getParent(); + $this->assertNotNull($parent); + $this->assertCount(4, $parent->getProperties()); + + /** @var LazyProxyType $managerType */ + $managerType = $api->getTypes()->getTypeByName('Manager'); + foreach ($managerType->getProperties() as $property) { + if ($property->getName() === 'clearanceLevel') { + $this->assertFalse($property->getRequired()); + } + } + } + + /** + * @test + */ + public function shouldParseLibraries() + { + $configuration = new ParseConfiguration(); + $configuration->allowRemoteFileInclusion(); + + $parser = new Parser(null, null, null, $configuration); + $api = $parser->parse(__DIR__ . '/fixture/raml-1.0/types.raml'); + /** @var ObjectType $fileType */ + $fileType = $api->getTypes()->getTypeByName('Library.File'); + /** @var ObjectType $folderType */ + $folderType = $api->getTypes()->getTypeByName('Library.Folder'); + + $this->assertNotNull($fileType); + $this->assertNotNull($folderType); + + /** @var ArrayType $property */ + $property = $folderType->getPropertyByName('files'); + $this->assertSame($fileType, $property->getItems()); + } + + /** + * @test + */ + public function shouldParseTypesToSubTypes() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/types.raml'); + $types = $api->getTypes(); + $object = $types->current(); + $this->assertInstanceOf(ObjectType::class, $object); + $this->assertInstanceOf(IntegerType::class, $object->getPropertyByName('id')); + $this->assertInstanceOf(StringType::class, $object->getPropertyByName('name')); + } + + /** + * @test + */ + public function shouldParseComplexTypes() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); + // check types + $org = $api->getTypes()->getTypeByName('Org'); + $this->assertInstanceOf(ObjectType::class, $org); + // property will return a proxy object so to compare to actual type we will need to ask for the resolved object + $this->assertInstanceOf(UnionType::class, $org->getPropertyByName('onCall')->getResolvedObject()); + $head = $org->getPropertyByName('Head'); + $this->assertInstanceOf(ObjectType::class, $head->getResolvedObject()); + $this->assertInstanceOf(StringType::class, $head->getPropertyByName('firstname')); + $this->assertInstanceOf(StringType::class, $head->getPropertyByName('lastname')); + $this->assertInstanceOf(StringType::class, $head->getPropertyByName('title')); + $this->assertInstanceOf(StringType::class, $head->getPropertyByName('kind')); + $reports = $head->getPropertyByName('reports'); + $this->assertInstanceOf(ArrayType::class, $reports); + $phone = $head->getPropertyByName('phone')->getResolvedObject(); + $this->assertInstanceOf(StringType::class, $phone); + // check resources + $type = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json')->getType(); + $this->assertInstanceOf(ObjectType::class, $type->getResolvedObject()); + } + + /** + * @test + */ + public function shouldPassValidResponse() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); + $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType( + 'application/json' + ); + /** @var Body $body */ + $validResponse = '{ + "onCall": { + "firstname": "John", + "lastname": "Flare", + "age": 41, + "kind": "AlertableAdmin", + "clearanceLevel": "low", + "phone": "12321" + }, + "Head": { + "firstname": "Nico", + "lastname": "Ark", + "age": 41, + "kind": "Manager", + "reports": [ + { + "firstname": "Archie", + "lastname": "Ark", + "age": 40, + "kind": "Admin" + } + ], + "phone": "123-23" + } + }'; + $type = $body->getType(); + $type->validate(json_decode($validResponse, true)); + $this->assertTrue($type->isValid(), sprintf('Validation failed with following errors: %s', implode(', ', array_map('strval', $type->getErrors())))); + } + + /** + * @test + */ + public function shouldRejectMissingParameterResponse() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); + $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); + /** @var Body $body */ + $type = $body->getType(); + + $invalidResponse = [ + 'onCall' => 'this is not an object', + 'Head' => 'this is not a Head object' + ]; + + $type->validate($invalidResponse); + $this->assertValidationFailedWithErrors( + $type, + [ + new TypeValidationError( + 'Alertable', + 'Value did not pass validation against any type: ' + . 'Manager (Manager (Expected object, got (string) "this is not an object")), ' + . 'AlertableAdmin (AlertableAdmin (Expected object, got (string) "this is not an object"))' + ), + new TypeValidationError('Head', 'Expected object, got (string) "this is not a Head object"'), + ] + ); + } + + /** + * @test + */ + public function shouldRejectInvalidIntegerParameterResponse() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); + $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); + /** @var Body $body */ + $type = $body->getType(); + + $invalidResponse = [ + 'onCall' => [ + 'firstname' => 'John', + 'lastname' => 'Flare', + 'age' => 18.5, + 'kind' => 'AlertableAdmin', + 'clearanceLevel' => 'low', + 'phone' => '12321', + ], + 'Head' => [ + 'firstname' => 'Nico', + 'lastname' => 'Ark', + 'age' => 71, + 'kind' => 'Manager', + 'reports' => [ + [ + 'firstname' => 'Archie', + 'lastname' => 'Ark', + 'kind' => 'Admin', + 'age' => 17, + 'clearanceLevel' => 'low', + ], + ], + 'phone' => '123-23', + ], + ]; + + $type->validate($invalidResponse); + $this->assertValidationFailedWithErrors( + $type, + [ + new TypeValidationError('age', 'Maximum allowed value: 70, got 71'), + new TypeValidationError('age', 'Minimum allowed value: 18, got 17'), + new TypeValidationError( + 'Alertable', + 'Value did not pass validation against any type: ' + . 'AlertableAdmin (age (Expected int, got (double) "18.5"))' + ), + ] + ); + } + + /** + * @test + */ + public function shouldRejectInvalidStringParameterResponse() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); + $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); + /* @var $body Body */ + $type = $body->getType(); + + $invalidResponse = [ + 'onCall' => [ + 'firstname' => 'John', + 'lastname' => 'F', + 'age' => 30, + 'kind' => 'AlertableAdmin', + 'clearanceLevel' => 'low', + 'phone' => '12321', + ], + 'Head' => [ + 'firstname' => 'Nico von Teufelspieler, the true duke of northern Blasphomores', + 'lastname' => 'Ark', + 'age' => 30, + 'kind' => 'Manager', + 'reports' => [ + [ + 'firstname' => 'Archie', + 'lastname' => 'Ark', + 'kind' => 'Admin', + 'age' => 30, + 'clearanceLevel' => 'low', + ], + ], + 'phone' => '123-23 33 22', + ], + ]; + + $type->validate($invalidResponse); + $this->assertValidationFailedWithErrors( + $type, + [ + new TypeValidationError('firstname', 'Maximum allowed length: 50, got 62'), + new TypeValidationError('Phone', 'String "123-23 33 22" did not match pattern /^[0-9|-]+$/'), + new TypeValidationError( + 'Alertable', + 'Value did not pass validation against any type: ' + . 'AlertableAdmin (lastname (Minimum allowed length: 2, got 1))' + ), + ] + ); + } + + /** + * @test + */ + public function shouldRejectInvalidEnumParameterResponse() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/raml-1.0/complexTypes.raml'); + $body = $api->getResourceByPath('/orgs/{orgId}')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); + /** @var Body $body */ + $type = $body->getType(); + + $invalidResponse = [ + 'onCall' => [ + 'firstname' => 'John', + 'lastname' => 'Flare', + 'age' => 30, + 'kind' => 'AlertableAdmin', + 'clearanceLevel' => 'average', + 'phone' => '12321', + ], + 'Head' => [ + 'firstname' => 'Nico', + 'lastname' => 'Ark', + 'age' => 30, + 'kind' => 'Manager', + 'reports' => [ + [ + 'firstname' => 'Archie', + 'lastname' => 'Ark', + 'kind' => 'Admin', + 'age' => 30, + ], + ], + 'phone' => '123-23', + ], + ]; + + $type->validate($invalidResponse); + $this->assertValidationFailedWithErrors( + $type, + [ + new TypeValidationError( + 'Alertable', + 'Value did not pass validation against any type: ' + . 'AlertableAdmin (ClearanceLevels (Expected any of [low, high], got (string) "average"))' + ), + ] + ); + } + + /** + * @test + */ + public function shouldReturnProtocolsIfSpecified() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/protocols/protocolsSpecified.raml'); + $this->assertCount(2, $api->getProtocols()); + $this->assertSame([ + 'HTTP', + 'HTTPS', + ], $api->getProtocols()); + } + + /** + * @test + */ + public function shouldThrowInvalidProtocolExceptionIfWrongProtocol() + { + $this->expectException(InvalidProtocolException::class); + + $this->buildParser()->parse(__DIR__ . '/fixture/protocols/invalidProtocolsSpecified.raml'); + } + + /** + * @param TypeValidationError[] $errors + */ + private function assertValidationFailedWithErrors(ValidatorInterface $validator, $errors) + { + $this->assertFalse($validator->isValid(), 'Validator expected to fail'); + foreach ($errors as $error) { + $this->assertContains( + $error, + $validator->getErrors(), + $message = sprintf('Validator expected to contain error: %s', $error->__toString()), + $ignoreCase = false, + $checkForObjectIdentity = false + ); + } + } + + /** + * @test + */ + public function shouldReturnFullResourcesNameForRamlFileWithUrlPrefix() + { + $api = $this->buildParser()->parse(__DIR__ . '/fixture/includeUrlPrefix.raml'); + $this->assertEquals([ + '/prefix/songs', + ], array_keys($api->getResources())); + } +} diff --git a/test/JsonSchemaTest.php b/tests/JsonSchemaTest.php similarity index 85% rename from test/JsonSchemaTest.php rename to tests/JsonSchemaTest.php index d577f937..5ce92297 100644 --- a/test/JsonSchemaTest.php +++ b/tests/JsonSchemaTest.php @@ -1,19 +1,27 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - /** @test */ + /** + * @test + */ public function shouldReturnJsonString() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); @@ -28,7 +36,9 @@ public function shouldReturnJsonString() $this->assertEquals('A list of songs', json_decode($schemaString)->description); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateCorrectJson() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); @@ -42,10 +52,11 @@ public function shouldCorrectlyValidateCorrectJson() $this->assertTrue($schema->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateIncorrectJson() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -57,7 +68,9 @@ public function shouldCorrectlyValidateIncorrectJson() $this->assertFalse($schema->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateInvalidJson() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); @@ -71,14 +84,16 @@ public function shouldCorrectlyValidateInvalidJson() $this->assertFalse($schema->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateJsonAsArray() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/{songId}'); $method = $resource->getMethod('post'); $request = $method->getBodyByType('application/json'); - /** @var \Raml\Schema\Definition\JsonSchemaDefinition $schema */ + /** @var JsonSchemaDefinition $schema */ $schema = $request->getSchema(); $schema->validate(json_decode('{"title":"Title", "artist": "Artist"}', true)); diff --git a/tests/MethodTest.php b/tests/MethodTest.php new file mode 100644 index 00000000..900b6d11 --- /dev/null +++ b/tests/MethodTest.php @@ -0,0 +1,160 @@ +assertSame('GET', $method->getType()); + + $method = Method::createFromArray('Post', [], $apiDefinition); + $this->assertSame('POST', $method->getType()); + + $method = Method::createFromArray('options', [], $apiDefinition); + $this->assertSame('OPTIONS', $method->getType()); + } + + /** + * @test + */ + public function shouldGetTheDescriptionIfPassedInTheDataArray() + { + $apiDefinition = new ApiDefinition('The title'); + + $method = Method::createFromArray('get', ['description' => 'A dummy description'], $apiDefinition); + $this->assertSame('A dummy description', $method->getDescription()); + + $method = Method::createFromArray('get', [], $apiDefinition); + $this->assertNull($method->getDescription()); + } + + /** + * @test + */ + public function shouldGetNullForResponseIfNoneIsExists() + { + $apiDefinition = new ApiDefinition('The title'); + + $method = Method::createFromArray('get', [], $apiDefinition); + $this->assertSame([], $method->getResponses()); + $this->assertSame(null, $method->getResponse(200)); + } + + /** + * @test + */ + public function shouldGetNullForResponseIfNotAnArrayIsPassed() + { + $apiDefinition = new ApiDefinition('The title'); + + $method = Method::createFromArray('get', ['responses' => 'invalid'], $apiDefinition); + $this->assertSame([], $method->getResponses()); + $this->assertSame(null, $method->getResponse(200)); + } + + /** + * @test + */ + public function shouldGetValidResponsesIfPassedExpectedValues() + { + $apiDefinition = new ApiDefinition('The title'); + + $method = Method::createFromArray( + 'get', + [ + 'description' => 'A dummy method', + 'responses' => [ + 200 => [ + 'body' => [ + 'text/xml' => ['description' => 'xml body'], + 'text/txt' => ['description' => 'plain text'], + ], + 'description' => 'A dummy response', + 'headers' => [], + ] + ] + ], + $apiDefinition + ); + + $this->assertInternalType('array', $method->getResponses()); + $this->assertCount(1, $method->getResponses()); + + $responses = $method->getResponses(); + $this->assertInstanceOf(Response::class, array_values($responses)[0]); + $this->assertInstanceOf(Response::class, $method->getResponse(200)); + $this->assertSame(null, $method->getResponse(400)); + + $this->assertSame('A dummy method', $method->getDescription()); + $this->assertSame('A dummy response', array_values($responses)[0]->getDescription()); + } + + /** + * @test + */ + public function shouldGetEmptyArrayForQueryParametersIfNoneIsExists() + { + $apiDefinition = new ApiDefinition('The title'); + $method = Method::createFromArray('get', [], $apiDefinition); + $this->assertEquals([], $method->getQueryParameters()); + } + + /** + * @test + */ + public function shouldGetGlobalProtocols() + { + $parser = new Parser(); + $apiDefinition = $parser->parse(__DIR__ . '/fixture/protocols/noProtocolSpecified.raml'); + + $method = Method::createFromArray( + 'get', + [ + 'description' => 'A dummy method', + ], + $apiDefinition + ); + + $this->assertInternalType('array', $method->getProtocols()); + $this->assertCount(1, $method->getProtocols()); + $this->assertSame(['HTTP'], $method->getProtocols()); + } + + /** + * @test + */ + public function shouldGetOverrideProtocols() + { + $parser = new Parser(); + $apiDefinition = $parser->parse(__DIR__ . '/fixture/protocols/noProtocolSpecified.raml'); + + $method = Method::createFromArray( + 'get', + [ + 'description' => 'A dummy method', + 'protocols' => ['HTTP', 'HTTPS'], + ], + $apiDefinition + ); + + $this->assertInternalType('array', $method->getProtocols()); + $this->assertCount(2, $method->getProtocols()); + $this->assertSame([ + 'HTTP', + 'HTTPS', + ], $method->getProtocols()); + } +} diff --git a/test/NamedParameters/BaseUriParameterTest.php b/tests/NamedParameters/BaseUriParameterTest.php similarity index 87% rename from test/NamedParameters/BaseUriParameterTest.php rename to tests/NamedParameters/BaseUriParameterTest.php index c6fff136..3f633a94 100644 --- a/test/NamedParameters/BaseUriParameterTest.php +++ b/tests/NamedParameters/BaseUriParameterTest.php @@ -1,25 +1,26 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); setlocale(LC_NUMERIC, 'C'); } /** - * Returns a valid api def - * - * @throws \Exception - * - * @return \Raml\ApiDefinition + * @return ApiDefinition */ private function getValidDef() { @@ -60,7 +61,9 @@ public function shouldCorrectlySubstituteTheVersion() $this->assertEquals('https://{apiDomain}.someapi.com/1.2', $apiDef->getBaseUri()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyParseBaseUriParameters() { $apiDef = $this->getValidDef(); @@ -75,7 +78,9 @@ public function shouldCorrectlyParseBaseUriParameters() $this->assertTrue($baseUriParameters['apiDomain']->isRequired()); } - /** @test */ + /** + * @test + */ public function shouldOverrideBaseUriParametersInResource() { $apiDef = $this->getValidDef(); @@ -83,7 +88,9 @@ public function shouldOverrideBaseUriParametersInResource() $this->assertEquals(['static'], $resource->getBaseUriParameters()['apiDomain']->getEnum()); } - /** @test */ + /** + * @test + */ public function shouldOverrideBaseUriParametersInMethod() { $apiDef = $this->getValidDef(); @@ -97,14 +104,13 @@ public function shouldOverrideBaseUriParametersInMethod() */ public function shouldCorrectlyParseNetBaseUri() { - $raml = <<parser->parseFromString($raml, '')->getProtocols(); $this->assertContains('HTTP', $protocols); @@ -116,7 +122,7 @@ public function shouldCorrectlyParseNetBaseUri() */ public function shouldOverrideBaseUriProtocols() { - $raml = <<parser->parseFromString($raml, '')->getProtocols(); $this->assertContains('HTTP', $protocols); diff --git a/test/NamedParameters/MultipleTypesTest.php b/tests/NamedParameters/MultipleTypesTest.php similarity index 59% rename from test/NamedParameters/MultipleTypesTest.php rename to tests/NamedParameters/MultipleTypesTest.php index 8c8f2ecd..b16ddfbe 100644 --- a/test/NamedParameters/MultipleTypesTest.php +++ b/tests/NamedParameters/MultipleTypesTest.php @@ -1,29 +1,39 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - /** @test */ + /** + * @test + */ public function shouldGetTheResourceOnTheBaseUrl() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/multipleTypes.raml'); $resource = $apiDefinition->getResourceByUri('/'); - $this->assertInstanceOf('Raml\Resource', $resource); + $this->assertInstanceOf(Resource::class, $resource); } - /** @test */ + /** + * @test + */ public function shouldReturnAnArrayOfTypes() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/multipleTypes.raml'); @@ -32,6 +42,6 @@ public function shouldReturnAnArrayOfTypes() $method = $resource->getMethod('post'); $body = $method->getBodyByType('application/x-www-form-urlencoded'); - $this->assertInstanceOf('\Raml\WebFormBody', $body); + $this->assertInstanceOf(WebFormBody::class, $body); } } diff --git a/test/NamedParameters/ParameterTypesTest.php b/tests/NamedParameters/ParameterTypesTest.php similarity index 59% rename from test/NamedParameters/ParameterTypesTest.php rename to tests/NamedParameters/ParameterTypesTest.php index a9f7f104..a42ecec6 100644 --- a/test/NamedParameters/ParameterTypesTest.php +++ b/tests/NamedParameters/ParameterTypesTest.php @@ -1,24 +1,29 @@ parser = new \Raml\Parser(); - - $validateRaml = <<parser = new Parser(); + + $validateRaml = <<validateBody = $method->getBodyByType('application/x-www-form-urlencoded'); } - // --- - - /** @test */ + /** + * @test + */ public function shouldValidateWithoutExceptions() { // Not Required $namedParameter = $this->validateBody->getParameter('string'); $namedParameter->validate(null); - + // Valid date $namedParameter = $this->validateBody->getParameter('date'); $namedParameter->validate('Sun, 06 Nov 1994 08:49:37 GMT'); } - - /** @test */ + + /** + * @test + */ public function shouldValidateRequired() { // Required - $this->setExpectedException('\Raml\Exception\ValidationException', 'requiredstring is required', 7); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(7); + $this->expectExceptionMessage('requiredstring is required'); $namedParameter = $this->validateBody->getParameter('requiredstring'); $namedParameter->validate(null); } - - /** @test */ + + /** + * @test + */ public function shouldValidateString() { // When is a string not a string? When it's an integer. - $this->setExpectedException('\Raml\Exception\ValidationException', 'string is not a string', 3); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(3); + $this->expectExceptionMessage('string is not a string'); $namedParameter = $this->validateBody->getParameter('string'); $namedParameter->validate(1); } - - /** @test */ + + /** + * @test + */ public function shouldValidateShortString() { // String is too short - $this->setExpectedException('\Raml\Exception\ValidationException', 'string must be at least 3 characters long', 8); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(8); + $this->expectExceptionMessage('string must be at least 3 characters long'); $namedParameter = $this->validateBody->getParameter('string'); $namedParameter->validate('a'); } - - /** @test */ + + /** + * @test + */ public function shouldValidateLongString() { // String is too long - $this->setExpectedException('\Raml\Exception\ValidationException', 'string must be no more than 5 characters long', 9); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(9); + $this->expectExceptionMessage('string must be no more than 5 characters long'); $namedParameter = $this->validateBody->getParameter('string'); $namedParameter->validate('aaaaaa'); } - - /** @test */ + + /** + * @test + */ public function shouldValidateNumber() { // When is a number not a number? When it's an... array! - $this->setExpectedException('\Raml\Exception\ValidationException', 'number is not a number', 5); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(5); + $this->expectExceptionMessage('number is not a number'); $namedParameter = $this->validateBody->getParameter('number'); - $namedParameter->validate(array()); + $namedParameter->validate([]); } - - /** @test */ + + /** + * @test + */ public function shouldValidateSmallNumber() { // Number is less than the minimum value - $this->setExpectedException('\Raml\Exception\ValidationException', 'number must be greater than or equal to 1', 10); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(10); + $this->expectExceptionMessage('number must be greater than or equal to 1'); $namedParameter = $this->validateBody->getParameter('number'); $namedParameter->validate(0); } - - /** @test */ + + /** + * @test + */ public function shouldValidateLargeNumber() { // Number is more than the maximum value - $this->setExpectedException('\Raml\Exception\ValidationException', 'number must be less than or equal to 10', 11); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(11); + $this->expectExceptionMessage('number must be less than or equal to 10'); $namedParameter = $this->validateBody->getParameter('number'); $namedParameter->validate(11); } - - /** @test */ + + /** + * @test + */ public function shouldValidateInteger() { // When is aniteger not an integer? Well... you get the picture. - $this->setExpectedException('\Raml\Exception\ValidationException', 'integer is not an integer', 4); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(4); + $this->expectExceptionMessage('integer is not an integer'); $namedParameter = $this->validateBody->getParameter('integer'); $namedParameter->validate('a'); } - - /** @test */ + + /** + * @test + */ public function shouldValidatePattern() { // Pattern validation - $this->setExpectedException('\Raml\Exception\ValidationException', 'pattern does not match the specified pattern', 12); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(12); + $this->expectExceptionMessage('pattern does not match the specified pattern'); $namedParameter = $this->validateBody->getParameter('pattern'); $namedParameter->validate(6); } - - /** @test */ + + /** + * @test + */ public function shouldValidateEnum() { // Enum validation - $this->setExpectedException('\Raml\Exception\ValidationException', 'enum must be one of the following: laughing, my, butt, off', 13); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(13); + $this->expectExceptionMessage('enum must be one of the following: laughing, my, butt, off'); $namedParameter = $this->validateBody->getParameter('enum'); $namedParameter->validate('Grandma'); } - - /** @test */ + + /** + * @test + */ public function shouldValidateBoolean() { // Boolean validation - $this->setExpectedException('\Raml\Exception\ValidationException', 'boolean is not boolean', 1); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(1); + $this->expectExceptionMessage('boolean is not boolean'); $namedParameter = $this->validateBody->getParameter('boolean'); $namedParameter->validate(1); } - - /** @test */ + + /** + * @test + */ public function shouldValidateDate() { // Date validation - $this->setExpectedException('\Raml\Exception\ValidationException', 'date is not a valid date', 2); + $this->expectException(ValidationException::class); + $this->expectExceptionCode(2); + $this->expectExceptionMessage('date is not a valid date'); $namedParameter = $this->validateBody->getParameter('date'); $namedParameter->validate('Sun, 06 Nov 1994 08:49:37 BUNS'); } - // --- - - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidString() { - $raml = <<setExpectedException('Exception', 'Default parameter is not a string'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Default parameter is not a string'); $this->parser->parseFromString($raml, ''); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidNumber() { - $raml = <<setExpectedException('Exception', 'Default parameter is not a number'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Default parameter is not a number'); $this->parser->parseFromString($raml, ''); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidInteger() { - $raml = <<setExpectedException('Exception', 'Default parameter is not an integer'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Default parameter is not an integer'); $this->parser->parseFromString($raml, ''); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidDate() { - $raml = <<setExpectedException('Exception', 'Default parameter is not a dateTime object'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Default parameter is not a dateTime object'); $this->parser->parseFromString($raml, ''); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidBoolean() { - $raml = <<setExpectedException('Exception', 'Default parameter is not a boolean'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Default parameter is not a boolean'); $this->parser->parseFromString($raml, ''); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidFile() { - $raml = <<setExpectedException('Exception', 'A default value cannot be set for a file'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('A default value cannot be set for a file'); $this->parser->parseFromString($raml, ''); } } diff --git a/test/NamedParameters/UriParameterTest.php b/tests/NamedParameters/UriParameterTest.php similarity index 51% rename from test/NamedParameters/UriParameterTest.php rename to tests/NamedParameters/UriParameterTest.php index 689e8513..277dc12e 100644 --- a/test/NamedParameters/UriParameterTest.php +++ b/tests/NamedParameters/UriParameterTest.php @@ -1,22 +1,27 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - // --- - - /** @test */ + /** + * @test + */ public function shouldCorrectlyParseBaseUriParameters() { $raml = <<parser->parseFromString($raml, ''); $resource = $apiDef->getResourceByUri('/user/1'); - $this->assertInstanceOf('\Raml\Resource', $resource); + $this->assertInstanceOf(Resource::class, $resource); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyParseRegexUriParameters() { $raml = <<parser->parseFromString($raml, ''); $resource = $apiDef->getResourceByUri('/user/alec'); - $this->assertInstanceOf('\Raml\Resource', $resource); + $this->assertInstanceOf(Resource::class, $resource); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyParseEnumUriParameters() { $raml = <<parser->parseFromString($raml, ''); $resource = $apiDef->getResourceByUri('/user/one'); - $this->assertInstanceOf('\Raml\Resource', $resource); + $this->assertInstanceOf(Resource::class, $resource); + } + + /** + * @test + */ + public function shouldPassUriParametersFromParentToSub() + { + $raml = <<parser->parseFromString($raml, ''); + + $resource = $apiDef->getResourceByPath('/base/{param1}/sub/{param2}'); + $this->assertInstanceOf(Resource::class, $resource); + + $uriParameters = $resource->getUriParameters(); + + $this->assertCount(2, $uriParameters, 'should contain 2 uri parameters'); + + $this->assertArrayHasKey('param1', $uriParameters, 'should contain uri parameter from parent'); + $this->assertArrayHasKey('param2', $uriParameters, 'should contain uri parameter from sub'); } } diff --git a/test/NamedParameters/WebFormBodyTest.php b/tests/NamedParameters/WebFormBodyTest.php similarity index 73% rename from test/NamedParameters/WebFormBodyTest.php rename to tests/NamedParameters/WebFormBodyTest.php index b266c100..5bcbcadf 100644 --- a/test/NamedParameters/WebFormBodyTest.php +++ b/tests/NamedParameters/WebFormBodyTest.php @@ -1,32 +1,41 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - // --- - - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidType() { - $this->setExpectedException('Exception', 'Invalid type'); - new \Raml\WebFormBody('test'); + $this->expectException(\InvalidArgumentException::class); + new WebFormBody('test'); } - /** @test */ + /** + * @test + */ public function shouldBeCreatedForValidMediaTypeUrlEncoded() { - $raml = <<getMethod('post'); $body = $method->getBodyByType('application/x-www-form-urlencoded'); - $this->assertInstanceOf('\Raml\WebFormBody', $body); + $this->assertInstanceOf(WebFormBody::class, $body); } - /** @test */ + /** + * @test + */ public function shouldBeCreatedForValidMediaTypeFormData() { - $raml = <<getMethod('post'); $body = $method->getBodyByType('multipart/form-data'); - $this->assertInstanceOf('\Raml\WebFormBody', $body); + $this->assertInstanceOf(WebFormBody::class, $body); } - /** @test */ + /** + * @test + */ public function shouldThrowErrorOnAttemptGetInvalidParameter() { - $raml = <<getMethod('post'); $body = $method->getBodyByType('multipart/form-data'); - $this->setExpectedException('\Raml\Exception\InvalidKeyException', 'The key badKey does not exist.'); + $this->expectException(InvalidKeyException::class); try { $body->getParameter('badKey'); - } catch (\Raml\Exception\InvalidKeyException $e) { + } catch (InvalidKeyException $e) { $this->assertEquals('badKey', $e->getKey()); + throw $e; } } - /** @test */ + /** + * @test + */ public function shouldBeAbleToGetAllParameters() { - $raml = <<getBodyByType('multipart/form-data'); $parameters = $body->getParameters(); - $expectedParameter = new \Raml\NamedParameter('string'); + $expectedParameter = new NamedParameter('string'); $expectedParameter->setDefault('A string'); $this->assertEquals([ diff --git a/test/NamedParameters/fixture/multipleTypes.raml b/tests/NamedParameters/fixture/multipleTypes.raml similarity index 100% rename from test/NamedParameters/fixture/multipleTypes.raml rename to tests/NamedParameters/fixture/multipleTypes.raml diff --git a/test/ParseTest.php b/tests/ParseTest.php similarity index 67% rename from test/ParseTest.php rename to tests/ParseTest.php index f30dcc41..a57e45d6 100644 --- a/test/ParseTest.php +++ b/tests/ParseTest.php @@ -1,26 +1,43 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - // --- - - /** @test */ + /** + * @test + */ public function shouldCorrectlyLoadASimpleRamlString() { $raml = <<assertEquals('v2', $simpleRaml->getVersion()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyLoadASimpleRamlStringWithInclude() { $raml = <<assertEquals([__DIR__ . '/fixture/child.raml'], $this->parser->getIncludedFiles()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyLoadASimpleRamlFile() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $this->assertEquals('World Music API', $simpleRaml->getTitle()); $this->assertEquals('v1', $simpleRaml->getVersion()); $this->assertEquals('http://example.api.com/v1', $simpleRaml->getBaseUri()); @@ -82,253 +103,294 @@ public function shouldCorrectlyLoadASimpleRamlFile() */ public function shouldThrowCorrectExceptionOnBadJson() { - $this->setExpectedException(Raml\Exception\InvalidJsonException::class); - $this->parser->parse(__DIR__.'/fixture/invalid/badJson.raml'); + $this->expectException(InvalidJsonException::class); + $this->parser->parse(__DIR__ . '/fixture/invalid/badJson.raml'); } - /** @test */ + /** + * @test + */ public function shouldThrowFileNotFoundExceptionOnBadRamlFileWithNotExistingFile() { - $this->setExpectedException(FileNotFoundException::class); - $this->parser->parse(__DIR__.'/fixture/invalid/bad.raml'); + $this->expectException(FileNotFoundException::class); + $this->parser->parse(__DIR__ . '/fixture/invalid/bad.raml'); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnPathManipulationIfNotAllowed() { $config = new \Raml\ParseConfiguration(); $config->disableDirectoryTraversal(); $this->parser->setConfiguration($config); - $this->setExpectedException(FileNotFoundException::class); - $this->parser->parse(__DIR__.'/fixture/treeTraversal/bad.raml'); + $this->expectException(FileNotFoundException::class); + $this->parser->parse(__DIR__ . '/fixture/treeTraversal/bad.raml'); } - /** @test */ + /** + * @test + */ public function shouldPreventDirectoryTraversalByDefault() { - $this->setExpectedException(FileNotFoundException::class); - $this->parser->parse(__DIR__.'/fixture/treeTraversal/bad.raml'); + $this->expectException(FileNotFoundException::class); + $this->parser->parse(__DIR__ . '/fixture/treeTraversal/bad.raml'); } - - /** @test */ + /** + * @test + */ public function shouldNotThrowExceptionOnPathManipulationIfAllowed() { $config = new \Raml\ParseConfiguration(); $config->enableDirectoryTraversal(); $this->parser->setConfiguration($config); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/treeTraversal/bad.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/treeTraversal/bad.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyReturnHttpProtocol() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $this->assertTrue($simpleRaml->supportsHttp()); $this->assertFalse($simpleRaml->supportsHttps()); } - /** @test */ + /** + * @test + */ public function shouldReturnAResourceObjectForAResource() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); - $this->assertInstanceOf('\Raml\Resource', $resource); + $this->assertInstanceOf(Resource::class, $resource); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionIfUriNotFound() { - $this->setExpectedException('Raml\Exception\BadParameter\ResourceNotFoundException'); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $this->expectException(ResourceNotFoundException::class); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); try { $simpleRaml->getResourceByUri('/invalid'); } catch (\Raml\Exception\BadParameter\ResourceNotFoundException $e) { $this->assertEquals('/invalid', $e->getUri()); + throw $e; } } - /** @test */ + /** + * @test + */ public function shouldNotMatchForwardSlashInURIParameter() { - $this->setExpectedException('\Raml\Exception\BadParameter\ResourceNotFoundException'); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $this->expectException(ResourceNotFoundException::class); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $simpleRaml->getResourceByUri('/songs/1/e'); } - /** @test */ + /** + * @test + */ public function shouldNotMatchForwardSlashAndDuplicationInURIParameter() { - $this->setExpectedException('\Raml\Exception\BadParameter\ResourceNotFoundException'); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $this->expectException(ResourceNotFoundException::class); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $simpleRaml->getResourceByUri('/songs/1/1'); } - /** @test */ + /** + * @test + */ public function shouldGiveTheResourceTheCorrectDisplayNameIfNotProvided() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $this->assertEquals('/songs', $resource->getDisplayName()); } - /** @test */ + /** + * @test + */ public function shouldExcludeQueryParametersWhenFindingAResource() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs?1'); $this->assertEquals('/songs', $resource->getDisplayName()); } - /** @test */ + /** + * @test + */ public function shouldGiveTheResourceTheCorrectDisplayNameIfProvided() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/traitsAndTypes.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/traitsAndTypes.raml'); $resource = $simpleRaml->getResourceByUri('/dvds'); $this->assertEquals('DVD', $resource->getDisplayName()); } - /** @test */ + /** + * @test + */ public function shouldParseMultiLevelUrisAndParameters() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $this->assertEquals('/songs/{songId}', $resource->getDisplayName()); } - - /** @test */ + /** + * @test + */ public function shouldReturnAMethodObjectForAMethod() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('post'); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $method->getBodyByType('application/json'); $schema = $body->getSchema(); $this->assertCount(3, $resource->getMethods()); - $this->assertInstanceOf('\Raml\Method', $method); + $this->assertInstanceOf(Method::class, $method); $this->assertEquals('POST', $method->getType()); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldReturnAResponseForAResponse() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); $this->assertNotEmpty($method->getResponses()); - $this->assertInstanceOf('\Raml\Response', $response); + $this->assertInstanceOf(Response::class, $response); } - /** @test **/ + /** + * @test + */ public function shouldReturnAnExampleForType() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); $this->assertEquals(['application/json', 'application/xml'], $response->getTypes()); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getExample(); $this->assertEquals([ - "title" => "Wish You Were Here", - "artist" => "Pink Floyd" + 'title' => 'Wish You Were Here', + 'artist' => 'Pink Floyd' ], json_decode($schema, true)); } - /** @test */ + /** + * @test + */ public function shouldParseJson() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldParseJsonSchemaInRaml() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/schemaInRoot.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/schemaInRoot.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldIncludeChildJsonObjects() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/parentAndChildSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/parentAndChildSchema.raml'); $resource = $simpleRaml->getResourceByUri('/'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldNotParseJsonIfNotRequested() { $config = new \Raml\ParseConfiguration(); $config->disableSchemaParsing(); $this->parser->setConfiguration($config); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); $this->assertInternalType('string', $schema); } - /** @test */ + /** + * @test + */ public function shouldParseJsonRefs() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); /** @var JsonSchemaDefinition $schema */ $schema = $body->getSchema(); @@ -337,14 +399,16 @@ public function shouldParseJsonRefs() $this->assertEquals('A canonical song', $schemaObject->items->description); } - /** @test */ + /** + * @test + */ public function shouldParseJsonIntoArray() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); /** @var JsonSchemaDefinition $schema */ @@ -353,50 +417,55 @@ public function shouldParseJsonIntoArray() $this->assertEquals('A canonical song', $schemaArray['items']['description']); } - - /** @test */ + /** + * @test + */ public function shouldParseIncludedJson() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldNotParseIncludedJsonIfNotRequired() { - $config = new \Raml\ParseConfiguration(); + $config = new ParseConfiguration(); $config->disableSchemaParsing(); $this->parser->setConfiguration($config); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); $schema = $body->getSchema(); $this->assertInternalType('string', $schema); } - /** @test */ + /** + * @test + */ public function shouldParseIncludedJsonRefs() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); /** @var JsonSchemaDefinition $schema */ $schema = $body->getSchema(); @@ -405,63 +474,74 @@ public function shouldParseIncludedJsonRefs() $this->assertEquals('A canonical song', $schemaObject->items->description); } - /** @test */ + /** + * @test + */ public function shouldSetCorrectSourceUriOnSchemaParsers() { - $schemaParser = $this->getMock(SchemaParserInterface::class); + $schemaParser = $this->createMock(SchemaParserInterface::class); $schemaParser->method('createSchemaDefinition')->willReturn( - $this->getMock(SchemaDefinitionInterface::class) + $this->createMock(SchemaDefinitionInterface::class) ); - $schemaParser->expects($this->any())->method('getCompatibleContentTypes')->willReturn([ 'application/json' ]); - $schemaParser->expects($this->any())->method('setSourceUri')->withConsecutive( - [ 'file://'.__DIR__.'/fixture/songs.json' ] + $schemaParser->method('getCompatibleContentTypes')->willReturn(['application/json']); + $schemaParser->method('setSourceUri')->withConsecutive( + ['file://' . __DIR__ . '/fixture/songs.json'] ); - + $parser = new \Raml\Parser([ $schemaParser ]); - - $parser->parse(__DIR__.'/fixture/includeSchema.raml'); + + $parser->parse(__DIR__ . '/fixture/includeSchema.raml'); } - - /** @test */ + + /** + * @test + */ public function shouldThrowErrorIfEmpty() { - $this->setExpectedException('Exception', 'RAML file appears to be empty'); - $this->parser->parse(__DIR__.'/fixture/invalid/empty.raml'); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('RAML file appears to be empty'); + $this->parser->parse(__DIR__ . '/fixture/invalid/empty.raml'); } - /** @test */ + /** + * @test + */ public function shouldThrowErrorIfNoTitle() { - $this->setExpectedException('\Raml\Exception\RamlParserException'); - $this->parser->parse(__DIR__.'/fixture/invalid/noTitle.raml'); + $this->expectException(RamlParserException::class); + $this->parser->parse(__DIR__ . '/fixture/invalid/noTitle.raml'); } - /** @test */ + /** + * @test + */ public function shouldBeAbleToAddAdditionalSchemaTypes() { - $schemaParser = new \Raml\Schema\Parser\JsonSchemaParser(); + $schemaParser = new JsonSchemaParser(); $schemaParser->addCompatibleContentType('application/vnd.api-v1+json'); $this->parser->addSchemaParser($schemaParser); - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeUnknownSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeUnknownSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/vnd.api-v1+json'); $schema = $body->getSchema(); - $this->assertInstanceOf('\Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldApplyTraitVariables() { - $traitsAndTypes = $this->parser->parse(__DIR__.'/fixture/traitsAndTypes.raml'); + $traitsAndTypes = $this->parser->parse(__DIR__ . '/fixture/traitsAndTypes.raml'); $resource = $traitsAndTypes->getResourceByUri('/books'); $method = $resource->getMethod('get'); @@ -484,30 +564,36 @@ public function shouldApplyTraitVariables() $this->assertEquals('Return DVD that have their title matching the given value for path /dvds', $queryParameters['title']->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldParseIncludedRaml() { - $parent = $this->parser->parse(__DIR__.'/fixture/includeRaml.raml'); + $parent = $this->parser->parse(__DIR__ . '/fixture/includeRaml.raml'); $documentation = $parent->getDocumentationList(); $this->assertEquals('Home', $documentation['title']); $this->assertEquals('Welcome to the _Zencoder API_ Documentation', $documentation['content']); } - /** @test */ + /** + * @test + */ public function shouldParseIncludedYaml() { - $parent = $this->parser->parse(__DIR__.'/fixture/includeYaml.raml'); + $parent = $this->parser->parse(__DIR__ . '/fixture/includeYaml.raml'); $documentation = $parent->getDocumentationList(); $this->assertEquals('Home', $documentation['title']); $this->assertEquals('Welcome to the _Zencoder API_ Documentation', $documentation['content']); } - /** @test */ + /** + * @test + */ public function shouldIncludeTraits() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $queryParameters = $method->getQueryParameters(); @@ -517,73 +603,85 @@ public function shouldIncludeTraits() $this->assertEquals('number', $queryParameter->getType()); } - /** @test */ + /** + * @test + */ public function shouldThrowErrorIfPassedFileDoesNotExist() { - $fileName = __DIR__.'/fixture/gone.raml'; + $fileName = __DIR__ . '/fixture/gone.raml'; - $this->setExpectedException( - '\Raml\Exception\BadParameter\FileNotFoundException', - 'The file '.$fileName.' does not exist or is unreadable.' - ); + $this->expectException(FileNotFoundException::class); + $this->expectExceptionMessage(sprintf('The file %s does not exist or is unreadable.', $fileName)); try { $this->parser->parse($fileName); - } catch (\Raml\Exception\BadParameter\FileNotFoundException $e) { + } catch (FileNotFoundException $e) { $this->assertEquals($fileName, $e->getFileName()); + throw $e; } } - /** @test */ + /** + * @test + */ public function shouldParseHateoasExample() { - $hateoasRaml = $this->parser->parse(__DIR__.'/fixture/hateoas/example.raml'); - $this->assertInstanceOf('\Raml\ApiDefinition', $hateoasRaml); + $hateoasRaml = $this->parser->parse(__DIR__ . '/fixture/hateoas/example.raml'); + $this->assertInstanceOf(ApiDefinition::class, $hateoasRaml); } - /** @test */ + /** + * @test + */ public function shouldParseMethodDescription() { - $methodDescriptionRaml = $this->parser->parse(__DIR__.'/fixture/methodDescription.raml'); + $methodDescriptionRaml = $this->parser->parse(__DIR__ . '/fixture/methodDescription.raml'); $this->assertEquals('Get a list of available songs', $methodDescriptionRaml->getResourceByUri('/songs')->getMethod('get')->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldParseResourceDescription() { - $resourceDescriptionRaml = $this->parser->parse(__DIR__.'/fixture/resourceDescription.raml'); + $resourceDescriptionRaml = $this->parser->parse(__DIR__ . '/fixture/resourceDescription.raml'); $this->assertEquals('Collection of available songs resource', $resourceDescriptionRaml->getResourceByUri('/songs')->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldParseStatusCode() { - $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $response = $resource->getMethod('get')->getResponse(200); $this->assertEquals(200, $response->getStatusCode()); } - /** @test */ + /** + * @test + */ public function shouldParseMethodHeaders() { - $headersRaml = $this->parser->parse(__DIR__.'/fixture/headers.raml'); + $headersRaml = $this->parser->parse(__DIR__ . '/fixture/headers.raml'); $resource = $headersRaml->getResourceByUri('/jobs'); - $this->assertEquals(['Zencoder-Api-Key' => - \Raml\NamedParameter::createFromArray('Zencoder-Api-Key', ['displayName'=>'ZEncoder API Key']) + $this->assertEquals(['Zencoder-Api-Key' => NamedParameter::createFromArray('Zencoder-Api-Key', ['displayName' => 'ZEncoder API Key']) ], $resource->getMethod('post')->getHeaders()); } - /** @test */ + /** + * @test + */ public function shouldParseResponseHeaders() { - $headersRaml = $this->parser->parse(__DIR__.'/fixture/headers.raml'); + $headersRaml = $this->parser->parse(__DIR__ . '/fixture/headers.raml'); $resource = $headersRaml->getResourceByUri('/jobs'); - $this->assertEquals(['X-waiting-period' => \Raml\NamedParameter::createFromArray('X-waiting-period', [ - 'description' => 'The number of seconds to wait before you can attempt to make a request again.'."\n", + $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, @@ -592,14 +690,18 @@ public function shouldParseResponseHeaders() ])], $resource->getMethod('post')->getResponse(503)->getHeaders()); } - /** @test */ + /** + * @test + */ public function shouldReplaceReservedParameter() { - $def = $this->parser->parse(__DIR__.'/fixture/reservedParameter.raml'); + $def = $this->parser->parse(__DIR__ . '/fixture/reservedParameter.raml'); $this->assertEquals('Get list of songs at /songs', $def->getResourceByUri('/songs')->getMethod('get')->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldParameterTransformerWorks() { $def = $this->parser->parse(__DIR__ . '/fixture/parameterTransformer.raml'); @@ -607,7 +709,9 @@ public function shouldParameterTransformerWorks() $this->assertEquals('song /song songs /songs', $def->getResourceByUri('/song')->getMethod('get')->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldParseSchemasDefinedInTheRoot() { $def = $this->parser->parse(__DIR__ . '/fixture/rootSchemas.raml'); @@ -615,10 +719,12 @@ public function shouldParseSchemasDefinedInTheRoot() $this->assertCount(2, $def->getSchemaCollections()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyHandleQueryParameters() { - $def = $this->parser->parse(__DIR__.'/fixture/queryParameters.raml'); + $def = $this->parser->parse(__DIR__ . '/fixture/queryParameters.raml'); $resource = $def->getResourceByUri('/books/1'); $method = $resource->getMethod('get'); @@ -633,27 +739,32 @@ public function shouldCorrectlyHandleQueryParameters() $this->assertFalse($queryParameters['page']->isRequired()); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnBadQueryParameter() { - $this->setExpectedException('\Raml\Exception\InvalidQueryParameterTypeException'); + $this->expectException(InvalidQueryParameterTypeException::class); try { $this->parser->parse(__DIR__ . '/fixture/invalid/queryParameters.raml'); - } catch (\Raml\Exception\InvalidQueryParameterTypeException $e) { + } catch (InvalidQueryParameterTypeException $e) { $this->assertEquals('invalid', $e->getType()); $this->assertEquals([ - 'string', 'number', 'integer', 'date', 'boolean', 'file' + 'string', 'number', 'integer', 'date', 'boolean', 'file', 'array' ], $e->getValidTypes()); + throw $e; } } - /** @test */ + /** + * @test + */ public function shouldReplaceParameterByJsonString() { $def = $this->parser->parse(__DIR__ . '/fixture/jsonStringExample.raml'); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $def->getResourceByUri('/songs')->getMethod('get')->getResponse(200)->getBodyByType('application/json'); $example = $body->getExample(); @@ -664,9 +775,12 @@ public function shouldReplaceParameterByJsonString() ]] ], json_decode($example, true)); } + // --- - /** @test */ + /** + * @test + */ public function shouldParseSecuritySchemes() { $def = $this->parser->parse(__DIR__ . '/fixture/securitySchemes.raml'); @@ -677,8 +791,8 @@ public function shouldParseSecuritySchemes() $securitySchemes = $method->getSecuritySchemes(); $this->assertEquals(2, count($securitySchemes)); - $this->assertInstanceOf('\Raml\SecurityScheme', $securitySchemes['oauth_1_0']); - $this->assertInstanceOf('\Raml\SecurityScheme', $securitySchemes['oauth_2_0']); + $this->assertInstanceOf(SecurityScheme::class, $securitySchemes['oauth_1_0']); + $this->assertInstanceOf(SecurityScheme::class, $securitySchemes['oauth_2_0']); $this->assertEquals( 'OAuth 1.0 continues to be supported for all API requests, but OAuth 2.0 is now preferred.', @@ -699,10 +813,11 @@ public function shouldParseSecuritySchemes() $settings, $securitySchemes['oauth_1_0']->getSettings() ); - } - /** @test */ + /** + * @test + */ public function shouldAddHeadersOfSecuritySchemes() { $def = $this->parser->parse(__DIR__ . '/fixture/securitySchemes.raml'); @@ -712,44 +827,52 @@ public function shouldAddHeadersOfSecuritySchemes() $headers = $method->getHeaders(); $this->assertEquals(1, count($headers)); - $this->assertInstanceOf('\Raml\NamedParameter', $headers['Authorization']); + $this->assertInstanceOf(NamedParameter::class, $headers['Authorization']); } - /** @test */ + /** + * @test + */ public function shouldReplaceSchemaByRootSchema() { $def = $this->parser->parse(__DIR__ . '/fixture/replaceSchemaByRootSchema.raml'); $response = $def->getResourceByUri('/songs/{id}')->getMethod('get')->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('application/json'); /** @var JsonSchemaDefinition $schema */ $schema = $body->getSchema(); - $this->assertInstanceOf('Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); $schema = $schema->getJsonArray(); $this->assertCount(2, $schema['properties']); } - /** @test */ + /** + * @test + */ public function shouldParseAndReplaceSchemaOnlyInResources() { $def = $this->parser->parse(__DIR__ . '/fixture/schemaInTypes.raml'); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $def->getResourceByUri('/projects')->getMethod('post')->getBodyByType('application/json'); $schema = $body->getSchema(); - $this->assertInstanceOf('Raml\Schema\Definition\JsonSchemaDefinition', $schema); + $this->assertInstanceOf(JsonSchemaDefinition::class, $schema); } - /** @test */ + /** + * @test + */ public function shouldParseInfoQExample() { $infoQ = $this->parser->parse(__DIR__ . '/fixture/infoq/eventlog.raml'); $this->assertEquals('Eventlog API', $infoQ->getTitle()); } - /** @test */ + /** + * @test + */ public function shouldLoadATree() { $tree = $this->parser->parse(__DIR__ . '/fixture/includeTreeRaml.raml'); @@ -776,10 +899,12 @@ public function shouldLoadATree() $this->assertEquals('application/json', $jsonBody->getMediaType()); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionOnInvalidBodyType() { - $raml = <<<'RAML' + $raml = <<<'RAML' #%RAML 0.8 title: Test body /: @@ -804,14 +929,18 @@ public function shouldThrowExceptionOnInvalidBodyType() $response->getBodyByType('application/json'); - $this->setExpectedException('\Exception', 'No body found for type "text/xml"'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('No body found for type "text/xml"'); + $response->getBodyByType('text/xml'); } - /** @test */ + /** + * @test + */ public function shouldSupportGenericResponseType() { - $raml = <<<'RAML' + $raml = <<<'RAML' #%RAML 0.8 title: Test body /: @@ -829,12 +958,14 @@ public function shouldSupportGenericResponseType() $resource = $apiDefinition->getResourceByUri('/'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); - /** @var \Raml\Body $body */ + /** @var Body $body */ $body = $response->getBodyByType('text/xml'); $this->assertEquals('A generic description', $body->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldMergeMethodSecurityScheme() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/securitySchemes.raml'); @@ -844,7 +975,9 @@ public function shouldMergeMethodSecurityScheme() $this->assertFalse(empty($headers['Authorization'])); } - /** @test */ + /** + * @test + */ public function shouldAddSecuritySchemeToResource() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/resourceSecuritySchemes.raml'); @@ -853,10 +986,12 @@ public function shouldAddSecuritySchemeToResource() $schemes = $method->getSecuritySchemes(); $this->assertArrayHasKey('oauth_1_0', $schemes); $this->assertArrayHasKey('oauth_2_0', $schemes); - $this->assertTrue(in_array(null, array_keys($schemes))); + $this->assertArrayHasKey('', $schemes); } - /** @test */ + /** + * @test + */ public function shouldParseCustomSettingsOnResource() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/securedByCustomProps.raml'); @@ -869,7 +1004,9 @@ public function shouldParseCustomSettingsOnResource() $this->assertEquals($schemes['custom']->getSettings()['myKey'], 'heLikesItNotSoMuch'); } - /** @test */ + /** + * @test + */ public function shouldParseCustomSettingsOnMethodWithOAuthParser() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/securedByCustomProps.raml'); @@ -877,11 +1014,13 @@ public function shouldParseCustomSettingsOnMethodWithOAuthParser() $method = $resource->getMethod('get'); $schemes = $method->getSecuritySchemes(); $settingsObject = $schemes['oauth_2_0']->getSettings(); - $this->assertSame($settingsObject->getScopes(), array('ADMINISTRATOR', 'USER')); + $this->assertSame($settingsObject->getScopes(), ['ADMINISTRATOR', 'USER']); $this->assertSame($settingsObject->getAuthorizationUri(), 'https://www.dropbox.com/1/oauth2/authorize'); } - /** @test */ + /** + * @test + */ public function shouldParseIncludedTraits() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/includedTraits.raml'); @@ -893,15 +1032,17 @@ public function shouldParseIncludedTraits() $this->assertSame(['id', 'parent_id', 'title'], array_keys($queryParams)); } - /** @test */ + /** + * @test + */ public function shouldParseResourcePathNameCorrectly() { $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/resourcePathName.raml'); $foo = $apiDefinition->getResources()['/foo']; - /** @var \Raml\Resource $fooId */ + /** @var Resource $fooId */ $fooId = $foo->getResources()['/foo/{fooId}']; - /** @var \Raml\Resource $bar */ + /** @var Resource $bar */ $bar = $fooId->getResources()['/foo/{fooId}/bar']; $this->assertEquals('Get a list of foo', $foo->getDescription()); @@ -909,14 +1050,82 @@ public function shouldParseResourcePathNameCorrectly() $this->assertEquals('Get a list of bar', $bar->getDescription()); $baz = $apiDefinition->getResources()['/baz']; - /** @var \Raml\Resource $bazId */ + /** @var Resource $bazId */ $bazId = $baz->getResources()['/baz/{bazId}']; - /** @var \Raml\Response $qux */ + /** @var Response $qux */ $qux = $bazId->getResources()['/baz/{bazId}/qux']; - $this->assertEquals('Get a list of bazDisplayname', $baz->getDescription()); $this->assertEquals('Get a single bazDisplayname', $bazId->getDescription()); $this->assertEquals('Get a list of quxDisplayname', $qux->getDescription()); } + + /** + * @test + */ + public function shouldNestedResourcesHaveParentResourceDefined() + { + $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/resourcePathName.raml'); + + $foo = $apiDefinition->getResources()['/foo']; + /** @var Resource $fooId */ + $fooId = $foo->getResources()['/foo/{fooId}']; + /** @var Resource $bar */ + $bar = $fooId->getResources()['/foo/{fooId}/bar']; + + $this->assertEquals($fooId, $bar->getParentResource()); + $this->assertEquals($foo, $fooId->getParentResource()); + $this->assertEquals(null, $foo->getParentResource()); + } + + /** + * @test + */ + public function shouldParseTraits() + { + $raml = <<>: + description: A valid <> is required + paged: + queryParameters: + page: + type: string + required: true + filtered: + is: [paged] + queryParameters: + limit: + type: integer + required: false + offset: + type: integer + required: false +/users: + is: [secured: { tokenName: access_token }] + get: + is: [filtered] +RAML; + + $simpleRaml = $this->parser->parseFromString($raml, ''); + + $this->assertCount(3, $simpleRaml->getTraits()->toArray()); + $this->assertCount(1, $simpleRaml->getTraits()->getTraitByName('filtered')->getTraits()); + $this->assertCount(1, $simpleRaml->getResourceByPath('/users')->getTraits()); + $this->assertCount(2, $simpleRaml->getResourceByPath('/users')->getMethod('get')->getTraits()); + } } diff --git a/test/TypeTest.php b/tests/TypeTest.php similarity index 85% rename from test/TypeTest.php rename to tests/TypeTest.php index 9dad8160..60a0196c 100644 --- a/test/TypeTest.php +++ b/tests/TypeTest.php @@ -1,19 +1,26 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateCorrectType() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -24,10 +31,12 @@ public function shouldCorrectlyValidateCorrectType() $type = $body->getType(); $type->validate(json_decode('{"title":"Good Song","artist":"An artist"}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateCorrectTypeMissingUnrequired() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -38,13 +47,14 @@ public function shouldCorrectlyValidateCorrectTypeMissingUnrequired() $type = $body->getType(); $type->validate(json_decode('{"title":"Good Song"}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateCorrectTypeMissingRequired() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -53,10 +63,12 @@ public function shouldCorrectlyValidateCorrectTypeMissingRequired() $type = $body->getType(); $type->validate(json_decode('{"artist":"An artist"}', true)); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateIncorrectType() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -67,10 +79,12 @@ public function shouldCorrectlyValidateIncorrectType() $type = $body->getType(); $type->validate([]); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateAdditionalProperties() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -81,10 +95,12 @@ public function shouldCorrectlyValidateAdditionalProperties() $type = $body->getType(); $type->validate(json_decode('{"title": "Good Song", "duration":"3:09"}', true)); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateNullTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -95,14 +111,15 @@ public function shouldCorrectlyValidateNullTypes() $type = $body->getType(); $type->validate(json_decode('{"var": null}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); $type->validate(json_decode('{"var": 10}', true)); - self::assertFalse($type->isValid()); - + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateRightDateTimeOnlyTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -113,10 +130,12 @@ public function shouldCorrectlyValidateRightDateTimeOnlyTypes() $type = $body->getType(); $type->validate(json_decode('{"datetimeOnly": "2017-12-07T15:50:48"}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateWrongDateTimeOnlyTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -127,11 +146,12 @@ public function shouldCorrectlyValidateWrongDateTimeOnlyTypes() $type = $body->getType(); $type->validate(json_decode('{"datetimeOnly": "2017-12 15:50:48"}', true)); - self::assertFalse($type->isValid()); - + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateRightDateOnlyTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -142,11 +162,12 @@ public function shouldCorrectlyValidateRightDateOnlyTypes() $type = $body->getType(); $type->validate(json_decode('{"dateOnly": "2016-02-28"}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateWrongDateOnlyTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -157,11 +178,12 @@ public function shouldCorrectlyValidateWrongDateOnlyTypes() $type = $body->getType(); $type->validate(json_decode('{"dateOnly": "2017-12-07T15:50:48"}', true)); - self::assertFalse($type->isValid()); - + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayIntegerRightTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -172,10 +194,12 @@ public function shouldCorrectlyValidateArrayIntegerRightTypes() $type = $body->getType(); $type->validate(json_decode('{"intArray": [1,2,3]}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayIntegerWrongTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -186,10 +210,12 @@ public function shouldCorrectlyValidateArrayIntegerWrongTypes() $type = $body->getType(); $type->validate(json_decode('{"intArray": [1,2,"str"]}', true)); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayStringRightTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -200,10 +226,12 @@ public function shouldCorrectlyValidateArrayStringRightTypes() $type = $body->getType(); $type->validate(json_decode('{"strArray": ["one", "two"]}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayStringWrongTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -214,10 +242,12 @@ public function shouldCorrectlyValidateArrayStringWrongTypes() $type = $body->getType(); $type->validate(json_decode('{"strArray": [1, "two"]}', true)); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayBooleanRightTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -228,10 +258,12 @@ public function shouldCorrectlyValidateArrayBooleanRightTypes() $type = $body->getType(); $type->validate(json_decode('{"boolArray": [true, false]}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayBooleanWrongTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -242,10 +274,12 @@ public function shouldCorrectlyValidateArrayBooleanWrongTypes() $type = $body->getType(); $type->validate(json_decode('{"boolArray": [true, 0]}', true)); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayNumberRightTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -256,10 +290,12 @@ public function shouldCorrectlyValidateArrayNumberRightTypes() $type = $body->getType(); $type->validate(json_decode('{"numberArray": [12, 13.5, 0]}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateArrayNumberWrongTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple_types.raml'); @@ -270,6 +306,6 @@ public function shouldCorrectlyValidateArrayNumberWrongTypes() $type = $body->getType(); $type->validate(json_decode('{"numberArray": ["12", 0]}', true)); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } } diff --git a/tests/Types/ArrayTest.php b/tests/Types/ArrayTest.php new file mode 100644 index 00000000..d85955b6 --- /dev/null +++ b/tests/Types/ArrayTest.php @@ -0,0 +1,98 @@ +parser = new Parser(); + } + + /** + * @test + */ + public function shouldCorrectlyValidateCorrectType() + { + $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); + $resource = $simpleRaml->getResourceByUri('/songs'); + $method = $resource->getMethod('get'); + $response = $method->getResponse(202); + $body = $response->getBodyByType('application/json'); + $type = $body->getType(); + + $type->validate([ + ['id' => 1, 'name' => 'Sample 1'] + ]); + + $this->assertTrue($type->isValid()); + } + + /** + * @test + */ + public function shouldCorrectlyValidateIncorrectArraySizeLessThanMinimum() + { + $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); + $resource = $simpleRaml->getResourceByUri('/songs'); + $method = $resource->getMethod('get'); + $response = $method->getResponse(202); + $body = $response->getBodyByType('application/json'); + $type = $body->getType(); + + $type->validate([]); + + $this->assertFalse($type->isValid()); + $this->assertEquals('Sample[] (Allowed array size: between 1 and 2, got 0)', (string) $type->getErrors()[0]); + } + + /** + * @test + */ + public function shouldCorrectlyParseTypeFacet() + { + $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); + $resource = $simpleRaml->getResourceByUri('/songs'); + $method = $resource->getMethod('get'); + $response = $method->getResponse(208); + /** @var Body $body */ + $body = $response->getBodyByType('application/json'); + /** @var ArrayType $type */ + $type = $body->getType(); + + $this->assertSame('Sample', $type->getItems()->getName()); + } + + /** + * @test + */ + public function shouldCorrectlyValidateIncorrectTypeWhenArraySizeExceedsMaximum() + { + $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); + $resource = $simpleRaml->getResourceByUri('/songs'); + $method = $resource->getMethod('get'); + $response = $method->getResponse(202); + $body = $response->getBodyByType('application/json'); + $type = $body->getType(); + + $type->validate([ + ['id' => 1, 'name' => 'Sample 1'], + ['id' => 2, 'name' => 'Sample 2'], + ['id' => 3, 'name' => 'Sample 3'] + ]); + + $this->assertFalse($type->isValid()); + $this->assertEquals('Sample[] (Allowed array size: between 1 and 2, got 3)', (string) $type->getErrors()[0]); + } +} diff --git a/test/Types/UnionTypeTest.php b/tests/Types/UnionTypeTest.php similarity index 75% rename from test/Types/UnionTypeTest.php rename to tests/Types/UnionTypeTest.php index b3981db5..9cc53fdd 100644 --- a/test/Types/UnionTypeTest.php +++ b/tests/Types/UnionTypeTest.php @@ -1,19 +1,26 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateCorrectType() { $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); @@ -24,10 +31,12 @@ public function shouldCorrectlyValidateCorrectType() $type = $body->getType(); $type->validate(json_decode('{"id": 1, "name": "Sample name"}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateIncorrectType() { $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); @@ -38,8 +47,8 @@ public function shouldCorrectlyValidateIncorrectType() $type = $body->getType(); $type->validate(json_decode('{"id": 1, "name": false}', true)); - self::assertFalse($type->isValid()); - self::assertEquals( + $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) "")))', @@ -47,7 +56,9 @@ public function shouldCorrectlyValidateIncorrectType() ); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateNullableTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); @@ -58,12 +69,14 @@ public function shouldCorrectlyValidateNullableTypes() $type = $body->getType(); $type->validate(json_decode('{"var": 10}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); $type->validate(json_decode('{"var": null}', true)); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateNullableStringTypes() { $simpleRaml = $this->parser->parse(__DIR__ . '/../fixture/simple_types.raml'); @@ -74,15 +87,15 @@ public function shouldCorrectlyValidateNullableStringTypes() $type = $body->getType(); $type->validate(null); - self::assertTrue($type->isValid()); + $this->assertTrue($type->isValid()); - $type->validate(""); - self::assertTrue($type->isValid()); + $type->validate(''); + $this->assertTrue($type->isValid()); - $type->validate("string"); - self::assertTrue($type->isValid()); + $type->validate('string'); + $this->assertTrue($type->isValid()); $type->validate(1); - self::assertFalse($type->isValid()); + $this->assertFalse($type->isValid()); } } diff --git a/test/Validator/RequestValidatorTest.php b/tests/Validator/RequestValidatorTest.php similarity index 68% rename from test/Validator/RequestValidatorTest.php rename to tests/Validator/RequestValidatorTest.php index 402c8535..497ff4eb 100644 --- a/test/Validator/RequestValidatorTest.php +++ b/tests/Validator/RequestValidatorTest.php @@ -1,30 +1,37 @@ parser = new \Raml\Parser(); - $this->uri = $this->getMock('\Psr\Http\Message\UriInterface'); - $this->request = $this->getMock('\Psr\Http\Message\RequestInterface'); + $this->parser = new Parser(); + $this->uri = $this->createMock(UriInterface::class); + $this->request = $this->createMock(RequestInterface::class); $this->request->method('getUri')->willReturn($this->uri); } @@ -33,16 +40,14 @@ public function setUp() */ public function shouldCatchWrongMediaType() { + $this->expectException(ValidatorRequestException::class); + $this->expectExceptionMessage('Invalid Media type'); + $this->request->method('getMethod')->willReturn('get'); $this->uri->method('getPath')->willReturn('/songs'); $this->uri->method('getQuery')->willReturn(''); $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/xml'); - $this->setExpectedException( - '\Raml\Validator\ValidatorRequestException', - 'Invalid Media type' - ); - $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/requestAcceptHeader.raml'); $validator->validateRequest($this->request); } @@ -66,7 +71,7 @@ public function shouldSuccessfullyAssertWildcardAcceptHeader() */ public function shouldNotAssertBodyOnGetRequest() { - $body = $this->getMock('\Psr\Http\Message\StreamInterface'); + $body = $this->createMock(StreamInterface::class); $body->method('getContents')->willReturn(''); $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); @@ -89,10 +94,8 @@ public function shouldCatchMissingParameters() $this->uri->method('getQuery')->willReturn(''); $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); - $this->setExpectedException( - '\Raml\Validator\ValidatorRequestException', - 'required_number' - ); + $this->expectException(ValidatorRequestException::class); + $this->expectExceptionMessage('required_number'); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/queryParameters.raml'); $validator->validateRequest($this->request); @@ -108,10 +111,8 @@ public function shouldCatchInvalidParameters() $this->uri->method('getQuery')->willReturn('required_number=5&optional_long_string=ABC'); $this->request->method('getHeaderLine')->with('Accept')->willReturn('application/json'); - $this->setExpectedException( - '\Raml\Validator\ValidatorRequestException', - 'optional_long_string' - ); + $this->expectException(ValidatorRequestException::class); + $this->expectExceptionMessage('optional_long_string'); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/queryParameters.raml'); $validator->validateRequest($this->request); @@ -122,7 +123,7 @@ public function shouldCatchInvalidParameters() */ public function shouldCatchInvalidBody() { - $body = $this->getMock('\Psr\Http\Message\StreamInterface'); + $body = $this->createMock(StreamInterface::class); $body->method('getContents')->willReturn('{"title":"Aaa"}'); $this->request->method('getMethod')->willReturn('post'); @@ -130,7 +131,24 @@ public function shouldCatchInvalidBody() $this->request->method('getHeaderLine')->willReturn('application/json'); $this->request->method('getBody')->willReturn($body); - $this->setExpectedException('\Raml\Validator\ValidatorRequestException'); + $this->expectException(ValidatorRequestException::class); + + $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/requestBody.raml'); + $validator->validateRequest($this->request); + } + + /** + * @test + */ + public function shouldAllowEmptyRequestBody() + { + $body = $this->createMock(StreamInterface::class); + $body->method('getContents')->willReturn(''); + + $this->request->method('getMethod')->willReturn('get'); + $this->uri->method('getPath')->willReturn('/songs'); + $this->request->method('getHeaderLine')->with('Content-Type')->willReturn('application/json'); + $this->request->method('getBody')->willReturn($body); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/requestBody.raml'); $validator->validateRequest($this->request); diff --git a/test/Validator/ResponseValidatorTest.php b/tests/Validator/ResponseValidatorTest.php similarity index 70% rename from test/Validator/ResponseValidatorTest.php rename to tests/Validator/ResponseValidatorTest.php index f6c31b99..20f17229 100644 --- a/test/Validator/ResponseValidatorTest.php +++ b/tests/Validator/ResponseValidatorTest.php @@ -1,36 +1,45 @@ parser = new \Raml\Parser(); - $this->uri = $this->getMock('\Psr\Http\Message\UriInterface'); - $this->request = $this->getMock('\Psr\Http\Message\RequestInterface'); + $this->parser = new Parser(); + $this->uri = $this->createMock(UriInterface::class); + $this->request = $this->createMock(RequestInterface::class); $this->request->method('getUri')->willReturn($this->uri); - $this->response = $this->getMock('\Psr\Http\Message\ResponseInterface'); + $this->response = $this->createMock(ResponseInterface::class); } /** @@ -45,7 +54,9 @@ private function getValidatorForSchema($fixturePath) return new ResponseValidator($helper); } - /** @test */ + /** + * @test + */ public function shouldCatchMissingHeaders() { $this->request->method('getMethod')->willReturn('get'); @@ -53,20 +64,20 @@ public function shouldCatchMissingHeaders() $this->response->method('getStatusCode')->willReturn(200); $this->response->method('getHeaders')->willReturn([]); - $this->setExpectedException( - '\Raml\Validator\ValidatorResponseException', - 'X-Required-Header' - ); + $this->expectException(ValidatorResponseException::class); + $this->expectExceptionMessage('X-Required-Header'); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/responseHeaders.raml'); $validator->validateResponse($this->request, $this->response); } - /** @test */ + /** + * @test + */ public function shouldCatchInvalidHeaders() { $headers = [ - 'X-Required-Header' => ['123456'], + 'X-Required-Header' => ['123456'], 'X-Long-Optional-Header' => ['Abcdefg', 'Abc'], ]; @@ -81,21 +92,22 @@ public function shouldCatchInvalidHeaders() $this->response->method('getHeader')->willReturnMap($map); $this->response->method('getHeaders')->willReturn($headers); - $this->setExpectedException( - '\Raml\Validator\ValidatorResponseException', - 'X-Long-Optional-Header' - ); + $this->expectException(ValidatorResponseException::class); + $this->expectExceptionMessage('X-Long-Optional-Header'); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/responseHeaders.raml'); $validator->validateResponse($this->request, $this->response); } + /** + * @test + */ public function shouldPassOnEmptyBodyIfNotRequired() { $json = ''; $headers = [ - 'X-Required-Header' => ['123456'], + 'X-Required-Header' => ['123456'], 'X-Long-Optional-Header' => ['Abcdefghijkl'], ]; @@ -104,7 +116,7 @@ public function shouldPassOnEmptyBodyIfNotRequired() ['X-Long-Optional-Header', [['Abcdefg', 'Abc']]], ]; - $body = $this->getMock('\Psr\Http\Message\StreamInterface'); + $body = $this->createMock(StreamInterface::class); $body->method('getContents')->willReturn($json); $this->request->method('getMethod')->willReturn('get'); @@ -114,22 +126,22 @@ public function shouldPassOnEmptyBodyIfNotRequired() $this->response->method('getHeaders')->willReturn($headers); $this->response->method('getBody')->willReturn($body); - $this->setExpectedException( - '\Raml\Validator\ValidatorResponseException', - 'X-Long-Optional-Header' - ); + $this->expectException(ValidatorResponseException::class); + $this->expectExceptionMessage('X-Long-Optional-Header'); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/responseHeaders.raml'); $validator->validateResponse($this->request, $this->response); } - /** @test */ + /** + * @test + */ public function shouldCatchInvalidBody() { $json = '{}'; $headers = [ - 'X-Required-Header' => ['123456'], + 'X-Required-Header' => ['123456'], 'X-Long-Optional-Header' => ['Abcdefghijkl'], ]; @@ -138,7 +150,7 @@ public function shouldCatchInvalidBody() ['X-Long-Optional-Header', [['Abcdefg', 'Abc']]], ]; - $body = $this->getMock('\Psr\Http\Message\StreamInterface'); + $body = $this->createMock(StreamInterface::class); $body->method('getContents')->willReturn($json); $this->request->method('getMethod')->willReturn('post'); @@ -149,7 +161,7 @@ public function shouldCatchInvalidBody() $this->response->method('getHeaderLine')->with('Content-Type')->willReturn('application/json'); $this->response->method('getBody')->willReturn($body); - $this->setExpectedException('\Raml\Validator\ValidatorResponseException'); + $this->expectException(ValidatorResponseException::class); $validator = $this->getValidatorForSchema(__DIR__ . '/../fixture/validator/responseBody.raml'); $validator->validateResponse($this->request, $this->response); diff --git a/test/Validator/ValidatorSchemaHelperTest.php b/tests/Validator/ValidatorSchemaHelperTest.php similarity index 72% rename from test/Validator/ValidatorSchemaHelperTest.php rename to tests/Validator/ValidatorSchemaHelperTest.php index 2abd132b..6d413566 100644 --- a/test/Validator/ValidatorSchemaHelperTest.php +++ b/tests/Validator/ValidatorSchemaHelperTest.php @@ -1,20 +1,26 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } - + /** * @param string $fixturePath * @return ValidatorSchemaHelper @@ -26,75 +32,79 @@ public function getHelperForSchema($fixturePath) return new ValidatorSchemaHelper($apiDefinition); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionIfResourceNotFound() { - $this->setExpectedException( - '\Raml\Validator\ValidatorSchemaException', - '/images was not found' - ); + $this->expectException(ValidatorSchemaException::class); + $this->expectExceptionMessage('/images was not found'); $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/simple.raml'); $helper->getResponse('get', '/images', 200); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionIfMethodNotFound() { - $this->setExpectedException( - '\Raml\Validator\ValidatorSchemaException', - 'POST /songs was not found' - ); + $this->expectException(ValidatorSchemaException::class); + $this->expectExceptionMessage('POST /songs was not found'); $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/simple.raml'); $helper->getResponse('post', '/songs', 200); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionIfResponseNotFound() { - $this->setExpectedException( - '\Raml\Validator\ValidatorSchemaException', - 'GET /songs with status code 300 was not found' - ); + $this->expectException(ValidatorSchemaException::class); + $this->expectExceptionMessage('GET /songs with status code 300 was not found'); $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/simple.raml'); $helper->getResponse('get', '/songs', 300); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionIfResponseBodyNotFound() { - $this->setExpectedException( - '\Raml\Validator\ValidatorSchemaException', - 'GET /songs with content type application/xml was not found' - ); + $this->expectException(ValidatorSchemaException::class); + $this->expectExceptionMessage('GET /songs with content type application/xml was not found'); $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/simple.raml'); $helper->getResponseBody('get', '/songs', 200, 'application/xml'); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyReturnResponseBodyWithCompositeMediaType() { $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/requestBody.raml'); $actual = $helper->getRequestBody('post', '/songs', 'application/json;charset=UTF-8'); - $this->assertInstanceOf('\Raml\Body', $actual); + $this->assertInstanceOf(Body::class, $actual); } - /** @test */ + /** + * @test + */ public function shouldThrowExceptionIfResponseBodyIsNotABodyObject() { - $this->setExpectedException( - '\Raml\Validator\ValidatorSchemaException', - 'not a Body object' - ); + $this->expectException(ValidatorSchemaException::class); + $this->expectExceptionMessage('not a Body object'); $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/webFormBody.raml'); $helper->getRequestBody('post', '/songs', 'application/x-www-form-urlencoded'); } - /** @test */ + /** + * @test + */ public function shouldGetRequiredOrAllQueryParameters() { $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/queryParameters.raml'); @@ -109,37 +119,45 @@ public function shouldGetRequiredOrAllQueryParameters() $this->assertSame(['required_number'], array_keys($requiredParameters)); } - /** @test */ + /** + * @test + */ public function shouldGetRequestBody() { $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/requestBody.raml'); $actual = $helper->getRequestBody('post', '/songs', 'application/json'); - $this->assertInstanceOf('\Raml\Body', $actual); + $this->assertInstanceOf(Body::class, $actual); $this->assertSame('POST /songs JSON body', $actual->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldGetResponse() { $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/requestBody.raml'); $actual = $helper->getRequestBody('post', '/songs', 'application/json'); - $this->assertInstanceOf('\Raml\Body', $actual); + $this->assertInstanceOf(Body::class, $actual); $this->assertSame('POST /songs JSON body', $actual->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldGetResponseBody() { $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/responseBody.raml'); $actual = $helper->getResponseBody('post', '/songs', 200, 'application/json'); - $this->assertInstanceOf('\Raml\Body', $actual); + $this->assertInstanceOf(Body::class, $actual); $this->assertSame('POST /songs 200 response JSON body', $actual->getDescription()); } - /** @test */ + /** + * @test + */ public function shouldGetResponseHeaders() { $helper = $this->getHelperForSchema(__DIR__ . '/../fixture/validator/responseHeaders.raml'); diff --git a/test/XmlSchemaTest.php b/tests/XmlSchemaTest.php similarity index 80% rename from test/XmlSchemaTest.php rename to tests/XmlSchemaTest.php index 87af215a..fbb28bdf 100644 --- a/test/XmlSchemaTest.php +++ b/tests/XmlSchemaTest.php @@ -1,23 +1,28 @@ parser = new \Raml\Parser(); + $this->parser = new Parser(); } /** - * @return \Raml\Schema\Definition\XmlSchemaDefinition + * @return XmlSchemaDefinition */ private function getSchema() { @@ -56,19 +61,22 @@ private function getSchema() return $body->getSchema(); } - // --- - - /** @test */ + /** + * @test + */ public function shouldReturnXmlSchemeDefinition() { $this->assertInstanceOf('Raml\Schema\Definition\XmlSchemaDefinition', $this->getSchema()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateCorrectXml() { - $xml = new DOMDocument(); - $xml->loadXML(<<<'XML' + $xml = new \DOMDocument(); + $xml->loadXML( + <<<'XML' v1.0 @@ -81,11 +89,14 @@ public function shouldCorrectlyValidateCorrectXml() $this->assertTrue($schema->isValid()); } - /** @test */ + /** + * @test + */ public function shouldCorrectlyValidateIncorrectXml() { - $xml = new DOMDocument(); - $xml->loadXML(<<<'XML' + $xml = new \DOMDocument(); + $xml->loadXML( + <<<'XML' v1.0 @@ -104,7 +115,6 @@ public function shouldCorrectlyValidateIncorrectXml() } /** - * Test __toString() * @test */ public function shouldConvertXmlToString() @@ -113,12 +123,11 @@ public function shouldConvertXmlToString() } /** - * Test validate() * @test */ public function shouldThrowExceptionOnIncorrectXml() { - $badXml = new DOMDocument(); + $badXml = new \DOMDocument(); $badXml->loadXML(''); $schema = $this->loadXmlSchema(); @@ -127,12 +136,12 @@ public function shouldThrowExceptionOnIncorrectXml() } /** - * Common to all tests - * @return \Raml\Schema\Definition\XmlSchemaDefinition + * @return XmlSchemaDefinition */ private function loadXmlSchema() { $xmlRaml = $this->parser->parse(__DIR__ . '/fixture/xmlSchema.raml'); + return $xmlRaml->getResourceByUri('/jobs') ->getMethod('get') ->getResponse(200) @@ -141,19 +150,18 @@ private function loadXmlSchema() } /** - * @param ValidatorInterface $validator * @param TypeValidationError[] $errors */ - private function assertValidationFailedWithErrors(ValidatorInterface $validator, $errors) + private function assertValidationFailedWithErrors(ValidatorInterface $validator, array $errors) { - self::assertFalse($validator->isValid(), 'Validator expected to fail'); + $this->assertFalse($validator->isValid(), 'Validator expected to fail'); foreach ($errors as $error) { - self::assertContains( + $this->assertContains( $error, $validator->getErrors(), $message = sprintf('Validator expected to contain error: %s', $error->__toString()), $ignoreCase = false, - $checkObjectidentity = false + $checkObjectIdentity = false ); } } diff --git a/test/fixture/child.raml b/tests/fixture/child.raml similarity index 100% rename from test/fixture/child.raml rename to tests/fixture/child.raml diff --git a/test/fixture/child.yaml b/tests/fixture/child.yaml similarity index 100% rename from test/fixture/child.yaml rename to tests/fixture/child.yaml diff --git a/test/fixture/hateoas/example.raml b/tests/fixture/hateoas/example.raml similarity index 100% rename from test/fixture/hateoas/example.raml rename to tests/fixture/hateoas/example.raml diff --git a/test/fixture/hateoas/posts.schema.json b/tests/fixture/hateoas/posts.schema.json similarity index 100% rename from test/fixture/hateoas/posts.schema.json rename to tests/fixture/hateoas/posts.schema.json diff --git a/test/fixture/hateoas/resourceTypes.raml b/tests/fixture/hateoas/resourceTypes.raml similarity index 100% rename from test/fixture/hateoas/resourceTypes.raml rename to tests/fixture/hateoas/resourceTypes.raml diff --git a/tests/fixture/hateoas/traits.raml b/tests/fixture/hateoas/traits.raml new file mode 100644 index 00000000..2910f99e --- /dev/null +++ b/tests/fixture/hateoas/traits.raml @@ -0,0 +1,9 @@ +# traits.raml +paginated: + queryParameters: + pages: + description: La página a retornar. + type: number + size: + description: El tamaño de la página. + type: number diff --git a/test/fixture/headers.raml b/tests/fixture/headers.raml similarity index 100% rename from test/fixture/headers.raml rename to tests/fixture/headers.raml diff --git a/test/fixture/includeRaml.raml b/tests/fixture/includeRaml.raml similarity index 100% rename from test/fixture/includeRaml.raml rename to tests/fixture/includeRaml.raml diff --git a/test/fixture/includeSchema.raml b/tests/fixture/includeSchema.raml similarity index 100% rename from test/fixture/includeSchema.raml rename to tests/fixture/includeSchema.raml diff --git a/test/fixture/includeTreeRaml.raml b/tests/fixture/includeTreeRaml.raml similarity index 100% rename from test/fixture/includeTreeRaml.raml rename to tests/fixture/includeTreeRaml.raml diff --git a/test/fixture/includeUnknownSchema.raml b/tests/fixture/includeUnknownSchema.raml similarity index 100% rename from test/fixture/includeUnknownSchema.raml rename to tests/fixture/includeUnknownSchema.raml diff --git a/test/fixture/includeUrlPrefix.raml b/tests/fixture/includeUrlPrefix.raml similarity index 100% rename from test/fixture/includeUrlPrefix.raml rename to tests/fixture/includeUrlPrefix.raml diff --git a/test/fixture/includeYaml.raml b/tests/fixture/includeYaml.raml similarity index 100% rename from test/fixture/includeYaml.raml rename to tests/fixture/includeYaml.raml diff --git a/test/fixture/includedTraits.raml b/tests/fixture/includedTraits.raml similarity index 100% rename from test/fixture/includedTraits.raml rename to tests/fixture/includedTraits.raml diff --git a/test/fixture/infoq/eventExample.json b/tests/fixture/infoq/eventExample.json similarity index 100% rename from test/fixture/infoq/eventExample.json rename to tests/fixture/infoq/eventExample.json diff --git a/test/fixture/infoq/eventListExample.json b/tests/fixture/infoq/eventListExample.json similarity index 100% rename from test/fixture/infoq/eventListExample.json rename to tests/fixture/infoq/eventListExample.json diff --git a/test/fixture/infoq/eventListSchema.json b/tests/fixture/infoq/eventListSchema.json similarity index 100% rename from test/fixture/infoq/eventListSchema.json rename to tests/fixture/infoq/eventListSchema.json diff --git a/test/fixture/infoq/eventSchema.json b/tests/fixture/infoq/eventSchema.json similarity index 100% rename from test/fixture/infoq/eventSchema.json rename to tests/fixture/infoq/eventSchema.json diff --git a/test/fixture/infoq/eventlog.raml b/tests/fixture/infoq/eventlog.raml similarity index 69% rename from test/fixture/infoq/eventlog.raml rename to tests/fixture/infoq/eventlog.raml index 82fe6f8d..505a655f 100644 --- a/test/fixture/infoq/eventlog.raml +++ b/tests/fixture/infoq/eventlog.raml @@ -11,7 +11,7 @@ schemas: eventListJson: !include eventListSchema.json securitySchemes: - - oauth_2: + oauth_2: description: Eventlog uses OAuth2 security scheme only. type: OAuth 2.0 describedBy: @@ -43,33 +43,33 @@ resourceTypes: body: traits: - - slidingwindow: - description: Query parameters related to retrieving a sliding window of timestamped entities relative to now. - queryParameters: - windowstart: - description: The begining of the sliding window expressed as a time interval from now represented as an integer concatenated with units h (hours), m (minutes), s (seconds) or ms (milliseconds). - type: string - example: 1h, 30m, 86164s - windowsize: - description: The end of the sliding window expressed as a time interval from the start as a concatenated integer and unit suffix. - type: string - example: 10s, 1h, 25m - - paginated: - queryParameters: - pagenumber: - description: The page number of the result-set to return. - type: integer - minimum: 0 - pagesize: - description: The number of rows in a page request. - type: integer - maximum: 100 - - limited: - queryParameters: - limit: - description: A general limit on the number of rows to return in any request. - type: integer - default: 100 + slidingwindow: + description: Query parameters related to retrieving a sliding window of timestamped entities relative to now. + queryParameters: + windowstart: + description: The begining of the sliding window expressed as a time interval from now represented as an integer concatenated with units h (hours), m (minutes), s (seconds) or ms (milliseconds). + type: string + example: 1h, 30m, 86164s + windowsize: + description: The end of the sliding window expressed as a time interval from the start as a concatenated integer and unit suffix. + type: string + example: 10s, 1h, 25m + paginated: + queryParameters: + pagenumber: + description: The page number of the result-set to return. + type: integer + minimum: 0 + pagesize: + description: The number of rows in a page request. + type: integer + maximum: 100 + limited: + queryParameters: + limit: + description: A general limit on the number of rows to return in any request. + type: integer + default: 100 /streams/{streamName}: diff --git a/test/fixture/invalid/bad.json b/tests/fixture/invalid/bad.json similarity index 100% rename from test/fixture/invalid/bad.json rename to tests/fixture/invalid/bad.json diff --git a/test/fixture/invalid/bad.raml b/tests/fixture/invalid/bad.raml similarity index 70% rename from test/fixture/invalid/bad.raml rename to tests/fixture/invalid/bad.raml index bbe47945..b408b677 100644 --- a/test/fixture/invalid/bad.raml +++ b/tests/fixture/invalid/bad.raml @@ -1,14 +1,14 @@ -#%RAML 0.8 +#%RAML 1.0 title: World Music API baseUri: http://example.api.com/{version} version: v1 traits: - - paged: - queryParameters: - pages: - description: The number of pages to return - type: number + paged: + queryParameters: + pages: + description: The number of pages to return + type: number /songs: get: is: [ paged ] diff --git a/test/fixture/invalid/badJson.raml b/tests/fixture/invalid/badJson.raml similarity index 100% rename from test/fixture/invalid/badJson.raml rename to tests/fixture/invalid/badJson.raml diff --git a/test/fixture/invalid/empty.raml b/tests/fixture/invalid/empty.raml similarity index 100% rename from test/fixture/invalid/empty.raml rename to tests/fixture/invalid/empty.raml diff --git a/test/fixture/invalid/noTitle.raml b/tests/fixture/invalid/noTitle.raml similarity index 100% rename from test/fixture/invalid/noTitle.raml rename to tests/fixture/invalid/noTitle.raml diff --git a/test/fixture/invalid/queryParameters.raml b/tests/fixture/invalid/queryParameters.raml similarity index 100% rename from test/fixture/invalid/queryParameters.raml rename to tests/fixture/invalid/queryParameters.raml diff --git a/test/fixture/jsonStringExample.raml b/tests/fixture/jsonStringExample.raml similarity index 100% rename from test/fixture/jsonStringExample.raml rename to tests/fixture/jsonStringExample.raml diff --git a/test/fixture/methodDescription.raml b/tests/fixture/methodDescription.raml similarity index 100% rename from test/fixture/methodDescription.raml rename to tests/fixture/methodDescription.raml diff --git a/test/fixture/parameterTransformer.raml b/tests/fixture/parameterTransformer.raml similarity index 100% rename from test/fixture/parameterTransformer.raml rename to tests/fixture/parameterTransformer.raml diff --git a/test/fixture/parentAndChildSchema.raml b/tests/fixture/parentAndChildSchema.raml similarity index 100% rename from test/fixture/parentAndChildSchema.raml rename to tests/fixture/parentAndChildSchema.raml diff --git a/test/fixture/protocols/invalidProtocolsSpecified.raml b/tests/fixture/protocols/invalidProtocolsSpecified.raml similarity index 100% rename from test/fixture/protocols/invalidProtocolsSpecified.raml rename to tests/fixture/protocols/invalidProtocolsSpecified.raml diff --git a/test/fixture/protocols/noProtocolSpecified.raml b/tests/fixture/protocols/noProtocolSpecified.raml similarity index 65% rename from test/fixture/protocols/noProtocolSpecified.raml rename to tests/fixture/protocols/noProtocolSpecified.raml index dc65c194..198b5a2a 100644 --- a/test/fixture/protocols/noProtocolSpecified.raml +++ b/tests/fixture/protocols/noProtocolSpecified.raml @@ -1,4 +1,3 @@ #%RAML 0.8 title: No protocol specified test -title: Invalid protocols specified test baseUri: http://example.api.com \ No newline at end of file diff --git a/test/fixture/protocols/protocolsSpecified.raml b/tests/fixture/protocols/protocolsSpecified.raml similarity index 100% rename from test/fixture/protocols/protocolsSpecified.raml rename to tests/fixture/protocols/protocolsSpecified.raml diff --git a/test/fixture/queryParameters.raml b/tests/fixture/queryParameters.raml similarity index 100% rename from test/fixture/queryParameters.raml rename to tests/fixture/queryParameters.raml diff --git a/test/fixture/raml-1.0/complexTypes.raml b/tests/fixture/raml-1.0/complexTypes.raml similarity index 100% rename from test/fixture/raml-1.0/complexTypes.raml rename to tests/fixture/raml-1.0/complexTypes.raml diff --git a/test/fixture/raml-1.0/example/test.json b/tests/fixture/raml-1.0/example/test.json similarity index 100% rename from test/fixture/raml-1.0/example/test.json rename to tests/fixture/raml-1.0/example/test.json diff --git a/tests/fixture/raml-1.0/inheritanceTypes.raml b/tests/fixture/raml-1.0/inheritanceTypes.raml new file mode 100644 index 00000000..07155c89 --- /dev/null +++ b/tests/fixture/raml-1.0/inheritanceTypes.raml @@ -0,0 +1,69 @@ +#%RAML 1.0 +title: My API with Types +mediaType: application/json +types: + Person: + type: object + properties: + firstname: + type: string + minLength: 2 + maxLength: 50 + lastname: + type: string + minLength: 2 + maxLength: 50 + age: + type: integer + minimum: 18 + maximum: 70 + title?: string + Manager: + type: Person + properties: + reports: Person[] + clearanceLevel: + type: ClearanceLevels + required: false + Admin: + type: Person + properties: + clearanceLevel: + type: ClearanceLevels + required: true + ClearanceLevels: + type: object + properties: + level: + type: string + enum: + - low + - high +/orgs/{orgId}: + get: + responses: + 200: + body: + application/json: + type: Org # reference to global type definition + example: + onCall: + firstname: John + lastname: Flare + kind: AlertableAdmin + age: 35 + clearanceLevel: low + phone: "12321" + Head: + firstname: Nico + lastname: Ark + age: 41 + kind: Manager + reports: + - + firstname: Archie + lastname: Ark + age: 40 + kind: Admin + clearanceLevel: low + phone: "123-23" diff --git a/tests/fixture/raml-1.0/library/library.raml b/tests/fixture/raml-1.0/library/library.raml new file mode 100644 index 00000000..f00ceb9a --- /dev/null +++ b/tests/fixture/raml-1.0/library/library.raml @@ -0,0 +1,31 @@ +#%RAML 1.0 Library +uses: + Sub: sub_library.raml + +types: + File: + properties: + name: + type: string + category: + type: Sub.Category + Folder: + properties: + items_count: + type: integer + name: + type: string + files: + type: array + items: + type: File +traits: + Iterable: + queryParameters: + next: + type: integer + Paged: + is: [Iterable, Sub.Book] + queryParameters: + page: + type: string diff --git a/tests/fixture/raml-1.0/library/sub_library.raml b/tests/fixture/raml-1.0/library/sub_library.raml new file mode 100644 index 00000000..69f947a8 --- /dev/null +++ b/tests/fixture/raml-1.0/library/sub_library.raml @@ -0,0 +1,11 @@ +#%RAML 1.0 Library +types: + Category: + properties: + name: + type: string +traits: + Book: + queryParameters: + isbn: + type: integer diff --git a/test/fixture/raml-1.0/traits.raml b/tests/fixture/raml-1.0/traits.raml similarity index 100% rename from test/fixture/raml-1.0/traits.raml rename to tests/fixture/raml-1.0/traits.raml diff --git a/test/fixture/raml-1.0/traitsAndTypes.raml b/tests/fixture/raml-1.0/traitsAndTypes.raml similarity index 100% rename from test/fixture/raml-1.0/traitsAndTypes.raml rename to tests/fixture/raml-1.0/traitsAndTypes.raml diff --git a/tests/fixture/raml-1.0/types.raml b/tests/fixture/raml-1.0/types.raml new file mode 100644 index 00000000..ebdd9988 --- /dev/null +++ b/tests/fixture/raml-1.0/types.raml @@ -0,0 +1,57 @@ +#%RAML 1.0 +title: Example API +version: v1 + +uses: + Library: library/library.raml + +types: + TestType: + type: object + properties: + id: integer + name: string + example: !include example/test.json + LibraryFile: + type: Library.File + properties: + author: + type: string + +/test: + get: + responses: + 200: + body: + application/json: + type: TestType + post: + body: + type: TestType + responses: + 200: + body: + application/json: + type: TestType + /library: + get: + responses: + 200: + body: + application/json: + type: LibraryFile + post: + body: + type: LibraryFile + responses: + 200: + body: + application/json: + type: LibraryFile + /file: + get: + responses: + 200: + body: + application/json: + type: Library.File diff --git a/test/fixture/replaceSchemaByRootSchema.raml b/tests/fixture/replaceSchemaByRootSchema.raml similarity index 100% rename from test/fixture/replaceSchemaByRootSchema.raml rename to tests/fixture/replaceSchemaByRootSchema.raml diff --git a/test/fixture/reservedParameter.raml b/tests/fixture/reservedParameter.raml similarity index 100% rename from test/fixture/reservedParameter.raml rename to tests/fixture/reservedParameter.raml diff --git a/test/fixture/resourceDescription.raml b/tests/fixture/resourceDescription.raml similarity index 100% rename from test/fixture/resourceDescription.raml rename to tests/fixture/resourceDescription.raml diff --git a/test/fixture/resourcePathName.raml b/tests/fixture/resourcePathName.raml similarity index 100% rename from test/fixture/resourcePathName.raml rename to tests/fixture/resourcePathName.raml diff --git a/test/fixture/resourceSecuritySchemes.raml b/tests/fixture/resourceSecuritySchemes.raml similarity index 96% rename from test/fixture/resourceSecuritySchemes.raml rename to tests/fixture/resourceSecuritySchemes.raml index 24eb1304..a8280ea8 100644 --- a/test/fixture/resourceSecuritySchemes.raml +++ b/tests/fixture/resourceSecuritySchemes.raml @@ -1,9 +1,9 @@ -#%RAML 0.8 +#%RAML 1.0 title: Dropbox API version: 1 baseUri: https://api.dropbox.com/{version} securitySchemes: - - oauth_2_0: + oauth_2_0: description: | Dropbox supports OAuth 2.0 for authenticating all API requests. type: OAuth 2.0 @@ -34,7 +34,7 @@ securitySchemes: authorizationUri: https://www.dropbox.com/1/oauth2/authorize accessTokenUri: https://api.dropbox.com/1/oauth2/token authorizationGrants: [ code, token ] - - oauth_1_0: + oauth_1_0: description: | OAuth 1.0 continues to be supported for all API requests, but OAuth 2.0 is now preferred. type: OAuth 1.0 @@ -42,7 +42,7 @@ securitySchemes: requestTokenUri: https://api.dropbox.com/1/oauth/request_token authorizationUri: https://www.dropbox.com/1/oauth/authorize tokenCredentialsUri: https://api.dropbox.com/1/oauth/access_token - - customHeader: + customHeader: description: | A custom diff --git a/test/fixture/rootSchemas.raml b/tests/fixture/rootSchemas.raml similarity index 100% rename from test/fixture/rootSchemas.raml rename to tests/fixture/rootSchemas.raml diff --git a/test/fixture/schema/child.json b/tests/fixture/schema/child.json similarity index 100% rename from test/fixture/schema/child.json rename to tests/fixture/schema/child.json diff --git a/test/fixture/schema/parent.json b/tests/fixture/schema/parent.json similarity index 100% rename from test/fixture/schema/parent.json rename to tests/fixture/schema/parent.json diff --git a/test/fixture/schemaInRoot.raml b/tests/fixture/schemaInRoot.raml similarity index 100% rename from test/fixture/schemaInRoot.raml rename to tests/fixture/schemaInRoot.raml diff --git a/test/fixture/schemaInTypes.raml b/tests/fixture/schemaInTypes.raml similarity index 100% rename from test/fixture/schemaInTypes.raml rename to tests/fixture/schemaInTypes.raml diff --git a/test/fixture/securedByCustomProps.raml b/tests/fixture/securedByCustomProps.raml similarity index 98% rename from test/fixture/securedByCustomProps.raml rename to tests/fixture/securedByCustomProps.raml index 7021b0e2..9e2238ef 100644 --- a/test/fixture/securedByCustomProps.raml +++ b/tests/fixture/securedByCustomProps.raml @@ -1,9 +1,9 @@ -#%RAML 0.8 +#%RAML 1.0 title: Dropbox API version: 1 baseUri: https://api.dropbox.com/{version} securitySchemes: - - oauth_2_0: + oauth_2_0: description: | Dropbox supports OAuth 2.0 for authenticating all API requests. type: OAuth 2.0 @@ -34,7 +34,7 @@ securitySchemes: authorizationUri: https://www.dropbox.com/1/oauth2/authorize accessTokenUri: https://api.dropbox.com/1/oauth2/token authorizationGrants: [ code, token ] - - custom: + custom: type: x-custom describedBy: headers: diff --git a/test/fixture/securitySchemes.raml b/tests/fixture/securitySchemes.raml similarity index 96% rename from test/fixture/securitySchemes.raml rename to tests/fixture/securitySchemes.raml index 2d4efaf5..74751fee 100644 --- a/test/fixture/securitySchemes.raml +++ b/tests/fixture/securitySchemes.raml @@ -1,9 +1,9 @@ -#%RAML 0.8 +#%RAML 1.0 title: Dropbox API version: 1 baseUri: https://api.dropbox.com/{version} securitySchemes: - - oauth_2_0: + oauth_2_0: description: | Dropbox supports OAuth 2.0 for authenticating all API requests. type: OAuth 2.0 @@ -34,7 +34,7 @@ securitySchemes: authorizationUri: https://www.dropbox.com/1/oauth2/authorize accessTokenUri: https://api.dropbox.com/1/oauth2/token authorizationGrants: [ code, token ] - - oauth_1_0: + oauth_1_0: description: | OAuth 1.0 continues to be supported for all API requests, but OAuth 2.0 is now preferred. type: OAuth 1.0 @@ -42,7 +42,7 @@ securitySchemes: requestTokenUri: https://api.dropbox.com/1/oauth/request_token authorizationUri: https://www.dropbox.com/1/oauth/authorize tokenCredentialsUri: https://api.dropbox.com/1/oauth/access_token - - customHeader: + customHeader: description: | A custom diff --git a/test/fixture/simple.raml b/tests/fixture/simple.raml similarity index 93% rename from test/fixture/simple.raml rename to tests/fixture/simple.raml index 7e2df691..929ce083 100644 --- a/test/fixture/simple.raml +++ b/tests/fixture/simple.raml @@ -5,11 +5,11 @@ baseUri: http://example.api.com/{version} version: v1 mediaType: application/json traits: - - paged: - queryParameters: - pages: - description: The number of pages to return - type: number + paged: + queryParameters: + pages: + description: The number of pages to return + type: number /songs: get: is: [ paged ] diff --git a/test/fixture/simple_types.raml b/tests/fixture/simple_types.raml similarity index 54% rename from test/fixture/simple_types.raml rename to tests/fixture/simple_types.raml index 330f64d9..b564f253 100644 --- a/test/fixture/simple_types.raml +++ b/tests/fixture/simple_types.raml @@ -8,41 +8,47 @@ mediaType: application/json /songs: get: responses: - 200: - body: - application/json: - type: Song - 201: - body: - application/json: + 200: + body: + application/json: + type: Song + 201: + body: + application/json: + type: Sample + 202: + body: + application/json: + type: Sample[] + minItems: 1 + maxItems: 2 + 203: + body: + application/json: + type: Nullable + 204: + body: + application/json: + type: _Null + 205: + body: + application/json: + type: Dates + 206: + body: + application/json: + type: ScalarArrays + 207: + body: + application/json: + type: string | nil + required: true + 208: + body: + application/json: + type: array + items: type: Sample - 202: - body: - application/json: - type: Sample[] - minItems: 1 - maxItems: 2 - 203: - body: - application/json: - type: Nullable - 204: - body: - application/json: - type: _Null - 205: - body: - application/json: - type: Dates - 206: - body: - application/json: - type: ScalarArrays - 207: - body: - application/json: - type: string | nil - required: true types: Song: diff --git a/test/fixture/song.json b/tests/fixture/song.json similarity index 100% rename from test/fixture/song.json rename to tests/fixture/song.json diff --git a/test/fixture/songs.json b/tests/fixture/songs.json similarity index 100% rename from test/fixture/songs.json rename to tests/fixture/songs.json diff --git a/test/fixture/traits/category.raml b/tests/fixture/traits/category.raml similarity index 100% rename from test/fixture/traits/category.raml rename to tests/fixture/traits/category.raml diff --git a/test/fixture/traitsAndTypes.raml b/tests/fixture/traitsAndTypes.raml similarity index 84% rename from test/fixture/traitsAndTypes.raml rename to tests/fixture/traitsAndTypes.raml index 056833c0..ac6d82ff 100644 --- a/test/fixture/traitsAndTypes.raml +++ b/tests/fixture/traitsAndTypes.raml @@ -21,14 +21,14 @@ resourceTypes: "type": "object" } traits: - - secured: - queryParameters: - <>: - description: A valid <> is required - paged: - queryParameters: - numPages: - description: The number of pages to return + secured: + queryParameters: + <>: + description: A valid <> is required + paged: + queryParameters: + numPages: + description: The number of pages to return /books: type: { searchableCollection: { queryParamName: title, fallbackParamName: digest_all_fields } } get: @@ -42,4 +42,4 @@ traits: is: [ secured: { tokenName: access_token }, paged ] queryParameters: fullDetails: - description: Should we return all the details about the user? \ No newline at end of file + description: Should we return all the details about the user? diff --git a/test/fixture/tree/child.raml b/tests/fixture/tree/child.raml similarity index 100% rename from test/fixture/tree/child.raml rename to tests/fixture/tree/child.raml diff --git a/test/fixture/tree/schema.json b/tests/fixture/tree/schema.json similarity index 100% rename from test/fixture/tree/schema.json rename to tests/fixture/tree/schema.json diff --git a/test/fixture/treeTraversal/bad.raml b/tests/fixture/treeTraversal/bad.raml similarity index 100% rename from test/fixture/treeTraversal/bad.raml rename to tests/fixture/treeTraversal/bad.raml diff --git a/test/fixture/types.raml b/tests/fixture/types.raml similarity index 100% rename from test/fixture/types.raml rename to tests/fixture/types.raml diff --git a/test/fixture/validator/queryParameters.raml b/tests/fixture/validator/queryParameters.raml similarity index 100% rename from test/fixture/validator/queryParameters.raml rename to tests/fixture/validator/queryParameters.raml diff --git a/test/fixture/validator/requestAcceptHeader.raml b/tests/fixture/validator/requestAcceptHeader.raml similarity index 100% rename from test/fixture/validator/requestAcceptHeader.raml rename to tests/fixture/validator/requestAcceptHeader.raml diff --git a/test/fixture/validator/requestBody.raml b/tests/fixture/validator/requestBody.raml similarity index 94% rename from test/fixture/validator/requestBody.raml rename to tests/fixture/validator/requestBody.raml index b1841e38..12a17a75 100644 --- a/test/fixture/validator/requestBody.raml +++ b/tests/fixture/validator/requestBody.raml @@ -3,6 +3,11 @@ title: Example API version: v1 /songs: + + get: + responses: + 200: + post: body: application/json: diff --git a/test/fixture/validator/responseBody.raml b/tests/fixture/validator/responseBody.raml similarity index 100% rename from test/fixture/validator/responseBody.raml rename to tests/fixture/validator/responseBody.raml diff --git a/test/fixture/validator/responseEmptyBody.raml b/tests/fixture/validator/responseEmptyBody.raml similarity index 100% rename from test/fixture/validator/responseEmptyBody.raml rename to tests/fixture/validator/responseEmptyBody.raml diff --git a/test/fixture/validator/responseHeaders.raml b/tests/fixture/validator/responseHeaders.raml similarity index 100% rename from test/fixture/validator/responseHeaders.raml rename to tests/fixture/validator/responseHeaders.raml diff --git a/test/fixture/validator/webFormBody.raml b/tests/fixture/validator/webFormBody.raml similarity index 100% rename from test/fixture/validator/webFormBody.raml rename to tests/fixture/validator/webFormBody.raml diff --git a/test/fixture/xmlSchema.raml b/tests/fixture/xmlSchema.raml similarity index 100% rename from test/fixture/xmlSchema.raml rename to tests/fixture/xmlSchema.raml From 566ea1615024ed037566cfa80d9663d376af9000 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 17 Sep 2018 14:31:34 +0300 Subject: [PATCH 04/11] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7=20=D0=BE=D1=80=D0=B8=D0=B3?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/NamedParameter.php | 121 +++++++++++++------ tests/ParseTest.php | 258 +++++++++++++++++++++++++---------------- 2 files changed, 248 insertions(+), 131 deletions(-) diff --git a/src/NamedParameter.php b/src/NamedParameter.php index 1dcf2762..84f55866 100644 --- a/src/NamedParameter.php +++ b/src/NamedParameter.php @@ -11,14 +11,20 @@ class NamedParameter implements ArrayInstantiationInterface { // Type constants + const TYPE_STRING = 'string'; const TYPE_NUMBER = 'number'; const TYPE_INTEGER = 'integer'; 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 const VAL_NOTBOOLEAN = 1; const VAL_NOTDATE = 2; @@ -46,6 +52,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 +185,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 +224,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 +237,7 @@ public function getKey() * repeat: ?boolean * required: ?boolean * default: ?string + * format: ?string * ] * * @throws \Exception @@ -297,6 +315,10 @@ public static function createFromArray($key, array $data = []) } } + if (isset($data['format'])) { + $namedParameter->setFormat($data['format']); + } + return $namedParameter; } @@ -441,7 +463,7 @@ public function setMinLength($minLength) throw new \Exception('minLength can only be set on type "string"'); } - $this->minLength = (int) $minLength; + $this->minLength = (int)$minLength; } // -- @@ -469,7 +491,7 @@ public function setMaxLength($maxLength) throw new \Exception('maxLength can only be set on type "string"'); } - $this->maxLength = (int) $maxLength; + $this->maxLength = (int)$maxLength; } // -- @@ -497,7 +519,7 @@ public function setMinimum($minimum) throw new \Exception('minimum can only be set on type "integer" or "number'); } - $this->minimum = (int) $minimum; + $this->minimum = (int)$minimum; } // -- @@ -525,7 +547,7 @@ public function setMaximum($maximum) throw new \Exception('maximum can only be set on type "integer" or "number'); } - $this->maximum = (int) $maximum; + $this->maximum = (int)$maximum; } // -- @@ -581,7 +603,7 @@ public function canRepeat() */ public function setRepeat($repeat) { - $this->repeat = (bool) $repeat; + $this->repeat = (bool)$repeat; } // -- @@ -603,7 +625,7 @@ public function isRequired() */ public function setRequired($required) { - $this->required = (bool) $required; + $this->required = (bool)$required; } // -- @@ -618,6 +640,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 * @@ -641,7 +679,7 @@ public function setDefault($default) break; case self::TYPE_INTEGER: - if (!is_numeric($default) || (int) $default != $default) { + if (!is_numeric($default) || (int)$default != $default) { throw new \InvalidArgumentException('Default parameter is not an integer'); } @@ -681,7 +719,7 @@ public function validate($param) */ if (in_array($param, [null, ''], true)) { if ($this->isRequired()) { - throw new ValidationException($this->getKey() . ' is required', static::VAL_ISREQUIRED); + throw new ValidationException($this->getKey().' is required', static::VAL_ISREQUIRED); } return; @@ -692,7 +730,7 @@ public function validate($param) // Must be boolean if (!is_bool($param)) { - throw new ValidationException($this->getKey() . ' is not boolean', static::VAL_NOTBOOLEAN); + throw new ValidationException($this->getKey().' is not boolean', static::VAL_NOTBOOLEAN); } break; @@ -701,15 +739,15 @@ public function validate($param) // Must be a valid date if (\DateTime::createFromFormat('D, d M Y H:i:s T', $param) === false) { - throw new ValidationException($this->getKey() . ' is not a valid date', static::VAL_NOTDATE); + throw new ValidationException($this->getKey().' is not a valid date', static::VAL_NOTDATE); } - // no break + // no break case static::TYPE_STRING: // Must be a string if (!is_string($param)) { - throw new ValidationException($this->getKey() . ' is not a string', static::VAL_NOTSTRING); + throw new ValidationException($this->getKey().' is not a string', static::VAL_NOTSTRING); } /** @@ -720,7 +758,7 @@ public function validate($param) $minLength = $this->getMinLength(); if (!empty($minLength) && strlen($param) < $minLength) { throw new ValidationException( - $this->getKey() . ' must be at least ' . $minLength . ' characters long', + $this->getKey().' must be at least '.$minLength.' characters long', static::VAL_TOOSHORT ); } @@ -733,7 +771,7 @@ public function validate($param) $maxLength = $this->getMaxLength(); if (!empty($maxLength) && strlen($param) > $maxLength) { throw new ValidationException( - $this->getKey() . ' must be no more than ' . $maxLength . ' characters long', + $this->getKey().' must be no more than '.$maxLength.' characters long', static::VAL_TOOLONG ); } @@ -748,9 +786,9 @@ public function validate($param) * @link http://raml.org/spec.html#type */ if (!is_int($param)) { - throw new ValidationException($this->getKey() . ' is not an integer', static::VAL_NOTINT); + throw new ValidationException($this->getKey().' is not an integer', static::VAL_NOTINT); } - // no break + // no break case static::TYPE_NUMBER: @@ -760,7 +798,7 @@ public function validate($param) * @link http://raml.org/spec.html#type */ if (!is_numeric($param)) { - throw new ValidationException($this->getKey() . ' is not a number', static::VAL_NOTNUMBER); + throw new ValidationException($this->getKey().' is not a number', static::VAL_NOTNUMBER); } /** @@ -771,7 +809,7 @@ public function validate($param) $min = $this->getMinimum(); if (!empty($min) && $param < $min) { throw new ValidationException( - $this->getKey() . ' must be greater than or equal to ' . $min, + $this->getKey().' must be greater than or equal to '.$min, static::VAL_NUMLESSTHAN ); } @@ -784,7 +822,7 @@ public function validate($param) $max = $this->getMaximum(); if (!empty($max) && $param > $max) { throw new ValidationException( - $this->getKey() . ' must be less than or equal to ' . $max, + $this->getKey().' must be less than or equal to '.$max, static::VAL_GREATERTHAN ); } @@ -795,6 +833,23 @@ public function validate($param) // 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; } /** @@ -804,10 +859,10 @@ public function validate($param) */ $validationPattern = $this->getValidationPattern(); if (!empty($validationPattern) && - preg_match('|' . $validationPattern . '|', $param) !== 1 + preg_match('|'.$validationPattern.'|', $param) !== 1 ) { throw new ValidationException( - $this->getKey() . ' does not match the specified pattern', + $this->getKey().' does not match the specified pattern', static::VAL_PATTERNFAIL ); } @@ -823,7 +878,7 @@ public function validate($param) */ if (is_array($enum = $this->getEnum()) && !in_array($param, $enum, true)) { throw new ValidationException( - $this->getKey() . ' must be one of the following: ' . implode(', ', $enum), + $this->getKey().' must be one of the following: '.implode(', ', $enum), static::VAL_NOTENUMVALUE ); } @@ -839,7 +894,7 @@ public function getMatchPattern() if ($this->validationPattern) { $pattern = $this->validationPattern; } elseif ($enum = $this->getEnum()) { - $pattern = '^(' . implode('|', array_map('preg_quote', $enum)) . ')$'; + $pattern = '^('.implode('|', array_map('preg_quote', $enum)).')$'; } else { switch ($this->getType()) { case self::TYPE_NUMBER: @@ -854,13 +909,13 @@ public function getMatchPattern() case self::TYPE_DATE: // @see https://snipt.net/DamienGarrido/ // http-date-regular-expression-validation-rfc-1123rfc-850asctime-f64e6aa3/ - $pattern = '^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?:[0-2][0-9]|3[01]) ' . - '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} ' . - '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] ' . - 'GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), ' . - '(?:[0-2][0-9]|3[01])-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} ' . - '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) ' . - '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:[ 1-2][0-9]|3[01]) ' . + $pattern = '^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?:[0-2][0-9]|3[01]) '. + '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} '. + '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] '. + 'GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), '. + '(?:[0-2][0-9]|3[01])-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} '. + '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) '. + '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:[ 1-2][0-9]|3[01]) '. '(?:[01][0-9]|2[0-3]):[012345][0-9]:[012345][0-9] \d{4})$'; break; @@ -874,7 +929,7 @@ public function getMatchPattern() break; case self::TYPE_STRING: if ($this->getMinLength() || $this->getMaxLength()) { - $pattern = '((?!\/).){' . $this->getMinLength() . ',' . $this->getMaxLength() . '}'; + $pattern = '((?!\/).){'.$this->getMinLength().','.$this->getMaxLength().'}'; } else { $pattern = '([^/]+)'; } diff --git a/tests/ParseTest.php b/tests/ParseTest.php index a57e45d6..59adab8c 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -77,13 +77,13 @@ public function shouldCorrectlyLoadASimpleRamlStringWithInclude() baseUri: https://app.zencoder.com/api/{version} RAML; - $simpleRaml = $this->parser->parseFromString($raml, __DIR__ . '/fixture'); + $simpleRaml = $this->parser->parseFromString($raml, __DIR__.'/fixture'); $docList = $simpleRaml->getDocumentationList(); $this->assertEquals('ZEncoder API', $simpleRaml->getTitle()); $this->assertEquals('Home', $docList['title']); $this->assertEquals('v2', $simpleRaml->getVersion()); - $this->assertEquals([__DIR__ . '/fixture/child.raml'], $this->parser->getIncludedFiles()); + $this->assertEquals([__DIR__.'/fixture/child.raml'], $this->parser->getIncludedFiles()); } /** @@ -91,7 +91,7 @@ public function shouldCorrectlyLoadASimpleRamlStringWithInclude() */ public function shouldCorrectlyLoadASimpleRamlFile() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $this->assertEquals('World Music API', $simpleRaml->getTitle()); $this->assertEquals('v1', $simpleRaml->getVersion()); $this->assertEquals('http://example.api.com/v1', $simpleRaml->getBaseUri()); @@ -104,7 +104,7 @@ public function shouldCorrectlyLoadASimpleRamlFile() public function shouldThrowCorrectExceptionOnBadJson() { $this->expectException(InvalidJsonException::class); - $this->parser->parse(__DIR__ . '/fixture/invalid/badJson.raml'); + $this->parser->parse(__DIR__.'/fixture/invalid/badJson.raml'); } /** @@ -113,7 +113,7 @@ public function shouldThrowCorrectExceptionOnBadJson() public function shouldThrowFileNotFoundExceptionOnBadRamlFileWithNotExistingFile() { $this->expectException(FileNotFoundException::class); - $this->parser->parse(__DIR__ . '/fixture/invalid/bad.raml'); + $this->parser->parse(__DIR__.'/fixture/invalid/bad.raml'); } /** @@ -125,7 +125,7 @@ public function shouldThrowExceptionOnPathManipulationIfNotAllowed() $config->disableDirectoryTraversal(); $this->parser->setConfiguration($config); $this->expectException(FileNotFoundException::class); - $this->parser->parse(__DIR__ . '/fixture/treeTraversal/bad.raml'); + $this->parser->parse(__DIR__.'/fixture/treeTraversal/bad.raml'); } /** @@ -134,7 +134,7 @@ public function shouldThrowExceptionOnPathManipulationIfNotAllowed() public function shouldPreventDirectoryTraversalByDefault() { $this->expectException(FileNotFoundException::class); - $this->parser->parse(__DIR__ . '/fixture/treeTraversal/bad.raml'); + $this->parser->parse(__DIR__.'/fixture/treeTraversal/bad.raml'); } /** @@ -146,7 +146,7 @@ public function shouldNotThrowExceptionOnPathManipulationIfAllowed() $config->enableDirectoryTraversal(); $this->parser->setConfiguration($config); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/treeTraversal/bad.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/treeTraversal/bad.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -163,7 +163,7 @@ public function shouldNotThrowExceptionOnPathManipulationIfAllowed() */ public function shouldCorrectlyReturnHttpProtocol() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $this->assertTrue($simpleRaml->supportsHttp()); $this->assertFalse($simpleRaml->supportsHttps()); } @@ -173,7 +173,7 @@ public function shouldCorrectlyReturnHttpProtocol() */ public function shouldReturnAResourceObjectForAResource() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $this->assertInstanceOf(Resource::class, $resource); } @@ -184,7 +184,7 @@ public function shouldReturnAResourceObjectForAResource() public function shouldThrowExceptionIfUriNotFound() { $this->expectException(ResourceNotFoundException::class); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); try { $simpleRaml->getResourceByUri('/invalid'); @@ -201,7 +201,7 @@ public function shouldThrowExceptionIfUriNotFound() public function shouldNotMatchForwardSlashInURIParameter() { $this->expectException(ResourceNotFoundException::class); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $simpleRaml->getResourceByUri('/songs/1/e'); } @@ -211,7 +211,7 @@ public function shouldNotMatchForwardSlashInURIParameter() public function shouldNotMatchForwardSlashAndDuplicationInURIParameter() { $this->expectException(ResourceNotFoundException::class); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $simpleRaml->getResourceByUri('/songs/1/1'); } @@ -220,7 +220,7 @@ public function shouldNotMatchForwardSlashAndDuplicationInURIParameter() */ public function shouldGiveTheResourceTheCorrectDisplayNameIfNotProvided() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $this->assertEquals('/songs', $resource->getDisplayName()); } @@ -230,7 +230,7 @@ public function shouldGiveTheResourceTheCorrectDisplayNameIfNotProvided() */ public function shouldExcludeQueryParametersWhenFindingAResource() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs?1'); $this->assertEquals('/songs', $resource->getDisplayName()); } @@ -240,7 +240,7 @@ public function shouldExcludeQueryParametersWhenFindingAResource() */ public function shouldGiveTheResourceTheCorrectDisplayNameIfProvided() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/traitsAndTypes.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/traitsAndTypes.raml'); $resource = $simpleRaml->getResourceByUri('/dvds'); $this->assertEquals('DVD', $resource->getDisplayName()); } @@ -250,7 +250,7 @@ public function shouldGiveTheResourceTheCorrectDisplayNameIfProvided() */ public function shouldParseMultiLevelUrisAndParameters() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $this->assertEquals('/songs/{songId}', $resource->getDisplayName()); @@ -261,7 +261,7 @@ public function shouldParseMultiLevelUrisAndParameters() */ public function shouldReturnAMethodObjectForAMethod() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('post'); /** @var Body $body */ @@ -279,7 +279,7 @@ public function shouldReturnAMethodObjectForAMethod() */ public function shouldReturnAResponseForAResponse() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); @@ -293,7 +293,7 @@ public function shouldReturnAResponseForAResponse() */ public function shouldReturnAnExampleForType() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); @@ -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) + ); } /** @@ -316,7 +319,7 @@ public function shouldReturnAnExampleForType() */ public function shouldParseJson() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); @@ -332,7 +335,7 @@ public function shouldParseJson() */ public function shouldParseJsonSchemaInRaml() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/schemaInRoot.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/schemaInRoot.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -349,7 +352,7 @@ public function shouldParseJsonSchemaInRaml() */ public function shouldIncludeChildJsonObjects() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/parentAndChildSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/parentAndChildSchema.raml'); $resource = $simpleRaml->getResourceByUri('/'); $method = $resource->getMethod('get'); @@ -370,7 +373,7 @@ public function shouldNotParseJsonIfNotRequested() $config->disableSchemaParsing(); $this->parser->setConfiguration($config); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); @@ -386,7 +389,7 @@ public function shouldNotParseJsonIfNotRequested() */ public function shouldParseJsonRefs() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); @@ -404,7 +407,7 @@ public function shouldParseJsonRefs() */ public function shouldParseJsonIntoArray() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $response = $method->getResponse(200); @@ -422,7 +425,7 @@ public function shouldParseJsonIntoArray() */ public function shouldParseIncludedJson() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -443,7 +446,7 @@ public function shouldNotParseIncludedJsonIfNotRequired() $config->disableSchemaParsing(); $this->parser->setConfiguration($config); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -460,7 +463,7 @@ public function shouldNotParseIncludedJsonIfNotRequired() */ public function shouldParseIncludedJsonRefs() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -485,14 +488,16 @@ public function shouldSetCorrectSourceUriOnSchemaParsers() ); $schemaParser->method('getCompatibleContentTypes')->willReturn(['application/json']); $schemaParser->method('setSourceUri')->withConsecutive( - ['file://' . __DIR__ . '/fixture/songs.json'] + ['file://'.__DIR__.'/fixture/songs.json'] ); - $parser = new \Raml\Parser([ - $schemaParser - ]); + $parser = new \Raml\Parser( + [ + $schemaParser, + ] + ); - $parser->parse(__DIR__ . '/fixture/includeSchema.raml'); + $parser->parse(__DIR__.'/fixture/includeSchema.raml'); } /** @@ -502,7 +507,7 @@ public function shouldThrowErrorIfEmpty() { $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('RAML file appears to be empty'); - $this->parser->parse(__DIR__ . '/fixture/invalid/empty.raml'); + $this->parser->parse(__DIR__.'/fixture/invalid/empty.raml'); } /** @@ -511,7 +516,7 @@ public function shouldThrowErrorIfEmpty() public function shouldThrowErrorIfNoTitle() { $this->expectException(RamlParserException::class); - $this->parser->parse(__DIR__ . '/fixture/invalid/noTitle.raml'); + $this->parser->parse(__DIR__.'/fixture/invalid/noTitle.raml'); } /** @@ -522,7 +527,7 @@ public function shouldBeAbleToAddAdditionalSchemaTypes() $schemaParser = new JsonSchemaParser(); $schemaParser->addCompatibleContentType('application/vnd.api-v1+json'); $this->parser->addSchemaParser($schemaParser); - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/includeUnknownSchema.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/includeUnknownSchema.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); @@ -541,7 +546,7 @@ public function shouldBeAbleToAddAdditionalSchemaTypes() */ public function shouldApplyTraitVariables() { - $traitsAndTypes = $this->parser->parse(__DIR__ . '/fixture/traitsAndTypes.raml'); + $traitsAndTypes = $this->parser->parse(__DIR__.'/fixture/traitsAndTypes.raml'); $resource = $traitsAndTypes->getResourceByUri('/books'); $method = $resource->getMethod('get'); @@ -552,8 +557,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 +572,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() + ); } /** @@ -569,7 +583,7 @@ public function shouldApplyTraitVariables() */ public function shouldParseIncludedRaml() { - $parent = $this->parser->parse(__DIR__ . '/fixture/includeRaml.raml'); + $parent = $this->parser->parse(__DIR__.'/fixture/includeRaml.raml'); $documentation = $parent->getDocumentationList(); $this->assertEquals('Home', $documentation['title']); @@ -581,7 +595,7 @@ public function shouldParseIncludedRaml() */ public function shouldParseIncludedYaml() { - $parent = $this->parser->parse(__DIR__ . '/fixture/includeYaml.raml'); + $parent = $this->parser->parse(__DIR__.'/fixture/includeYaml.raml'); $documentation = $parent->getDocumentationList(); $this->assertEquals('Home', $documentation['title']); @@ -593,7 +607,7 @@ public function shouldParseIncludedYaml() */ public function shouldIncludeTraits() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs'); $method = $resource->getMethod('get'); $queryParameters = $method->getQueryParameters(); @@ -608,7 +622,7 @@ public function shouldIncludeTraits() */ public function shouldThrowErrorIfPassedFileDoesNotExist() { - $fileName = __DIR__ . '/fixture/gone.raml'; + $fileName = __DIR__.'/fixture/gone.raml'; $this->expectException(FileNotFoundException::class); $this->expectExceptionMessage(sprintf('The file %s does not exist or is unreadable.', $fileName)); @@ -627,7 +641,7 @@ public function shouldThrowErrorIfPassedFileDoesNotExist() */ public function shouldParseHateoasExample() { - $hateoasRaml = $this->parser->parse(__DIR__ . '/fixture/hateoas/example.raml'); + $hateoasRaml = $this->parser->parse(__DIR__.'/fixture/hateoas/example.raml'); $this->assertInstanceOf(ApiDefinition::class, $hateoasRaml); } @@ -636,8 +650,11 @@ 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()); + $methodDescriptionRaml = $this->parser->parse(__DIR__.'/fixture/methodDescription.raml'); + $this->assertEquals( + 'Get a list of available songs', + $methodDescriptionRaml->getResourceByUri('/songs')->getMethod('get')->getDescription() + ); } /** @@ -645,8 +662,11 @@ 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()); + $resourceDescriptionRaml = $this->parser->parse(__DIR__.'/fixture/resourceDescription.raml'); + $this->assertEquals( + 'Collection of available songs resource', + $resourceDescriptionRaml->getResourceByUri('/songs')->getDescription() + ); } /** @@ -654,7 +674,7 @@ public function shouldParseResourceDescription() */ public function shouldParseStatusCode() { - $simpleRaml = $this->parser->parse(__DIR__ . '/fixture/simple.raml'); + $simpleRaml = $this->parser->parse(__DIR__.'/fixture/simple.raml'); $resource = $simpleRaml->getResourceByUri('/songs/1'); $response = $resource->getMethod('get')->getResponse(200); $this->assertEquals(200, $response->getStatusCode()); @@ -665,11 +685,18 @@ public function shouldParseStatusCode() */ public function shouldParseMethodHeaders() { - $headersRaml = $this->parser->parse(__DIR__ . '/fixture/headers.raml'); + $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() + ); } /** @@ -677,17 +704,25 @@ public function shouldParseMethodHeaders() */ public function shouldParseResponseHeaders() { - $headersRaml = $this->parser->parse(__DIR__ . '/fixture/headers.raml'); + $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() + ); } /** @@ -695,8 +730,11 @@ 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()); + $def = $this->parser->parse(__DIR__.'/fixture/reservedParameter.raml'); + $this->assertEquals( + 'Get list of songs at /songs', + $def->getResourceByUri('/songs')->getMethod('get')->getDescription() + ); } /** @@ -704,9 +742,15 @@ 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()); + $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() + ); } /** @@ -714,7 +758,7 @@ public function shouldParameterTransformerWorks() */ public function shouldParseSchemasDefinedInTheRoot() { - $def = $this->parser->parse(__DIR__ . '/fixture/rootSchemas.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/rootSchemas.raml'); $this->assertCount(2, $def->getSchemaCollections()); } @@ -724,7 +768,7 @@ public function shouldParseSchemasDefinedInTheRoot() */ public function shouldCorrectlyHandleQueryParameters() { - $def = $this->parser->parse(__DIR__ . '/fixture/queryParameters.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/queryParameters.raml'); $resource = $def->getResourceByUri('/books/1'); $method = $resource->getMethod('get'); @@ -747,12 +791,25 @@ public function shouldThrowExceptionOnBadQueryParameter() $this->expectException(InvalidQueryParameterTypeException::class); try { - $this->parser->parse(__DIR__ . '/fixture/invalid/queryParameters.raml'); + $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; } @@ -763,17 +820,22 @@ public function shouldThrowExceptionOnBadQueryParameter() */ public function shouldReplaceParameterByJsonString() { - $def = $this->parser->parse(__DIR__ . '/fixture/jsonStringExample.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/jsonStringExample.raml'); /** @var Body $body */ $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) + ); } // --- @@ -783,7 +845,7 @@ public function shouldReplaceParameterByJsonString() */ public function shouldParseSecuritySchemes() { - $def = $this->parser->parse(__DIR__ . '/fixture/securitySchemes.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/securitySchemes.raml'); $resource = $def->getResourceByUri('/users'); $method = $resource->getMethod('get'); @@ -820,7 +882,7 @@ public function shouldParseSecuritySchemes() */ public function shouldAddHeadersOfSecuritySchemes() { - $def = $this->parser->parse(__DIR__ . '/fixture/securitySchemes.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/securitySchemes.raml'); $resource = $def->getResourceByUri('/users'); $method = $resource->getMethod('get'); @@ -835,7 +897,7 @@ public function shouldAddHeadersOfSecuritySchemes() */ public function shouldReplaceSchemaByRootSchema() { - $def = $this->parser->parse(__DIR__ . '/fixture/replaceSchemaByRootSchema.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/replaceSchemaByRootSchema.raml'); $response = $def->getResourceByUri('/songs/{id}')->getMethod('get')->getResponse(200); /** @var Body $body */ $body = $response->getBodyByType('application/json'); @@ -854,7 +916,7 @@ public function shouldReplaceSchemaByRootSchema() */ public function shouldParseAndReplaceSchemaOnlyInResources() { - $def = $this->parser->parse(__DIR__ . '/fixture/schemaInTypes.raml'); + $def = $this->parser->parse(__DIR__.'/fixture/schemaInTypes.raml'); /** @var Body $body */ $body = $def->getResourceByUri('/projects')->getMethod('post')->getBodyByType('application/json'); $schema = $body->getSchema(); @@ -866,7 +928,7 @@ public function shouldParseAndReplaceSchemaOnlyInResources() */ public function shouldParseInfoQExample() { - $infoQ = $this->parser->parse(__DIR__ . '/fixture/infoq/eventlog.raml'); + $infoQ = $this->parser->parse(__DIR__.'/fixture/infoq/eventlog.raml'); $this->assertEquals('Eventlog API', $infoQ->getTitle()); } @@ -875,7 +937,7 @@ public function shouldParseInfoQExample() */ public function shouldLoadATree() { - $tree = $this->parser->parse(__DIR__ . '/fixture/includeTreeRaml.raml'); + $tree = $this->parser->parse(__DIR__.'/fixture/includeTreeRaml.raml'); $this->assertEquals('Test', $tree->getTitle()); $resource = $tree->getResourceByUri('/songs'); @@ -968,7 +1030,7 @@ public function shouldSupportGenericResponseType() */ public function shouldMergeMethodSecurityScheme() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/securitySchemes.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/securitySchemes.raml'); $resource = $apiDefinition->getResourceByUri('/users'); $method = $resource->getMethod('get'); $headers = $method->getHeaders(); @@ -980,7 +1042,7 @@ public function shouldMergeMethodSecurityScheme() */ public function shouldAddSecuritySchemeToResource() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/resourceSecuritySchemes.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/resourceSecuritySchemes.raml'); $resource = $apiDefinition->getResourceByUri('/users'); $method = $resource->getMethod('get'); $schemes = $method->getSecuritySchemes(); @@ -994,7 +1056,7 @@ public function shouldAddSecuritySchemeToResource() */ public function shouldParseCustomSettingsOnResource() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/securedByCustomProps.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/securedByCustomProps.raml'); $resource = $apiDefinition->getResourceByUri('/test'); $method = $resource->getMethod('get'); $schemes = $method->getSecuritySchemes(); @@ -1009,7 +1071,7 @@ public function shouldParseCustomSettingsOnResource() */ public function shouldParseCustomSettingsOnMethodWithOAuthParser() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/securedByCustomProps.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/securedByCustomProps.raml'); $resource = $apiDefinition->getResourceByUri('/test'); $method = $resource->getMethod('get'); $schemes = $method->getSecuritySchemes(); @@ -1023,7 +1085,7 @@ public function shouldParseCustomSettingsOnMethodWithOAuthParser() */ public function shouldParseIncludedTraits() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/includedTraits.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/includedTraits.raml'); $resource = $apiDefinition->getResourceByUri('/category'); $method = $resource->getMethod('get'); $queryParams = $method->getQueryParameters(); @@ -1037,7 +1099,7 @@ public function shouldParseIncludedTraits() */ public function shouldParseResourcePathNameCorrectly() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/resourcePathName.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/resourcePathName.raml'); $foo = $apiDefinition->getResources()['/foo']; /** @var Resource $fooId */ @@ -1065,7 +1127,7 @@ public function shouldParseResourcePathNameCorrectly() */ public function shouldNestedResourcesHaveParentResourceDefined() { - $apiDefinition = $this->parser->parse(__DIR__ . '/fixture/resourcePathName.raml'); + $apiDefinition = $this->parser->parse(__DIR__.'/fixture/resourcePathName.raml'); $foo = $apiDefinition->getResources()['/foo']; /** @var Resource $fooId */ From de450fe0ca983c620b2563ab3ad9f9ed82507837 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 17 Sep 2018 14:44:33 +0300 Subject: [PATCH 05/11] =?UTF-8?q?=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4=D0=BB=D1=8F=20php5.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5e006b97..aa2594e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ cache: - $HOME/.composer/cache php: + - 5.5 - 5.6 - 7.0 - 7.1 From e0da049866827b20e977c4da861b774267d97576 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 17 Sep 2018 14:51:10 +0300 Subject: [PATCH 06/11] =?UTF-8?q?=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=20travis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 67 +++++------------------------------------------------ 1 file changed, 6 insertions(+), 61 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa2594e3..a9113db6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,75 +2,20 @@ language: php sudo: false -cache: - directories: - - $HOME/.composer/cache - php: - 5.5 - 5.6 - 7.0 - 7.1 - 7.2 - - nightly - -env: - global: - - LATEST_PHP_VERSION="7.2" - matrix: - - DEPENDENCIES="beta" - - DEPENDENCIES="low" - - DEPENDENCIES="stable" - -matrix: - fast_finish: true - allow_failures: - - php: nightly - - env: DEPENDENCIES="beta" - - env: DEPENDENCIES="low" -before_install: - - echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - if [ -n "$GH_TOKEN" ]; then - composer config github-oauth.github.com ${GH_TOKEN}; - fi; - - mkdir -p build/logs - -install: - - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then - composer remove friendsofphp/php-cs-fixer phpstan/phpstan --dev; - fi - - if [ "$DEPENDENCIES" = "beta" ]; then - composer config minimum-stability beta; - composer update -n --prefer-dist; - elif [ "$DEPENDENCIES" = "low" ]; then - composer update -n --prefer-dist --prefer-lowest; - else - composer update -n --prefer-dist; - fi; +before_script: + - composer install --dev --no-interaction script: - - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then - echo "File validation is skipped for older PHP versions"; - else - composer validate-files; - fi; - - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then - echo "Static analysis is skipped for older PHP versions"; - else - composer run-static-analysis; - fi; - - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then - echo "Code style check is skipped for older PHP versions"; - else - composer check-code-style; - fi; - - if [ "$(phpenv version-name)" != "$LATEST_PHP_VERSION" ]; then - echo "Security check is skipped for older PHP versions"; - else - composer check-security; - fi; - - composer run-tests-with-clover + - mkdir -p build/logs + - ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml after_script: - - bin/php-coveralls -v + - ./vendor/bin/coveralls -v + - ./vendor/bin/phpcs --report=summary --standard=PSR1,PSR2 src From 25a84ce589f1f41e0660a0575520797a1d8eaf54 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 17 Sep 2018 15:18:52 +0300 Subject: [PATCH 07/11] =?UTF-8?q?=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=20composer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 87 ++++++++++----------------------------------------- 1 file changed, 16 insertions(+), 71 deletions(-) diff --git a/composer.json b/composer.json index e16dcb57..d8867ca2 100644 --- a/composer.json +++ b/composer.json @@ -1,95 +1,40 @@ { - "name": "raml-org/raml-php-parser", + "name": "elama/php-raml-parser", "type": "library", "description": "A RAML parser built in php", - "homepage": "https://github.com/raml-org/raml-php-parser", + "homepage": "https://github.com/eLama/php-raml-parser", "license": "MIT", "authors": [ - { - "name": "Alec Sammon", - "email": "alec.sammon@googlemail.com", - "role": "Original Author" - }, { "name": "eLama Team", - "email": "dev@elama.ru", - "role": "Main Contributor" + "email": "dev@elama.ru" }, { - "name": "Martin Georgiev", - "email": "martin.georgiev@gmail.com", - "role": "Maintainer" + "name": "Alec Sammon", + "email": "alec.sammon@googlemail.com" } ], - - "replace": { - "alecsammon/php-raml-parser": "*" - }, "autoload": { "psr-4": { "Raml\\": "src/" } }, - "autoload-dev": { - "psr-4": { - "Raml\\Tests\\": "tests/" - } + + "require-dev": { + "satooshi/php-coveralls": "~0.6", + "phpunit/phpunit": "5.7.*", + "squizlabs/php_codesniffer": "3.3.*" }, "require": { - "php": "^5.6|^7.0", - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "justinrainbow/json-schema": "^5.0", + "php": ">=5.5.9", + "justinrainbow/json-schema": "5.1.*", "symfony/yaml": "^3.0|^4.0", "symfony/routing": "^3.0|^4.0", - "oodle/inflect": "^0.2", + "oodle/inflect": "~0.2", "psr/http-message": "^1.0", - "willdurand/negotiation": "^2.2.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "jakub-onderka/php-parallel-lint": "^1.0", - "php-coveralls/php-coveralls": "^2.1", - "phpstan/phpstan": "^0.10.2", - "phpunit/phpunit": "^5.7", - "sensiolabs/security-checker": "^4.1" - }, - - "scripts": { - "check-code-style": [ - "bin/php-cs-fixer fix --config='./.php_cs' --show-progress=none --dry-run --no-interaction --diff -v" - ], - "check-security": [ - "bin/security-checker security:check" - ], - "fix-code-style": [ - "bin/php-cs-fixer fix --config='./.php_cs' --show-progress=none --no-interaction --diff -v" - ], - "run-static-analysis": [ - "bin/phpstan analyse --level=1 src/" - ], - "run-static-analysis-including-tests": [ - "@run-static-analysis", - "bin/phpstan analyse --level=1 tests/" - ], - "run-tests": [ - "bin/phpunit" - ], - "run-tests-with-clover": [ - "bin/phpunit --coverage-clover build/logs/clover.xml" - ], - "validate-files": [ - "bin/parallel-lint --exclude vendor --exclude bin ." - ] - }, - - "config": { - "bin-dir": "bin", - "sort-packages": true - }, - "prefer-stable": true -} + "willdurand/negotiation": "^2.2" + } +} \ No newline at end of file From 9d3a890c853e1e97c9e61351ad8806eec032eed8 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Mon, 17 Sep 2018 15:41:45 +0300 Subject: [PATCH 08/11] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 1 - composer.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a9113db6..9481ed6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: php sudo: false php: - - 5.5 - 5.6 - 7.0 - 7.1 diff --git a/composer.json b/composer.json index d8867ca2..42524445 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ }, "require": { - "php": ">=5.5.9", + "php": ">=5.6", "justinrainbow/json-schema": "5.1.*", "symfony/yaml": "^3.0|^4.0", "symfony/routing": "^3.0|^4.0", From f4416cccd1f0a2f738be9e5c0b8d5e0649a84dac Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Tue, 18 Sep 2018 15:57:42 +0300 Subject: [PATCH 09/11] Added inheritance support for union types --- .gitignore | 1 + src/Types/UnionType.php | 149 +++++++++++++++++++++++++++++--- tests/Types/UnionTypeTest.php | 44 ++++++++++ tests/fixture/simple_types.raml | 21 +++++ 4 files changed, 202 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index f13bd84b..b2cf5ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor .php_cs.cache composer.lock composer.phar +.idea 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/Types/UnionTypeTest.php b/tests/Types/UnionTypeTest.php index 9cc53fdd..4cb24131 100644 --- a/tests/Types/UnionTypeTest.php +++ b/tests/Types/UnionTypeTest.php @@ -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..f0bb15a9 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,16 @@ types: type: boolean[] numberArray?: type: number[] + UnionInherited: + type: Song | Sample + properties: + prop1: integer + prop2: + type: integer + required: false + + UnionInherited2: + type: UnionInherited + properties: + prop3: integer From c944232c3e65838767d3dbe4e0b59aa9d45ecfd6 Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Thu, 20 Sep 2018 18:03:06 +0300 Subject: [PATCH 10/11] Added dates item type for array type Fix union type parsing in libraries --- composer.json | 2 +- src/Parser.php | 14 +++- src/Types/ArrayType.php | 54 ++++----------- tests/TypeTest.php | 117 ++++++-------------------------- tests/fixture/simple_types.raml | 6 ++ 5 files changed, 55 insertions(+), 138 deletions(-) diff --git a/composer.json b/composer.json index 42524445..c6b5baa4 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { - "satooshi/php-coveralls": "~0.6", + "php-coveralls/php-coveralls": "2.1.*", "phpunit/phpunit": "5.7.*", "squizlabs/php_codesniffer": "3.3.*" }, diff --git a/src/Parser.php b/src/Parser.php index 40fc465e..e6a3462b 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 (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])) { 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/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/fixture/simple_types.raml b/tests/fixture/simple_types.raml index f0bb15a9..6baab109 100644 --- a/tests/fixture/simple_types.raml +++ b/tests/fixture/simple_types.raml @@ -101,6 +101,12 @@ 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: From 5a7b823d6bb4efffe1199faed2e9c919d402678e Mon Sep 17 00:00:00 2001 From: arheyy <12kopeek@gmail.com> Date: Wed, 7 Nov 2018 19:03:48 +0300 Subject: [PATCH 11/11] Fix error with string validation, when input is not a string --- src/Types/StringType.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Types/StringType.php b/src/Types/StringType.php index 114687fa..b7760e1d 100644 --- a/src/Types/StringType.php +++ b/src/Types/StringType.php @@ -145,6 +145,7 @@ public function validate($value) if (!is_string($value)) { $this->errors[] = TypeValidationError::unexpectedValueType($this->getName(), 'string', $value); + return; } if (null !== $this->pattern) { $pregMatchResult = preg_match('/' . $this->pattern . '/', $value);