Merge pull request #9743 from appwrite/fix-filters

Fix request filters with multi-method routes
This commit is contained in:
Matej Bačo 2025-05-12 10:22:26 +02:00 committed by GitHub
commit 1276e74e79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 172 additions and 30 deletions

12
composer.lock generated
View file

@ -4769,16 +4769,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.40.15",
"version": "0.40.16",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "65c708b931b29b3e01c5cc7504a734ce2cc3dc95"
"reference": "f1f506da74033f0cb5a11e3dffcfd1ee8daf237d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/65c708b931b29b3e01c5cc7504a734ce2cc3dc95",
"reference": "65c708b931b29b3e01c5cc7504a734ce2cc3dc95",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f1f506da74033f0cb5a11e3dffcfd1ee8daf237d",
"reference": "f1f506da74033f0cb5a11e3dffcfd1ee8daf237d",
"shasum": ""
},
"require": {
@ -4814,9 +4814,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.15"
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.16"
},
"time": "2025-04-25T08:50:44+00:00"
"time": "2025-05-09T12:06:09+00:00"
},
{
"name": "doctrine/annotations",

View file

@ -3,6 +3,7 @@
namespace Appwrite\Utopia;
use Appwrite\Auth\Auth;
use Appwrite\SDK\Method;
use Appwrite\Utopia\Request\Filter;
use Swoole\Http\Request as SwooleRequest;
use Utopia\Database\Validator\Authorization;
@ -29,35 +30,49 @@ class Request extends UtopiaRequest
{
$parameters = parent::getParams();
if ($this->hasFilters() && self::hasRoute()) {
$methods = self::getRoute()->getLabel('sdk', null);
if (!$this->hasFilters() || !self::hasRoute()) {
return $parameters;
}
if (!\is_array($methods)) {
$methods = [$methods];
}
$methods = self::getRoute()->getLabel('sdk', null);
$params = [];
foreach ($methods as $method) {
/** @var \Appwrite\SDK\Method $method */
if (empty($method)) {
$endpointIdentifier = 'unknown.unknown';
} else {
$endpointIdentifier = $method->getNamespace() . '.' . $method->getMethodName();
}
$params += $method->getParameters();
}
if (!empty($params)) {
$parameters = array_filter($parameters, function ($key) use ($params) {
return array_key_exists($key, $params);
}, \ARRAY_FILTER_USE_KEY);
}
if (empty($methods)) {
return $parameters;
}
if (!\is_array($methods)) {
$id = $methods->getNamespace() . '.' . $methods->getMethodName();
foreach ($this->getFilters() as $filter) {
$parameters = $filter->parse($parameters, $endpointIdentifier);
$parameters = $filter->parse($parameters, $id);
}
return $parameters;
}
$matched = null;
foreach ($methods as $method) {
/** @var Method|null $method */
if ($method === null) {
continue;
}
// Find the method that matches the parameters passed
$methodParamNames = \array_map(fn ($param) => $param->getName(), $method->getParameters());
$invalidParams = \array_diff(\array_keys($parameters), $methodParamNames);
// No params defined, or all params are valid
if (empty($methodParamNames) || empty($invalidParams)) {
$matched = $method;
break;
}
}
$id = $matched !== null
? $matched->getNamespace() . '.' . $matched->getMethodName()
: 'unknown.unknown';
// Apply filters
foreach ($this->getFilters() as $filter) {
$parameters = $filter->parse($parameters, $id);
}
return $parameters;

View file

@ -3,6 +3,7 @@
namespace Tests\Unit\Utopia;
use Appwrite\SDK\Method;
use Appwrite\SDK\Parameter;
use Appwrite\Utopia\Request;
use PHPUnit\Framework\TestCase;
use Swoole\Http\Request as SwooleRequest;
@ -57,4 +58,130 @@ class RequestTest extends TestCase
$this->assertTrue($output['second']);
$this->assertArrayNotHasKey('deleted', $output);
}
public function testGetParamsWithMultipleMethods(): void
{
$this->setupMultiMethodRoute();
// Pass only "foo", should match Method A
$this->request->setQueryString([
'foo' => 'valueFoo',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('foo', $params);
$this->assertSame('valueFoo', $params['foo']);
$this->assertArrayNotHasKey('baz', $params);
}
public function testGetParamsWithAllRequired(): void
{
$this->setupMultiMethodRoute();
// Pass "foo" and "bar", should match Method A
$this->request->setQueryString([
'foo' => 'valueFoo',
'bar' => 'valueBar',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('foo', $params);
$this->assertSame('valueFoo', $params['foo']);
$this->assertArrayHasKey('bar', $params);
$this->assertSame('valueBar', $params['bar']);
$this->assertArrayNotHasKey('baz', $params);
}
public function testGetParamsWithAllOptional(): void
{
$this->setupMultiMethodRoute();
// Pass only "bar", should match Method A
$this->request->setQueryString([
'bar' => 'valueBar',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('bar', $params);
$this->assertSame('valueBar', $params['bar']);
$this->assertArrayNotHasKey('foo', $params);
$this->assertArrayNotHasKey('baz', $params);
}
public function testGetParamsMatchesMethodB(): void
{
$this->setupMultiMethodRoute();
// Pass only "baz", should match Method B
$this->request->setQueryString([
'baz' => 'valueBaz',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('baz', $params);
$this->assertSame('valueBaz', $params['baz']);
$this->assertArrayNotHasKey('foo', $params);
}
public function testGetParamsFallbackForMixedAndUnknown(): void
{
$this->setupMultiMethodRoute();
// Mixed and unknown should fallback to raw params
$this->request->setQueryString([
'foo' => 'valueFoo',
'baz' => 'valueBaz',
'extra' => 'unexpected',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('foo', $params);
$this->assertSame('valueFoo', $params['foo']);
$this->assertArrayHasKey('baz', $params);
$this->assertSame('valueBaz', $params['baz']);
$this->assertArrayHasKey('extra', $params);
$this->assertSame('unexpected', $params['extra']);
}
/**
* Helper to attach a route with multiple SDK methods to the request.
*/
private function setupMultiMethodRoute(): void
{
$route = new Route(Request::METHOD_GET, '/multi');
$methodA = new Method(
namespace: 'namespace',
group: 'group',
name: 'methodA',
description: 'desc',
auth: [],
responses: [],
parameters: [
new Parameter('foo'),
new Parameter('bar', optional: true),
],
);
$methodB = new Method(
namespace: 'namespace',
group: 'group',
name: 'methodB',
description: 'desc',
auth: [],
responses: [],
parameters: [
new Parameter('baz'),
],
);
$route->label('sdk', [$methodA, $methodB]);
$this->request->addFilter(new First());
$this->request->addFilter(new Second());
$this->request->setRoute($route);
}
}