diff --git a/src/OpenApiType.php b/src/OpenApiType.php index 3a5891f..3a35321 100644 --- a/src/OpenApiType.php +++ b/src/OpenApiType.php @@ -113,7 +113,7 @@ enum: [0, 1], if ($this->description !== null && $this->description !== '' && !$isParameter) { $values['description'] = Helpers::cleanDocComment($this->description); } - if ($this->items instanceof \OpenAPIExtractor\OpenApiType) { + if ($this->items instanceof OpenApiType) { $values['items'] = $this->items->toArray(); } if ($this->minLength !== null) { @@ -139,7 +139,7 @@ enum: [0, 1], } if ($this->properties !== null && $this->properties !== []) { $values['properties'] = array_combine(array_keys($this->properties), - array_map(static fn (OpenApiType $property): array|\stdClass => $property->toArray(), array_values($this->properties)), + array_map(static fn (OpenApiType $property): array|stdClass => $property->toArray(), array_values($this->properties)), ); } if ($this->additionalProperties !== null) { @@ -150,13 +150,13 @@ enum: [0, 1], } } if ($this->oneOf !== null) { - $values['oneOf'] = array_map(fn (OpenApiType $type): array|\stdClass => $type->toArray(), $this->oneOf); + $values['oneOf'] = array_map(fn (OpenApiType $type): array|stdClass => $type->toArray(), $this->oneOf); } if ($this->anyOf !== null) { - $values['anyOf'] = array_map(fn (OpenApiType $type): array|\stdClass => $type->toArray(), $this->anyOf); + $values['anyOf'] = array_map(fn (OpenApiType $type): array|stdClass => $type->toArray(), $this->anyOf); } if ($this->allOf !== null) { - $values['allOf'] = array_map(fn (OpenApiType $type): array|\stdClass => $type->toArray(), $this->allOf); + $values['allOf'] = array_map(fn (OpenApiType $type): array|stdClass => $type->toArray(), $this->allOf); } return $values !== [] ? $values : new stdClass(); @@ -184,9 +184,9 @@ public static function resolve(string $context, array $definitions, ParamTagValu items: self::resolve($context . ': items', $definitions, $node->type), ); } - if ($node instanceof GenericTypeNode && ($node->type->name === 'array' || $node->type->name === 'list' || $node->type->name === 'non-empty-list') && count($node->genericTypes) === 1) { - if ($node->type->name === 'array') { - Logger::error($context, "The 'array' syntax for arrays is forbidden due to ambiguities. Use 'list' for JSON arrays or 'array' for JSON objects instead."); + if ($node instanceof GenericTypeNode && ($node->type->name === 'array' || $node->type->name === 'non-empty-array' || $node->type->name === 'list' || $node->type->name === 'non-empty-list') && count($node->genericTypes) === 1) { + if ($node->type->name === 'array' || $node->type->name === 'non-empty-array') { + Logger::error($context, "The 'array' and 'non-empty-array' syntax for arrays is forbidden due to ambiguities. Use 'list' for JSON arrays or 'non-empty-array' for JSON objects instead."); } if ($node->genericTypes[0] instanceof IdentifierTypeNode && $node->genericTypes[0]->name === 'empty') { @@ -232,7 +232,11 @@ public static function resolve(string $context, array $definitions, ParamTagValu ); } - if ($node instanceof GenericTypeNode && $node->type->name === 'array' && count($node->genericTypes) === 2 && $node->genericTypes[0] instanceof IdentifierTypeNode) { + if ($node instanceof GenericTypeNode && in_array($node->type->name, ['array', 'non-empty-array']) && count($node->genericTypes) === 2 && $node->genericTypes[0] instanceof IdentifierTypeNode) { + if ($node->type->name !== 'non-empty-array') { + Logger::error($context, 'You must ensure JSON objects are not empty using the "non-empty-array" type. To allow return empty JSON objects your code must manually check if the array is empty in order to return "new \\stdClass()" and use "non-empty-array|\\stdClass" as the type.'); + } + $allowedTypes = ['string', 'lowercase-string', 'non-empty-string', 'non-empty-lowercase-string']; if (in_array($node->genericTypes[0]->name, $allowedTypes, true)) { return new OpenApiType( @@ -428,7 +432,7 @@ private static function mergeEnums(string $context, array $types): array { } } - return array_merge($nonEnums, array_map(static fn (string $type): \OpenAPIExtractor\OpenApiType => new OpenApiType( + return array_merge($nonEnums, array_map(static fn (string $type): OpenApiType => new OpenApiType( context: $context, type: $type, enum: $enums[$type], ), array_keys($enums))); @@ -436,7 +440,7 @@ private static function mergeEnums(string $context, array $types): array { private static function resolveIdentifier(string $context, array $definitions, string $name): OpenApiType { if ($name === 'array') { - Logger::error($context, "Instead of 'array' use:\n'new stdClass()' for empty objects\n'array' for non-empty objects\n'array' for empty lists\n'array' for lists"); + Logger::error($context, "Instead of 'array' use:\n'new stdClass()' for empty objects\n'non-empty-array' for non-empty objects\n'list' for empty lists\n'list' for lists"); } if (str_starts_with($name, '\\')) { $name = substr($name, 1); @@ -455,7 +459,7 @@ private static function resolveIdentifier(string $context, array $definitions, s 'numeric' => new OpenApiType(context: $context, type: 'number'), // https://www.php.net/manual/en/language.types.float.php: Both float and double are always stored with double precision 'float', 'double' => new OpenApiType(context: $context, type: 'number', format: 'double'), - 'mixed', 'empty', 'array' => new OpenApiType(context: $context, type: 'object'), + 'mixed', 'empty' => new OpenApiType(context: $context, type: 'object'), 'object', 'stdClass' => new OpenApiType(context: $context, type: 'object', additionalProperties: true), 'null' => new OpenApiType(context: $context, nullable: true), default => (function () use ($context, $definitions, $name) { diff --git a/tests/lib/Controller/ReturnArraysController.php b/tests/lib/Controller/ReturnArraysController.php index 749ff0b..72ecf19 100644 --- a/tests/lib/Controller/ReturnArraysController.php +++ b/tests/lib/Controller/ReturnArraysController.php @@ -17,7 +17,7 @@ class ReturnArraysController extends OCSController { /** * Route with array using string keys * - * @return DataResponse, array{}> + * @return DataResponse|\stdClass, array{}> * * 200: OK */ @@ -28,7 +28,7 @@ public function stringArray(): DataResponse { /** * Route with array using non-empty-string keys * - * @return DataResponse, array{}> + * @return DataResponse|\stdClass, array{}> * * 200: OK */ @@ -39,7 +39,7 @@ public function nonEmptyStringArray(): DataResponse { /** * Route with array using lowercase-string keys * - * @return DataResponse, array{}> + * @return DataResponse|\stdClass, array{}> * * 200: OK */ @@ -50,7 +50,7 @@ public function lowercaseStringArray(): DataResponse { /** * Route with array using non-empty-lowercase-string keys * - * @return DataResponse, array{}> + * @return DataResponse|\stdClass, array{}> * * 200: OK */ diff --git a/tests/lib/Controller/SettingsController.php b/tests/lib/Controller/SettingsController.php index be66871..773944f 100644 --- a/tests/lib/Controller/SettingsController.php +++ b/tests/lib/Controller/SettingsController.php @@ -400,7 +400,7 @@ public function arrayListParameter(array $value = ['test']): DataResponse { /** * A route with keyed array * - * @param array $value Some array value + * @param non-empty-array|\stdClass $value Some array value * @return DataResponse, array{}> * * 200: Admin settings updated @@ -539,7 +539,7 @@ public function emptyArray(): DataResponse { * Route with parameter * * @param int $simple Value - * @param array $complex Values + * @param non-empty-array|\stdClass $complex Values * @return DataResponse}, array{}> * * 200: OK @@ -553,8 +553,8 @@ public function parameterRequestBody(int $simple, array $complex): DataResponse /** * Route with object defaults * - * @param array $empty Empty - * @param array $values Values + * @param non-empty-array|\stdClass $empty Empty + * @param non-empty-array|\stdClass $values Values * @return DataResponse}, array{}> * * 200: OK diff --git a/tests/lib/ResponseDefinitions.php b/tests/lib/ResponseDefinitions.php index c37f73b..572eb75 100644 --- a/tests/lib/ResponseDefinitions.php +++ b/tests/lib/ResponseDefinitions.php @@ -38,9 +38,9 @@ * link: string, * actions: list, * subjectRich?: string, - * subjectRichParameters?: array, + * subjectRichParameters?: non-empty-array|\stdClass, * messageRich?: string, - * messageRichParameters?: array, + * messageRichParameters?: non-empty-array|\stdClass, * icon?: string, * shouldNotify?: bool, * nonEmptyList: non-empty-list,