From d03a6cdcf75d0c08be0c15948eccd6056dab5639 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 27 Nov 2025 08:52:57 +0100 Subject: [PATCH 1/2] test: Add a broken sample Signed-off-by: Joas Schilling --- .../Controller/AdminSettingsController.php | 20 +++ tests/openapi-administration.json | 142 ++++++++++++++++++ tests/openapi-full.json | 142 ++++++++++++++++++ 3 files changed, 304 insertions(+) diff --git a/tests/lib/Controller/AdminSettingsController.php b/tests/lib/Controller/AdminSettingsController.php index 8af8ea7..6fc7d50 100644 --- a/tests/lib/Controller/AdminSettingsController.php +++ b/tests/lib/Controller/AdminSettingsController.php @@ -61,4 +61,24 @@ public function movedToSettingsTag(): DataResponse { public function movedToSettingsTagUnnamed(): DataResponse { return new DataResponse(); } + + public const ALSO_OPTIONAL = 1; + + /** + * OCS Route with attribute + * + * @param int $option1 This is optional with magic number + * @param int $option2 This is optional with constant + * @return DataResponse, array{}> + * + * 200: Success + */ + #[ApiRoute(verb: 'POST', url: '/optional-parameters')] + public function optionalParameters( + int $option1 = 0, + int $option2 = self::ALSO_OPTIONAL, + ) { + return DataResponse(); + } + } diff --git a/tests/openapi-administration.json b/tests/openapi-administration.json index c1b8450..63b3ff9 100644 --- a/tests/openapi-administration.json +++ b/tests/openapi-administration.json @@ -9259,6 +9259,148 @@ } } }, + "/ocs/v2.php/apps/notifications/optional-parameters": { + "post": { + "operationId": "admin_settings-optional-parameters", + "summary": "OCS Route with attribute", + "description": "This endpoint requires admin access", + "tags": [ + "admin_settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "option2" + ], + "properties": { + "option1": { + "type": "integer", + "format": "int64", + "default": 0, + "description": "This is optional with magic number" + }, + "option2": { + "type": "integer", + "format": "int64", + "description": "This is optional with constant" + } + } + } + } + } + }, + "parameters": [ + { + "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": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Logged in account must be an admin", + "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 c56a39c..f925ac4 100644 --- a/tests/openapi-full.json +++ b/tests/openapi-full.json @@ -9459,6 +9459,148 @@ } } }, + "/ocs/v2.php/apps/notifications/optional-parameters": { + "post": { + "operationId": "admin_settings-optional-parameters", + "summary": "OCS Route with attribute", + "description": "This endpoint requires admin access", + "tags": [ + "admin_settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "option2" + ], + "properties": { + "option1": { + "type": "integer", + "format": "int64", + "default": 0, + "description": "This is optional with magic number" + }, + "option2": { + "type": "integer", + "format": "int64", + "description": "This is optional with constant" + } + } + } + } + } + }, + "parameters": [ + { + "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": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "401": { + "description": "Current user is not logged in", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "403": { + "description": "Logged in account must be an admin", + "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", From 744ee171fd29708a386766d06d6d3eb804c569dd Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 27 Nov 2025 08:58:30 +0100 Subject: [PATCH 2/2] fix(openapi): Don't mark unknown default values as required Signed-off-by: Joas Schilling --- src/ControllerMethod.php | 2 ++ src/OpenApiType.php | 3 ++- tests/openapi-administration.json | 5 +---- tests/openapi-full.json | 5 +---- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ControllerMethod.php b/src/ControllerMethod.php index 81d7a67..c9c45e3 100644 --- a/src/ControllerMethod.php +++ b/src/ControllerMethod.php @@ -545,6 +545,8 @@ public static function parse(string $context, $type->defaultValue = Helpers::exprToValue($context, $methodParameter->default); $type->hasDefaultValue = true; } catch (UnsupportedExprException $e) { + $type->hasDefaultValue = true; + $type->hasUnknownDefaultValue = true; Logger::debug($context, $e); } } diff --git a/src/OpenApiType.php b/src/OpenApiType.php index 24c5a5f..6de07d9 100644 --- a/src/OpenApiType.php +++ b/src/OpenApiType.php @@ -42,6 +42,7 @@ public function __construct( public ?string $format = null, public bool $nullable = false, public bool $hasDefaultValue = false, + public bool $hasUnknownDefaultValue = false, public bool $deprecated = false, public mixed $defaultValue = null, public ?OpenApiType $items = null, @@ -92,7 +93,7 @@ public function toArray(bool $isParameter = false): array|stdClass { if ($this->deprecated) { $values['deprecated'] = true; } - if ($this->hasDefaultValue) { + if ($this->hasDefaultValue && !$this->hasUnknownDefaultValue) { $values['default'] = $this->type === 'object' && is_array($this->defaultValue) && count($this->defaultValue) === 0 ? new stdClass() : $this->defaultValue; } if ($this->enum !== null) { diff --git a/tests/openapi-administration.json b/tests/openapi-administration.json index 63b3ff9..4bc3659 100644 --- a/tests/openapi-administration.json +++ b/tests/openapi-administration.json @@ -9276,14 +9276,11 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "option2" - ], "properties": { "option1": { "type": "integer", diff --git a/tests/openapi-full.json b/tests/openapi-full.json index f925ac4..afb019c 100644 --- a/tests/openapi-full.json +++ b/tests/openapi-full.json @@ -9476,14 +9476,11 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "option2" - ], "properties": { "option1": { "type": "integer",