diff --git a/composer.lock b/composer.lock index b56b7b387d..cf15b7657e 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php index 480fce58b0..c50dea2713 100644 --- a/src/Appwrite/Utopia/Request.php +++ b/src/Appwrite/Utopia/Request.php @@ -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; diff --git a/tests/unit/Utopia/RequestTest.php b/tests/unit/Utopia/RequestTest.php index 26273f154e..63655de21d 100644 --- a/tests/unit/Utopia/RequestTest.php +++ b/tests/unit/Utopia/RequestTest.php @@ -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); + } }