Skip to content

Commit e87edc7

Browse files
committed
feat: Add warning for undefined serialization behavior
Signed-off-by: provokateurin <[email protected]>
1 parent 6572256 commit e87edc7

File tree

3 files changed

+82
-33
lines changed

3 files changed

+82
-33
lines changed

src/ControllerMethod.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ public static function parse(string $context, array $definitions, ClassMethod $m
8484
}
8585

8686
if (str_starts_with($type->name, 'OCS') && str_ends_with($type->name, 'Exception')) {
87-
$responses[] = new ControllerMethodResponse($docNode->value->type, $statusCode, "application/json", new OpenApiType(type: "array", maxItems: 0), null);
87+
$responses[] = new ControllerMethodResponse($docNode->value->type, $statusCode, "application/json", new OpenApiType(context: $context, type: "array", maxItems: 0), null);
8888
} else {
89-
$responses[] = new ControllerMethodResponse($docNode->value->type, $statusCode, "text/plain", new OpenApiType(type: "string"), null);
89+
$responses[] = new ControllerMethodResponse($docNode->value->type, $statusCode, "text/plain", new OpenApiType(context: $context, type: "string"), null);
9090
}
9191
}
9292
}

src/OpenApiType.php

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/ResponseType.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ public function __construct(
2222

2323
/** @return ResponseType[] */
2424
public static function getAll(): array {
25-
$stringType = new OpenApiType(type: "string");
26-
$binaryType = new OpenApiType(type: "string", format: "binary");
25+
$context = 'Response Types';
26+
$stringType = new OpenApiType(context: $context, type: "string");
27+
$binaryType = new OpenApiType(context: $context, type: "string", format: "binary");
2728
return [
2829
new ResponseType(
2930
"DataDisplayResponse",

0 commit comments

Comments
 (0)