Skip to content

Commit 4771319

Browse files
committed
feat(routing): Support new Route attribute
Signed-off-by: provokateurin <[email protected]>
1 parent fb82c9e commit 4771319

File tree

4 files changed

+537
-0
lines changed

4 files changed

+537
-0
lines changed

generate-spec

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ foreach ([__DIR__ . "/../../autoload.php", __DIR__ . "/vendor/autoload.php"] as
1212

1313
use Ahc\Cli\Input\Command;
1414
use DirectoryIterator;
15+
use PhpParser\Node\AttributeGroup;
16+
use PhpParser\Node\Expr\ClassConstFetch;
1517
use PhpParser\Node\Expr\New_;
1618
use PhpParser\Node\Name;
1719
use PhpParser\Node\Stmt\Class_;
@@ -249,6 +251,72 @@ if (file_exists($controllersDir)) {
249251
}
250252

251253
$routes = [];
254+
foreach ($controllers as $controllerName => $stmts) {
255+
$controllerClass = null;
256+
/** @var Class_ $class */
257+
foreach ($nodeFinder->findInstanceOf($stmts, Class_::class) as $class) {
258+
if ($class->name->name === $controllerName . 'Controller') {
259+
$controllerClass = $class;
260+
break;
261+
}
262+
}
263+
if ($controllerClass === null) {
264+
Logger::error($controllerName, "Controller '$controllerName' not found");
265+
continue;
266+
}
267+
268+
/** @var ClassMethod $classMethod */
269+
foreach ($nodeFinder->findInstanceOf($controllerClass->stmts, ClassMethod::class) as $classMethod) {
270+
$name = substr($class->name->name, 0, -strlen('Controller')) . '#' . $classMethod->name->name;
271+
272+
/** @var AttributeGroup $attrGroup */
273+
foreach ($classMethod->attrGroups as $attrGroup) {
274+
foreach ($attrGroup->attrs as $attr) {
275+
if ($attr->name->getLast() !== 'Route' && $attr->name->getLast() !== 'ApiRoute' && $attr->name->getLast() !== 'FrontpageRoute') {
276+
continue;
277+
}
278+
279+
$key = match ($attr->name->getLast()) {
280+
'Route' => null,
281+
'ApiRoute' => 'ocs',
282+
'FrontpageRoute' => 'routes',
283+
};
284+
$args = [
285+
'name' => $name,
286+
];
287+
for ($i = 0, $iMax = count($attr->args); $i < $iMax; $i++) {
288+
$arg = $attr->args[$i];
289+
290+
if ($arg->name !== null) {
291+
$argName = $arg->name->name;
292+
} else {
293+
$argNames = ['verb', 'url', 'requirements', 'defaults', 'root', 'postfix'];
294+
if ($attr->name->getLast() === 'Route') {
295+
array_unshift($argNames, 'type');
296+
}
297+
$argName = $argNames[$i];
298+
}
299+
300+
if ($argName === 'type' && $arg->value instanceof ClassConstFetch) {
301+
$type = $arg->value->name->name;
302+
$key = match ($type) {
303+
'TYPE_API' => 'ocs',
304+
'TYPE_FRONTPAGE' => 'routes',
305+
default => Logger::panic($name, 'Unknown Route type: ' . $type),
306+
};
307+
continue;
308+
}
309+
310+
$args[$argName] = Helpers::exprToValue($name, $arg->value);
311+
}
312+
313+
$parsedRoutes[$key] ??= [];
314+
$parsedRoutes[$key][] = $args;
315+
}
316+
}
317+
}
318+
}
319+
252320
foreach ($parsedRoutes as $key => $value) {
253321
$isOCS = $key === "ocs";
254322
$isIndex = $key === "routes";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OCA\Notifications\Controller;
6+
7+
use OCP\AppFramework\Http\Attribute\Route;
8+
use OCP\AppFramework\Http\DataResponse;
9+
use OCP\AppFramework\OCSController;
10+
11+
class RoutingController extends OCSController {
12+
/**
13+
* OCS Route with attribute
14+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
15+
*
16+
* 200: Success
17+
*/
18+
#[Route(Route::TYPE_API, verb: 'GET', url: '/attribute-ocs/{param}', requirements: ['param' => '[a-z]+'], defaults: ['param' => 'abc'], root: '/tests', postfix: 'Route')]
19+
#[ApiRoute(verb: 'POST', url: '/attribute-ocs/{param}', requirements: ['param' => '[a-z]+'], defaults: ['param' => 'abc'], root: '/tests', postfix: 'ApiRoute')]
20+
public function attributeOCS() {
21+
return DataResponse();
22+
}
23+
24+
/**
25+
* @NoCSRFRequired
26+
*
27+
* Index Route with attribute
28+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
29+
*
30+
* 200: Success
31+
*/
32+
#[Route(Route::TYPE_FRONTPAGE, verb: 'GET', url: '/attribute-index/{param}', requirements: ['param' => '[a-z]+'], defaults: ['param' => 'abc'], root: '/tests', postfix: 'Route')]
33+
#[FrontpageRoute(verb: 'POST', url: '/attribute-index/{param}', requirements: ['param' => '[a-z]+'], defaults: ['param' => 'abc'], root: '/tests', postfix: 'FrontpageRoute')]
34+
public function attributeIndex() {
35+
return DataResponse();
36+
}
37+
}

tests/openapi-administration.json

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,6 +2327,222 @@
23272327
}
23282328
}
23292329
}
2330+
},
2331+
"/ocs/v2.php/tests/attribute-ocs/{param}": {
2332+
"get": {
2333+
"operationId": "routing-attributeocs-route",
2334+
"summary": "OCS Route with attribute",
2335+
"description": "This endpoint requires admin access",
2336+
"tags": [
2337+
"routing"
2338+
],
2339+
"security": [
2340+
{
2341+
"bearer_auth": []
2342+
},
2343+
{
2344+
"basic_auth": []
2345+
}
2346+
],
2347+
"parameters": [
2348+
{
2349+
"name": "param",
2350+
"in": "path",
2351+
"required": true,
2352+
"schema": {
2353+
"type": "string",
2354+
"pattern": "^[a-z]+$",
2355+
"default": "abc"
2356+
}
2357+
},
2358+
{
2359+
"name": "OCS-APIRequest",
2360+
"in": "header",
2361+
"description": "Required to be true for the API request to pass",
2362+
"required": true,
2363+
"schema": {
2364+
"type": "boolean",
2365+
"default": true
2366+
}
2367+
}
2368+
],
2369+
"responses": {
2370+
"200": {
2371+
"description": "Success",
2372+
"content": {
2373+
"application/json": {
2374+
"schema": {
2375+
"type": "object",
2376+
"required": [
2377+
"ocs"
2378+
],
2379+
"properties": {
2380+
"ocs": {
2381+
"type": "object",
2382+
"required": [
2383+
"meta",
2384+
"data"
2385+
],
2386+
"properties": {
2387+
"meta": {
2388+
"$ref": "#/components/schemas/OCSMeta"
2389+
},
2390+
"data": {}
2391+
}
2392+
}
2393+
}
2394+
}
2395+
}
2396+
}
2397+
}
2398+
}
2399+
},
2400+
"post": {
2401+
"operationId": "routing-attributeocs-apiroute",
2402+
"summary": "OCS Route with attribute",
2403+
"description": "This endpoint requires admin access",
2404+
"tags": [
2405+
"routing"
2406+
],
2407+
"security": [
2408+
{
2409+
"bearer_auth": []
2410+
},
2411+
{
2412+
"basic_auth": []
2413+
}
2414+
],
2415+
"parameters": [
2416+
{
2417+
"name": "param",
2418+
"in": "path",
2419+
"required": true,
2420+
"schema": {
2421+
"type": "string",
2422+
"pattern": "^[a-z]+$",
2423+
"default": "abc"
2424+
}
2425+
},
2426+
{
2427+
"name": "OCS-APIRequest",
2428+
"in": "header",
2429+
"description": "Required to be true for the API request to pass",
2430+
"required": true,
2431+
"schema": {
2432+
"type": "boolean",
2433+
"default": true
2434+
}
2435+
}
2436+
],
2437+
"responses": {
2438+
"200": {
2439+
"description": "Success",
2440+
"content": {
2441+
"application/json": {
2442+
"schema": {
2443+
"type": "object",
2444+
"required": [
2445+
"ocs"
2446+
],
2447+
"properties": {
2448+
"ocs": {
2449+
"type": "object",
2450+
"required": [
2451+
"meta",
2452+
"data"
2453+
],
2454+
"properties": {
2455+
"meta": {
2456+
"$ref": "#/components/schemas/OCSMeta"
2457+
},
2458+
"data": {}
2459+
}
2460+
}
2461+
}
2462+
}
2463+
}
2464+
}
2465+
}
2466+
}
2467+
}
2468+
},
2469+
"/index.php/tests/attribute-index/{param}": {
2470+
"get": {
2471+
"operationId": "routing-attribute-index-route",
2472+
"summary": "Index Route with attribute",
2473+
"description": "This endpoint requires admin access",
2474+
"tags": [
2475+
"routing"
2476+
],
2477+
"security": [
2478+
{
2479+
"bearer_auth": []
2480+
},
2481+
{
2482+
"basic_auth": []
2483+
}
2484+
],
2485+
"parameters": [
2486+
{
2487+
"name": "param",
2488+
"in": "path",
2489+
"required": true,
2490+
"schema": {
2491+
"type": "string",
2492+
"pattern": "^[a-z]+$",
2493+
"default": "abc"
2494+
}
2495+
}
2496+
],
2497+
"responses": {
2498+
"200": {
2499+
"description": "Success",
2500+
"content": {
2501+
"application/json": {
2502+
"schema": {}
2503+
}
2504+
}
2505+
}
2506+
}
2507+
},
2508+
"post": {
2509+
"operationId": "routing-attribute-index-frontpageroute",
2510+
"summary": "Index Route with attribute",
2511+
"description": "This endpoint requires admin access",
2512+
"tags": [
2513+
"routing"
2514+
],
2515+
"security": [
2516+
{
2517+
"bearer_auth": []
2518+
},
2519+
{
2520+
"basic_auth": []
2521+
}
2522+
],
2523+
"parameters": [
2524+
{
2525+
"name": "param",
2526+
"in": "path",
2527+
"required": true,
2528+
"schema": {
2529+
"type": "string",
2530+
"pattern": "^[a-z]+$",
2531+
"default": "abc"
2532+
}
2533+
}
2534+
],
2535+
"responses": {
2536+
"200": {
2537+
"description": "Success",
2538+
"content": {
2539+
"application/json": {
2540+
"schema": {}
2541+
}
2542+
}
2543+
}
2544+
}
2545+
}
23302546
}
23312547
},
23322548
"tags": []

0 commit comments

Comments
 (0)