From a5b6ddfc1917435c4c050d78c46e7d0281f750d7 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 4 Feb 2022 12:56:37 +0100 Subject: [PATCH 1/6] Disallow extra fields in config to protect from typos --- src/DI/SlimApiExtension.php | 1 - tests/SlimApplicationFactoryTest.php | 14 ++++++++++++++ tests/config.neon | 1 - tests/typo-in-config.neon | 21 +++++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/typo-in-config.neon diff --git a/src/DI/SlimApiExtension.php b/src/DI/SlimApiExtension.php index 4925c9c..03b978c 100644 --- a/src/DI/SlimApiExtension.php +++ b/src/DI/SlimApiExtension.php @@ -42,7 +42,6 @@ public function getConfigSchema(): Schema Expect::arrayOf( Expect::structure($routeSchema) ->castTo('array') - ->otherItems() ) ) ), diff --git a/tests/SlimApplicationFactoryTest.php b/tests/SlimApplicationFactoryTest.php index 9540f3e..c208eea 100644 --- a/tests/SlimApplicationFactoryTest.php +++ b/tests/SlimApplicationFactoryTest.php @@ -9,6 +9,7 @@ use BrandEmbassyTest\Slim\Sample\GroupMiddleware; use BrandEmbassyTest\Slim\Sample\InvokeCounterMiddleware; use BrandEmbassyTest\Slim\Sample\OnlyApiGroupMiddleware; +use Nette\Utils\Strings; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Slim\Router; @@ -204,4 +205,17 @@ private function prepareEnvironment(string $requestMethod, string $requestUrlPat $_SERVER[$name] = $value; } } + + + public function testRouteConfigWillFailWhenMisconfigured(): void + { + $exceptionMessage = "Unexpected item 'slimApi › routes › app › /hello-world › get › middleware', did you mean 'middlewares'?"; + + // The exception message uses NBSP characters, see \Nette\Schema\Message + $exceptionMessageWithNbsp = Strings::replace($exceptionMessage, '~ › ~', ' › '); + + $this->expectExceptionMessage($exceptionMessageWithNbsp); + + SlimAppTester::createSlimApp(__DIR__ . '/typo-in-config.neon'); + } } diff --git a/tests/config.neon b/tests/config.neon index b0fbcd6..bf561af 100644 --- a/tests/config.neon +++ b/tests/config.neon @@ -33,7 +33,6 @@ slimApi: "/hello-world-as-class-name": get: service: BrandEmbassyTest\Slim\Sample\HelloWorldRoute - extraField: 'foo' middlewareGroups: - testGroup "/hello-world-as-service-name": diff --git a/tests/typo-in-config.neon b/tests/typo-in-config.neon new file mode 100644 index 0000000..49276c4 --- /dev/null +++ b/tests/typo-in-config.neon @@ -0,0 +1,21 @@ +extensions: + slimApi: \BrandEmbassy\Slim\DI\SlimApiExtension + + +services: + - BrandEmbassyTest\Slim\Sample\BeforeRouteMiddleware + - BrandEmbassyTest\Slim\Sample\HelloWorldRoute + +slimApi: + apiPrefix: '/tests' + slimConfiguration: + settings: + removeDefaultHandlers: true + + routes: + "app": + "/hello-world": + get: + service: BrandEmbassyTest\Slim\Sample\HelloWorldRoute + middleware: + - BrandEmbassyTest\Slim\Sample\BeforeRouteMiddleware From 971b87ee7c840c984f0bf07f41126e45524c52ba Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 4 Feb 2022 15:05:38 +0100 Subject: [PATCH 2/6] Allow extra fields in config as some users need it --- src/DI/SlimApiExtension.php | 1 + tests/config.neon | 1 + 2 files changed, 2 insertions(+) diff --git a/src/DI/SlimApiExtension.php b/src/DI/SlimApiExtension.php index 03b978c..4925c9c 100644 --- a/src/DI/SlimApiExtension.php +++ b/src/DI/SlimApiExtension.php @@ -42,6 +42,7 @@ public function getConfigSchema(): Schema Expect::arrayOf( Expect::structure($routeSchema) ->castTo('array') + ->otherItems() ) ) ), diff --git a/tests/config.neon b/tests/config.neon index f0742dc..a9559a2 100644 --- a/tests/config.neon +++ b/tests/config.neon @@ -34,6 +34,7 @@ slimApi: "/hello-world-as-class-name": get: service: BrandEmbassyTest\Slim\Sample\HelloWorldRoute + extraField: 'foo' middlewareGroups: - testGroup "/hello-world-as-service-name": From bdec72d26df949639e6c9d2be007af3df865998c Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 4 Feb 2022 15:24:08 +0100 Subject: [PATCH 3/6] Detect typo middleware / middlewares --- src/Route/InvalidRouteDefinitionException.php | 19 +++++++++++++++++++ src/Route/RouteRegister.php | 7 +++++++ tests/SlimApplicationFactoryTest.php | 10 +++------- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/Route/InvalidRouteDefinitionException.php diff --git a/src/Route/InvalidRouteDefinitionException.php b/src/Route/InvalidRouteDefinitionException.php new file mode 100644 index 0000000..8145b55 --- /dev/null +++ b/src/Route/InvalidRouteDefinitionException.php @@ -0,0 +1,19 @@ +routeDefinitionFactory->create($method, $routeDefinitionData); $routeName = $routeDefinition->getName() ?? $resolveRoutePath; diff --git a/tests/SlimApplicationFactoryTest.php b/tests/SlimApplicationFactoryTest.php index e158a72..66a5de8 100644 --- a/tests/SlimApplicationFactoryTest.php +++ b/tests/SlimApplicationFactoryTest.php @@ -9,7 +9,6 @@ use BrandEmbassyTest\Slim\Sample\GroupMiddleware; use BrandEmbassyTest\Slim\Sample\InvokeCounterMiddleware; use BrandEmbassyTest\Slim\Sample\OnlyApiGroupMiddleware; -use Nette\Utils\Strings; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Slim\Router; @@ -238,12 +237,9 @@ private function prepareEnvironment(string $requestMethod, string $requestUrlPat public function testRouteConfigWillFailWhenMisconfigured(): void { - $exceptionMessage = "Unexpected item 'slimApi › routes › app › /hello-world › get › middleware', did you mean 'middlewares'?"; - - // The exception message uses NBSP characters, see \Nette\Schema\Message - $exceptionMessageWithNbsp = Strings::replace($exceptionMessage, '~ › ~', ' › '); - - $this->expectExceptionMessage($exceptionMessageWithNbsp); + $this->expectExceptionMessage( + 'Unexpected route definition key in "app › /hello-world › get › middleware", did you mean "middlewares"?' + ); SlimAppTester::createSlimApp(__DIR__ . '/typo-in-config.neon'); } From 53e2d9e7bc3bc05fe4d08de8aa61329ea8bdcf6a Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 4 Feb 2022 15:33:01 +0100 Subject: [PATCH 4/6] Generalization of typo detection using levenshtein distance --- src/Route/RouteDefinition.php | 8 ++++++++ src/Route/RouteRegister.php | 30 ++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/Route/RouteDefinition.php b/src/Route/RouteDefinition.php index cef7cb9..2591b96 100644 --- a/src/Route/RouteDefinition.php +++ b/src/Route/RouteDefinition.php @@ -10,6 +10,14 @@ final class RouteDefinition public const IGNORE_VERSION_MIDDLEWARE_GROUP = 'ignoreVersionMiddlewareGroup'; public const NAME = 'name'; + public const ALL_DEFINED_KEYS = [ + self::SERVICE, + self::MIDDLEWARES, + self::MIDDLEWARE_GROUPS, + self::IGNORE_VERSION_MIDDLEWARE_GROUP, + self::NAME, + ]; + /** * @var string */ diff --git a/src/Route/RouteRegister.php b/src/Route/RouteRegister.php index a5854b8..3b296d2 100644 --- a/src/Route/RouteRegister.php +++ b/src/Route/RouteRegister.php @@ -66,12 +66,10 @@ public function register(string $apiNamespace, string $routePattern, array $rout continue; } - if (array_key_exists('middleware', $routeDefinitionData)) { - throw new InvalidRouteDefinitionException( - [$apiNamespace, $routePattern, $method, 'middleware'], - RouteDefinition::MIDDLEWARES - ); - } + $this->detectTyposInRouteConfiguration( + [$apiNamespace, $routePattern, $method], + $routeDefinitionData + ); $routeDefinition = $this->routeDefinitionFactory->create($method, $routeDefinitionData); @@ -128,4 +126,24 @@ private function getEmptyRouteDefinitionData(): array RouteDefinition::NAME => null, ]; } + + + /** + * @param string[] $path + * @param mixed[] $routeDefinitionData + */ + private function detectTyposInRouteConfiguration(array $path, array $routeDefinitionData): void + { + $usedKeys = array_keys($routeDefinitionData); + foreach ($usedKeys as $usedKey) { + foreach (RouteDefinition::ALL_DEFINED_KEYS as $definedKey) { + $levenshteinDistance = levenshtein($usedKey, $definedKey); + if ($levenshteinDistance > 0 && $levenshteinDistance < 2) { + $path[] = $usedKey; + + throw new InvalidRouteDefinitionException($path, $definedKey); + } + } + } + } } From 1c13881f7d43c3f919ca2d569905f94a83a4cdd8 Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 4 Feb 2022 15:35:08 +0100 Subject: [PATCH 5/6] phpcbf --- src/Route/InvalidRouteDefinitionException.php | 5 +++-- src/Route/RouteRegister.php | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Route/InvalidRouteDefinitionException.php b/src/Route/InvalidRouteDefinitionException.php index 8145b55..60df4f9 100644 --- a/src/Route/InvalidRouteDefinitionException.php +++ b/src/Route/InvalidRouteDefinitionException.php @@ -3,12 +3,13 @@ namespace BrandEmbassy\Slim\Route; use RuntimeException; +use function implode; +use function sprintf; final class InvalidRouteDefinitionException extends RuntimeException { /** * @param string[] $path - * @param string $hint */ public function __construct(array $path, string $hint) { @@ -16,4 +17,4 @@ public function __construct(array $path, string $hint) sprintf('Unexpected route definition key in "%s", did you mean "%s"?', implode(' › ', $path), $hint) ); } -} \ No newline at end of file +} diff --git a/src/Route/RouteRegister.php b/src/Route/RouteRegister.php index 3b296d2..bdf7ca1 100644 --- a/src/Route/RouteRegister.php +++ b/src/Route/RouteRegister.php @@ -5,7 +5,9 @@ use BrandEmbassy\Slim\Middleware\BeforeRouteMiddlewares; use BrandEmbassy\Slim\Middleware\MiddlewareGroups; use Slim\Interfaces\RouterInterface; +use function array_keys; use function array_merge_recursive; +use function levenshtein; final class RouteRegister { From 29317dfa7c4c5a32f4c3285688f8222a2293b83c Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 4 Feb 2022 15:40:35 +0100 Subject: [PATCH 6/6] single-line method call --- src/Route/RouteRegister.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Route/RouteRegister.php b/src/Route/RouteRegister.php index bdf7ca1..b30071d 100644 --- a/src/Route/RouteRegister.php +++ b/src/Route/RouteRegister.php @@ -68,10 +68,7 @@ public function register(string $apiNamespace, string $routePattern, array $rout continue; } - $this->detectTyposInRouteConfiguration( - [$apiNamespace, $routePattern, $method], - $routeDefinitionData - ); + $this->detectTyposInRouteConfiguration([$apiNamespace, $routePattern, $method], $routeDefinitionData); $routeDefinition = $this->routeDefinitionFactory->create($method, $routeDefinitionData);