Skip to content

Commit 6283132

Browse files
Merge pull request #90 from nextcloud/fix/openapitype/array-parsing
2 parents 3b7b6ff + 28f0b01 commit 6283132

File tree

7 files changed

+425
-46
lines changed

7 files changed

+425
-46
lines changed

src/ControllerMethodParameter.php

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,7 @@
22

33
namespace OpenAPIExtractor;
44

5-
use PhpParser\Node\Expr;
6-
use PhpParser\Node\Expr\Array_;
7-
use PhpParser\Node\Expr\ArrayItem;
8-
use PhpParser\Node\Expr\ConstFetch;
9-
use PhpParser\Node\Expr\UnaryMinus;
105
use PhpParser\Node\Param;
11-
use PhpParser\Node\Scalar\LNumber;
12-
use PhpParser\Node\Scalar\String_;
136

147
class ControllerMethodParameter {
158
public OpenApiType $type;
@@ -21,46 +14,12 @@ public function __construct(string $context, array $definitions, public string $
2114
$this->type = OpenApiType::resolve($context, $definitions, $methodParameter->type);
2215
}
2316
if ($methodParameter->default != null) {
24-
$this->type->hasDefaultValue = true;
25-
$this->type->defaultValue = self::exprToValue($context, $methodParameter->default);
26-
}
27-
}
28-
29-
private static function exprToValue(string $context, Expr $expr): mixed {
30-
if ($expr instanceof ConstFetch) {
31-
$value = $expr->name->getLast();
32-
return match ($value) {
33-
"null" => null,
34-
"true" => true,
35-
"false" => false,
36-
default => Logger::panic($context, "Unable to evaluate constant value '" . $value . "'"),
37-
};
38-
}
39-
if ($expr instanceof String_) {
40-
return $expr->value;
41-
}
42-
if ($expr instanceof LNumber) {
43-
return intval($expr->value);
44-
}
45-
if ($expr instanceof UnaryMinus) {
46-
return -self::exprToValue($context, $expr->expr);
47-
}
48-
if ($expr instanceof Array_) {
49-
$values = array_map(fn (ArrayItem $item): mixed => self::exprToValue($context, $item), $expr->items);
50-
$filteredValues = array_filter($values, fn (mixed $value) => $value !== null);
51-
if (count($filteredValues) != count($values)) {
52-
return null;
17+
try {
18+
$this->type->defaultValue = Helpers::exprToValue($context, $methodParameter->default);
19+
$this->type->hasDefaultValue = true;
20+
} catch (UnsupportedExprException $e) {
21+
Logger::debug($context, $e);
5322
}
54-
return $values;
55-
}
56-
if ($expr instanceof ArrayItem) {
57-
return self::exprToValue($context, $expr->value);
5823
}
59-
if ($expr instanceof Expr\ClassConstFetch || $expr instanceof Expr\BinaryOp) {
60-
// Not supported
61-
return null;
62-
}
63-
64-
Logger::panic($context, "Unable to evaluate expression '" . get_class($expr) . "'");
6524
}
6625
}

src/Helpers.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
use PhpParser\Node;
77
use PhpParser\Node\Arg;
88
use PhpParser\Node\AttributeGroup;
9+
use PhpParser\Node\Expr;
910
use PhpParser\Node\Expr\Array_;
1011
use PhpParser\Node\Expr\ClassConstFetch;
12+
use PhpParser\Node\Expr\ConstFetch;
13+
use PhpParser\Node\Expr\UnaryMinus;
14+
use PhpParser\Node\Scalar\LNumber;
1115
use PhpParser\Node\Scalar\String_;
1216
use PhpParser\Node\Stmt\Class_;
1317
use PhpParser\Node\Stmt\ClassMethod;
@@ -260,4 +264,47 @@ public static function collectUsedRefs(array $data): array {
260264
});
261265
return $refs;
262266
}
267+
268+
/**
269+
* @throws LoggerException
270+
* @throws UnsupportedExprException
271+
*/
272+
public static function exprToValue(string $context, Expr $expr): mixed {
273+
if ($expr instanceof ConstFetch) {
274+
$value = $expr->name->getLast();
275+
return match ($value) {
276+
'null' => null,
277+
'true' => true,
278+
'false' => false,
279+
default => Logger::panic($context, "Unable to evaluate constant value '$value'"),
280+
};
281+
}
282+
if ($expr instanceof String_) {
283+
return $expr->value;
284+
}
285+
if ($expr instanceof LNumber) {
286+
return intval($expr->value);
287+
}
288+
if ($expr instanceof UnaryMinus) {
289+
return -self::exprToValue($context, $expr->expr);
290+
}
291+
if ($expr instanceof Array_) {
292+
$array = [];
293+
foreach ($expr->items as $item) {
294+
try {
295+
$value = self::exprToValue($context, $item->value);
296+
if ($item->key !== null) {
297+
$array[self::exprToValue($context, $item->key)] = $value;
298+
} else {
299+
$array[] = $value;
300+
}
301+
} catch (UnsupportedExprException $e) {
302+
Logger::debug($context, $e);
303+
}
304+
}
305+
return $array;
306+
}
307+
308+
throw new UnsupportedExprException($expr, $context);
309+
}
263310
}

src/UnsupportedExprException.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace OpenAPIExtractor;
4+
5+
use Exception;
6+
use PhpParser\Node\Expr;
7+
8+
class UnsupportedExprException extends Exception {
9+
public function __construct(
10+
public Expr $expr,
11+
public string $context,
12+
) {
13+
parent::__construct($this->context . ': Unable to parse Expr: ' . get_class($this->expr));
14+
}
15+
}

