Skip to content

Commit

Permalink
Merge pull request #90 from nextcloud/fix/openapitype/array-parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
provokateurin authored Jan 29, 2024
2 parents 3b7b6ff + 28f0b01 commit 6283132
Show file tree
Hide file tree
Showing 7 changed files with 425 additions and 46 deletions.
51 changes: 5 additions & 46 deletions src/ControllerMethodParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@

namespace OpenAPIExtractor;

use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\UnaryMinus;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;

class ControllerMethodParameter {
public OpenApiType $type;
Expand All @@ -21,46 +14,12 @@ public function __construct(string $context, array $definitions, public string $
$this->type = OpenApiType::resolve($context, $definitions, $methodParameter->type);
}
if ($methodParameter->default != null) {
$this->type->hasDefaultValue = true;
$this->type->defaultValue = self::exprToValue($context, $methodParameter->default);
}
}

private static function exprToValue(string $context, Expr $expr): mixed {
if ($expr instanceof ConstFetch) {
$value = $expr->name->getLast();
return match ($value) {
"null" => null,
"true" => true,
"false" => false,
default => Logger::panic($context, "Unable to evaluate constant value '" . $value . "'"),
};
}
if ($expr instanceof String_) {
return $expr->value;
}
if ($expr instanceof LNumber) {
return intval($expr->value);
}
if ($expr instanceof UnaryMinus) {
return -self::exprToValue($context, $expr->expr);
}
if ($expr instanceof Array_) {
$values = array_map(fn (ArrayItem $item): mixed => self::exprToValue($context, $item), $expr->items);
$filteredValues = array_filter($values, fn (mixed $value) => $value !== null);
if (count($filteredValues) != count($values)) {
return null;
try {
$this->type->defaultValue = Helpers::exprToValue($context, $methodParameter->default);
$this->type->hasDefaultValue = true;
} catch (UnsupportedExprException $e) {
Logger::debug($context, $e);
}
return $values;
}
if ($expr instanceof ArrayItem) {
return self::exprToValue($context, $expr->value);
}
if ($expr instanceof Expr\ClassConstFetch || $expr instanceof Expr\BinaryOp) {
// Not supported
return null;
}

Logger::panic($context, "Unable to evaluate expression '" . get_class($expr) . "'");
}
}
47 changes: 47 additions & 0 deletions src/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\UnaryMinus;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
Expand Down Expand Up @@ -260,4 +264,47 @@ public static function collectUsedRefs(array $data): array {
});
return $refs;
}

/**
* @throws LoggerException
* @throws UnsupportedExprException
*/
public static function exprToValue(string $context, Expr $expr): mixed {
if ($expr instanceof ConstFetch) {
$value = $expr->name->getLast();
return match ($value) {
'null' => null,
'true' => true,
'false' => false,
default => Logger::panic($context, "Unable to evaluate constant value '$value'"),
};
}
if ($expr instanceof String_) {
return $expr->value;
}
if ($expr instanceof LNumber) {
return intval($expr->value);
}
if ($expr instanceof UnaryMinus) {
return -self::exprToValue($context, $expr->expr);
}
if ($expr instanceof Array_) {
$array = [];
foreach ($expr->items as $item) {
try {
$value = self::exprToValue($context, $item->value);
if ($item->key !== null) {
$array[self::exprToValue($context, $item->key)] = $value;
} else {
$array[] = $value;
}
} catch (UnsupportedExprException $e) {
Logger::debug($context, $e);
}
}
return $array;
}

throw new UnsupportedExprException($expr, $context);
}
}
15 changes: 15 additions & 0 deletions src/UnsupportedExprException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace OpenAPIExtractor;

use Exception;
use PhpParser\Node\Expr;

class UnsupportedExprException extends Exception {
public function __construct(
public Expr $expr,
public string $context,
) {
parent::__construct($this->context . ': Unable to parse Expr: ' . get_class($this->expr));
}
}
2 changes: 2 additions & 0 deletions tests/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,7 @@
['name' => 'Settings#stringValueParameter', 'url' => '/api/{apiVersion}/string-value', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
['name' => 'Settings#intValueParameter', 'url' => '/api/{apiVersion}/int-value', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
['name' => 'Settings#numericParameter', 'url' => '/api/{apiVersion}/numeric', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
['name' => 'Settings#arrayListParameter', 'url' => '/api/{apiVersion}/array-list', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
['name' => 'Settings#arrayKeyedParameter', 'url' => '/api/{apiVersion}/array-keyed', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
],
];
24 changes: 24 additions & 0 deletions tests/lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,28 @@ public function intValueParameter(int $value): DataResponse {
public function numericParameter(mixed $value): DataResponse {
return new DataResponse();
}

/**
* A route with list
*
* @param list<string> $value Some array value
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Admin settings updated
*/
public function arrayListParameter(array $value = ['test']): DataResponse {
return new DataResponse();
}

/**
* A route with keyed array
*
* @param array<string, string> $value Some array value
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Admin settings updated
*/
public function arrayKeyedParameter(array $value = ['test' => 'abc']): DataResponse {
return new DataResponse();
}
}
166 changes: 166 additions & 0 deletions tests/openapi-administration.json
Original file line number Diff line number Diff line change
Expand Up @@ -2161,6 +2161,172 @@
}
}
}
},
"/ocs/v2.php/apps/notifications/api/{apiVersion}/array-list": {
"post": {
"operationId": "settings-array-list-parameter",
"summary": "A route with list",
"description": "This endpoint requires admin access",
"tags": [
"settings"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "value[]",
"in": "query",
"description": "Some array value",
"schema": {
"type": "array",
"default": [
"test"
],
"items": {
"type": "string"
}
}
},
{
"name": "apiVersion",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": [
"v2"
],
"default": "v2"
}
},
{
"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": "Admin settings updated",
"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/apps/notifications/api/{apiVersion}/array-keyed": {
"post": {
"operationId": "settings-array-keyed-parameter",
"summary": "A route with keyed array",
"description": "This endpoint requires admin access",
"tags": [
"settings"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "value",
"in": "query",
"description": "Some array value",
"schema": {
"type": "string"
}
},
{
"name": "apiVersion",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": [
"v2"
],
"default": "v2"
}
},
{
"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": "Admin settings updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
}
}
},
"tags": []
Expand Down
Loading

0 comments on commit 6283132

Please sign in to comment.