diff --git a/Dockerfile b/Dockerfile
index 1922a0d2b9..9a61635415 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
-FROM appwrite/base:1.2.1 AS base
+FROM appwrite/base:1.4.1 AS base
LABEL maintainer="team@appwrite.io"
diff --git a/app/config/scopes/project.php b/app/config/scopes/project.php
index 7f39d6db95..bd30da9aac 100644
--- a/app/config/scopes/project.php
+++ b/app/config/scopes/project.php
@@ -215,6 +215,16 @@ return [
'description' => 'Access to create function executions',
'category' => 'Functions',
],
+ 'execution.read' => [
+ 'description' => 'Access to read function executions. This scope is deprecated for consistency purposes, and replaced by `executions.read`.',
+ 'category' => 'Functions',
+ 'deprecated' => true,
+ ],
+ 'execution.write' => [
+ 'description' => 'Access to create function executions. This scope is deprecated for consistency purposes, and replaced by `executions.write`.',
+ 'category' => 'Functions',
+ 'deprecated' => true,
+ ],
// Sites
'sites.read' => [
diff --git a/app/config/templates/function.php b/app/config/templates/function.php
index df3a569705..c6ac446509 100644
--- a/app/config/templates/function.php
+++ b/app/config/templates/function.php
@@ -79,12 +79,13 @@ return [
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter', $allowList),
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter', $allowList),
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter', $allowList),
+ ...getRuntimes($templateRuntimes['RUST'], '', 'main.rs', 'rust/starter', $allowList),
],
- 'instructions' => 'For documentation and instructions check out file.',
+ 'instructions' => 'For documentation and instructions check out the templates repository.',
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
- 'providerVersion' => '0.2.*',
+ 'providerVersion' => '0.3.*',
'variables' => [],
'scopes' => ['users.read']
],
diff --git a/app/realtime.php b/app/realtime.php
index 12d2a5c600..f72609dee8 100644
--- a/app/realtime.php
+++ b/app/realtime.php
@@ -325,14 +325,8 @@ if (!function_exists('triggerPresenceUsage')) {
if (!function_exists('getQueueForEventsForProject')) {
function getQueueForEventsForProject(Document $project, User $user): QueueEvent
{
- global $register;
-
- /** @var Group $pools */
- $pools = $register->get('pools');
-
- $queueForEvents = new QueueEvent(new BrokerPool(
- publisher: $pools->get('publisher')
- ));
+ global $container;
+ $queueForEvents = $container->get('queueForEvents');
$queueForEvents->setProject($project);
$queueForEvents->setUser($user);
@@ -343,8 +337,6 @@ if (!function_exists('getQueueForEventsForProject')) {
if (!function_exists('triggerPresenceEvent')) {
function triggerPresenceEvent(
- Server $server,
- Realtime $realtime,
Document $project,
User $user,
string $eventName,
@@ -410,12 +402,25 @@ if (!function_exists('setPermission')) {
}
global $container;
+
$container->set('pools', function ($register) {
return $register->get('pools');
}, ['register']);
-$container->set('queueForRealtime', function () {
- return new QueueRealtime();
-}, []);
+
+if (!$container->has('queueForEvents')) {
+ $container->set('queueForEvents', function ($pools) {
+ var_dump("ce");
+ return new QueueEvent(new BrokerPool(
+ publisher: $pools->get('publisher')
+ ));
+ }, ['pools']);
+}
+
+if (!$container->has('queueForRealtime')) {
+ $container->set('queueForRealtime', function () {
+ return new QueueRealtime();
+ }, []);
+}
$realtime = getRealtime();
$presenceState = new PresenceState();
@@ -1292,7 +1297,7 @@ $server->onMessage(function (int $connection, string $message) use ($container,
}
});
-$server->onClose(function (int $connection) use ($server, $realtime, $stats, $register) {
+$server->onClose(function (int $connection) use ($realtime, $stats, $register) {
$projectId = null;
$userId = null;
$subscriptionsBeforeClose = 0;
@@ -1349,7 +1354,7 @@ $server->onClose(function (int $connection) use ($server, $realtime, $stats, $re
foreach ($presences as $presence) {
try {
- triggerPresenceEvent($server, $realtime, $project, new User([]), 'presences.[presenceId].delete', $presence);
+ triggerPresenceEvent($project, new User([]), 'presences.[presenceId].delete', $presence);
} catch (Throwable) {
// Swallow errors to avoid breaking disconnect cleanup
}
diff --git a/composer.json b/composer.json
index 683da6f21b..236e3aa83d 100644
--- a/composer.json
+++ b/composer.json
@@ -49,7 +49,7 @@
"ext-openssl": "*",
"ext-zlib": "*",
"ext-sockets": "*",
- "appwrite/php-runtimes": "0.19.*",
+ "appwrite/php-runtimes": "0.20.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.2.*",
"utopia-php/agents": "1.2.*",
@@ -93,7 +93,7 @@
"chillerlan/php-qrcode": "4.3.*",
"adhocore/jwt": "1.1.*",
"spomky-labs/otphp": "11.*",
- "webonyx/graphql-php": "15.31.*",
+ "webonyx/graphql-php": "15.32.*",
"league/csv": "9.14.*",
"enshrined/svg-sanitize": "0.22.*"
},
diff --git a/composer.lock b/composer.lock
index d2018fdea0..de52fe7854 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4bee36b21a57e754d2b3417e72dc9599",
+ "content-hash": "2af4d953af2a624be8bf2f89ea27336c",
"packages": [
{
"name": "adhocore/jwt",
@@ -161,16 +161,16 @@
},
{
"name": "appwrite/php-runtimes",
- "version": "0.19.5",
+ "version": "0.20.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
- "reference": "aa2f7760cd0493c0880209b92df812c9386b3546"
+ "reference": "7d9b7f4eef5c0a142a60907b06de2219d025c5c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/appwrite/runtimes/zipball/aa2f7760cd0493c0880209b92df812c9386b3546",
- "reference": "aa2f7760cd0493c0880209b92df812c9386b3546",
+ "url": "https://api.github.com/repos/appwrite/runtimes/zipball/7d9b7f4eef5c0a142a60907b06de2219d025c5c3",
+ "reference": "7d9b7f4eef5c0a142a60907b06de2219d025c5c3",
"shasum": ""
},
"require": {
@@ -210,9 +210,9 @@
],
"support": {
"issues": "https://github.com/appwrite/runtimes/issues",
- "source": "https://github.com/appwrite/runtimes/tree/0.19.5"
+ "source": "https://github.com/appwrite/runtimes/tree/0.20.0"
},
- "time": "2026-04-01T01:39:23+00:00"
+ "time": "2026-05-01T07:47:07+00:00"
},
{
"name": "brick/math",
@@ -3502,16 +3502,16 @@
},
{
"name": "utopia-php/audit",
- "version": "2.2.1",
+ "version": "2.2.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
- "reference": "e3e2d6ad5c7f6377d9237df296a12eb7943892fd"
+ "reference": "90886c202e7983999e6b6a8201004d5ab61d4b57"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/audit/zipball/e3e2d6ad5c7f6377d9237df296a12eb7943892fd",
- "reference": "e3e2d6ad5c7f6377d9237df296a12eb7943892fd",
+ "url": "https://api.github.com/repos/utopia-php/audit/zipball/90886c202e7983999e6b6a8201004d5ab61d4b57",
+ "reference": "90886c202e7983999e6b6a8201004d5ab61d4b57",
"shasum": ""
},
"require": {
@@ -3545,9 +3545,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
- "source": "https://github.com/utopia-php/audit/tree/2.2.1"
+ "source": "https://github.com/utopia-php/audit/tree/2.2.2"
},
- "time": "2026-02-02T10:39:25+00:00"
+ "time": "2026-05-04T06:48:58+00:00"
},
{
"name": "utopia-php/auth",
@@ -5384,16 +5384,16 @@
},
{
"name": "webonyx/graphql-php",
- "version": "v15.31.5",
+ "version": "v15.32.3",
"source": {
"type": "git",
"url": "https://github.com/webonyx/graphql-php.git",
- "reference": "089c4ef7e112df85788cfe06596278a8f99f4aa9"
+ "reference": "993bf0bea17f870412ad8a90f60c41cb8d5f1145"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/089c4ef7e112df85788cfe06596278a8f99f4aa9",
- "reference": "089c4ef7e112df85788cfe06596278a8f99f4aa9",
+ "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/993bf0bea17f870412ad8a90f60c41cb8d5f1145",
+ "reference": "993bf0bea17f870412ad8a90f60c41cb8d5f1145",
"shasum": ""
},
"require": {
@@ -5402,16 +5402,16 @@
"php": "^7.4 || ^8"
},
"require-dev": {
- "amphp/amp": "^2.6",
- "amphp/http-server": "^2.1",
+ "amphp/amp": "^2.6 || ^3",
+ "amphp/http-server": "^2.1 || ^3",
"dms/phpunit-arraysubset-asserts": "dev-master",
"ergebnis/composer-normalize": "^2.28",
- "friendsofphp/php-cs-fixer": "3.94.2",
+ "friendsofphp/php-cs-fixer": "3.95.1",
"mll-lab/php-cs-fixer-config": "5.13.0",
"nyholm/psr7": "^1.5",
"phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.1",
- "phpstan/phpstan": "2.1.46",
+ "phpstan/phpstan": "2.1.51",
"phpstan/phpstan-phpunit": "2.0.16",
"phpstan/phpstan-strict-rules": "2.0.10",
"phpunit/phpunit": "^9.5 || ^10.5.21 || ^11",
@@ -5425,6 +5425,7 @@
"ticketswap/phpstan-error-formatter": "1.3.0"
},
"suggest": {
+ "amphp/amp": "To leverage async resolving on AMPHP platform (v3 with AmpFutureAdapter, v2 with AmpPromiseAdapter)",
"amphp/http-server": "To leverage async resolving with webserver on AMPHP platform",
"psr/http-message": "To use standard GraphQL server",
"react/promise": "To leverage async resolving on React PHP platform"
@@ -5447,7 +5448,7 @@
],
"support": {
"issues": "https://github.com/webonyx/graphql-php/issues",
- "source": "https://github.com/webonyx/graphql-php/tree/v15.31.5"
+ "source": "https://github.com/webonyx/graphql-php/tree/v15.32.3"
},
"funding": [
{
@@ -5459,7 +5460,7 @@
"type": "open_collective"
}
],
- "time": "2026-04-11T18:06:15+00:00"
+ "time": "2026-04-24T13:49:35+00:00"
}
],
"packages-dev": [
@@ -8444,7 +8445,7 @@
],
"aliases": [],
"minimum-stability": "dev",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
@@ -8465,5 +8466,5 @@
"platform-dev": {
"ext-fileinfo": "*"
},
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.9.0"
}
diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php
index 3004392f76..24803eeaa7 100644
--- a/src/Appwrite/Utopia/Request.php
+++ b/src/Appwrite/Utopia/Request.php
@@ -51,38 +51,49 @@ class Request extends UtopiaRequest
if (!\is_array($methods)) {
$id = $methods->getNamespace() . '.' . $methods->getMethodName();
+ } else {
+ $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';
+ }
+
+ try {
foreach ($this->getFilters() as $filter) {
$parameters = $filter->parse($parameters, $id);
}
- $this->filteredParams = $parameters;
- return $parameters;
- }
-
- $matched = null;
- foreach ($methods as $method) {
- /** @var Method|null $method */
- if ($method === null) {
- continue;
+ } catch (\Throwable $e) {
+ /*
+ * 4xx filter throws are user-input errors that the action layer
+ * revalidates and reports. Cache the raw, pre-filter parameters
+ * so a subsequent getParams() — e.g. when the framework builds
+ * arguments for an error hook — returns without re-running
+ * filters. Otherwise the second throw gets wrapped as
+ * "Error handler had an error: ..." (HTTP 500), masking the
+ * intended 400.
+ */
+ $code = $e->getCode();
+ if (\is_int($code) && $code >= 400 && $code < 500) {
+ $this->filteredParams = $parameters;
}
-
- // 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);
+ throw $e;
}
$this->filteredParams = $parameters;
diff --git a/tests/e2e/Services/Presence/PresenceBase.php b/tests/e2e/Services/Presence/PresenceBase.php
index 9131af18dd..8f05b4144a 100644
--- a/tests/e2e/Services/Presence/PresenceBase.php
+++ b/tests/e2e/Services/Presence/PresenceBase.php
@@ -105,6 +105,26 @@ trait PresenceBase
return $presence;
}
+ protected function resolvePresenceForUser(string $userId, array $headers): array
+ {
+ $presence = $this->client->call(
+ Client::METHOD_GET,
+ '/presences',
+ $headers,
+ [
+ 'queries' => [
+ Query::equal('userId', [$userId])->toString(),
+ ],
+ ]
+ );
+
+ $this->assertEquals(200, $presence['headers']['status-code']);
+ $this->assertGreaterThanOrEqual(1, $presence['body']['total'] ?? 0);
+ $this->assertNotEmpty($presence['body']['presences'][0] ?? []);
+
+ return $presence['body']['presences'][0];
+ }
+
public function testUpsertAndGetPresence(): void
{
if ($this->getSide() === 'client') {
@@ -270,16 +290,12 @@ trait PresenceBase
}
$projectId = $this->getProject()['$id'];
- $user1 = $this->getUser();
- $headersUser1 = $this->getHeaders(false);
-
+ $user1 = $this->getUser(true);
$user2 = $this->getUser(true);
-
- // Avoid overwriting the cached user for the rest of the test run.
- self::$user[$projectId] = $user1;
-
- $headersUser2 = $this->getHeaders(false);
- $headersUser2['cookie'] = 'a_session_' . $projectId . '=' . $user2['session'];
+ $headersUser2 = [
+ 'origin' => 'http://localhost',
+ 'cookie' => 'a_session_' . $projectId . '=' . $user2['session'],
+ ];
$permissionsForUser2 = [
Permission::read(Role::user($user2['$id'])),
@@ -430,7 +446,14 @@ trait PresenceBase
);
$this->assertEquals(200, $upsert['headers']['status-code']);
- $presenceId = $upsert['body']['$id'];
+ $presence = $this->resolvePresenceForUser(
+ $upsert['body']['userId'],
+ \array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders(false))
+ );
+ $presenceId = $presence['$id'];
$update = $this->client->call(
Client::METHOD_PATCH,
@@ -498,7 +521,14 @@ trait PresenceBase
);
$this->assertEquals(200, $upsert['headers']['status-code']);
- $presenceId = $upsert['body']['$id'];
+ $presence = $this->resolvePresenceForUser(
+ $upsert['body']['userId'],
+ \array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders(false))
+ );
+ $presenceId = $presence['$id'];
$delete = $this->client->call(
Client::METHOD_DELETE,
@@ -547,11 +577,11 @@ trait PresenceBase
]
);
$this->assertEquals(200, $upsert['headers']['status-code']);
- $presence = $upsert['body'];
$headers = \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders(false));
+ $presence = $this->resolvePresenceForUser($upsert['body']['userId'], $headers);
} else {
$presence = $this->setupPresence([
'status' => 'cache-update-setup',
@@ -619,11 +649,11 @@ trait PresenceBase
]
);
$this->assertEquals(200, $upsert['headers']['status-code']);
- $presence = $upsert['body'];
$headers = \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders(false));
+ $presence = $this->resolvePresenceForUser($upsert['body']['userId'], $headers);
} else {
$presence = $this->setupPresence([
'status' => 'cache-purge-only-setup',
@@ -690,11 +720,11 @@ trait PresenceBase
]
);
$this->assertEquals(200, $upsert['headers']['status-code']);
- $presence = $upsert['body'];
$headers = \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders(false));
+ $presence = $this->resolvePresenceForUser($upsert['body']['userId'], $headers);
} else {
$presence = $this->setupPresence([
'status' => 'cache-delete-setup',
diff --git a/tests/unit/Utopia/Request/Filters/ThrowingFilter.php b/tests/unit/Utopia/Request/Filters/ThrowingFilter.php
new file mode 100644
index 0000000000..8e02b92e39
--- /dev/null
+++ b/tests/unit/Utopia/Request/Filters/ThrowingFilter.php
@@ -0,0 +1,24 @@
+calls++;
+ throw new \Exception($this->reason, $this->code);
+ }
+}
diff --git a/tests/unit/Utopia/RequestTest.php b/tests/unit/Utopia/RequestTest.php
index 57ebae6d1e..2247ff71f1 100644
--- a/tests/unit/Utopia/RequestTest.php
+++ b/tests/unit/Utopia/RequestTest.php
@@ -5,10 +5,12 @@ namespace Tests\Unit\Utopia;
use Appwrite\SDK\Method;
use Appwrite\SDK\Parameter;
use Appwrite\Utopia\Request;
+use Appwrite\Utopia\Request\Filter;
use PHPUnit\Framework\TestCase;
use Swoole\Http\Request as SwooleRequest;
use Tests\Unit\Utopia\Request\Filters\First;
use Tests\Unit\Utopia\Request\Filters\Second;
+use Tests\Unit\Utopia\Request\Filters\ThrowingFilter;
use Utopia\Http\Route;
class RequestTest extends TestCase
@@ -192,6 +194,109 @@ class RequestTest extends TestCase
$this->assertSame('fallback', $request->getHeader('referer', 'fallback'));
}
+ public function testGetParamsCachesRawParamsWhenFilterThrows4xx(): void
+ {
+ /*
+ * Regression: when a request filter throws a 4xx exception during
+ * Request::getParams() (e.g. RequestV20 rejecting an unparseable
+ * queries[]), the framework's error path calls getParams() again to
+ * build error-hook arguments. Without caching, that second call
+ * re-runs the filter and re-throws, which the framework wraps as
+ * "Error handler had an error: ..." (HTTP 500), masking the intended
+ * 400. This test pins that behavior: the first call throws (so the
+ * action's argument resolution aborts), but the second call returns
+ * the raw, pre-filter params without re-invoking filters.
+ */
+ $filter = new ThrowingFilter(400, 'invalid input');
+
+ $this->setupSingleMethodRoute($filter);
+ $this->request->setQueryString(['foo' => 'bar']);
+
+ $threw = false;
+ try {
+ $this->request->getParams();
+ } catch (\Throwable $e) {
+ $threw = true;
+ $this->assertSame(400, $e->getCode());
+ $this->assertSame('invalid input', $e->getMessage());
+ }
+ $this->assertTrue($threw, 'First getParams() call must rethrow the filter exception.');
+ $this->assertSame(1, $filter->calls, 'Filter ran once on the first call.');
+
+ // Second call: framework's error hook arg resolution. Must return raw
+ // params without re-invoking the filter.
+ $params = $this->request->getParams();
+ $this->assertSame(['foo' => 'bar'], $params);
+ $this->assertSame(1, $filter->calls, 'Filter must not run again after a cached 4xx failure.');
+ }
+
+ public function testGetParamsDoesNotCacheRawParamsForServerError(): void
+ {
+ /*
+ * 5xx filter throws indicate genuine server-side problems, not
+ * user-input mistakes. They must keep rethrowing on every call so
+ * the framework's normal error handling sees the failure each time
+ * — caching raw params would silently swallow real bugs.
+ */
+ $filter = new ThrowingFilter(500, 'boom');
+
+ $this->setupSingleMethodRoute($filter);
+ $this->request->setQueryString(['foo' => 'bar']);
+
+ for ($attempt = 1; $attempt <= 2; $attempt++) {
+ $threw = false;
+ try {
+ $this->request->getParams();
+ } catch (\Throwable $e) {
+ $threw = true;
+ $this->assertSame(500, $e->getCode());
+ }
+ $this->assertTrue($threw, "Call #$attempt must rethrow.");
+ $this->assertSame($attempt, $filter->calls, "Filter must run on call #$attempt.");
+ }
+ }
+
+ public function testGetParamsDoesNotCacheRawParamsForUncodedException(): void
+ {
+ // \Exception with the default code of 0 is treated as "unknown" and
+ // must propagate every call — same reasoning as 5xx.
+ $filter = new ThrowingFilter(0, 'unknown');
+
+ $this->setupSingleMethodRoute($filter);
+ $this->request->setQueryString(['foo' => 'bar']);
+
+ for ($attempt = 1; $attempt <= 2; $attempt++) {
+ $threw = false;
+ try {
+ $this->request->getParams();
+ } catch (\Throwable) {
+ $threw = true;
+ }
+ $this->assertTrue($threw, "Call #$attempt must rethrow.");
+ $this->assertSame($attempt, $filter->calls, "Filter must run on call #$attempt.");
+ }
+ }
+
+ /**
+ * Helper to attach a route with a single SDK method and one filter.
+ */
+ private function setupSingleMethodRoute(Filter $filter): void
+ {
+ $route = new Route(Request::METHOD_GET, '/single');
+ $route->label('sdk', new Method(
+ namespace: 'namespace',
+ group: 'group',
+ name: 'method',
+ description: 'description',
+ auth: [],
+ responses: [],
+ ));
+
+ $this->request->addHeader('EXAMPLE', 'VALUE');
+ $this->request->setRoute($route);
+ $this->request->addFilter($filter);
+ }
+
/**
* Helper to attach a route with multiple SDK methods to the request.
*/