From fc548257a1e6289318697226347de176eb2da125 Mon Sep 17 00:00:00 2001 From: maxyo Date: Tue, 2 Feb 2021 16:08:37 +0300 Subject: [PATCH] Added route info generation --- CHANGELOG.md | 1 + .../Request/JsonApiServerRequestDecorator.php | 264 ++++++++++++++++++ .../Request/JsonApiServerRequestInterface.php | 37 +++ .../JsonApi/Request/MappingConfigProvider.php | 61 ++++ .../JsonApi/Request/Route/MemberRoute.php | 47 ++++ .../Request/Route/RelationshipRoute.php | 42 +++ .../JsonApi/Request/Route/ResourceRoute.php | 32 +++ .../Route/ResourcesCollectionRoute.php | 56 ++++ .../JsonApi/Request/Route/RouteFactory.php | 57 ++++ .../JsonApi/Request/Route/RouteInterface.php | 21 ++ .../Request/Route/RouteRulesProvider.php | 31 ++ .../Dispatcher/JsonApiDispatcher.php | 33 +++ ...ptyOperationIdPatchedDispatcherFactory.php | 47 +++- .../JsonApi/Request/Route/MemberRouteTest.php | 73 +++++ .../Request/Route/RelationshipRouteTest.php | 73 +++++ .../Request/Route/ResourceRouteTest.php | 52 ++++ .../Route/ResourcesCollectionRouteTest.php | 59 ++++ .../Request/Route/RouteFactoryTest.php | 128 +++++++++ ...perationIdPatchedDispatcherFactoryTest.php | 36 +++ 19 files changed, 1138 insertions(+), 12 deletions(-) create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestDecorator.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestInterface.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/MappingConfigProvider.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRoute.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRoute.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRoute.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRoute.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactory.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteInterface.php create mode 100644 src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteRulesProvider.php create mode 100644 src/FreeElephants/JsonApiToolkit/Routing/FastRoute/Dispatcher/JsonApiDispatcher.php create mode 100644 tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRouteTest.php create mode 100644 tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRouteTest.php create mode 100644 tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRouteTest.php create mode 100644 tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRouteTest.php create mode 100644 tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactoryTest.php create mode 100644 tests/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactoryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b969bfc..6f08d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Data Transfer Object classes generation from swagger spec +- Route info in the request attribute ## [0.0.13] - 2021-01-27 ### Added diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestDecorator.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestDecorator.php new file mode 100644 index 0000000..ad634d8 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestDecorator.php @@ -0,0 +1,264 @@ +request = $request; + } + + public function getProtocolVersion() + { + return $this->request->getProtocolVersion(); + } + + public function withProtocolVersion($version) + { + return $this->request->withProtocolVersion($version); + } + + public function getHeaders() + { + return $this->request->getHeaders(); + } + + public function hasHeader($name) + { + return $this->request->hasHeader($name); + } + + public function getHeader($name) + { + return $this->request->getHeader($name); + } + + public function getHeaderLine($name) + { + return $this->request->getHeaderLine($name); + } + + public function withHeader($name, $value) + { + return $this->request->withHeader($name, $value); + } + + public function withAddedHeader($name, $value) + { + return $this->request->withAddedHeader($name, $value); + } + + public function withoutHeader($name) + { + return $this->request->withoutHeader($name); + } + + public function getBody() + { + return $this->request->getBody(); + } + + public function withBody(StreamInterface $body) + { + return $this->request->withBody($body); + } + + public function getRequestTarget() + { + return $this->request->getRequestTarget(); + } + + public function withRequestTarget($requestTarget) + { + return $this->request->withRequestTarget($requestTarget); + } + + public function getMethod() + { + return $this->request->getMethod(); + } + + public function withMethod($method) + { + return $this->request->withMethod($method); + } + + public function getUri() + { + return $this->request->getUri(); + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + return $this->request->withUri($uri, $preserveHost); + } + + public function getServerParams() + { + return $this->request->getServerParams(); + } + + public function getCookieParams() + { + return $this->request->getCookieParams(); + } + + public function withCookieParams(array $cookies) + { + return $this->request->withCookieParams($cookies); + } + + public function getQueryParams() + { + return $this->request->getQueryParams(); + } + + public function withQueryParams(array $query) + { + return $this->request->withQueryParams($query); + } + + public function getUploadedFiles() + { + return $this->request->getUploadedFiles(); + } + + public function withUploadedFiles(array $uploadedFiles) + { + return $this->request->withUploadedFiles($uploadedFiles); + } + + public function getParsedBody() + { + return $this->request->getParsedBody(); + } + + public function withParsedBody($data) + { + return $this->request->withParsedBody($data); + } + + public function getAttributes() + { + return $this->request->getAttributes(); + } + + public function getAttribute($name, $default = null) + { + return $this->request->getAttribute($name, $default); + } + + public function withAttribute($name, $value) + { + return $this->request->withAttribute($name, $value); + } + + public function withoutAttribute($name) + { + return $this->request->withoutAttribute($name); + } + + private function getRoute(): RouteInterface + { + return $this->request->getAttribute(JsonApiServerRequestInterface::ATTRIBUTE_ROUTE_NAME); + } + + private function getDecodedBody(): ?array + { + if (isset($this->decodedBody)) { + return $this->decodedBody; + } + + $this->request->getBody()->rewind(); + $this->decodedBody = json_decode($this->request->getBody()->getContents(), true); + + return $this->decodedBody; + } + + public function getDocumentId(): ?string + { + return $this->getDecodedBody()['data']['id'] ?? null; + } + + public function getDocumentType(): ?string + { + return $this->getDecodedBody()['data']['type'] ?? null; + } + + public function getPrimeAttributeName(): ?string + { + return $this->getRoute()->getRouteParamName(); + } + + /** + * @return mixed + */ + public function getPrimeAttributeValue() + { + return $this->getAttribute($this->getPrimeAttributeName()); + } + + public function getEndpointTypeName(): string + { + return $this->getRoute()->getEndpointTypeName(); + } + + /** + * @see MemberTypeEnum + */ + public function getRelationshipOriginName(): string + { + return $this->getRoute()->getRelationshipOriginName(); + } + + public function getRequestType(): int + { + return $this->getRoute()->getType(); + } + + public function getDocumentAttributes(): ?stdClass + { + $this->request->getBody()->rewind(); + + $decodedBody = json_decode($this->request->getBody()->getContents()); + + return $decodedBody->data->attributes ?? null; + } + + /** + * @return array + */ + public function getRelationships(): array + { + $body = $this->getDecodedBody(); + + switch ($this->getRoute()->getType()) { + case RouteInterface::TYPE_RESOURCES_COLLECTION: + case RouteInterface::TYPE_RESOURCE: + case RouteInterface::TYPE_MEMBER: + $relationships = $body['data']['relationships'] ?? []; + break; + case RouteInterface::TYPE_RELATIONSHIP: + /** @var RelationshipRoute $route */ + $route = $this->getRoute(); + $relationships = [$route->getRelationshipName() => $body]; + break; + default: + $relationships = []; + } + + return $relationships; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestInterface.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestInterface.php new file mode 100644 index 0000000..9acba73 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/JsonApiServerRequestInterface.php @@ -0,0 +1,37 @@ + + */ + public function getRelationships(): array; +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/MappingConfigProvider.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/MappingConfigProvider.php new file mode 100644 index 0000000..194e3f8 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/MappingConfigProvider.php @@ -0,0 +1,61 @@ + [ + * 'foo' => 'bar', + * ], + * ] + * + * @param array> $mapping + */ + public function __construct(array $mapping) + { + $this->endpointTypeToDocumentTypeMapping = $mapping[self::ENDPOINT_TYPE_TO_DOCUMENT_TYPE] ?? []; + $this->resourceTypeToRouteParamKeyTypeMapping = $mapping[self::RESOURCE_TYPE_TO_ROUTE_PARAM_KEY] ?? []; + $this->typenameToClassnameMapping = $mapping[self::TYPE_TO_CLASSNAME] ?? []; + } + + public function isEndpointToDocumentMapped(string $endpointType): bool + { + return array_key_exists($endpointType, $this->endpointTypeToDocumentTypeMapping); + } + + public function isResourceToRouteParamKeyMapped(string $resourceType): bool + { + return array_key_exists($resourceType, $this->resourceTypeToRouteParamKeyTypeMapping); + } + + public function isTypenameToClassnameMapped(string $typename): bool + { + return array_key_exists($typename, $this->typenameToClassnameMapping); + } + + public function getDocumentTypeByEndpointType(string $endpointType): ?string + { + return $this->endpointTypeToDocumentTypeMapping[$endpointType] ?? null; + } + + public function getRouteParamKeyByResourceType(string $resourceType): ?string + { + return $this->resourceTypeToRouteParamKeyTypeMapping[$resourceType] ?? null; + } + + public function getClassnameByTypename(?string $typename): ?string + { + return $this->typenameToClassnameMapping[$typename] ?? null; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRoute.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRoute.php new file mode 100644 index 0000000..293f4de --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRoute.php @@ -0,0 +1,47 @@ +isEndpointToDocumentMapped($memberType)) { + throw new InvalidArgumentException('Member type in provided path not recognized'); + } + + parent::__construct($mappingProvider, $apiVersion, $resourceType, $id); + + $this->member = $memberType; + } + + public function getType(): int + { + return RouteInterface::TYPE_MEMBER; + } + + public function getEndpointTypeName(): string + { + return $this->mappingProvider->getDocumentTypeByEndpointType($this->member); + } + + public function getRouteParamName(): ?string + { + return $this->mappingProvider->getRouteParamKeyByResourceType($this->resource); + } + + public function getRelationshipOriginName(): string + { + return $this->member; + } + + public function getMemberName(): string + { + return $this->member; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRoute.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRoute.php new file mode 100644 index 0000000..273cb48 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRoute.php @@ -0,0 +1,42 @@ +isEndpointToDocumentMapped($relationshipType)) { + throw new InvalidArgumentException('Relationship in provided path not recognized'); + } + + parent::__construct($mappingProvider, $apiVersion, $resourceType, $id); + + $this->relationship = $relationshipType; + } + + public function getType(): int + { + return RouteInterface::TYPE_RELATIONSHIP; + } + + public function getEndpointTypeName(): string + { + return $this->mappingProvider->getDocumentTypeByEndpointType($this->relationship); + } + + public function getRouteParamName(): ?string + { + return $this->mappingProvider->getRouteParamKeyByResourceType($this->resource); + } + + public function getRelationshipName(): string + { + return $this->relationship; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRoute.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRoute.php new file mode 100644 index 0000000..6fdc030 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRoute.php @@ -0,0 +1,32 @@ +id = $id; + } + + public function getType(): int + { + return RouteInterface::TYPE_RESOURCE; + } + + public function getRouteParamName(): ?string + { + return $this->mappingProvider->getRouteParamKeyByResourceType($this->getEndpointTypeName()); + } + + public function getId(): string + { + return $this->id; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRoute.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRoute.php new file mode 100644 index 0000000..3583069 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRoute.php @@ -0,0 +1,56 @@ +isEndpointToDocumentMapped($resourceType)) { + throw new InvalidArgumentException('Resource type in provided path not recognized'); + } + + $this->mappingProvider = $mappingProvider; + + $this->version = $apiVersion; + $this->resource = $resourceType; + } + + public function getType(): int + { + return RouteInterface::TYPE_RESOURCES_COLLECTION; + } + + public function getEndpointTypeName(): string + { + return $this->resource; + } + + public function getRouteParamName(): ?string + { + return null; + } + + public function getRelationshipOriginName(): string + { + return $this->resource; + } + + public function getApiVersion(): string + { + return $this->version; + } + + public function getResourceName(): string + { + return $this->resource; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactory.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactory.php new file mode 100644 index 0000000..387ca53 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactory.php @@ -0,0 +1,57 @@ +mappingProvider = $mappingProvider; + $this->routeRulesProvider = $routeRulesProvider; + } + + public function create(string $path): RouteInterface + { + $routeType = $this->guessType($path); + $parts = explode('/', ltrim($path, '/')); + if (!$this->routeRulesProvider->hasApiVersion()) { + array_unshift($parts, ''); + } + + switch ($routeType) { + case RouteInterface::TYPE_RESOURCES_COLLECTION: + return new ResourcesCollectionRoute($this->mappingProvider, ...$parts); + case RouteInterface::TYPE_RESOURCE: + return new ResourceRoute($this->mappingProvider, ...$parts); + case RouteInterface::TYPE_RELATIONSHIP: + return new RelationshipRoute($this->mappingProvider, ...array_filter($parts, fn ($value) => $value !== 'relationships')); + case RouteInterface::TYPE_MEMBER: + return new MemberRoute($this->mappingProvider, ...$parts); + default: + throw new InvalidArgumentException('Type of provided path not recognized'); + } + } + + private function guessType(string $path): int + { + $routeTypes = [ + RouteInterface::TYPE_RESOURCES_COLLECTION, + RouteInterface::TYPE_RESOURCE, + RouteInterface::TYPE_RELATIONSHIP, + RouteInterface::TYPE_MEMBER, + ]; + foreach ($routeTypes as $type) { + if (preg_match($this->routeRulesProvider->getRegexForRoute($type), $path)) { + return $type; + } + } + + return -1; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteInterface.php b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteInterface.php new file mode 100644 index 0000000..2b9c1f3 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteInterface.php @@ -0,0 +1,21 @@ + + */ + private array $routeRules; + + private bool $hasApiVersion; + + public function __construct(array $routeRules, bool $hasApiVersion = false) + { + $this->routeRules = $routeRules; + $this->hasApiVersion = $hasApiVersion; + } + + public function getRegexForRoute(int $routeType): ?string + { + return $this->routeRules[$routeType] ?? null; + } + + public function hasApiVersion(): bool + { + return $this->hasApiVersion; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/Dispatcher/JsonApiDispatcher.php b/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/Dispatcher/JsonApiDispatcher.php new file mode 100644 index 0000000..d335249 --- /dev/null +++ b/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/Dispatcher/JsonApiDispatcher.php @@ -0,0 +1,33 @@ +routeFactory = $routeFactory; + } + + public function dispatch($routeData, $uri) + { + $result = parent::dispatch($routeData, $uri); + if (isset($result[2])) { + + if ($this->routeFactory) { + $result[2][JsonApiServerRequestInterface::ATTRIBUTE_ROUTE_NAME] = $this->routeFactory->create($uri); + } + } + + return $result; + } +} diff --git a/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactory.php b/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactory.php index 45eaecb..9839f70 100644 --- a/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactory.php +++ b/src/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactory.php @@ -4,8 +4,12 @@ use cebe\openapi\spec\Operation; use cebe\openapi\spec\PathItem; +use FastRoute\DataGenerator; use FastRoute\Dispatcher; use FastRoute\RouteCollector; +use FastRoute\RouteParser; +use FreeElephants\JsonApiToolkit\JsonApi\Request\Route\RouteFactory; +use FreeElephants\JsonApiToolkit\Routing\FastRoute\Dispatcher\JsonApiDispatcher; use function FastRoute\simpleDispatcher; use FreeElephants\JsonApiToolkit\OasToolsAdapter\OpenApiDocumentParserInterface; use FreeElephants\JsonApiToolkit\OasToolsAdapter\YamlStringParser; @@ -17,30 +21,49 @@ class EmptyOperationIdPatchedDispatcherFactory implements DispatcherFactoryInter { private OpenApiDocumentParserInterface $apiDocumentParser; private OperationHandlerNormalizerInterface $operationHandlerNormalizer; + private RouteFactory $routeFactory; - public function __construct(OperationHandlerNormalizerInterface $operationHandlerNormalizer = null, OpenApiDocumentParserInterface $apiDocumentParser = null) + public function __construct( + RouteFactory $routeFactory, + OperationHandlerNormalizerInterface $operationHandlerNormalizer = null, + OpenApiDocumentParserInterface $apiDocumentParser = null + ) { $this->operationHandlerNormalizer = $operationHandlerNormalizer ?: new DefaultOperationHandlerNormalizer(); $this->apiDocumentParser = $apiDocumentParser ?: new YamlStringParser(); + $this->routeFactory = $routeFactory; } public function buildDispatcher(string $openApiDocumentSource): Dispatcher { $openapi = $this->apiDocumentParser->parse($openApiDocumentSource); $paths = $openapi->paths->getIterator(); - $dispatcher = simpleDispatcher(function (RouteCollector $routeCollector) use ($paths) { - /** @var PathItem $pathItem */ - foreach ($paths as $path => $pathItem) { - /** @var Operation $operation */ - foreach ($pathItem->getOperations() as $method => $operation) { - $httpMethod = $this->normalizeHttpMethod($method); - $handler = $this->normalizeOperationHandler($operation); - $routeCollector->addRoute($httpMethod, $path, $handler); - } + + $routeCollector = $this->buildRouteCollector(); + foreach ($paths as $path => $pathItem) { + foreach ($pathItem->getOperations() as $method => $operation) { + $httpMethod = $this->normalizeHttpMethod($method); + $handler = $this->normalizeOperationHandler($operation); + $routeCollector->addRoute($httpMethod, $path, $handler); } - }); + } + + return new JsonApiDispatcher($routeCollector->getData(), $this->routeFactory); + } + + private function buildDataGenerator(): DataGenerator + { + return new DataGenerator\GroupCountBased(); + } - return $dispatcher; + private function buildRouteParser(): RouteParser + { + return new RouteParser\Std(); + } + + private function buildRouteCollector(): RouteCollector + { + return new RouteCollector($this->buildRouteParser(), $this->buildDataGenerator()); } private function normalizeHttpMethod(string $method): string diff --git a/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRouteTest.php b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRouteTest.php new file mode 100644 index 0000000..850f343 --- /dev/null +++ b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/MemberRouteTest.php @@ -0,0 +1,73 @@ +toString(); + $route = new MemberRoute($this->getMappingProvider(), 'v1', 'foo', $uuid, 'bar'); + $this->assertEquals('v1', $route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + $this->assertEquals($uuid, $route->getId()); + $this->assertEquals('bar', $route->getMemberName()); + + $route = new MemberRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEmpty($route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + $this->assertEquals($uuid, $route->getId()); + $this->assertEquals('bar', $route->getMemberName()); + + $this->expectException(InvalidArgumentException::class); + new MemberRoute($this->getMappingProvider(), '', 'foo', $uuid, 'baz'); + } + + public function testGetType() + { + $uuid = Uuid::uuid4()->toString(); + $route = new MemberRoute($this->getMappingProvider(), 'v1', 'foo', $uuid, 'bar'); + $this->assertEquals(RouteInterface::TYPE_MEMBER, $route->getType()); + } + + public function testGetEndpointTypeName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new MemberRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals('BAR', $route->getEndpointTypeName()); + } + + public function testGetRouteParamName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new MemberRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals('fooId', $route->getRouteParamName()); + } + + public function testGetRelationshipOriginName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new MemberRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals('bar', $route->getRelationshipOriginName()); + } + + private function getMappingProvider() + { + return new MappingConfigProvider( + [ + MappingConfigProvider::ENDPOINT_TYPE_TO_DOCUMENT_TYPE => [ + 'foo' => 'FOO', + 'bar' => 'BAR', + ], + MappingConfigProvider::RESOURCE_TYPE_TO_ROUTE_PARAM_KEY => [ + 'foo' => 'fooId', + ], + ] + ); + } +} diff --git a/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRouteTest.php b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRouteTest.php new file mode 100644 index 0000000..e143ebd --- /dev/null +++ b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RelationshipRouteTest.php @@ -0,0 +1,73 @@ +toString(); + $route = new RelationshipRoute($this->getMappingProvider(), 'v1', 'foo', $uuid, 'bar'); + $this->assertEquals('v1', $route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + $this->assertEquals($uuid, $route->getId()); + $this->assertEquals('bar', $route->getRelationshipName()); + + $route = new RelationshipRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEmpty($route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + $this->assertEquals($uuid, $route->getId()); + $this->assertEquals('bar', $route->getRelationshipName()); + + $this->expectException(InvalidArgumentException::class); + new RelationshipRoute($this->getMappingProvider(), '', 'foo', $uuid, 'baz'); + } + + public function testGetType() + { + $uuid = Uuid::uuid4()->toString(); + $route = new RelationshipRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals(RouteInterface::TYPE_RELATIONSHIP, $route->getType()); + } + + public function testGetEndpointTypeName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new RelationshipRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals('BAR', $route->getEndpointTypeName()); + } + + public function testGetRouteParamName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new RelationshipRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals('fooId', $route->getRouteParamName()); + } + + public function testGetRelationshipOriginName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new RelationshipRoute($this->getMappingProvider(), '', 'foo', $uuid, 'bar'); + $this->assertEquals('foo', $route->getRelationshipOriginName()); + } + + private function getMappingProvider() + { + return new MappingConfigProvider( + [ + MappingConfigProvider::ENDPOINT_TYPE_TO_DOCUMENT_TYPE => [ + 'foo' => 'FOO', + 'bar' => 'BAR', + ], + MappingConfigProvider::RESOURCE_TYPE_TO_ROUTE_PARAM_KEY => [ + 'foo' => 'fooId', + ], + ], + ); + } +} diff --git a/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRouteTest.php b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRouteTest.php new file mode 100644 index 0000000..6041896 --- /dev/null +++ b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourceRouteTest.php @@ -0,0 +1,52 @@ +toString(); + $route = new ResourceRoute($this->getMappingProvider(), 'v1', 'foo', $uuid); + $this->assertEquals('v1', $route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + $this->assertEquals($uuid, $route->getId()); + + $route = new ResourceRoute($this->getMappingProvider(), '', 'foo', $uuid); + $this->assertEmpty($route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + $this->assertEquals($uuid, $route->getId()); + } + + public function testGetType() + { + $uuid = Uuid::uuid4()->toString(); + $route = new ResourceRoute($this->getMappingProvider(), '', 'foo', $uuid); + $this->assertEquals(RouteInterface::TYPE_RESOURCE, $route->getType()); + } + + public function testGetRouteParamName() + { + $uuid = Uuid::uuid4()->toString(); + $route = new ResourceRoute($this->getMappingProvider(), '', 'foo', $uuid); + $this->assertEquals('fooId', $route->getRouteParamName()); + } + + private function getMappingProvider() + { + return new MappingConfigProvider( + [ + MappingConfigProvider::ENDPOINT_TYPE_TO_DOCUMENT_TYPE => [ + 'foo' => 'FOO', + ], + MappingConfigProvider::RESOURCE_TYPE_TO_ROUTE_PARAM_KEY => [ + 'foo' => 'fooId', + ], + ] + ); + } +} diff --git a/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRouteTest.php b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRouteTest.php new file mode 100644 index 0000000..3cbfb78 --- /dev/null +++ b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/ResourcesCollectionRouteTest.php @@ -0,0 +1,59 @@ +getMappingProvider(), 'v1', 'foo'); + $this->assertEquals('v1', $route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + + $route = new ResourcesCollectionRoute($this->getMappingProvider(), '', 'foo'); + $this->assertEmpty($route->getApiVersion()); + $this->assertEquals('foo', $route->getResourceName()); + + $this->expectException(InvalidArgumentException::class); + new ResourcesCollectionRoute($this->getMappingProvider(), '', 'baz'); + } + + public function testGetType() + { + $route = new ResourcesCollectionRoute($this->getMappingProvider(), '', 'foo'); + $this->assertEquals(RouteInterface::TYPE_RESOURCES_COLLECTION, $route->getType()); + } + + public function testGetEndpointTypeName() + { + $route = new ResourcesCollectionRoute($this->getMappingProvider(), '', 'foo'); + $this->assertEquals('foo', $route->getEndpointTypeName()); + } + + public function testGetRouteParamName() + { + $route = new ResourcesCollectionRoute($this->getMappingProvider(), '', 'foo'); + $this->assertNull($route->getRouteParamName()); + } + + public function testGetRelationshipOriginName() + { + $route = new ResourcesCollectionRoute($this->getMappingProvider(), '', 'foo'); + $this->assertEquals('foo', $route->getRelationshipOriginName()); + } + + private function getMappingProvider() + { + return new MappingConfigProvider( + [ + MappingConfigProvider::ENDPOINT_TYPE_TO_DOCUMENT_TYPE => [ + 'foo' => 'FOO', + ], + ] + ); + } +} diff --git a/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactoryTest.php b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactoryTest.php new file mode 100644 index 0000000..bad39fb --- /dev/null +++ b/tests/FreeElephants/JsonApiToolkit/JsonApi/Request/Route/RouteFactoryTest.php @@ -0,0 +1,128 @@ +getMappingProvider(), $routeRulesProvider); + $actual = $factory->create($path); + $this->assertTrue(is_a($actual, $expected)); + } + + public function correctDataProvider() + { + $routeRulesProviderWithoutApiVersion = $this->getRouteRulesProviderWithoutApiVersion(); + $routeRulesProviderWithApiVersion = $this->getRouteRulesProviderWithApiVersion(); + $uuid = Uuid::uuid4()->toString(); + + return [ + ['/foo', $routeRulesProviderWithoutApiVersion, ResourcesCollectionRoute::class], + ["/foo/$uuid", $routeRulesProviderWithoutApiVersion, ResourceRoute::class], + ["/foo/$uuid/relationships/bar", $routeRulesProviderWithoutApiVersion, RelationshipRoute::class], + ["/foo/$uuid/baz", $routeRulesProviderWithoutApiVersion, MemberRoute::class], + ['/v1/foo', $routeRulesProviderWithApiVersion, ResourcesCollectionRoute::class], + ["/v1/foo/$uuid", $routeRulesProviderWithApiVersion, ResourceRoute::class], + ["/v1/foo/$uuid/relationships/bar", $routeRulesProviderWithApiVersion, RelationshipRoute::class], + ["/v1/foo/$uuid/baz", $routeRulesProviderWithApiVersion, MemberRoute::class], + ]; + } + + /** + * @dataProvider incorrectDataProvider + */ + public function testCreateIncorrect(string $path, RouteRulesProvider $routeRulesProvider) + { + $factory = new RouteFactory($this->getMappingProvider(), $routeRulesProvider); + + $this->expectException(InvalidArgumentException::class); + + $factory->create($path); + } + + public function incorrectDataProvider() + { + $routeRulesProviderWithoutApiVersion = $this->getRouteRulesProviderWithoutApiVersion(); + $routeRulesProviderWithApiVersion = $this->getRouteRulesProviderWithApiVersion(); + $uuid = Uuid::uuid4()->toString(); + + return [ + ['', $routeRulesProviderWithoutApiVersion], + ['/v1/foo', $routeRulesProviderWithoutApiVersion], + ['foo', $routeRulesProviderWithoutApiVersion], + ['/zor', $routeRulesProviderWithoutApiVersion], + ['/Foo', $routeRulesProviderWithoutApiVersion], + ['/foo/bar', $routeRulesProviderWithoutApiVersion], + ["/foo/$uuid/relationship/bar", $routeRulesProviderWithoutApiVersion], + ["/foo/$uuid/relationships", $routeRulesProviderWithoutApiVersion], + ["/foo/$uuid/relationships/zor", $routeRulesProviderWithoutApiVersion], + ["/foo/$uuid/zor", $routeRulesProviderWithoutApiVersion], + ['', $routeRulesProviderWithApiVersion], + ['v1/foo', $routeRulesProviderWithApiVersion], + ['/foo', $routeRulesProviderWithApiVersion], + ['/v1/Foo', $routeRulesProviderWithApiVersion], + ['/v1/zor', $routeRulesProviderWithApiVersion], + ["/v1/foo/$uuid/relationship/bar", $routeRulesProviderWithApiVersion], + ["/v1/foo/$uuid/relationships", $routeRulesProviderWithApiVersion], + ["/v1/foo/$uuid/zor", $routeRulesProviderWithApiVersion], + ]; + } + + private function getMappingProvider(): MappingConfigProvider + { + return new MappingConfigProvider( + [ + MappingConfigProvider::ENDPOINT_TYPE_TO_DOCUMENT_TYPE => [ + 'foo' => 'FOO', + 'Foo' => 'FOO', + 'bar' => 'BAR', + 'baz' => 'BAZ', + ], + ] + ); + } + + private function getRouteRulesProviderWithoutApiVersion(): RouteRulesProvider + { + $resourceRegex = '([a-z][A-Za-z\d]+)'; + $hexRegex = '[\da-f]'; + $uuidRegex = "($hexRegex{8}-$hexRegex{4}-$hexRegex{4}-$hexRegex{4}-$hexRegex{12})"; + + return new RouteRulesProvider( + [ + RouteInterface::TYPE_RESOURCES_COLLECTION => sprintf('/^\/%s$/', $resourceRegex), + RouteInterface::TYPE_RESOURCE => sprintf('/^\/%s\/%s$/', $resourceRegex, $uuidRegex), + RouteInterface::TYPE_RELATIONSHIP => sprintf('/^\/%s\/%s\/relationships\/%s$/', $resourceRegex, $uuidRegex, $resourceRegex), + RouteInterface::TYPE_MEMBER => sprintf('/^\/%s\/%s\/%s$/', $resourceRegex, $uuidRegex, $resourceRegex), + ], + false + ); + } + + private function getRouteRulesProviderWithApiVersion(): RouteRulesProvider + { + $versionRegex = '(v\d+)'; + $resourceRegex = '([a-z][A-Za-z\d]+)'; + $hexRegex = '[\da-f]'; + $uuidRegex = "($hexRegex{8}-$hexRegex{4}-$hexRegex{4}-$hexRegex{4}-$hexRegex{12})"; + + return new RouteRulesProvider( + [ + RouteInterface::TYPE_RESOURCES_COLLECTION => sprintf('/^\/%s\/%s$/', $versionRegex, $resourceRegex), + RouteInterface::TYPE_RESOURCE => sprintf('/^\/%s\/%s\/%s$/', $versionRegex, $resourceRegex, $uuidRegex), + RouteInterface::TYPE_RELATIONSHIP => sprintf('/^\/%s\/%s\/%s\/relationships\/%s$/', $versionRegex, $resourceRegex, $uuidRegex, $resourceRegex), + RouteInterface::TYPE_MEMBER => sprintf('/^\/%s\/%s\/%s\/%s$/', $versionRegex, $resourceRegex, $uuidRegex, $resourceRegex), + ], + true + ); + } +} diff --git a/tests/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactoryTest.php b/tests/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactoryTest.php new file mode 100644 index 0000000..94ae079 --- /dev/null +++ b/tests/FreeElephants/JsonApiToolkit/Routing/FastRoute/EmptyOperationIdPatchedDispatcherFactoryTest.php @@ -0,0 +1,36 @@ +createMock(RouteFactory::class) + ); + $rf->expects($this->once())->method('create')->willReturn( + $r = $this->createMock(RouteInterface::class) + ); + + $dispatcher = $factory->buildDispatcher(<<assertSame( + [Dispatcher::FOUND, 'ArticlesCollectionHandler::handle', [ + JsonApiServerRequestInterface::ATTRIBUTE_ROUTE_NAME => $r + ]], + $dispatcher->dispatch('GET', '/articles') + ); + } +}