As REST API developer, in need to test my application. I recently moved to the OpenAPI specification, and I really want to test my API responses against it. My application is based on Lumen.
While routes with no path parameters cause no problem, I'm facing a "reverse routing" problem. For example, in my functional tests, I'm testing the route: GET /themes/{themeId}/questions, which should return the questions
related to the theme
number themeId
.
The actual URI may be something like /themes/17/questions
. The problem is that I need to find the exact pattern with {params} in order to test my API against the specification. To be clear, I need a way to :
/themes/17/questions => /themes/{themeId}/questions
I already found some kind of solution, but I'm absolutely not satisfied and it feels really messy:
/**
* Get the OpenAPI URI based on the actual request URI.
*
* @param string $method The HTTP method (GET, POST, PUT, PATCH, DELETE etc.)
* @param string $actualUri The actual request URI
*
* @return string
*/
public function getOpenAPIUri(string $method, $actualUri)
{
$currentRouter = $this->dispatch($method, $actualUri); // Calls the Lumen dispatcher
$filteredRoutes = Collection::make($this->app->router->getRoutes())
// Reject routes which do not match the method
->reject(function ($routeData, $routeKey) use ($method) {
return !Str::startsWith($routeKey, strtoupper($method));
})
// Reject routes which do not match the controller/action
->reject(function ($routeData) use ($currentRouter) {
if (!isset($routeData["action"]["uses"])) {
return true;
}
return $currentRouter[1]["uses"] !== $routeData["action"]["uses"];
})
// Reject routes which have not the same amount of parts (splitted by "/")
->reject(function ($routeData) use ($actualUri) {
$actualParts = explode("/", trim($actualUri, "/"));
$routeParts = explode("/", trim($routeData["uri"], "/"));
return count($actualParts) !== count($routeParts);
})
// Reject routes which does not have the same arguments
->reject(function ($routeData, $routeKey) use ($currentRouter) {
$pattern = "/{([a-zA-Z]+):[a-zA-Z0-9\/\\\+\-_\[\]\*\{\}\|\.\^]+}/";
$strippedUri = preg_replace($pattern, '{$1}', $routeKey);
$paramKeys = Collection::make(array_keys($currentRouter[2]))->map(function ($param) {
return "{{$param}}";
});
return !Str::contains($strippedUri, $paramKeys->toArray());
});
if ($filteredRoutes->isEmpty()) {
return $actualUri;
} else {
$match = $filteredRoutes->first();
return $match["uri"];
}
}
Any suggestion would be greatly appreciated!