diff --git a/generate-spec.php b/generate-spec.php index 285c723..c47a4c9 100755 --- a/generate-spec.php +++ b/generate-spec.php @@ -838,6 +838,20 @@ $parameters[] = $parameter; } + + foreach ($route->controllerMethod->requestHeaders as $requestHeader) { + $parameters[] = [ + 'name' => $requestHeader, + 'in' => 'header', + // Not required, because getHeader() will return an empty string by default. + // It might still mean that a header is always required, but the controller method has to check the + // value manually anyway. + 'schema' => [ + 'type' => 'string', + ], + ]; + } + if ($route->isOCS || !$route->isNoCSRFRequired) { $parameters[] = [ 'name' => 'OCS-APIRequest', diff --git a/src/ControllerMethod.php b/src/ControllerMethod.php index f1950b3..f0bf920 100644 --- a/src/ControllerMethod.php +++ b/src/ControllerMethod.php @@ -7,6 +7,9 @@ namespace OpenAPIExtractor; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode; @@ -23,6 +26,7 @@ class ControllerMethod { /** * @param ControllerMethodParameter[] $parameters + * @param list $requestHeaders * @param list $responses * @param OpenApiType[] $returns * @param array $responseDescription @@ -30,6 +34,7 @@ class ControllerMethod { */ public function __construct( public array $parameters, + public array $requestHeaders, public array $responses, public array $responseDescription, public array $description, @@ -274,7 +279,18 @@ public static function parse(string $context, Logger::warning($context, 'Summary ends with a punctuation mark'); } - return new ControllerMethod($parameters, $responses, $responseDescriptions, $methodDescription, $methodSummary, $isDeprecated); + $headers = []; + foreach ($nodeFinder->findInstanceOf($method->getStmts(), MethodCall::class) as $methodCall) { + if ($methodCall->var instanceof PropertyFetch && + $methodCall->var->var instanceof Variable && + $methodCall->var->var->name === 'this' && + $methodCall->var->name->name === 'request' && + $methodCall->name->name === 'getHeader') { + $headers[] = Helpers::exprToValue($context . ': getHeader', $methodCall->args[0]->value); + } + } + + return new ControllerMethod($parameters, array_unique($headers), $responses, $responseDescriptions, $methodDescription, $methodSummary, $isDeprecated); } } diff --git a/tests/appinfo/routes.php b/tests/appinfo/routes.php index 8969f21..800ef55 100644 --- a/tests/appinfo/routes.php +++ b/tests/appinfo/routes.php @@ -85,6 +85,7 @@ ['name' => 'Settings#deprecatedRouteAndParameterGet', 'url' => '/api/{apiVersion}/deprecated-route-parameter-get', 'verb' => 'GET', 'requirements' => ['apiVersion' => '(v2)']], ['name' => 'Settings#samePathGet', 'url' => '/api/{apiVersion}/same-path', 'verb' => 'GET', 'requirements' => ['apiVersion' => '(v2)']], ['name' => 'Settings#samePathPost', 'url' => '/api/{apiVersion}/same-path', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']], + ['name' => 'Settings#requestHeader', 'url' => '/api/{apiVersion}/request-header', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']], ['name' => 'V1\SubDir#subDirRoute', 'url' => '/sub-dir', 'verb' => 'GET'], ], ]; diff --git a/tests/lib/Controller/SettingsController.php b/tests/lib/Controller/SettingsController.php index 36356d6..d3adefb 100644 --- a/tests/lib/Controller/SettingsController.php +++ b/tests/lib/Controller/SettingsController.php @@ -739,4 +739,17 @@ public function samePathGet(): DataResponse { public function samePathPost(): DataResponse { return new DataResponse(); } + + /** + * A method with a request header. + * + * @return DataResponse, array{}> + * + * 200: Admin settings updated + */ + public function requestHeader(): DataResponse { + $value = $this->request->getHeader('X-Custom-Header'); + + return new DataResponse(); + } } diff --git a/tests/openapi-administration.json b/tests/openapi-administration.json index cb2acf9..bc54088 100644 --- a/tests/openapi-administration.json +++ b/tests/openapi-administration.json @@ -5352,6 +5352,85 @@ } } }, + "/ocs/v2.php/apps/notifications/api/{apiVersion}/request-header": { + "post": { + "operationId": "settings-request-header", + "summary": "A method with a request header.", + "description": "This endpoint requires admin access", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v2" + ], + "default": "v2" + } + }, + { + "name": "X-Custom-Header", + "in": "header", + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Admin settings updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/tests/attribute-ocs/{param}": { "get": { "operationId": "routing-attributeocs-route", diff --git a/tests/openapi-full.json b/tests/openapi-full.json index 9426e5a..3376f31 100644 --- a/tests/openapi-full.json +++ b/tests/openapi-full.json @@ -5509,6 +5509,85 @@ } } }, + "/ocs/v2.php/apps/notifications/api/{apiVersion}/request-header": { + "post": { + "operationId": "settings-request-header", + "summary": "A method with a request header.", + "description": "This endpoint requires admin access", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v2" + ], + "default": "v2" + } + }, + { + "name": "X-Custom-Header", + "in": "header", + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Admin settings updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/tests/attribute-ocs/{param}": { "get": { "operationId": "routing-attributeocs-route",