From 176cfe3da3a06d1b66ddcf6eedc46679df169653 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Wed, 8 Jun 2022 15:51:53 +0200 Subject: [PATCH 01/35] Update raml2html's README.md --- tools/raml2html/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index 4dc562afaa..96590ebc4d 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -4,15 +4,20 @@ Tool for generating static HTML from RAML definitions. ## Installation -Install required dependencies before use. Go to raml2html root directory and run: +PHP 7.2 and [Composer](https://getcomposer.org/) are required. -``` -composer install +To install required dependencies, go to raml2html directory and run: + +```sh +composer install; ``` -To generate static HTML from RAML definitions, use the following code: +## Usage +To generate static HTML from RAML definitions, use the following code from project root: ```sh php tools/raml2html/raml2html.php build --non-standard-http-methods=COPY,MOVE,PUBLISH,SWAP -t default -o docs/api/rest_api_reference/output/ docs/api/rest_api_reference/input/ez.raml ``` + +Note: If PHP 7.2 is not the default PHP, please, adapt. From 648bb8dfa33da759ec9a4c4d719b03ae15f204f9 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Wed, 28 Dec 2022 17:00:19 +0100 Subject: [PATCH 02/35] tools/raml2html/README.md: Update dir paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/api/rest_api_reference/ → docs/api/rest_api/rest_api_reference/ --- tools/raml2html/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index 96590ebc4d..b277b8f116 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -17,7 +17,7 @@ composer install; To generate static HTML from RAML definitions, use the following code from project root: ```sh -php tools/raml2html/raml2html.php build --non-standard-http-methods=COPY,MOVE,PUBLISH,SWAP -t default -o docs/api/rest_api_reference/output/ docs/api/rest_api_reference/input/ez.raml +php tools/raml2html/raml2html.php build --non-standard-http-methods=COPY,MOVE,PUBLISH,SWAP -t default -o docs/api/rest_api/rest_api_reference/output/ docs/api/rest_api/rest_api_reference/input/ez.raml ``` Note: If PHP 7.2 is not the default PHP, please, adapt. From 79e058ea522a0ec350ba1620473a957d76d8e326 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Tue, 3 Jan 2023 17:31:49 +0100 Subject: [PATCH 03/35] mv rest_api_reference_test.php to `raml2html.php test` --- tools/raml2html/README.md | 19 +- tools/raml2html/src/Application.php | 2 + tools/raml2html/src/Command/TestCommand.php | 91 +++++ tools/raml2html/src/Test/ReferenceTester.php | 372 +++++++++++++++++++ 4 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 tools/raml2html/src/Command/TestCommand.php create mode 100644 tools/raml2html/src/Test/ReferenceTester.php diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index b277b8f116..ec8691fdce 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -14,10 +14,27 @@ composer install; ## Usage +Note: If PHP 7.2 is not the default PHP, please, adapt. + To generate static HTML from RAML definitions, use the following code from project root: ```sh php tools/raml2html/raml2html.php build --non-standard-http-methods=COPY,MOVE,PUBLISH,SWAP -t default -o docs/api/rest_api/rest_api_reference/output/ docs/api/rest_api/rest_api_reference/input/ez.raml ``` -Note: If PHP 7.2 is not the default PHP, please, adapt. +To test static HTML against an Ibexa DXP to find route removed and missing routes: + +```sh +php tools/raml2html/raml2html.php test docs/api/rest_api/rest_api_reference/rest_api_reference.html ~/ibexa-dxp +``` + +Note: The Ibexa DXP doesn't need to run + +```shell +mkdir ~/ibexa-dxp; +cd ~/ibexa-dxp; +composer create-project ibexa/commerce-skeleton . --no-install --ignore-platform-reqs --no-scripts; +composer install --ignore-platform-reqs --no-scripts; +cd -; +php tools/raml2html/raml2html.php test docs/api/rest_api/rest_api_reference/rest_api_reference.html ~/ibexa-dxp +``` diff --git a/tools/raml2html/src/Application.php b/tools/raml2html/src/Application.php index 4125437b4f..fa01f4b5fe 100644 --- a/tools/raml2html/src/Application.php +++ b/tools/raml2html/src/Application.php @@ -7,6 +7,7 @@ use EzSystems\Raml2Html\Command\BuildCommand; use EzSystems\Raml2Html\Command\ClearCacheCommand; use EzSystems\Raml2Html\Command\LintTypesCommand; +use EzSystems\Raml2Html\Command\TestCommand; use EzSystems\Raml2Html\Generator\Generator; use EzSystems\Raml2Html\RAML\ParserFactory; use EzSystems\Raml2Html\Twig\Extension\HashExtension; @@ -39,6 +40,7 @@ protected function getDefaultCommands(): array $this->getRamlParserFactory() ), new ClearCacheCommand(self::CACHE_DIR), + new TestCommand(), ]); } diff --git a/tools/raml2html/src/Command/TestCommand.php b/tools/raml2html/src/Command/TestCommand.php new file mode 100644 index 0000000000..983a9367d1 --- /dev/null +++ b/tools/raml2html/src/Command/TestCommand.php @@ -0,0 +1,91 @@ +setName('test') + ->setDescription('Compare REST API Reference with Ibexa DXP routing configuration under /api/ibexa/v2 prefix') + ->setHelp('It is recommended to not use --console-path and --routing-file options while testing the Rest API Reference HTML file against configuration. Those options are used to test that the default configuration file list is up-to-date and other subtleties.') + ->addArgument('rest-api-reference', InputArgument::REQUIRED, 'Path to the REST API Reference HTML file') + ->addArgument('ibexa-dxp-root', InputArgument::REQUIRED, 'Path to an Ibexa DXP root directory') + ->addOption('console-path', 'c', InputOption::VALUE_OPTIONAL, 'Path to the console relative to Ibexa DXP root directory (if no value, use `bin/console`)', false) + ->addOption('routing-file', 'f', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Path to a routing configuration YAML file relative to Ibexa DXP root directory', ReferenceTester::DEFAULT_FILE_LIST) + ->addOption('tested-routes', 't', InputOption::VALUE_OPTIONAL, + "ref: Test if reference routes are found in the configuration file;\n + conf: Test if configuration routes are found in the reference file;\n + both: Test both.", 'both'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $restApiReference = $input->getArgument('rest-api-reference'); + if (!is_file($restApiReference)) { + $output->writeln("$restApiReference doesn't exist or is not a file."); + return 1; + } + + $dxpRoot = $input->getArgument('ibexa-dxp-root'); + if (!is_dir($dxpRoot)) { + $output->writeln("$dxpRoot doesn't exist or is not a directory."); + return 2; + } + + $consolePath = $input->getOption('console-path'); + if (null === $consolePath) { + $consolePath = 'bin/console'; + } + + $routingFiles = $input->getOption('routing-file'); + + $referenceTester = new ReferenceTester($restApiReference, $dxpRoot, $consolePath, $routingFiles); + + $testedRoutes = @[ + 'ref' => ReferenceTester::TEST_REFERENCE_ROUTES, + 'conf' => ReferenceTester::TEST_CONFIG_ROUTES, + 'both' => ReferenceTester::TEST_ALL_ROUTES, + ][$input->getOption('tested-routes')] ?? ReferenceTester::TEST_ALL_ROUTES; + + $referenceTester->run($testedRoutes); + + return 0; + } + + private function createParserConfigurationFromInput(InputInterface $input): ParserConfiguration + { + $configuration = new ParserConfiguration(); + + $nonStandardHTTPMethods = $input->getOption(self::OPTION_NON_STANDARD_HTTP_METHODS); + if ($nonStandardHTTPMethods !== null) { + $configuration->setNonStandardHttpMethods(explode(',', $nonStandardHTTPMethods)); + } + + return $configuration; + } + + private function createGeneratorOptionsFromInput(InputInterface $input): GeneratorOptions + { + $generatorOptions = new GeneratorOptions(); + $generatorOptions->setOutputDir($input->getOption(self::OPTION_OUTPUT_DIR)); + $generatorOptions->setTheme($input->getOption(self::OPTION_THEME)); + + return $generatorOptions; + } +} diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php new file mode 100644 index 0000000000..05feec98ef --- /dev/null +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -0,0 +1,372 @@ +restApiReference = $restApiReference; + $this->dxpRoot = $dxpRoot; + $this->parseApiReference($this->restApiReference); + $this->parseRoutes($consolePath, $routingFiles); + } + + private function parseApiReference($restApiReference): void + { + $refRoutes = []; + + $restApiRefDoc = new \DOMDocument(); + $restApiRefDoc->loadHTMLFile($restApiReference, LIBXML_NOERROR); + $restApiRefXpath = new \DOMXpath($restApiRefDoc); + + /** @var DOMElement $urlElement */ + foreach ($restApiRefXpath->query('//*[@data-field="url"]') as $urlElement) { + if (!array_key_exists($urlElement->nodeValue, $refRoutes)) { + $refRoutes[$urlElement->nodeValue] = [ + 'methods' => [], + ]; + } + $refRoutes[$urlElement->nodeValue]['methods'][$urlElement->previousSibling->previousSibling->nodeValue] = true; + } + + $this->refRoutes = $refRoutes; + ksort($this->refRoutes); + } + + private function parseRoutes($consolePath = 'bin/console', $routingFiles = null) + { + if (is_string($consolePath)) { + $this->parseRouterOutput($consolePath); + } elseif (is_array($routingFiles)) { + $this->parseRoutingFiles($routingFiles); + } elseif (is_string($routingFiles)) { + $this->parseRoutingFiles([$routingFiles]); + } else { + $this->parseRoutingFiles(self::DEFAULT_FILE_LIST); + } + ksort($this->confRoutes); + } + + private function parseRouterOutput($consolePath) + { + $confRoutes = []; + + $routerCommand = 'debug:router --format=txt'; + $consolePathLastChar = substr($consolePath, -1); + if (in_array($consolePathLastChar, ['"', "'"])) { + $consoleCommand = substr($consolePath, 0, -1) . " {$routerCommand}{$consolePathLastChar}"; + } else { + $consoleCommand = "$consolePath $routerCommand"; + } + + $routerOutput = shell_exec("cd {$this->dxpRoot} && $consoleCommand | grep '{$this->apiUri}'"); + + foreach (explode("\n", $routerOutput) as $outputLine) { + $outputLine = trim($outputLine); + if (empty($outputLine)) { + continue; + } + $lineParts = preg_split('/\s+/', $outputLine); + $routeId = $lineParts[0]; + $methods = explode('|', $lineParts[1]); + foreach ($methods as $method) { + if ('OPTIONS' === $method) { + continue; + } + $routePath = str_replace($this->apiUri, '', $lineParts[4]); + if (!array_key_exists($routePath, $confRoutes)) { + $confRoutes[$routePath] = ['methods' => []]; + } + $confRoutes[$routePath]['methods'][$method] = [ + 'id' => $routeId, + 'file' => null, + 'line' => null, + ]; + } + } + + $this->confRoutes = $confRoutes; + } + + private function parseRoutingFiles($routingFiles): void + { + $confRoutes = []; + + $parsedRoutingFiles = []; + foreach ($routingFiles as $routingFile) { + $routingFilePath = "{$this->dxpRoot}/$routingFile"; + if (!is_file($routingFilePath)) { + user_error("$routingFilePath doesn't exist or is not a file", E_USER_WARNING); + continue; + } + $parsedRoutingFiles[$routingFile] = yaml_parse_file($routingFilePath); + } + + foreach ($parsedRoutingFiles as $routingFile => $parsedRoutingFile) { + foreach ($parsedRoutingFile as $routeId => $routeDef) { + $line = (int)explode(':', `grep -n '^$routeId:$' {$this->dxpRoot}/$routingFile`)[0]; + if (!array_key_exists('methods', $routeDef)) { + user_error("$routeId ($routingFile@$line) matches every methods by default; skipped", E_USER_WARNING); + continue; + } + if (!array_key_exists($routeDef['path'], $confRoutes)) { + $confRoutes[$routeDef['path']] = [ + 'methods' => [], + ]; + } + foreach ($routeDef['methods'] as $method) { + $confRoutes[$routeDef['path']]['methods'][$method] = [ + 'id' => $routeId, + 'file' => $routingFile, + 'line' => $line, + ]; + } + } + } + + $this->confRoutes = $confRoutes; + } + + public function run(int $testedRoutes = self::TEST_ALL_ROUTES) + { + $refRoutes = $this->refRoutes; + $confRoutes = $this->confRoutes; + + foreach (array_intersect(array_keys($refRoutes), array_keys($confRoutes)) as $commonRoute) { + $missingMethods = $this->compareMethods($commonRoute, $commonRoute, $testedRoutes); + if (!array_key_exists('GET', $refRoutes[$commonRoute]['methods']) && array_key_exists('HEAD', $refRoutes[$commonRoute]['methods']) + && array_key_exists('GET', $confRoutes[$commonRoute]['methods']) && array_key_exists('HEAD', $confRoutes[$commonRoute]['methods']) + && !is_null($confRoutes[$commonRoute]['methods']['HEAD']['id']) && $confRoutes[$commonRoute]['methods']['GET']['id'] === $confRoutes[$commonRoute]['methods']['HEAD']['id']) { + echo "\t$commonRoute has no GET reference but has a HEAD reference, HEAD and GET share the same route id ({$confRoutes[$commonRoute]['methods']['GET']['id']}) so GET might be just a fallback for HEAD.\n"; + } + if ($missingMethods && false !== strpos($commonRoute, '{')) { + $similarRefRoutes = $this->getSimilarRoutes($commonRoute, $refRoutes); + $similarConfRoutes = $this->getSimilarRoutes($commonRoute, $confRoutes); + foreach (['highly', 'poorly'] as $similarityLevel) { + foreach ($similarRefRoutes[$similarityLevel] as $refRoute) { + if ($refRoute === $commonRoute) { + continue; + } + $stillMissingMethod = $this->compareMethods($refRoute, $commonRoute, $testedRoutes, $missingMethods); + $foundMethods = array_diff($missingMethods, $stillMissingMethod); + if (!empty($foundMethods)) { + foreach ($foundMethods as $foundMethod) { + if ('highly' === $similarityLevel) { + echo "\t$refRoute has $foundMethod and is highly similar to $commonRoute\n"; + } else { + echo "\t$refRoute has $foundMethod and is a bit similar to $commonRoute\n"; + } + } + } + } + foreach ($similarConfRoutes[$similarityLevel] as $confRoute) { + if ($confRoute === $commonRoute) { + continue; + } + $stillMissingMethod = $this->compareMethods($commonRoute, $confRoute, $testedRoutes, $missingMethods); + $foundMethods = array_diff($missingMethods, $stillMissingMethod); + if (!empty($foundMethods)) { + foreach ($foundMethods as $foundMethod) { + if ('highly' === $similarityLevel) { + echo "\t{$this->getConfRoutePrompt($confRoute)} has $foundMethod and is highly similar to $commonRoute\n"; + } else { + echo "\t{$this->getConfRoutePrompt($confRoute)} has $foundMethod and is a bit similar to $commonRoute\n"; + } + } + } + } + } + } + } + + if (self::TEST_REFERENCE_ROUTES & $testedRoutes) { + foreach (array_diff(array_keys($refRoutes), array_keys($confRoutes)) as $refRouteWithoutConf) { + if (false !== strpos($refRouteWithoutConf, '{')) { + $similarConfRoutes = $this->getSimilarRoutes($refRouteWithoutConf, $confRoutes); + if (!empty($similarConfRoutes['highly'])) { + echo "$refRouteWithoutConf not found in config files but\n"; + foreach ($similarConfRoutes['highly'] as $confRoute) { + echo "\t$refRouteWithoutConf is highly similar to $confRoute\n"; + $this->compareMethods($refRouteWithoutConf, $confRoute, $testedRoutes); + } + continue; + } + if (!empty($similarConfRoutes['poorly'])) { + echo "$refRouteWithoutConf not found in config files but\n"; + foreach ($similarConfRoutes['poorly'] as $confRoute) { + echo "\t$refRouteWithoutConf is a bit similar to $confRoute\n"; + $this->compareMethods($refRouteWithoutConf, $confRoute, $testedRoutes); + } + continue; + } + } + echo "$refRouteWithoutConf not found in config files.\n"; + } + } + + if (self::TEST_CONFIG_ROUTES & $testedRoutes) { + foreach (array_diff(array_keys($confRoutes), array_keys($refRoutes)) as $confRouteWithoutRef) { + if (false !== strpos($confRouteWithoutRef, '{')) { + $similarRefRoutes = $this->getSimilarRoutes($confRouteWithoutRef, $refRoutes); + if (!empty($similarRefRoutes['highly'])) { + echo "{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference but\n"; + foreach ($similarRefRoutes['highly'] as $refRoute) { + echo "\t$confRouteWithoutRef is highly similar to $refRoute\n"; + $this->compareMethods($refRoute, $confRouteWithoutRef, $testedRoutes); + } + continue; + } + if (!empty($similarRefRoutes['poorly'])) { + echo "{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference but\n"; + foreach ($similarRefRoutes['poorly'] as $refRoute) { + echo "\t$confRouteWithoutRef is a bit similar to $refRoute\n"; + $this->compareMethods($refRoute, $confRouteWithoutRef, $testedRoutes); + } + continue; + } + } + echo "{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference.\n"; + } + } + } + + private function compareMethods(string $refRoute, string $confRoute, int $testedRoutes = self::TEST_ALL_ROUTES, ?array $testedMethods = null): array + { + $refRoutes = $this->refRoutes; + $confRoutes = $this->confRoutes; + $missingMethods = []; + + if (self::TEST_REFERENCE_ROUTES & $testedRoutes) { + foreach (array_diff(array_keys($refRoutes[$refRoute]['methods']), array_keys($confRoutes[$confRoute]['methods'])) as $refMethodWithoutConf) { + if (null === $testedMethods || in_array($refMethodWithoutConf, $testedMethods)) { + echo "$refRoute: $refMethodWithoutConf method not found in conf files" . ($refRoute === $confRoute ? '' : " (while comparing to $confRoute)") . ".\n"; + $missingMethods[] = $refMethodWithoutConf; + } + } + } + + if (self::TEST_CONFIG_ROUTES & $testedRoutes) { + foreach (array_diff(array_keys($confRoutes[$confRoute]['methods']), array_keys($refRoutes[$refRoute]['methods'])) as $confMethodWithoutRef) { + if (null === $testedMethods || in_array($confMethodWithoutRef, $testedMethods)) { + echo "{$this->getConfRoutePrompt($confRoute, $confMethodWithoutRef)}: $confMethodWithoutRef not found in reference" . ($refRoute === $confRoute ? '' : " (while comparing to $refRoute)") . ".\n"; + $missingMethods[] = $confMethodWithoutRef; + } + } + } + + return $missingMethods; + } + + private function getSimilarRoutes(string $path, array $routeCollection): array + { + $routePattern = $this->getRoutePattern($path); + $highlySimilarRoutes = []; + $poorlySimilarRoutes = []; + foreach (array_keys($routeCollection) as $route) { + if (preg_match($routePattern, $route)) { + if ($this->getSimplifiedRoute($route) === $this->getSimplifiedRoute($path)) { + $highlySimilarRoutes[] = $route; + } else { + $poorlySimilarRoutes[] = $route; + } + } + } + return [ + 'highly' => $highlySimilarRoutes, + 'poorly' => $poorlySimilarRoutes, + ]; + } + + private function getSimplifiedRoute(string $path): string + { + return str_replace(['identifier', 'number', '_', '-'], ['id', 'no', ''], strtolower($path)); + } + + private function getRoutePattern(string $path): string + { + return '@^' . preg_replace('@\{[^}]+\}@', '\{[^}]+\}', $path) . '$@'; + } + + private function getConfRoutePrompt(string $path, $method = null): string + { + $prompt = $path; + + if (array_key_exists($path, $this->confRoutes)) { + if ($method && array_key_exists($method, $this->confRoutes[$path]['methods'])) { + if (array_key_exists('file', $this->confRoutes[$path]['methods'][$method]) && !is_null($this->confRoutes[$path]['methods'][$method]['file'])) { + $location = $this->confRoutes[$path]['methods'][$method]['file']; + if (array_key_exists('line', $this->confRoutes[$path]['methods'][$method]) && !is_null($this->confRoutes[$path]['methods'][$method]['line'])) { + $location .= "@{$this->confRoutes[$path]['methods'][$method]['line']}"; + } + $prompt = "$prompt ($location)"; + } + } else { + $files = []; + $lines = []; + $pairs = []; + foreach ($this->confRoutes[$path]['methods'] as $methodDetail) { + if (array_key_exists('file', $methodDetail) && !is_null($methodDetail['file'])) { + $files[] = $methodDetail['file']; + if (array_key_exists('line', $methodDetail) && !is_null($methodDetail['line'])) { + $lines[] = $methodDetail['line']; + $pairs[] = "{$methodDetail['file']}@{$methodDetail['line']}"; + } else { + $pairs[] = $methodDetail['file']; + } + } + } + $filteredFiles = array_unique($files); + if (!empty($filteredFiles)) { + if (1 < count($filteredFiles)) { + $pairs = implode(',', array_unique($pairs)); + $prompt = "$prompt ($pairs)"; + } else { + $file = $filteredFiles[0]; + $lines = implode(',', array_unique($lines)); + $prompt = "$prompt ($file@$lines)"; + } + } + } + } + + return $prompt; + } +} From 62ff107733baa00213218c660817a868feb4776d Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Wed, 4 Jan 2023 17:01:44 +0100 Subject: [PATCH 04/35] TestCommand.php: Enh. output --- tools/raml2html/src/Command/TestCommand.php | 5 +- tools/raml2html/src/Test/ReferenceTester.php | 187 ++++++++++++------- 2 files changed, 127 insertions(+), 65 deletions(-) diff --git a/tools/raml2html/src/Command/TestCommand.php b/tools/raml2html/src/Command/TestCommand.php index 983a9367d1..d6bbd8bd92 100644 --- a/tools/raml2html/src/Command/TestCommand.php +++ b/tools/raml2html/src/Command/TestCommand.php @@ -6,6 +6,7 @@ use EzSystems\Raml2Html\Test\ReferenceTester; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -19,7 +20,7 @@ final class TestCommand extends Command protected function configure(): void { $this->setName('test') - ->setDescription('Compare REST API Reference with Ibexa DXP routing configuration under /api/ibexa/v2 prefix') + ->setDescription('Compare REST API Reference documentation with Ibexa DXP routing configuration under /api/ibexa/v2 prefix') ->setHelp('It is recommended to not use --console-path and --routing-file options while testing the Rest API Reference HTML file against configuration. Those options are used to test that the default configuration file list is up-to-date and other subtleties.') ->addArgument('rest-api-reference', InputArgument::REQUIRED, 'Path to the REST API Reference HTML file') ->addArgument('ibexa-dxp-root', InputArgument::REQUIRED, 'Path to an Ibexa DXP root directory') @@ -55,7 +56,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $routingFiles = $input->getOption('routing-file'); - $referenceTester = new ReferenceTester($restApiReference, $dxpRoot, $consolePath, $routingFiles); + $referenceTester = new ReferenceTester($restApiReference, $dxpRoot, $consolePath, $routingFiles, $output); $testedRoutes = @[ 'ref' => ReferenceTester::TEST_REFERENCE_ROUTES, diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 05feec98ef..efd2426062 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -2,13 +2,15 @@ namespace EzSystems\Raml2Html\Test; +use Symfony\Component\Console\Output\Output; + class ReferenceTester { - const TEST_REFERENCE_ROUTES = 1; - const TEST_CONFIG_ROUTES = 2; - const TEST_ALL_ROUTES = 3; + public const TEST_REFERENCE_ROUTES = 1; + public const TEST_CONFIG_ROUTES = 2; + public const TEST_ALL_ROUTES = 3; - const DEFAULT_FILE_LIST = [ + public const DEFAULT_FILE_LIST = [ 'vendor/ibexa/rest/src/bundle/Resources/config/routing.yml', 'vendor/ibexa/commerce-rest/src/bundle/Resources/config/routing.yaml', // `find $dxpRoot/vendor/ibexa -name "routing_rest.y*ml"` @@ -21,7 +23,23 @@ class ReferenceTester 'vendor/ibexa/taxonomy/src/bundle/Resources/config/routing_rest.yaml', ]; - private $apiUri = '/api/ibexa/v2'; + public const METHOD_LIST = [ + 'OPTIONS', + 'GET', + 'HEAD', + 'POST', + 'PATCH', + 'COPY', + 'MOVE', + 'SWAP', + 'PUBLISH', + 'DELETE', + ]; + + public $apiUri = '/api/ibexa/v2'; + + private const REF_METHOD_NOT_IN_CONF = 'ref_route_method_missing_from_conf'; + private const CONF_METHOD_NOT_IN_REF = 'conf_route_method_missing_from_ref'; private $restApiReference; private $dxpRoot; @@ -29,7 +47,10 @@ class ReferenceTester private $refRoutes; private $confRoutes; - public function __construct($restApiReference, $dxpRoot, $consolePath = 'bin/console', $routingFiles = null) + /** @var Output */ + private $output; + + public function __construct($restApiReference, $dxpRoot, $consolePath = 'bin/console', $routingFiles = null, Output $output = null) { if (!is_file($restApiReference)) { user_error("$restApiReference doesn't exist or is not a file", E_USER_ERROR); @@ -43,6 +64,8 @@ public function __construct($restApiReference, $dxpRoot, $consolePath = 'bin/con exit(2); } + $this->output = $output; + $this->restApiReference = $restApiReference; $this->dxpRoot = $dxpRoot; $this->parseApiReference($this->restApiReference); @@ -144,8 +167,7 @@ private function parseRoutingFiles($routingFiles): void foreach ($parsedRoutingFile as $routeId => $routeDef) { $line = (int)explode(':', `grep -n '^$routeId:$' {$this->dxpRoot}/$routingFile`)[0]; if (!array_key_exists('methods', $routeDef)) { - user_error("$routeId ($routingFile@$line) matches every methods by default; skipped", E_USER_WARNING); - continue; + $routeDef['methods'] = self::METHOD_LIST; } if (!array_key_exists($routeDef['path'], $confRoutes)) { $confRoutes[$routeDef['path']] = [ @@ -170,45 +192,54 @@ public function run(int $testedRoutes = self::TEST_ALL_ROUTES) $refRoutes = $this->refRoutes; $confRoutes = $this->confRoutes; + // Check methods from routes found in both reference and configuration foreach (array_intersect(array_keys($refRoutes), array_keys($confRoutes)) as $commonRoute) { $missingMethods = $this->compareMethods($commonRoute, $commonRoute, $testedRoutes); if (!array_key_exists('GET', $refRoutes[$commonRoute]['methods']) && array_key_exists('HEAD', $refRoutes[$commonRoute]['methods']) && array_key_exists('GET', $confRoutes[$commonRoute]['methods']) && array_key_exists('HEAD', $confRoutes[$commonRoute]['methods']) && !is_null($confRoutes[$commonRoute]['methods']['HEAD']['id']) && $confRoutes[$commonRoute]['methods']['GET']['id'] === $confRoutes[$commonRoute]['methods']['HEAD']['id']) { - echo "\t$commonRoute has no GET reference but has a HEAD reference, HEAD and GET share the same route id ({$confRoutes[$commonRoute]['methods']['GET']['id']}) so GET might be just a fallback for HEAD.\n"; + $this->output("\t$commonRoute has no GET reference but has a HEAD reference, HEAD and GET share the same configuration route id ({$confRoutes[$commonRoute]['methods']['GET']['id']}) so GET might be just a fallback for HEAD."); } - if ($missingMethods && false !== strpos($commonRoute, '{')) { - $similarRefRoutes = $this->getSimilarRoutes($commonRoute, $refRoutes); - $similarConfRoutes = $this->getSimilarRoutes($commonRoute, $confRoutes); - foreach (['highly', 'poorly'] as $similarityLevel) { - foreach ($similarRefRoutes[$similarityLevel] as $refRoute) { - if ($refRoute === $commonRoute) { - continue; - } - $stillMissingMethod = $this->compareMethods($refRoute, $commonRoute, $testedRoutes, $missingMethods); - $foundMethods = array_diff($missingMethods, $stillMissingMethod); - if (!empty($foundMethods)) { - foreach ($foundMethods as $foundMethod) { - if ('highly' === $similarityLevel) { - echo "\t$refRoute has $foundMethod and is highly similar to $commonRoute\n"; - } else { - echo "\t$refRoute has $foundMethod and is a bit similar to $commonRoute\n"; + if (false !== strpos($commonRoute, '{')) { + if (self::TEST_REFERENCE_ROUTES & $testedRoutes && $missingMethods[self::REF_METHOD_NOT_IN_CONF]) { + // Check reference route's methods not found in the configuration against similar routes from configuration + $similarConfRoutes = $this->getSimilarRoutes($commonRoute, $confRoutes); + foreach (['highly', 'poorly'] as $similarityLevel) { + foreach ($similarConfRoutes[$similarityLevel] as $confRoute) { + if ($confRoute === $commonRoute) { + continue; + } + $stillMissingMethod = $this->compareMethods($commonRoute, $confRoute, self::TEST_REFERENCE_ROUTES, $missingMethods[self::REF_METHOD_NOT_IN_CONF]); + $foundMethods = array_diff($missingMethods[self::REF_METHOD_NOT_IN_CONF], $stillMissingMethod[self::REF_METHOD_NOT_IN_CONF]); + if (!empty($foundMethods)) { + foreach ($foundMethods as $foundMethod) { + if ('highly' === $similarityLevel) { + $this->output("\t{$this->getConfRoutePrompt($confRoute)} has $foundMethod and is highly similar to $commonRoute"); + } else { + $this->output("\t{$this->getConfRoutePrompt($confRoute)} has $foundMethod and is a bit similar to $commonRoute"); + } } } } } - foreach ($similarConfRoutes[$similarityLevel] as $confRoute) { - if ($confRoute === $commonRoute) { - continue; - } - $stillMissingMethod = $this->compareMethods($commonRoute, $confRoute, $testedRoutes, $missingMethods); - $foundMethods = array_diff($missingMethods, $stillMissingMethod); - if (!empty($foundMethods)) { - foreach ($foundMethods as $foundMethod) { - if ('highly' === $similarityLevel) { - echo "\t{$this->getConfRoutePrompt($confRoute)} has $foundMethod and is highly similar to $commonRoute\n"; - } else { - echo "\t{$this->getConfRoutePrompt($confRoute)} has $foundMethod and is a bit similar to $commonRoute\n"; + } + if (self::TEST_CONFIG_ROUTES & $testedRoutes) { + // Check configuration route's methods not found in the reference against similar routes from reference + $similarRefRoutes = $this->getSimilarRoutes($commonRoute, $refRoutes); + foreach (['highly', 'poorly'] as $similarityLevel) { + foreach ($similarRefRoutes[$similarityLevel] as $refRoute) { + if ($refRoute === $commonRoute) { + continue; + } + $stillMissingMethod = $this->compareMethods($refRoute, $commonRoute, self::TEST_CONFIG_ROUTES, $missingMethods[self::CONF_METHOD_NOT_IN_REF]); + $foundMethods = array_diff($missingMethods[self::CONF_METHOD_NOT_IN_REF], $stillMissingMethod[self::CONF_METHOD_NOT_IN_REF]); + if (!empty($foundMethods)) { + foreach ($foundMethods as $foundMethod) { + if ('highly' === $similarityLevel) { + $this->output("\t$refRoute has $foundMethod and is highly similar to $commonRoute"); + } else { + $this->output("\t$refRoute has $foundMethod and is a bit similar to $commonRoute"); + } } } } @@ -218,76 +249,92 @@ public function run(int $testedRoutes = self::TEST_ALL_ROUTES) } if (self::TEST_REFERENCE_ROUTES & $testedRoutes) { + // Check reference routes not found in the configuration foreach (array_diff(array_keys($refRoutes), array_keys($confRoutes)) as $refRouteWithoutConf) { + $this->output("$refRouteWithoutConf not found in config files."); if (false !== strpos($refRouteWithoutConf, '{')) { $similarConfRoutes = $this->getSimilarRoutes($refRouteWithoutConf, $confRoutes); if (!empty($similarConfRoutes['highly'])) { - echo "$refRouteWithoutConf not found in config files but\n"; foreach ($similarConfRoutes['highly'] as $confRoute) { - echo "\t$refRouteWithoutConf is highly similar to $confRoute\n"; - $this->compareMethods($refRouteWithoutConf, $confRoute, $testedRoutes); + $this->output("\t$refRouteWithoutConf is highly similar to $confRoute"); + $this->compareMethods($refRouteWithoutConf, $confRoute, self::TEST_REFERENCE_ROUTES); } continue; } if (!empty($similarConfRoutes['poorly'])) { - echo "$refRouteWithoutConf not found in config files but\n"; foreach ($similarConfRoutes['poorly'] as $confRoute) { - echo "\t$refRouteWithoutConf is a bit similar to $confRoute\n"; - $this->compareMethods($refRouteWithoutConf, $confRoute, $testedRoutes); + $this->output("\t$refRouteWithoutConf is a bit similar to $confRoute"); + $this->compareMethods($refRouteWithoutConf, $confRoute, self::TEST_REFERENCE_ROUTES); } - continue; } } - echo "$refRouteWithoutConf not found in config files.\n"; } } if (self::TEST_CONFIG_ROUTES & $testedRoutes) { + // Check configuration routes not found in the reference foreach (array_diff(array_keys($confRoutes), array_keys($refRoutes)) as $confRouteWithoutRef) { + $this->output("{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference."); if (false !== strpos($confRouteWithoutRef, '{')) { $similarRefRoutes = $this->getSimilarRoutes($confRouteWithoutRef, $refRoutes); if (!empty($similarRefRoutes['highly'])) { - echo "{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference but\n"; foreach ($similarRefRoutes['highly'] as $refRoute) { - echo "\t$confRouteWithoutRef is highly similar to $refRoute\n"; - $this->compareMethods($refRoute, $confRouteWithoutRef, $testedRoutes); + $this->output("\t$confRouteWithoutRef is highly similar to $refRoute"); + $this->compareMethods($refRoute, $confRouteWithoutRef, self::TEST_CONFIG_ROUTES); } continue; } if (!empty($similarRefRoutes['poorly'])) { - echo "{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference but\n"; foreach ($similarRefRoutes['poorly'] as $refRoute) { - echo "\t$confRouteWithoutRef is a bit similar to $refRoute\n"; - $this->compareMethods($refRoute, $confRouteWithoutRef, $testedRoutes); + $this->output("\t$confRouteWithoutRef is a bit similar to $refRoute"); + $this->compareMethods($refRoute, $confRouteWithoutRef, self::TEST_CONFIG_ROUTES); } - continue; } } - echo "{$this->getConfRoutePrompt($confRouteWithoutRef)} not found in reference.\n"; } } } - private function compareMethods(string $refRoute, string $confRoute, int $testedRoutes = self::TEST_ALL_ROUTES, ?array $testedMethods = null): array + /** + * Compare reference route methods and configuration route methods, output methods missing on one side or the other. + * @param array|null $testedMethods A list of methods to search for and compare; if null, all existing methods are compared + * @return array A list of missing methods + */ + private + function compareMethods(string $refRoute, string $confRoute, int $testedRoutes = self::TEST_ALL_ROUTES, ?array $testedMethods = null): array { $refRoutes = $this->refRoutes; $confRoutes = $this->confRoutes; - $missingMethods = []; + $missingMethods = [ + self::REF_METHOD_NOT_IN_CONF => [], + self::CONF_METHOD_NOT_IN_REF => [], + + ]; if (self::TEST_REFERENCE_ROUTES & $testedRoutes) { + // Check reference route's methods missing from configuration route foreach (array_diff(array_keys($refRoutes[$refRoute]['methods']), array_keys($confRoutes[$confRoute]['methods'])) as $refMethodWithoutConf) { if (null === $testedMethods || in_array($refMethodWithoutConf, $testedMethods)) { - echo "$refRoute: $refMethodWithoutConf method not found in conf files" . ($refRoute === $confRoute ? '' : " (while comparing to $confRoute)") . ".\n"; - $missingMethods[] = $refMethodWithoutConf; + if ($refRoute === $confRoute) { + $this->output("$refRoute: $refMethodWithoutConf not found in configuration."); + } else { + $this->output("\t$refMethodWithoutConf not found in configuration while comparing to $confRoute."); + } + $missingMethods[self::REF_METHOD_NOT_IN_CONF][] = $refMethodWithoutConf; } } } if (self::TEST_CONFIG_ROUTES & $testedRoutes) { + // Check configuration route's methods missing from reference route foreach (array_diff(array_keys($confRoutes[$confRoute]['methods']), array_keys($refRoutes[$refRoute]['methods'])) as $confMethodWithoutRef) { if (null === $testedMethods || in_array($confMethodWithoutRef, $testedMethods)) { - echo "{$this->getConfRoutePrompt($confRoute, $confMethodWithoutRef)}: $confMethodWithoutRef not found in reference" . ($refRoute === $confRoute ? '' : " (while comparing to $refRoute)") . ".\n"; - $missingMethods[] = $confMethodWithoutRef; + if ($refRoute === $confRoute) { + $this->output("{$this->getConfRoutePrompt($confRoute, $confMethodWithoutRef)}: $confMethodWithoutRef not found in reference."); + } else { + $this->output("\t$confMethodWithoutRef not found in reference while comparing to $refRoute."); + } + $missingMethods[self::CONF_METHOD_NOT_IN_REF][] = $confMethodWithoutRef; } } } @@ -295,7 +342,8 @@ private function compareMethods(string $refRoute, string $confRoute, int $tested return $missingMethods; } - private function getSimilarRoutes(string $path, array $routeCollection): array + private + function getSimilarRoutes(string $path, array $routeCollection): array { $routePattern = $this->getRoutePattern($path); $highlySimilarRoutes = []; @@ -315,17 +363,20 @@ private function getSimilarRoutes(string $path, array $routeCollection): array ]; } - private function getSimplifiedRoute(string $path): string + private + function getSimplifiedRoute(string $path): string { return str_replace(['identifier', 'number', '_', '-'], ['id', 'no', ''], strtolower($path)); } - private function getRoutePattern(string $path): string + private + function getRoutePattern(string $path): string { return '@^' . preg_replace('@\{[^}]+\}@', '\{[^}]+\}', $path) . '$@'; } - private function getConfRoutePrompt(string $path, $method = null): string + private + function getConfRoutePrompt(string $path, $method = null): string { $prompt = $path; @@ -369,4 +420,14 @@ private function getConfRoutePrompt(string $path, $method = null): string return $prompt; } + + private + function output($message) + { + if ($this->output) { + $this->output->writeln($message); + } else { + echo strip_tags($message) . "\n"; + } + } } From 44fa8661e7bf37ba6cc6c050157129eca6caa9b1 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Wed, 4 Jan 2023 17:05:19 +0100 Subject: [PATCH 05/35] TestCommand.php: clean-up --- tools/raml2html/src/Command/TestCommand.php | 22 -------------------- tools/raml2html/src/Test/ReferenceTester.php | 6 +++--- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/tools/raml2html/src/Command/TestCommand.php b/tools/raml2html/src/Command/TestCommand.php index d6bbd8bd92..1399bf4449 100644 --- a/tools/raml2html/src/Command/TestCommand.php +++ b/tools/raml2html/src/Command/TestCommand.php @@ -6,7 +6,6 @@ use EzSystems\Raml2Html\Test\ReferenceTester; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -68,25 +67,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - - private function createParserConfigurationFromInput(InputInterface $input): ParserConfiguration - { - $configuration = new ParserConfiguration(); - - $nonStandardHTTPMethods = $input->getOption(self::OPTION_NON_STANDARD_HTTP_METHODS); - if ($nonStandardHTTPMethods !== null) { - $configuration->setNonStandardHttpMethods(explode(',', $nonStandardHTTPMethods)); - } - - return $configuration; - } - - private function createGeneratorOptionsFromInput(InputInterface $input): GeneratorOptions - { - $generatorOptions = new GeneratorOptions(); - $generatorOptions->setOutputDir($input->getOption(self::OPTION_OUTPUT_DIR)); - $generatorOptions->setTheme($input->getOption(self::OPTION_THEME)); - - return $generatorOptions; - } } diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index efd2426062..056cc57039 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -2,7 +2,7 @@ namespace EzSystems\Raml2Html\Test; -use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\OutputInterface; class ReferenceTester { @@ -47,10 +47,10 @@ class ReferenceTester private $refRoutes; private $confRoutes; - /** @var Output */ + /** @var OutputInterface */ private $output; - public function __construct($restApiReference, $dxpRoot, $consolePath = 'bin/console', $routingFiles = null, Output $output = null) + public function __construct($restApiReference, $dxpRoot, $consolePath = 'bin/console', $routingFiles = null, OutputInterface $output = null) { if (!is_file($restApiReference)) { user_error("$restApiReference doesn't exist or is not a file", E_USER_ERROR); From d22ef9bbfa570f98187d899bbfda327415a3a52b Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Wed, 4 Jan 2023 17:07:38 +0100 Subject: [PATCH 06/35] ReferenceTester.php: Fix DOMElement namespace --- tools/raml2html/src/Test/ReferenceTester.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 056cc57039..68077a3a94 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -80,7 +80,7 @@ private function parseApiReference($restApiReference): void $restApiRefDoc->loadHTMLFile($restApiReference, LIBXML_NOERROR); $restApiRefXpath = new \DOMXpath($restApiRefDoc); - /** @var DOMElement $urlElement */ + /** @var \DOMElement $urlElement */ foreach ($restApiRefXpath->query('//*[@data-field="url"]') as $urlElement) { if (!array_key_exists($urlElement->nodeValue, $refRoutes)) { $refRoutes[$urlElement->nodeValue] = [ From 97bc053b1d1e6079adda82eb061424d5c75e20cb Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 19 Jan 2023 15:24:06 +0100 Subject: [PATCH 07/35] update the REST API Reference tester for 4.4.0-beta1 --- tools/raml2html/src/Test/ReferenceTester.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 68077a3a94..b27a415a3b 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -12,14 +12,16 @@ class ReferenceTester public const DEFAULT_FILE_LIST = [ 'vendor/ibexa/rest/src/bundle/Resources/config/routing.yml', - 'vendor/ibexa/commerce-rest/src/bundle/Resources/config/routing.yaml', + //'vendor/ibexa/commerce-rest/src/bundle/Resources/config/routing.yaml', // Removed as of 4.4 // `find $dxpRoot/vendor/ibexa -name "routing_rest.y*ml"` //'vendor/ibexa/admin-ui/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/calendar/src/bundle/Resources/config/routing_rest.yaml', + 'vendor/ibexa/cart/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', - //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 + 'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 + 'vendor/ibexa/segmentation/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/taxonomy/src/bundle/Resources/config/routing_rest.yaml', ]; From 227d9ca7912028bfd9d731b8b73eda4cebb1e105 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Mon, 23 Jan 2023 17:09:31 +0100 Subject: [PATCH 08/35] ReferenceTester.php: Fix exclusion of /api/datebasedpublisher/v1 --- tools/raml2html/src/Test/ReferenceTester.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index b27a415a3b..c0faf9e761 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -20,7 +20,7 @@ class ReferenceTester 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', - 'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 + //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 'vendor/ibexa/segmentation/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/taxonomy/src/bundle/Resources/config/routing_rest.yaml', ]; From 909d0ba1d4564099c2b528f7faf2b00a03833857 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Tue, 24 Jan 2023 09:39:18 +0100 Subject: [PATCH 09/35] ReferenceTester.php: Also test (deprecated) and (removed) flags --- tools/raml2html/src/Test/ReferenceTester.php | 59 +++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index c0faf9e761..27ace1b6d4 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -79,17 +79,53 @@ private function parseApiReference($restApiReference): void $refRoutes = []; $restApiRefDoc = new \DOMDocument(); + $restApiRefDoc->preserveWhiteSpace = false; $restApiRefDoc->loadHTMLFile($restApiReference, LIBXML_NOERROR); $restApiRefXpath = new \DOMXpath($restApiRefDoc); /** @var \DOMElement $urlElement */ foreach ($restApiRefXpath->query('//*[@data-field="url"]') as $urlElement) { - if (!array_key_exists($urlElement->nodeValue, $refRoutes)) { - $refRoutes[$urlElement->nodeValue] = [ + $route = $urlElement->nodeValue; + if (!array_key_exists($route, $refRoutes)) { + $refRoutes[$route] = [ 'methods' => [], ]; } - $refRoutes[$urlElement->nodeValue]['methods'][$urlElement->previousSibling->previousSibling->nodeValue] = true; + $method = $urlElement->previousSibling->previousSibling->nodeValue; + $displayName = trim(str_replace('¶', '', $urlElement->parentNode->parentNode->previousSibling->previousSibling->nodeValue)); + $removed = '(removed)' === substr($displayName, -strlen('(removed)')); + $deprecated = '(deprecated)' === substr($displayName, -strlen('(deprecated)')); + $substitute = null; + if ($removed || $deprecated) { + $matches = []; + if (preg_match('/use (?[A-Z]+) (?[{}\/a-zA-Z]+) instead./', $urlElement->parentNode->nextSibling->nextSibling->nodeValue, $matches)) { + $substitute = array_intersect_key($matches, array_flip(array('method', 'route'))); + if (!array_key_exists($substitute['route'], $refRoutes)) { + $refRoutes[$substitute['route']] = [ + 'methods' => [], + ]; + } + if (!array_key_exists($substitute['method'], $refRoutes[$substitute['route']]['methods'])) { + $refRoutes[$substitute['route']]['methods'][$substitute['method']] = [ + 'replace' => [], + ]; + } + $refRoutes[$substitute['route']]['methods'][$substitute['method']]['replace'][] = [ + 'method' => $method, + 'route' => $route, + ]; + } + } + $replace = []; + if (array_key_exists($method, $refRoutes[$route]['methods'])) { + $replace = $refRoutes[$route]['methods'][$method]['replace']; + } + $refRoutes[$route]['methods'][$method] = [ + 'removed' => $removed, + 'deprecated' => $deprecated, + 'substitute' => $substitute, + 'replace' => $replace, + ]; } $this->refRoutes = $refRoutes; @@ -268,6 +304,23 @@ public function run(int $testedRoutes = self::TEST_ALL_ROUTES) $this->output("\t$refRouteWithoutConf is a bit similar to $confRoute"); $this->compareMethods($refRouteWithoutConf, $confRoute, self::TEST_REFERENCE_ROUTES); } + continue; + } + } + foreach ($refRoutes[$refRouteWithoutConf]['methods'] as $method=>$methodStatus) { + if ($methodStatus['removed']) { + $this->output("\t$method $refRouteWithoutConf is flagged as removed"); + } else if ($methodStatus['deprecated']) { + $this->output("\t$method $refRouteWithoutConf is flagged as deprecated and can now be flagged as removed"); + } else { + $this->output("\t$method $refRouteWithoutConf is not flagged."); + } + if ($methodStatus['removed'] || $methodStatus['deprecated']) { + if ($methodStatus['substitute']) { + $this->output("\tand the substitute {$methodStatus['substitute']['method']} {$methodStatus['substitute']['route']} is proposed."); + } else { + $this->output("\twithout substitute proposal."); + } } } } From 17a369109cbcab0e83212ff624e455aab057e063 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 9 Feb 2023 11:31:31 +0100 Subject: [PATCH 10/35] Upgrade raml2html/composer.json to PHP 7.4 --- tools/raml2html/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index ec8691fdce..1ad0fb1f63 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -4,7 +4,7 @@ Tool for generating static HTML from RAML definitions. ## Installation -PHP 7.2 and [Composer](https://getcomposer.org/) are required. +PHP 7.4 and [Composer](https://getcomposer.org/) are required. To install required dependencies, go to raml2html directory and run: From 52cffaa6cc6e294d3162ba9a001ca75e5f3d2a4b Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 16 Feb 2023 15:37:49 +0100 Subject: [PATCH 11/35] ReferenceTester.php: Use extension's yaml_parse_file In case of missing YAML library, the error message won't be the same. Before, it pretended the use of `undefined function EzSystems\Raml2Html\Test\yaml_parse_file()`. After, it notifies the use of `undefined function yaml_parse_file()`. --- tools/raml2html/src/Test/ReferenceTester.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 27ace1b6d4..45499210ca 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -198,7 +198,7 @@ private function parseRoutingFiles($routingFiles): void user_error("$routingFilePath doesn't exist or is not a file", E_USER_WARNING); continue; } - $parsedRoutingFiles[$routingFile] = yaml_parse_file($routingFilePath); + $parsedRoutingFiles[$routingFile] = \yaml_parse_file($routingFilePath); } foreach ($parsedRoutingFiles as $routingFile => $parsedRoutingFile) { From 7c3feeeb35ba9cb561a131f597cb268bf23cbded Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 16 Feb 2023 15:40:17 +0100 Subject: [PATCH 12/35] composer.json: add required PHP extensions --- tools/raml2html/composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/raml2html/composer.json b/tools/raml2html/composer.json index adc9a1f5b3..9f819a97a9 100644 --- a/tools/raml2html/composer.json +++ b/tools/raml2html/composer.json @@ -16,6 +16,10 @@ ], "require": { "php": "^7.4", + "ext-json": "*", + "ext-yaml": "*", + "ext-dom": "*", + "ext-libxml": "*", "symfony/console": "^4.2", "raml-org/raml-php-parser": "dev-master", "twig/twig": "^2.0", From 0d4e4e5d8091b0ad8d049fec33f280840fcacb4c Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 16 Feb 2023 15:40:43 +0100 Subject: [PATCH 13/35] README.md: Update to PHP 7.4 --- tools/raml2html/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index 1ad0fb1f63..f15ba34fc3 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -14,7 +14,7 @@ composer install; ## Usage -Note: If PHP 7.2 is not the default PHP, please, adapt. +Note: If PHP 7.4 is not the default PHP, please, adapt. To generate static HTML from RAML definitions, use the following code from project root: @@ -28,7 +28,7 @@ To test static HTML against an Ibexa DXP to find route removed and missing route php tools/raml2html/raml2html.php test docs/api/rest_api/rest_api_reference/rest_api_reference.html ~/ibexa-dxp ``` -Note: The Ibexa DXP doesn't need to run +Note: The Ibexa DXP doesn't need to run. ```shell mkdir ~/ibexa-dxp; From ae740b945f73708d97c8f858a0d1b9d01fa908d3 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 16 Feb 2023 15:43:44 +0100 Subject: [PATCH 14/35] raml2html/README.md: Remove late note about PHP 7.4 --- tools/raml2html/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index f15ba34fc3..dfea9164d6 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -14,8 +14,6 @@ composer install; ## Usage -Note: If PHP 7.4 is not the default PHP, please, adapt. - To generate static HTML from RAML definitions, use the following code from project root: ```sh From d58efc8f6c3a9da9dc061dc177bd7be35ab6c6c7 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:48:06 +0100 Subject: [PATCH 15/35] Update tools/raml2html/src/Command/TestCommand.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Niedzielski --- tools/raml2html/src/Command/TestCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/src/Command/TestCommand.php b/tools/raml2html/src/Command/TestCommand.php index 1399bf4449..641ca2dd68 100644 --- a/tools/raml2html/src/Command/TestCommand.php +++ b/tools/raml2html/src/Command/TestCommand.php @@ -57,7 +57,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $referenceTester = new ReferenceTester($restApiReference, $dxpRoot, $consolePath, $routingFiles, $output); - $testedRoutes = @[ + $testedRoutes = [ 'ref' => ReferenceTester::TEST_REFERENCE_ROUTES, 'conf' => ReferenceTester::TEST_CONFIG_ROUTES, 'both' => ReferenceTester::TEST_ALL_ROUTES, From adb0644d49fdd3a1dd6164cc0bb4ab5852cca639 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Thu, 16 Feb 2023 16:21:22 +0100 Subject: [PATCH 16/35] ReferenceTester.php: Remove path testing already done by TestCommand If the TestCommand doesn't do its job, let the ReferenceTester crash. --- tools/raml2html/src/Test/ReferenceTester.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 45499210ca..6f62a0399d 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -54,18 +54,6 @@ class ReferenceTester public function __construct($restApiReference, $dxpRoot, $consolePath = 'bin/console', $routingFiles = null, OutputInterface $output = null) { - if (!is_file($restApiReference)) { - user_error("$restApiReference doesn't exist or is not a file", E_USER_ERROR); - exit(1); - } - if ('~' === $dxpRoot[0]) { - $dxpRoot = trim(shell_exec("echo $dxpRoot")); - } - if (!is_dir($dxpRoot)) { - user_error("$dxpRoot doesn't exist or is not a directory", E_USER_ERROR); - exit(2); - } - $this->output = $output; $this->restApiReference = $restApiReference; From 6d91570f2fc0bcafbfa178c834a72b3b44dc3f9b Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Mon, 3 Jul 2023 12:51:17 +0200 Subject: [PATCH 17/35] raml2html TestCommand: set a default path to reference --- tools/raml2html/README.md | 2 +- tools/raml2html/src/Command/TestCommand.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index dfea9164d6..66b1c4c2f5 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -34,5 +34,5 @@ cd ~/ibexa-dxp; composer create-project ibexa/commerce-skeleton . --no-install --ignore-platform-reqs --no-scripts; composer install --ignore-platform-reqs --no-scripts; cd -; -php tools/raml2html/raml2html.php test docs/api/rest_api/rest_api_reference/rest_api_reference.html ~/ibexa-dxp +php tools/raml2html/raml2html.php test ~/ibexa-dxp ``` diff --git a/tools/raml2html/src/Command/TestCommand.php b/tools/raml2html/src/Command/TestCommand.php index 641ca2dd68..5054053d80 100644 --- a/tools/raml2html/src/Command/TestCommand.php +++ b/tools/raml2html/src/Command/TestCommand.php @@ -21,8 +21,8 @@ protected function configure(): void $this->setName('test') ->setDescription('Compare REST API Reference documentation with Ibexa DXP routing configuration under /api/ibexa/v2 prefix') ->setHelp('It is recommended to not use --console-path and --routing-file options while testing the Rest API Reference HTML file against configuration. Those options are used to test that the default configuration file list is up-to-date and other subtleties.') - ->addArgument('rest-api-reference', InputArgument::REQUIRED, 'Path to the REST API Reference HTML file') ->addArgument('ibexa-dxp-root', InputArgument::REQUIRED, 'Path to an Ibexa DXP root directory') + ->addArgument('rest-api-reference', InputArgument::OPTIONAL, 'Path to the REST API Reference HTML file', 'docs/api/rest_api/rest_api_reference/rest_api_reference.html') ->addOption('console-path', 'c', InputOption::VALUE_OPTIONAL, 'Path to the console relative to Ibexa DXP root directory (if no value, use `bin/console`)', false) ->addOption('routing-file', 'f', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Path to a routing configuration YAML file relative to Ibexa DXP root directory', ReferenceTester::DEFAULT_FILE_LIST) ->addOption('tested-routes', 't', InputOption::VALUE_OPTIONAL, From b7da863ec2d6656c215fef9dc86e25075371fd08 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Mon, 3 Jul 2023 12:51:50 +0200 Subject: [PATCH 18/35] ReferenceTester.php: Update route config files for 4.5 --- tools/raml2html/src/Test/ReferenceTester.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 6f62a0399d..6d9b0cbb59 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -13,11 +13,17 @@ class ReferenceTester public const DEFAULT_FILE_LIST = [ 'vendor/ibexa/rest/src/bundle/Resources/config/routing.yml', //'vendor/ibexa/commerce-rest/src/bundle/Resources/config/routing.yaml', // Removed as of 4.4 - // `find $dxpRoot/vendor/ibexa -name "routing_rest.y*ml"` + // `find vendor/ibexa -name "routing_rest.y*ml" | sort` //'vendor/ibexa/admin-ui/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/calendar/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/cart/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', + //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // Import the 3 following files + 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/companies.yaml', + 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/members.yaml', + 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/root.yaml', + 'vendor/ibexa/order-management/src/bundle/Resources/config/routing_rest.yaml', + 'vendor/ibexa/payment/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 @@ -192,6 +198,10 @@ private function parseRoutingFiles($routingFiles): void foreach ($parsedRoutingFiles as $routingFile => $parsedRoutingFile) { foreach ($parsedRoutingFile as $routeId => $routeDef) { $line = (int)explode(':', `grep -n '^$routeId:$' {$this->dxpRoot}/$routingFile`)[0]; + if (!array_key_exists('path', $routeDef) && array_key_exists('resource', $routeDef)) { + user_error("$routeId in $routingFile imports another file {$routeDef['resource']}", E_USER_WARNING); + continue; + } if (!array_key_exists('methods', $routeDef)) { $routeDef['methods'] = self::METHOD_LIST; } From 9b34b695a88d2811558701e5d597b3990d2a816b Mon Sep 17 00:00:00 2001 From: Adrien Dupuis Date: Tue, 11 Jul 2023 11:20:50 +0200 Subject: [PATCH 19/35] Update raml2html/README.md --- tools/raml2html/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index 645d6d52d9..906eb428a7 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -4,7 +4,7 @@ Tool for generating static HTML from RAML definitions. ## Installation -PHP 7.4 and [Composer](https://getcomposer.org/) are required. +PHP 8 and [Composer](https://getcomposer.org/) are required. To install required dependencies, go to raml2html directory and run: From cb66155bd63fec9d7b6b4eda30dd1645f1c080dc Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:59:55 +0200 Subject: [PATCH 20/35] TestTypeUsageCommand.php --- tools/raml2html/src/Application.php | 2 + .../src/Command/TestTypeUsageCommand.php | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 tools/raml2html/src/Command/TestTypeUsageCommand.php diff --git a/tools/raml2html/src/Application.php b/tools/raml2html/src/Application.php index 87fb3051cf..c05478a499 100644 --- a/tools/raml2html/src/Application.php +++ b/tools/raml2html/src/Application.php @@ -5,6 +5,7 @@ namespace EzSystems\Raml2Html; use EzSystems\Raml2Html\Command\BuildCommand; +use EzSystems\Raml2Html\Command\TestTypeUsageCommand; use EzSystems\Raml2Html\Command\ClearCacheCommand; use EzSystems\Raml2Html\Command\LintTypesCommand; use EzSystems\Raml2Html\Command\TestCommand; @@ -37,6 +38,7 @@ protected function getDefaultCommands(): array $this->getGenerator(), $this->getRamlParserFactory() ), + new TestTypeUsageCommand(), new LintTypesCommand( $this->getRamlParserFactory() ), diff --git a/tools/raml2html/src/Command/TestTypeUsageCommand.php b/tools/raml2html/src/Command/TestTypeUsageCommand.php new file mode 100644 index 0000000000..db2e571fd6 --- /dev/null +++ b/tools/raml2html/src/Command/TestTypeUsageCommand.php @@ -0,0 +1,78 @@ +setName('test:type:usage') + ->setDescription('Check that types are used') + ->setHelp('Parse ibexa-types.raml and check if each type is used.'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $definedTypes = yaml_parse_file('docs/api//rest_api/rest_api_reference/input/ibexa-types.raml'); + $usedByRouteTypeList = []; + $usedByOtherTypeTypeList = []; + + foreach ($definedTypes as $type => $typeDefinition) { + $usageFileList = shell_exec("grep 'type: $type' -R docs/api/rest_api/rest_api_reference/input | grep -v examples | grep -v ibexa-types.raml"); + if (!empty($usageFileList)) { + $usedByRouteTypeList[] = $type; + } + } + + foreach ($usedByRouteTypeList as $usedByRouteType) { + $definedType = $definedTypes[$usedByRouteType]; + $usedType = str_ends_with($definedType['type'], '[]') ? substr($definedType['type'], 0, -2) : $definedType['type']; + if (!in_array($usedType, $usedByOtherTypeTypeList)) { + $usedByOtherTypeTypeList[] = $usedType; + } + if (array_key_exists('properties', $definedType)) { + foreach ($definedType['properties'] as $property => $propertyDefinition) { + if (is_array($propertyDefinition)) { + if (array_key_exists('type', $propertyDefinition)) { + if ('array' === $propertyDefinition['type']) { + $usedType = $propertyDefinition['items']; + } elseif (str_ends_with($propertyDefinition['type'], '[]')) { + $usedType = substr($propertyDefinition['type'], 0, -2); + } else { + $usedType = $propertyDefinition['type']; + } + } + } else { + if (str_ends_with($propertyDefinition, '[]')) { + $usedType = substr($propertyDefinition, 0, -2); + } else { + $usedType = $propertyDefinition; + } + } + if (!in_array($usedType, $usedByOtherTypeTypeList)) { + $usedByOtherTypeTypeList[] = $usedType; + } + } + } + } + + $usedTypeList = array_merge($usedByRouteTypeList, $usedByOtherTypeTypeList); + $unusedTypeList = array_diff(array_keys($definedTypes), $usedTypeList); + + dump($unusedTypeList); + + return 0; + } +} From e636c0f9bf065f83fda4eda6282d7ce60c977c22 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Mon, 22 Apr 2024 20:00:57 +0200 Subject: [PATCH 21/35] ReferenceTester.php: Update conf file list --- tools/raml2html/src/Test/ReferenceTester.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 6d9b0cbb59..5241035670 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -12,22 +12,26 @@ class ReferenceTester public const DEFAULT_FILE_LIST = [ 'vendor/ibexa/rest/src/bundle/Resources/config/routing.yml', - //'vendor/ibexa/commerce-rest/src/bundle/Resources/config/routing.yaml', // Removed as of 4.4 - // `find vendor/ibexa -name "routing_rest.y*ml" | sort` + // `find vendor/ibexa -name "*rest*.yaml" -and -wholename "*rout*" -and -not -wholename "*test*" | sort` + 'vendor/ibexa/activity-log/src/bundle/Resources/config/routing/rest.yaml', //'vendor/ibexa/admin-ui/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/calendar/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/cart/src/bundle/Resources/config/routing_rest.yaml', + 'vendor/ibexa/connect/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', - //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // Import the 3 following files + //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // Import the 4 following files 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/companies.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/members.yaml', + 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/sales_representatives.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/root.yaml', + 'vendor/ibexa/fieldtype-query/src/bundle/Resources/config/routing/rest.yaml', 'vendor/ibexa/order-management/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/payment/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 'vendor/ibexa/segmentation/src/bundle/Resources/config/routing_rest.yaml', + 'vendor/ibexa/shipping/src/bundle/Resources/config/routing/rest.yaml', 'vendor/ibexa/taxonomy/src/bundle/Resources/config/routing_rest.yaml', ]; From dee808395048453a4ea6f9a67941eaa89c65337a Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Mon, 22 Apr 2024 20:02:04 +0200 Subject: [PATCH 22/35] =?UTF-8?q?TestCommand.php=20=E2=86=92=20TestCompare?= =?UTF-8?q?Command.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/raml2html/src/Application.php | 4 ++-- .../src/Command/{TestCommand.php => TestCompareCommand.php} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename tools/raml2html/src/Command/{TestCommand.php => TestCompareCommand.php} (96%) diff --git a/tools/raml2html/src/Application.php b/tools/raml2html/src/Application.php index c05478a499..a0d85d75b7 100644 --- a/tools/raml2html/src/Application.php +++ b/tools/raml2html/src/Application.php @@ -8,7 +8,7 @@ use EzSystems\Raml2Html\Command\TestTypeUsageCommand; use EzSystems\Raml2Html\Command\ClearCacheCommand; use EzSystems\Raml2Html\Command\LintTypesCommand; -use EzSystems\Raml2Html\Command\TestCommand; +use EzSystems\Raml2Html\Command\TestCompareCommand; use EzSystems\Raml2Html\Generator\Generator; use EzSystems\Raml2Html\RAML\ParserFactory; use EzSystems\Raml2Html\Twig\Extension\HashExtension; @@ -43,7 +43,7 @@ protected function getDefaultCommands(): array $this->getRamlParserFactory() ), new ClearCacheCommand(self::CACHE_DIR), - new TestCommand(), + new TestCompareCommand(), ]); } diff --git a/tools/raml2html/src/Command/TestCommand.php b/tools/raml2html/src/Command/TestCompareCommand.php similarity index 96% rename from tools/raml2html/src/Command/TestCommand.php rename to tools/raml2html/src/Command/TestCompareCommand.php index 5054053d80..bf5250a01d 100644 --- a/tools/raml2html/src/Command/TestCommand.php +++ b/tools/raml2html/src/Command/TestCompareCommand.php @@ -11,14 +11,14 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -final class TestCommand extends Command +final class TestCompareCommand extends Command { /** * {@inheritdoc} */ protected function configure(): void { - $this->setName('test') + $this->setName('test:compare') ->setDescription('Compare REST API Reference documentation with Ibexa DXP routing configuration under /api/ibexa/v2 prefix') ->setHelp('It is recommended to not use --console-path and --routing-file options while testing the Rest API Reference HTML file against configuration. Those options are used to test that the default configuration file list is up-to-date and other subtleties.') ->addArgument('ibexa-dxp-root', InputArgument::REQUIRED, 'Path to an Ibexa DXP root directory') @@ -65,6 +65,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $referenceTester->run($testedRoutes); - return 0; + return Command::SUCCESS; } } From 2a85d7d43c86c418eeb504ec875f40b511c15321 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:14:02 +0200 Subject: [PATCH 23/35] ReferenceTester.php: Sort prefixes --- tools/raml2html/src/Test/ReferenceTester.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 5241035670..f7994df5cb 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -13,13 +13,13 @@ class ReferenceTester public const DEFAULT_FILE_LIST = [ 'vendor/ibexa/rest/src/bundle/Resources/config/routing.yml', // `find vendor/ibexa -name "*rest*.yaml" -and -wholename "*rout*" -and -not -wholename "*test*" | sort` - 'vendor/ibexa/activity-log/src/bundle/Resources/config/routing/rest.yaml', + 'vendor/ibexa/activity-log/src/bundle/Resources/config/routing/rest.yaml', // directly prefixed /api/ibexa/v2 //'vendor/ibexa/admin-ui/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/calendar/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/cart/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connect/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', - //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // Import the 4 following files + //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // imports the 4 following files 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/companies.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/members.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/sales_representatives.yaml', @@ -27,7 +27,7 @@ class ReferenceTester 'vendor/ibexa/fieldtype-query/src/bundle/Resources/config/routing/rest.yaml', 'vendor/ibexa/order-management/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/payment/src/bundle/Resources/config/routing_rest.yaml', - 'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', + //'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', // prefixed /personalization/v1 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 'vendor/ibexa/segmentation/src/bundle/Resources/config/routing_rest.yaml', @@ -209,6 +209,9 @@ private function parseRoutingFiles($routingFiles): void if (!array_key_exists('methods', $routeDef)) { $routeDef['methods'] = self::METHOD_LIST; } + if (str_starts_with($routeDef['path'], $this->apiUri)) { + $routeDef['path'] = str_replace($this->apiUri, '', $routeDef['path']); + } if (!array_key_exists($routeDef['path'], $confRoutes)) { $confRoutes[$routeDef['path']] = [ 'methods' => [], From ee761d9420812de64198f7e2e52be639b632011a Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:20:05 +0200 Subject: [PATCH 24/35] ReferenceTester.php: Skip some prefixed routes --- tools/raml2html/src/Test/ReferenceTester.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index f7994df5cb..dae3aa0403 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -28,7 +28,7 @@ class ReferenceTester 'vendor/ibexa/order-management/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/payment/src/bundle/Resources/config/routing_rest.yaml', //'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', // prefixed /personalization/v1 - 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', + 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', // contains few /personalization/v1 //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 'vendor/ibexa/segmentation/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/shipping/src/bundle/Resources/config/routing/rest.yaml', @@ -50,6 +50,11 @@ class ReferenceTester public $apiUri = '/api/ibexa/v2'; + public $ignoredApiUris = [ + '/api/datebasedpublisher/v1', + '/personalization/v1', + ]; + private const REF_METHOD_NOT_IN_CONF = 'ref_route_method_missing_from_conf'; private const CONF_METHOD_NOT_IN_REF = 'conf_route_method_missing_from_ref'; @@ -209,6 +214,11 @@ private function parseRoutingFiles($routingFiles): void if (!array_key_exists('methods', $routeDef)) { $routeDef['methods'] = self::METHOD_LIST; } + foreach($this->ignoredApiUris as $ignoredApiUri) { + if (str_starts_with($routeDef['path'], $ignoredApiUri)) { + continue 2; + } + } if (str_starts_with($routeDef['path'], $this->apiUri)) { $routeDef['path'] = str_replace($this->apiUri, '', $routeDef['path']); } From 4239e12e68c39a93dbc8f0b486a265ad66c25103 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:57:35 +0200 Subject: [PATCH 25/35] REST TestLogicCommand --- tools/raml2html/src/Application.php | 6 +- .../src/Command/TestLogicCommand.php | 80 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tools/raml2html/src/Command/TestLogicCommand.php diff --git a/tools/raml2html/src/Application.php b/tools/raml2html/src/Application.php index a0d85d75b7..028e8b1c5b 100644 --- a/tools/raml2html/src/Application.php +++ b/tools/raml2html/src/Application.php @@ -5,10 +5,11 @@ namespace EzSystems\Raml2Html; use EzSystems\Raml2Html\Command\BuildCommand; -use EzSystems\Raml2Html\Command\TestTypeUsageCommand; use EzSystems\Raml2Html\Command\ClearCacheCommand; use EzSystems\Raml2Html\Command\LintTypesCommand; use EzSystems\Raml2Html\Command\TestCompareCommand; +use EzSystems\Raml2Html\Command\TestLogicCommand; +use EzSystems\Raml2Html\Command\TestTypeUsageCommand; use EzSystems\Raml2Html\Generator\Generator; use EzSystems\Raml2Html\RAML\ParserFactory; use EzSystems\Raml2Html\Twig\Extension\HashExtension; @@ -38,12 +39,13 @@ protected function getDefaultCommands(): array $this->getGenerator(), $this->getRamlParserFactory() ), - new TestTypeUsageCommand(), new LintTypesCommand( $this->getRamlParserFactory() ), new ClearCacheCommand(self::CACHE_DIR), new TestCompareCommand(), + new TestTypeUsageCommand(), + new TestLogicCommand(), ]); } diff --git a/tools/raml2html/src/Command/TestLogicCommand.php b/tools/raml2html/src/Command/TestLogicCommand.php new file mode 100644 index 0000000000..cf2f4a6b7b --- /dev/null +++ b/tools/raml2html/src/Command/TestLogicCommand.php @@ -0,0 +1,80 @@ +setName('test:logic') + ->setDescription('Check REST logic in RAML files') + ->addArgument('raml-input-dir', InputArgument::OPTIONAL, 'Path to the REST API Reference\'s RAML input directory', 'docs/api/rest_api/rest_api_reference/input') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $dir = $input->getArgument('raml-input-dir'); + $files = shell_exec("find $dir -type f -name '*.raml'"); + if (!empty($files)) { + foreach (explode(PHP_EOL, trim($files)) as $file) { + $output->writeln("$file"); + $definitions = yaml_parse_file($file); + if (is_array($definitions)) { + $this->checkDefinitions($file, yaml_parse_file($file), $output); + } + } + } + + return Command::SUCCESS; + } + + private function checkDefinitions(string $parent, array $definitions, OutputInterface $output) { + foreach($definitions as $key => $definition) { + if ('/' === $key[0]) { // $key is a route + if (is_array($definition)) { + $this->checkDefinitions($key, $definition, $output); + } + } + switch ($key) { // $key is a method + case 'get': + $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); + $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); + break; + case 'post': + case 'patch': + $this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output); + $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); + break; + case 'delete': + $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); + $this->checkUselessHeader($parent, $key, $definition, 'Accept', $output); + } + } + } + + private function checkUselessHeader($route, $method, $methodDefinition, $header, $output) { + if (array_key_exists('headers', $methodDefinition) && array_key_exists($header, $methodDefinition['headers'])) { + $output->writeln("$method $route doesn't need a '$header' header."); + } + } + + private function checkMandatoryHeader($route, $method, $methodDefinition, $header, $output) { + if (!array_key_exists('headers', $methodDefinition) || !array_key_exists($header, $methodDefinition['headers'])) { + $output->writeln("$method $route needs a '$header' header."); + } + } +} From 76e1aa9a9a6ceac5797b1c135cd1759b05f05a5c Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Wed, 24 Apr 2024 09:24:04 +0200 Subject: [PATCH 26/35] TestLogicCommand.php: Tune down the header checking --- tools/raml2html/src/Command/TestLogicCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/raml2html/src/Command/TestLogicCommand.php b/tools/raml2html/src/Command/TestLogicCommand.php index cf2f4a6b7b..5883065e19 100644 --- a/tools/raml2html/src/Command/TestLogicCommand.php +++ b/tools/raml2html/src/Command/TestLogicCommand.php @@ -55,13 +55,16 @@ private function checkDefinitions(string $parent, array $definitions, OutputInte $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); break; case 'post': + //$this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output);// POST may not have a payload like `POST /content/objects/{contentId}/hide` + //$this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output);// …and may return 204 No Content. + break; case 'patch': $this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output); $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); break; case 'delete': $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); - $this->checkUselessHeader($parent, $key, $definition, 'Accept', $output); + //$this->checkUselessHeader($parent, $key, $definition, 'Accept', $output);// Can return the updated "parent"/"container"/"list" } } } From 1e7e61f7077acd64e7b736ee53dac207a0831a0d Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:13:35 +0200 Subject: [PATCH 27/35] TestLogicCommand::checkAcceptHeaderAgainstBody --- .../src/Command/TestLogicCommand.php | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/tools/raml2html/src/Command/TestLogicCommand.php b/tools/raml2html/src/Command/TestLogicCommand.php index 5883065e19..88678fd143 100644 --- a/tools/raml2html/src/Command/TestLogicCommand.php +++ b/tools/raml2html/src/Command/TestLogicCommand.php @@ -5,8 +5,8 @@ namespace EzSystems\Raml2Html\Command; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; final class TestLogicCommand extends Command @@ -42,8 +42,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function checkDefinitions(string $parent, array $definitions, OutputInterface $output) { - foreach($definitions as $key => $definition) { + private function checkDefinitions(string $parent, array $definitions, OutputInterface $output) + { + foreach ($definitions as $key => $definition) { if ('/' === $key[0]) { // $key is a route if (is_array($definition)) { $this->checkDefinitions($key, $definition, $output); @@ -53,31 +54,55 @@ private function checkDefinitions(string $parent, array $definitions, OutputInte case 'get': $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); + $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); break; case 'post': //$this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output);// POST may not have a payload like `POST /content/objects/{contentId}/hide` //$this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output);// …and may return 204 No Content. + $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); break; case 'patch': $this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output); $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); - break; + $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); + break; case 'delete': $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); //$this->checkUselessHeader($parent, $key, $definition, 'Accept', $output);// Can return the updated "parent"/"container"/"list" + $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); + break; } } } - private function checkUselessHeader($route, $method, $methodDefinition, $header, $output) { + private function checkUselessHeader($route, $method, $methodDefinition, $header, $output) + { if (array_key_exists('headers', $methodDefinition) && array_key_exists($header, $methodDefinition['headers'])) { $output->writeln("$method $route doesn't need a '$header' header."); } } - private function checkMandatoryHeader($route, $method, $methodDefinition, $header, $output) { + private function checkMandatoryHeader($route, $method, $methodDefinition, $header, $output) + { if (!array_key_exists('headers', $methodDefinition) || !array_key_exists($header, $methodDefinition['headers'])) { $output->writeln("$method $route needs a '$header' header."); } } + + private function checkAcceptHeaderAgainstBody($route, $method, $methodDefinition, $output) + { + if (array_key_exists('headers', $methodDefinition) + && array_key_exists('Accept', $methodDefinition['headers']) + && array_key_exists('example', $methodDefinition['headers']['Accept']) + && array_key_exists('body', $methodDefinition)) { + $acceptedTypes = explode(PHP_EOL, trim($methodDefinition['headers']['Accept']['example'])); + $returnedTypes = array_keys($methodDefinition['body']); + $missingAcceptedType = array_diff($returnedTypes, $acceptedTypes); + $missingReturnedType = array_diff($acceptedTypes, $returnedTypes); + if (!empty($missingAcceptedType) || !empty($missingReturnedType)) { + $output->writeln("$method $route 'Accept' header and body doesn't contain the same types."); + //dump($missingAcceptedType, $missingReturnedType); + } + } + } } From e91a1eb9e548331c1fd1f96ee79fe84f6d96fdb3 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 25 Apr 2024 09:05:40 +0200 Subject: [PATCH 28/35] Enhance TestLogicCommand headers VS bodies checking --- .../src/Command/TestLogicCommand.php | 105 ++++++++++++++---- 1 file changed, 84 insertions(+), 21 deletions(-) diff --git a/tools/raml2html/src/Command/TestLogicCommand.php b/tools/raml2html/src/Command/TestLogicCommand.php index 88678fd143..cc47b0921c 100644 --- a/tools/raml2html/src/Command/TestLogicCommand.php +++ b/tools/raml2html/src/Command/TestLogicCommand.php @@ -52,56 +52,119 @@ private function checkDefinitions(string $parent, array $definitions, OutputInte } switch ($key) { // $key is a method case 'get': - $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); - $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); - $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); + $this->checkUselessRequestHeader($parent, $key, $definition, 'Content-Type', $output);// Used in rare case of GET with a body… + $this->checkUselessRequestBody($parent, $key, $definition, $output);// … + $this->checkMandatoryRequestHeader($parent, $key, $definition, 'Accept', $output); + $this->checkAcceptHeaderAgainstResponseBody($parent, $key, $definition, $output); break; case 'post': //$this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output);// POST may not have a payload like `POST /content/objects/{contentId}/hide` + //$this->checkUselessRequestBody($parent, $key, $definition, $output);// … //$this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output);// …and may return 204 No Content. - $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); + $this->checkAcceptHeaderAgainstResponseBody($parent, $key, $definition, $output); + $this->checkContentTypeHeaderAgainstRequestBody($parent, $key, $definition, $output); break; case 'patch': - $this->checkMandatoryHeader($parent, $key, $definition, 'Content-Type', $output); - $this->checkMandatoryHeader($parent, $key, $definition, 'Accept', $output); - $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); + $this->checkMandatoryRequestHeader($parent, $key, $definition, 'Content-Type', $output); + $this->checkMandatoryRequestBody($parent, $key, $definition, $output); + $this->checkMandatoryRequestHeader($parent, $key, $definition, 'Accept', $output); + $this->checkAcceptHeaderAgainstResponseBody($parent, $key, $definition, $output); + $this->checkContentTypeHeaderAgainstRequestBody($parent, $key, $definition, $output); break; case 'delete': - $this->checkUselessHeader($parent, $key, $definition, 'Content-Type', $output); + //$this->checkUselessRequestHeader($parent, $key, $definition, 'Content-Type', $output);// Can need a payload to precise what to delete + //$this->checkUselessRequestBody($parent, $key, $definition, $output);// … //$this->checkUselessHeader($parent, $key, $definition, 'Accept', $output);// Can return the updated "parent"/"container"/"list" - $this->checkAcceptHeaderAgainstBody($parent, $key, $definition, $output); + $this->checkAcceptHeaderAgainstResponseBody($parent, $key, $definition, $output); + $this->checkContentTypeHeaderAgainstRequestBody($parent, $key, $definition, $output); break; } } } - private function checkUselessHeader($route, $method, $methodDefinition, $header, $output) + private function checkUselessRequestHeader($route, $method, $methodDefinition, $header, $output) { if (array_key_exists('headers', $methodDefinition) && array_key_exists($header, $methodDefinition['headers'])) { - $output->writeln("$method $route doesn't need a '$header' header."); + $output->writeln("$method $route may not need a '$header' request header."); } } - private function checkMandatoryHeader($route, $method, $methodDefinition, $header, $output) + private function checkMandatoryRequestHeader($route, $method, $methodDefinition, $header, $output) { if (!array_key_exists('headers', $methodDefinition) || !array_key_exists($header, $methodDefinition['headers'])) { - $output->writeln("$method $route needs a '$header' header."); + if ('Accept' === $header && array_key_exists('responses', $methodDefinition)) { + if (!array_key_exists('200', array_keys($methodDefinition['responses'])) && empty(array_intersect(['301', '307'], array_keys($methodDefinition['responses'])))) { + // No need for an Accept header when expecting a redirection, right? + $output->writeln("$method $route needs a '$header' request header."); + } + } else { + $output->writeln("$method $route needs a '$header' request header."); + } + } + } + + private function checkUselessRequestBody($route, $method, $methodDefinition, $output) + { + if (array_key_exists('body', $methodDefinition)) { + $output->writeln("$method $route may not need a request body."); + } + } + + private function checkMandatoryRequestBody($route, $method, $methodDefinition, $output) + { + if (!array_key_exists('body', $methodDefinition)) { + $output->writeln("$method $route needs a request body."); } } - private function checkAcceptHeaderAgainstBody($route, $method, $methodDefinition, $output) + private function checkAcceptHeaderAgainstResponseBody($route, $method, $methodDefinition, $output) { if (array_key_exists('headers', $methodDefinition) && array_key_exists('Accept', $methodDefinition['headers']) && array_key_exists('example', $methodDefinition['headers']['Accept']) + && array_key_exists('responses', $methodDefinition) + ) { + if (array_key_exists('200', $methodDefinition['responses'])) { + if (array_key_exists('body', $methodDefinition['responses']['200'])) { + $acceptedTypes = explode(PHP_EOL, trim($methodDefinition['headers']['Accept']['example'])); + $returnedTypes = array_keys($methodDefinition['responses']['200']['body']); + $missingAcceptedType = array_diff($returnedTypes, $acceptedTypes); + $missingReturnedType = array_diff($acceptedTypes, $returnedTypes); + if (!empty($missingAcceptedType) || !empty($missingReturnedType)) { + $output->writeln("$method $route 'Accept' header and body doesn't contain the same types."); + if (!empty($missingAcceptedType)) { + $output->writeln("\tThe following are returned but not accepted: ".implode(', ', $missingAcceptedType)); + } + if (!empty($missingReturnedType)) { + $output->writeln("\tThe following are accepted but not returned: ".implode(', ', $missingReturnedType)); + } + } + } + } else if (array_key_exists('204', $methodDefinition['responses'])) { + //Accept header can be used to indicate the format to use in case of returning an error + $output->writeln("$method $route may not need an 'Accept' header as it responses with an HTTP code meaning an empty body."); + } + } + } + + private function checkContentTypeHeaderAgainstRequestBody($route, $method, $methodDefinition, $output) + { + if (array_key_exists('headers', $methodDefinition) + && array_key_exists('Content-Type', $methodDefinition['headers']) + && array_key_exists('example', $methodDefinition['headers']['Content-Type']) && array_key_exists('body', $methodDefinition)) { - $acceptedTypes = explode(PHP_EOL, trim($methodDefinition['headers']['Accept']['example'])); - $returnedTypes = array_keys($methodDefinition['body']); - $missingAcceptedType = array_diff($returnedTypes, $acceptedTypes); - $missingReturnedType = array_diff($acceptedTypes, $returnedTypes); - if (!empty($missingAcceptedType) || !empty($missingReturnedType)) { - $output->writeln("$method $route 'Accept' header and body doesn't contain the same types."); - //dump($missingAcceptedType, $missingReturnedType); + $saidTypes = explode(PHP_EOL, trim($methodDefinition['headers']['Content-Type']['example'])); + $bodyTypes = array_keys($methodDefinition['body']); + $missingSaidType = array_diff($bodyTypes, $saidTypes); + $missingBodyType = array_diff($saidTypes, $bodyTypes); + if (!empty($missingSaidType) || !empty($missingBodyType)) { + $output->writeln("$method $route 'Content-Type' header and request body doesn't contain the same types."); + if (!empty($missingSaidType)) { + $output->writeln("\tThe following are sent as request body but are not available as Content-Type: ".implode(', ', $missingSaidType)); + } + if (!empty($missingBodyType)) { + $output->writeln("\tThe following can be declared in Content-Type but aren't used in request body: ".implode(', ', $missingBodyType)); + } } } } From cdb1f730e2eeaec15b156dbc85e598a6f43eaba3 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:24:01 +0200 Subject: [PATCH 29/35] TestTypeUsageCommand.php: Fix recursion --- .../src/Command/TestTypeUsageCommand.php | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/tools/raml2html/src/Command/TestTypeUsageCommand.php b/tools/raml2html/src/Command/TestTypeUsageCommand.php index db2e571fd6..50dff024fd 100644 --- a/tools/raml2html/src/Command/TestTypeUsageCommand.php +++ b/tools/raml2html/src/Command/TestTypeUsageCommand.php @@ -10,6 +10,10 @@ final class TestTypeUsageCommand extends Command { + private array $definedTypes; + + private array $usedByOtherTypeTypeList; + /** * {@inheritdoc} */ @@ -25,11 +29,11 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { - $definedTypes = yaml_parse_file('docs/api//rest_api/rest_api_reference/input/ibexa-types.raml'); + $this->definedTypes = yaml_parse_file('docs/api//rest_api/rest_api_reference/input/ibexa-types.raml'); $usedByRouteTypeList = []; - $usedByOtherTypeTypeList = []; + $this->usedByOtherTypeTypeList = []; - foreach ($definedTypes as $type => $typeDefinition) { + foreach ($this->definedTypes as $type => $typeDefinition) { $usageFileList = shell_exec("grep 'type: $type' -R docs/api/rest_api/rest_api_reference/input | grep -v examples | grep -v ibexa-types.raml"); if (!empty($usageFileList)) { $usedByRouteTypeList[] = $type; @@ -37,42 +41,53 @@ protected function execute(InputInterface $input, OutputInterface $output): int } foreach ($usedByRouteTypeList as $usedByRouteType) { - $definedType = $definedTypes[$usedByRouteType]; + $definedType = $this->definedTypes[$usedByRouteType]; $usedType = str_ends_with($definedType['type'], '[]') ? substr($definedType['type'], 0, -2) : $definedType['type']; - if (!in_array($usedType, $usedByOtherTypeTypeList)) { - $usedByOtherTypeTypeList[] = $usedType; + if (!in_array($usedType, $this->usedByOtherTypeTypeList)) { + $this->usedByOtherTypeTypeList[] = $usedType; } - if (array_key_exists('properties', $definedType)) { - foreach ($definedType['properties'] as $property => $propertyDefinition) { - if (is_array($propertyDefinition)) { - if (array_key_exists('type', $propertyDefinition)) { - if ('array' === $propertyDefinition['type']) { - $usedType = $propertyDefinition['items']; - } elseif (str_ends_with($propertyDefinition['type'], '[]')) { - $usedType = substr($propertyDefinition['type'], 0, -2); - } else { - $usedType = $propertyDefinition['type']; - } - } - } else { - if (str_ends_with($propertyDefinition, '[]')) { - $usedType = substr($propertyDefinition, 0, -2); + $this->exploreProperties($definedType); + } + + $usedTypeList = array_merge($usedByRouteTypeList, $this->usedByOtherTypeTypeList); + $unusedTypeList = array_values(array_diff(array_keys($this->definedTypes), $usedTypeList)); + sort($unusedTypeList); + dump($unusedTypeList); + + return Command::SUCCESS; + } + + private function exploreProperties(array $definedType) + { + if (array_key_exists('properties', $definedType)) { + foreach ($definedType['properties'] as $property => $propertyDefinition) { + $usedType = null; + if (is_array($propertyDefinition)) { + if (array_key_exists('type', $propertyDefinition)) { + if ('array' === $propertyDefinition['type']) { + $usedType = $propertyDefinition['items']; + } elseif (array_key_exists('properties', $propertyDefinition)) { + $this->exploreProperties($propertyDefinition, $this->usedByOtherTypeTypeList); + } elseif (str_ends_with($propertyDefinition['type'], '[]')) { + $usedType = substr($propertyDefinition['type'], 0, -2); } else { - $usedType = $propertyDefinition; + $usedType = $propertyDefinition['type']; } } - if (!in_array($usedType, $usedByOtherTypeTypeList)) { - $usedByOtherTypeTypeList[] = $usedType; + } else { + if (str_ends_with($propertyDefinition, '[]')) { + $usedType = substr($propertyDefinition, 0, -2); + } else { + $usedType = $propertyDefinition; + } + } + if (null !== $usedType && !in_array($usedType, $this->usedByOtherTypeTypeList)) { + $this->usedByOtherTypeTypeList[] = $usedType; + if (array_key_exists($usedType, $this->definedTypes)) { + $this->exploreProperties($this->definedTypes[$usedType]); } } } } - - $usedTypeList = array_merge($usedByRouteTypeList, $usedByOtherTypeTypeList); - $unusedTypeList = array_diff(array_keys($definedTypes), $usedTypeList); - - dump($unusedTypeList); - - return 0; } } From ca4cf8daf3b6e0cfbb9e98abd4c1b18219b886a0 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:59:24 +0200 Subject: [PATCH 30/35] tools/raml2html/README.md: Update test:compare example --- tools/raml2html/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index 1de26a3384..45366043b0 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -20,10 +20,10 @@ To generate static HTML from RAML definitions, use the following code from proje php tools/raml2html/raml2html.php build --non-standard-http-methods=COPY,MOVE,PUBLISH,SWAP -t default -o docs/api/rest_api/rest_api_reference/ docs/api/rest_api/rest_api_reference/input/ibexa.raml ``` -To test static HTML against an Ibexa DXP to find route removed and missing routes: +To test static HTML against an Ibexa DXP to find missing routes and removed routes: ```sh -php tools/raml2html/raml2html.php test docs/api/rest_api/rest_api_reference/rest_api_reference.html ~/ibexa-dxp +php tools/raml2html/raml2html.php test:compare ~/ibexa-dxp docs/api/rest_api/rest_api_reference/rest_api_reference.html ``` Note: The Ibexa DXP doesn't need to run. From ea9fbf4d7af9203e60ddc698c40af6b0665875ec Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:39:20 +0200 Subject: [PATCH 31/35] ReferenceTester.php: add connector-qualifio --- tools/raml2html/src/Test/ReferenceTester.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index dae3aa0403..c8d473a6a8 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -19,6 +19,7 @@ class ReferenceTester 'vendor/ibexa/cart/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connect/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', + 'vendor/ibexa/connector-qualifio/src/bundle/Resources/config/routing_rest.yaml', //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // imports the 4 following files 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/companies.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/members.yaml', From ad1a184a3e84b4ba59804a877b898c27df7149bd Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:45:26 +0200 Subject: [PATCH 32/35] ReferenceTester.php: inc. code readability --- tools/raml2html/src/Test/ReferenceTester.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index c8d473a6a8..55afbd83a7 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -170,13 +170,14 @@ private function parseRouterOutput($consolePath) continue; } $lineParts = preg_split('/\s+/', $outputLine); - $routeId = $lineParts[0]; - $methods = explode('|', $lineParts[1]); + $routeProperties = array_combine(['Name', 'Method', 'Scheme', 'Host', 'Path'], $lineParts); + $routeId = $routeProperties['Name']; + $methods = explode('|', $routeProperties['Method']); foreach ($methods as $method) { if ('OPTIONS' === $method) { continue; } - $routePath = str_replace($this->apiUri, '', $lineParts[4]); + $routePath = str_replace($this->apiUri, '', $routeProperties['Path']); if (!array_key_exists($routePath, $confRoutes)) { $confRoutes[$routePath] = ['methods' => []]; } From b976ef5cd0d70efdf6b18291be0261a578c25987 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:54:18 +0200 Subject: [PATCH 33/35] ReferenceTester.php: Exclude few bundles when using debug:router --- tools/raml2html/src/Test/ReferenceTester.php | 23 ++++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tools/raml2html/src/Test/ReferenceTester.php b/tools/raml2html/src/Test/ReferenceTester.php index 55afbd83a7..e74886cfb7 100644 --- a/tools/raml2html/src/Test/ReferenceTester.php +++ b/tools/raml2html/src/Test/ReferenceTester.php @@ -12,30 +12,35 @@ class ReferenceTester public const DEFAULT_FILE_LIST = [ 'vendor/ibexa/rest/src/bundle/Resources/config/routing.yml', - // `find vendor/ibexa -name "*rest*.yaml" -and -wholename "*rout*" -and -not -wholename "*test*" | sort` - 'vendor/ibexa/activity-log/src/bundle/Resources/config/routing/rest.yaml', // directly prefixed /api/ibexa/v2 + // `find vendor/ibexa -wholename "*rout*rest*.y*ml" -and -not -wholename "*test*" | sort` + 'vendor/ibexa/activity-log/src/bundle/Resources/config/routing/rest.yaml', //'vendor/ibexa/admin-ui/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/calendar/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/cart/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connect/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-dam/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/connector-qualifio/src/bundle/Resources/config/routing_rest.yaml', - //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // imports the 4 following files 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/companies.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/members.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/sales_representatives.yaml', 'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing/rest/root.yaml', + //'vendor/ibexa/corporate-account/src/bundle/Resources/config/routing_rest.yaml', // only imports the 4 previous files 'vendor/ibexa/fieldtype-query/src/bundle/Resources/config/routing/rest.yaml', 'vendor/ibexa/order-management/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/payment/src/bundle/Resources/config/routing_rest.yaml', - //'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', // prefixed /personalization/v1 - 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', // contains few /personalization/v1 + //'vendor/ibexa/personalization/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/ibexa/v2/personalization/v1 + 'vendor/ibexa/product-catalog/src/bundle/Resources/config/routing_rest.yaml', // contains few /api/ibexa/v2/personalization/v1 //'vendor/ibexa/scheduler/src/bundle/Resources/config/routing_rest.yaml', // prefixed /api/datebasedpublisher/v1 'vendor/ibexa/segmentation/src/bundle/Resources/config/routing_rest.yaml', 'vendor/ibexa/shipping/src/bundle/Resources/config/routing/rest.yaml', 'vendor/ibexa/taxonomy/src/bundle/Resources/config/routing_rest.yaml', ]; + public const EXCLUDED_BUNDLE_LIST = [ + 'Ibexa\Bundle\AdminUi', + 'Ibexa\Bundle\Personalization', + ]; + public const METHOD_LIST = [ 'OPTIONS', 'GET', @@ -154,7 +159,7 @@ private function parseRouterOutput($consolePath) { $confRoutes = []; - $routerCommand = 'debug:router --format=txt'; + $routerCommand = 'debug:router --format=txt --show-controllers'; $consolePathLastChar = substr($consolePath, -1); if (in_array($consolePathLastChar, ['"', "'"])) { $consoleCommand = substr($consolePath, 0, -1) . " {$routerCommand}{$consolePathLastChar}"; @@ -170,9 +175,13 @@ private function parseRouterOutput($consolePath) continue; } $lineParts = preg_split('/\s+/', $outputLine); - $routeProperties = array_combine(['Name', 'Method', 'Scheme', 'Host', 'Path'], $lineParts); + $routeProperties = array_combine(['Name', 'Method', 'Scheme', 'Host', 'Path', 'Controller'], $lineParts); $routeId = $routeProperties['Name']; $methods = explode('|', $routeProperties['Method']); + $bundle = implode('\\', array_slice(explode('\\', $routeProperties['Controller']), 0, 3)); + if (in_array($bundle, self::EXCLUDED_BUNDLE_LIST)) { + continue; + } foreach ($methods as $method) { if ('OPTIONS' === $method) { continue; From 9a5db0a600fbcd7389e9f03791f85dae35bcf66c Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:26:26 +0200 Subject: [PATCH 34/35] raml2html/README.md: enh. test:compare doc --- tools/raml2html/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/raml2html/README.md b/tools/raml2html/README.md index 45366043b0..1a9ab55df2 100644 --- a/tools/raml2html/README.md +++ b/tools/raml2html/README.md @@ -14,16 +14,20 @@ composer install; ## Usage +### Generate + To generate static HTML from RAML definitions, use the following code from project root: ```sh php tools/raml2html/raml2html.php build --non-standard-http-methods=COPY,MOVE,PUBLISH,SWAP -t default -o docs/api/rest_api/rest_api_reference/ docs/api/rest_api/rest_api_reference/input/ibexa.raml ``` -To test static HTML against an Ibexa DXP to find missing routes and removed routes: +### Compare documented routes with DXP configured routes + +To test static HTML against an Ibexa DXP to find routes missing from the doc, and routes removed from the DXP: ```sh -php tools/raml2html/raml2html.php test:compare ~/ibexa-dxp docs/api/rest_api/rest_api_reference/rest_api_reference.html +php tools/raml2html/raml2html.php test:compare ~/ibexa-dxp ``` Note: The Ibexa DXP doesn't need to run. @@ -34,5 +38,5 @@ cd ~/ibexa-dxp; composer create-project ibexa/commerce-skeleton . --no-install --ignore-platform-reqs --no-scripts; composer install --ignore-platform-reqs --no-scripts; cd -; -php tools/raml2html/raml2html.php test ~/ibexa-dxp +php tools/raml2html/raml2html.php test:compare ~/ibexa-dxp; ``` From 622df54c8123fb1259b195a328da1c255014d4ba Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:43:45 +0200 Subject: [PATCH 35/35] test:type:usage: handle arrays of objects --- .../src/Command/TestTypeUsageCommand.php | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tools/raml2html/src/Command/TestTypeUsageCommand.php b/tools/raml2html/src/Command/TestTypeUsageCommand.php index 50dff024fd..ac632266b7 100644 --- a/tools/raml2html/src/Command/TestTypeUsageCommand.php +++ b/tools/raml2html/src/Command/TestTypeUsageCommand.php @@ -7,6 +7,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; final class TestTypeUsageCommand extends Command { @@ -42,6 +43,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($usedByRouteTypeList as $usedByRouteType) { $definedType = $this->definedTypes[$usedByRouteType]; + if (!array_key_exists('type', $definedType)) { + //TODO: warning + dump($definedType); + } $usedType = str_ends_with($definedType['type'], '[]') ? substr($definedType['type'], 0, -2) : $definedType['type']; if (!in_array($usedType, $this->usedByOtherTypeTypeList)) { $this->usedByOtherTypeTypeList[] = $usedType; @@ -52,7 +57,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $usedTypeList = array_merge($usedByRouteTypeList, $this->usedByOtherTypeTypeList); $unusedTypeList = array_values(array_diff(array_keys($this->definedTypes), $usedTypeList)); sort($unusedTypeList); - dump($unusedTypeList); + + if (!empty($unusedTypeList)) { + $style = new SymfonyStyle($input, $output); + $style->title('Unused types'); + $style->listing($unusedTypeList); + return Command::FAILURE; + } return Command::SUCCESS; } @@ -65,9 +76,13 @@ private function exploreProperties(array $definedType) if (is_array($propertyDefinition)) { if (array_key_exists('type', $propertyDefinition)) { if ('array' === $propertyDefinition['type']) { - $usedType = $propertyDefinition['items']; + if (is_array($propertyDefinition['items']) && array_key_exists('properties', $propertyDefinition['items'])) { + $this->exploreProperties($propertyDefinition['items']); + } else { + $usedType = $propertyDefinition['items']; + } } elseif (array_key_exists('properties', $propertyDefinition)) { - $this->exploreProperties($propertyDefinition, $this->usedByOtherTypeTypeList); + $this->exploreProperties($propertyDefinition); } elseif (str_ends_with($propertyDefinition['type'], '[]')) { $usedType = substr($propertyDefinition['type'], 0, -2); } else { @@ -82,8 +97,16 @@ private function exploreProperties(array $definedType) } } if (null !== $usedType && !in_array($usedType, $this->usedByOtherTypeTypeList)) { + if (!is_string($usedType)) { + //TODO: warning + dump($propertyDefinition, $usedType); + } $this->usedByOtherTypeTypeList[] = $usedType; if (array_key_exists($usedType, $this->definedTypes)) { + if (!is_array($this->definedTypes[$usedType])) { + //TODO: warning + dump($usedType, $this->definedTypes[$usedType]); + } $this->exploreProperties($this->definedTypes[$usedType]); } }