@@ -29,6 +29,7 @@ class OpenApiType {
2929 * @param OpenApiType[]|null $allOf
3030 */
3131 public function __construct (
32+ public string $ context ,
3233 public ?string $ ref = null ,
3334 public ?string $ type = null ,
3435 public ?string $ format = null ,
@@ -57,6 +58,7 @@ public function toArray(bool $isParameter = false): array|stdClass {
5758 if ($ isParameter ) {
5859 if ($ this ->type === 'boolean ' ) {
5960 return (new OpenApiType (
61+ context: $ this ->context ,
6062 type: 'integer ' ,
6163 nullable: $ this ->nullable ,
6264 hasDefaultValue: $ this ->hasDefaultValue ,
@@ -67,7 +69,10 @@ enum: [0, 1],
6769 }
6870
6971 if ($ this ->type === 'object ' || $ this ->ref !== null || $ this ->anyOf !== null || $ this ->allOf !== null ) {
72+ Logger::warning ($ this ->context , 'Complex types can not be part of query or URL parameters. Falling back to string due to undefined serialization! ' );
73+
7074 return (new OpenApiType (
75+ context: $ this ->context ,
7176 type: 'string ' ,
7277 nullable: $ this ->nullable ,
7378 description: $ this ->description ,
@@ -160,13 +165,25 @@ public static function resolve(string $context, array $definitions, ParamTagValu
160165 }
161166
162167 if ($ node instanceof ArrayTypeNode) {
163- return new OpenApiType (type: "array " , items: self ::resolve ($ context , $ definitions , $ node ->type ));
168+ return new OpenApiType (
169+ context: $ context ,
170+ type: 'array ' ,
171+ items: self ::resolve ($ context . ': items ' , $ definitions , $ node ->type ),
172+ );
164173 }
165174 if ($ node instanceof GenericTypeNode && ($ node ->type ->name == "array " || $ node ->type ->name == "list " ) && count ($ node ->genericTypes ) == 1 ) {
166175 if ($ node ->genericTypes [0 ] instanceof IdentifierTypeNode && $ node ->genericTypes [0 ]->name == "empty " ) {
167- return new OpenApiType (type: "array " , maxItems: 0 );
176+ return new OpenApiType (
177+ context: $ context ,
178+ type: 'array ' ,
179+ maxItems: 0 ,
180+ );
168181 }
169- return new OpenApiType (type: "array " , items: self ::resolve ($ context , $ definitions , $ node ->genericTypes [0 ]));
182+ return new OpenApiType (
183+ context: $ context ,
184+ type: 'array ' ,
185+ items: self ::resolve ($ context , $ definitions , $ node ->genericTypes [0 ]),
186+ );
170187 }
171188 if ($ node instanceof GenericTypeNode && $ node ->type ->name === 'value-of ' ) {
172189 Logger::panic ($ context , "'value-of' is not supported " );
@@ -183,12 +200,21 @@ public static function resolve(string $context, array $definitions, ParamTagValu
183200 $ required [] = $ name ;
184201 }
185202 }
186- return new OpenApiType (type: "object " , properties: $ properties , required: count ($ required ) > 0 ? $ required : null );
203+ return new OpenApiType (
204+ context: $ context ,
205+ type: 'object ' ,
206+ properties: $ properties ,
207+ required: count ($ required ) > 0 ? $ required : null ,
208+ );
187209 }
188210
189211 if ($ node instanceof GenericTypeNode && $ node ->type ->name === "array " && count ($ node ->genericTypes ) === 2 && $ node ->genericTypes [0 ] instanceof IdentifierTypeNode) {
190212 if ($ node ->genericTypes [0 ]->name === "string " ) {
191- return new OpenApiType (type: "object " , additionalProperties: self ::resolve ($ context , $ definitions , $ node ->genericTypes [1 ]));
213+ return new OpenApiType (
214+ context: $ context ,
215+ type: 'object ' ,
216+ additionalProperties: self ::resolve ($ context . ': additionalProperties ' , $ definitions , $ node ->genericTypes [1 ]),
217+ );
192218 }
193219
194220 Logger::panic ($ context , "JSON objects can only be indexed by 'string' but got ' " . $ node ->genericTypes [0 ]->name . "' " );
@@ -204,6 +230,7 @@ public static function resolve(string $context, array $definitions, ParamTagValu
204230 $ max = $ node ->genericTypes [1 ]->constExpr ->value ;
205231 }
206232 return new OpenApiType (
233+ context: $ context ,
207234 type: "integer " ,
208235 format: "int64 " ,
209236 minimum: $ min ,
@@ -228,10 +255,17 @@ public static function resolve(string $context, array $definitions, ParamTagValu
228255
229256 if (count (array_filter ($ values , fn (string $ value ) => $ value == '' )) > 0 ) {
230257 // Not a valid enum
231- return new OpenApiType (type: "string " );
258+ return new OpenApiType (
259+ context: $ context ,
260+ type: 'string ' ,
261+ );
232262 }
233263
234- return new OpenApiType (type: "string " , enum: $ values );
264+ return new OpenApiType (
265+ context: $ context ,
266+ type: 'string ' ,
267+ enum: $ values ,
268+ );
235269 }
236270 if ($ isUnion && count ($ node ->types ) == count (array_filter ($ node ->types , fn ($ type ) => $ type instanceof ConstTypeNode && $ type ->constExpr instanceof ConstExprIntegerNode))) {
237271 $ values = [];
@@ -243,12 +277,14 @@ public static function resolve(string $context, array $definitions, ParamTagValu
243277 if (count (array_filter ($ values , fn (string $ value ) => $ value == '' )) > 0 ) {
244278 // Not a valid enum
245279 return new OpenApiType (
280+ context: $ context ,
246281 type: "integer " ,
247282 format: "int64 " ,
248283 );
249284 }
250285
251286 return new OpenApiType (
287+ context: $ context ,
252288 type: "integer " ,
253289 format: "int64 " ,
254290 enum: $ values ,
@@ -271,7 +307,7 @@ enum: $values,
271307 }
272308
273309 $ items = array_unique ($ items , SORT_REGULAR );
274- $ items = self ::mergeEnums ($ items );
310+ $ items = self ::mergeEnums ($ context , $ items );
275311
276312 if (count ($ items ) == 1 ) {
277313 $ type = $ items [0 ];
@@ -281,6 +317,7 @@ enum: $values,
281317
282318 if ($ isIntersection ) {
283319 return new OpenApiType (
320+ context: $ context ,
284321 nullable: $ nullable ,
285322 allOf: $ items ,
286323 );
@@ -295,12 +332,14 @@ enum: $values,
295332
296333 if (!empty (array_filter ($ itemTypes , static fn (?string $ type ) => $ type === null )) || count ($ itemTypes ) !== count (array_unique ($ itemTypes ))) {
297334 return new OpenApiType (
335+ context: $ context ,
298336 nullable: $ nullable ,
299337 anyOf: $ items ,
300338 );
301339 }
302340
303341 return new OpenApiType (
342+ context: $ context ,
304343 nullable: $ nullable ,
305344 oneOf: $ items ,
306345 );
@@ -310,18 +349,23 @@ enum: $values,
310349 $ value = $ node ->constExpr ->value ;
311350 if ($ value == '' ) {
312351 // Not a valid enum
313- return new OpenApiType (type: "string " );
352+ return new OpenApiType (
353+ context: $ context ,
354+ type: 'string '
355+ );
314356 }
315357 return new OpenApiType (
316- type: "string " ,
358+ context: $ context ,
359+ type: 'string ' ,
317360 enum: [$ node ->constExpr ->value ],
318361 );
319362 }
320363
321364 if ($ node instanceof ConstTypeNode && $ node ->constExpr instanceof ConstExprIntegerNode) {
322365 return new OpenApiType (
323- type: "integer " ,
324- format: "int64 " ,
366+ context: $ context ,
367+ type: 'integer ' ,
368+ format: 'int64 ' ,
325369 enum: [(int )$ node ->constExpr ->value ],
326370 );
327371 }
@@ -337,7 +381,7 @@ enum: [(int)$node->constExpr->value],
337381 * @param OpenApiType[] $types
338382 * @return OpenApiType[]
339383 */
340- private static function mergeEnums (array $ types ): array {
384+ private static function mergeEnums (string $ context , array $ types ): array {
341385 $ enums = [];
342386 $ nonEnums = [];
343387
@@ -359,7 +403,10 @@ private static function mergeEnums(array $types): array {
359403 }
360404 }
361405
362- return array_merge ($ nonEnums , array_map (fn (string $ type ) => new OpenApiType (type: $ type , enum: $ enums [$ type ]), array_keys ($ enums )));
406+ return array_merge ($ nonEnums , array_map (static fn (string $ type ) => new OpenApiType (
407+ context: $ context ,
408+ type: $ type , enum: $ enums [$ type ],
409+ ), array_keys ($ enums )));
363410 }
364411
365412 private static function resolveIdentifier (string $ context , array $ definitions , string $ name ): OpenApiType {
@@ -370,25 +417,26 @@ private static function resolveIdentifier(string $context, array $definitions, s
370417 $ name = substr ($ name , 1 );
371418 }
372419 return match ($ name ) {
373- "string " , "non-falsy-string " , "numeric-string " => new OpenApiType (type: "string " ),
374- "non-empty-string " => new OpenApiType (type: "string " , minLength: 1 ),
375- "int " , "integer " => new OpenApiType (type: "integer " , format: "int64 " ),
376- "non-negative-int " => new OpenApiType (type: "integer " , format: "int64 " , minimum: 0 ),
377- "positive-int " => new OpenApiType (type: "integer " , format: "int64 " , minimum: 1 ),
378- "negative-int " => new OpenApiType (type: "integer " , format: "int64 " , maximum: -1 ),
379- "non-positive-int " => new OpenApiType (type: "integer " , format: "int64 " , maximum: 0 ),
380- "bool " , "boolean " => new OpenApiType (type: "boolean " ),
381- "true " => new OpenApiType (type: "boolean " , enum: [true ]),
382- "false " => new OpenApiType (type: "boolean " , enum: [false ]),
383- "numeric " => new OpenApiType (type: "number " ),
420+ "string " , "non-falsy-string " , "numeric-string " => new OpenApiType (context: $ context , type: "string " ),
421+ "non-empty-string " => new OpenApiType (context: $ context , type: "string " , minLength: 1 ),
422+ "int " , "integer " => new OpenApiType (context: $ context , type: "integer " , format: "int64 " ),
423+ "non-negative-int " => new OpenApiType (context: $ context , type: "integer " , format: "int64 " , minimum: 0 ),
424+ "positive-int " => new OpenApiType (context: $ context , type: "integer " , format: "int64 " , minimum: 1 ),
425+ "negative-int " => new OpenApiType (context: $ context , type: "integer " , format: "int64 " , maximum: -1 ),
426+ "non-positive-int " => new OpenApiType (context: $ context , type: "integer " , format: "int64 " , maximum: 0 ),
427+ "bool " , "boolean " => new OpenApiType (context: $ context , type: "boolean " ),
428+ "true " => new OpenApiType (context: $ context , type: "boolean " , enum: [true ]),
429+ "false " => new OpenApiType (context: $ context , type: "boolean " , enum: [false ]),
430+ "numeric " => new OpenApiType (context: $ context , type: "number " ),
384431 // https://www.php.net/manual/en/language.types.float.php: Both float and double are always stored with double precision
385- "float " , "double " => new OpenApiType (type: "number " , format: "double " ),
386- "mixed " , "empty " , "array " => new OpenApiType (type: "object " ),
387- "object " , "stdClass " => new OpenApiType (type: "object " , additionalProperties: true ),
388- "null " => new OpenApiType (nullable: true ),
432+ "float " , "double " => new OpenApiType (context: $ context , type: "number " , format: "double " ),
433+ "mixed " , "empty " , "array " => new OpenApiType (context: $ context , type: "object " ),
434+ "object " , "stdClass " => new OpenApiType (context: $ context , type: "object " , additionalProperties: true ),
435+ "null " => new OpenApiType (context: $ context , nullable: true ),
389436 default => (function () use ($ context , $ definitions , $ name ) {
390437 if (array_key_exists ($ name , $ definitions )) {
391438 return new OpenApiType (
439+ context: $ context ,
392440 ref: "#/components/schemas/ " . Helpers::cleanSchemaName ($ name ),
393441 );
394442 }
0 commit comments