Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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