tests/appinfo/routes.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,7 @@
6161
['name' => 'Settings#stringValueParameter', 'url' => '/api/{apiVersion}/string-value', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
6262
['name' => 'Settings#intValueParameter', 'url' => '/api/{apiVersion}/int-value', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
6363
['name' => 'Settings#numericParameter', 'url' => '/api/{apiVersion}/numeric', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
64+
['name' => 'Settings#arrayListParameter', 'url' => '/api/{apiVersion}/array-list', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
65+
['name' => 'Settings#arrayKeyedParameter', 'url' => '/api/{apiVersion}/array-keyed', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
6466
],
6567
];

tests/lib/Controller/SettingsController.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,4 +386,28 @@ public function intValueParameter(int $value): DataResponse {
386386
public function numericParameter(mixed $value): DataResponse {
387387
return new DataResponse();
388388
}
389+
390+
/**
391+
* A route with list
392+
*
393+
* @param list<string> $value Some array value
394+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
395+
*
396+
* 200: Admin settings updated
397+
*/
398+
public function arrayListParameter(array $value = ['test']): DataResponse {
399+
return new DataResponse();
400+
}
401+
402+
/**
403+
* A route with keyed array
404+
*
405+
* @param array<string, string> $value Some array value
406+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
407+
*
408+
* 200: Admin settings updated
409+
*/
410+
public function arrayKeyedParameter(array $value = ['test' => 'abc']): DataResponse {
411+
return new DataResponse();
412+
}
389413
}

tests/openapi-administration.json

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2161,6 +2161,172 @@
21612161
}
21622162
}
21632163
}
2164+
},
2165+
"/ocs/v2.php/apps/notifications/api/{apiVersion}/array-list": {
2166+
"post": {
2167+
"operationId": "settings-array-list-parameter",
2168+
"summary": "A route with list",
2169+
"description": "This endpoint requires admin access",
2170+
"tags": [
2171+
"settings"
2172+
],
2173+
"security": [
2174+
{
2175+
"bearer_auth": []
2176+
},
2177+
{
2178+
"basic_auth": []
2179+
}
2180+
],
2181+
"parameters": [
2182+
{
2183+
"name": "value[]",
2184+
"in": "query",
2185+
"description": "Some array value",
2186+
"schema": {
2187+
"type": "array",
2188+
"default": [
2189+
"test"
2190+
],
2191+
"items": {
2192+
"type": "string"
2193+
}
2194+
}
2195+
},
2196+
{
2197+
"name": "apiVersion",
2198+
"in": "path",
2199+
"required": true,
2200+
"schema": {
2201+
"type": "string",
2202+
"enum": [
2203+
"v2"
2204+
],
2205+
"default": "v2"
2206+
}
2207+
},
2208+
{
2209+
"name": "OCS-APIRequest",
2210+
"in": "header",
2211+
"description": "Required to be true for the API request to pass",
2212+
"required": true,
2213+
"schema": {
2214+
"type": "boolean",
2215+
"default": true
2216+
}
2217+
}
2218+
],
2219+
"responses": {
2220+
"200": {
2221+
"description": "Admin settings updated",
2222+
"content": {
2223+
"application/json": {
2224+
"schema": {
2225+
"type": "object",
2226+
"required": [
2227+
"ocs"
2228+
],
2229+
"properties": {
2230+
"ocs": {
2231+
"type": "object",
2232+
"required": [
2233+
"meta",
2234+
"data"
2235+
],
2236+
"properties": {
2237+
"meta": {
2238+
"$ref": "#/components/schemas/OCSMeta"
2239+
},
2240+
"data": {}
2241+
}
2242+
}
2243+
}
2244+
}
2245+
}
2246+
}
2247+
}
2248+
}
2249+
}
2250+
},
2251+
"/ocs/v2.php/apps/notifications/api/{apiVersion}/array-keyed": {
2252+
"post": {
2253+
"operationId": "settings-array-keyed-parameter",
2254+
"summary": "A route with keyed array",
2255+
"description": "This endpoint requires admin access",
2256+
"tags": [
2257+
"settings"
2258+
],
2259+
"security": [
2260+
{
2261+
"bearer_auth": []
2262+
},
2263+
{
2264+
"basic_auth": []
2265+
}
2266+
],
2267+
"parameters": [
2268+
{
2269+
"name": "value",
2270+
"in": "query",
2271+
"description": "Some array value",
2272+
"schema": {
2273+
"type": "string"
2274+
}
2275+
},
2276+
{
2277+
"name": "apiVersion",
2278+
"in": "path",
2279+
"required": true,
2280+
"schema": {
2281+
"type": "string",
2282+
"enum": [
2283+
"v2"
2284+
],
2285+
"default": "v2"
2286+
}
2287+
},
2288+
{
2289+
"name": "OCS-APIRequest",
2290+
"in": "header",
2291+
"description": "Required to be true for the API request to pass",
2292+
"required": true,
2293+
"schema": {
2294+
"type": "boolean",
2295+
"default": true
2296+
}
2297+
}
2298+
],
2299+
"responses": {
2300+
"200": {
2301+
"description": "Admin settings updated",
2302+
"content": {
2303+
"application/json": {
2304+
"schema": {
2305+
"type": "object",
2306+
"required": [
2307+
"ocs"
2308+
],
2309+
"properties": {
2310+
"ocs": {
2311+
"type": "object",
2312+
"required": [
2313+
"meta",
2314+
"data"
2315+
],
2316+
"properties": {
2317+
"meta": {
2318+
"$ref": "#/components/schemas/OCSMeta"
2319+
},
2320+
"data": {}
2321+
}
2322+
}
2323+
}
2324+
}
2325+
}
2326+
}
2327+
}
2328+
}
2329+
}
21642330
}
21652331
},
21662332
"tags": []

0 commit comments

Comments
 (0)