Compare commits

...
Author SHA1 Message Date
ArnabChatterjee20k 737c85822d multi line comment 2026-05-04 12:46:44 +05:30
ArnabChatterjee20k 1fc3a8803c multiline comment 2026-05-04 12:45:32 +05:30
ArnabChatterjee20k c32294743a reverted e2e 2026-05-04 12:31:50 +05:30
ArnabChatterjee20k f5a7cfd2ea fix: resolve query syntax errors and improve error handling in Request class 2026-05-04 12:22:48 +05:30
Luke B. SilverandGitHub 76e6239d32 Merge pull request #12204 from appwrite/chore/bump-image-1.4.1
chore: bump base image to 1.4.1
2026-05-03 20:18:03 +01:00
Matej BačoandGitHub 1da5b549af Merge pull request #12203 from appwrite/fix-missing-scopes-console
Fix: Add deprecated function scopes
2026-05-03 20:21:28 +02:00
loks0nandClaude Opus 4.7 92eceba218 chore: bump base image to 1.4.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:21:00 +01:00
8f68a59a79 Apply suggestions from code review
Co-authored-by: Matej Bačo <matejbaco2000@gmail.com>
2026-05-03 19:51:56 +02:00
6051b8150c Apply suggestions from code review
Co-authored-by: Matej Bačo <matejbaco2000@gmail.com>
2026-05-03 19:51:10 +02:00
Matej Bačo 4e20e382d2 Add deprecated function scopes 2026-05-03 19:49:13 +02:00
Luke B. SilverandGitHub c96922422f Merge pull request #12201 from appwrite/chore/bump-image-1.3.1
chore: bump base image to 1.3.1
2026-05-03 15:27:45 +01:00
loks0nandClaude Opus 4.7 7f27851dab chore: bump base image to 1.3.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:15:27 +01:00
Chirag AggarwalandGitHub f396064c49 Merge pull request #12198 from appwrite/add-rust-starter-function-template 2026-05-02 16:53:41 +05:30
Chirag Aggarwal 413930a15e Add Rust starter function template runtime 2026-05-02 12:19:07 +05:30
Chirag AggarwalandGitHub f672891c42 Merge pull request #12196 from appwrite/chore/update-runtimes-0.20 2026-05-01 19:33:01 +05:30
Chirag Aggarwal 65f926b4c5 Update appwrite runtimes to 0.20 2026-05-01 18:55:13 +05:30
8 changed files with 190 additions and 39 deletions
+1 -1
View File
@@ -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"
+10
View File
@@ -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' => [
+3 -2
View File
@@ -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 <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/starter">file</a>.',
'instructions' => 'For documentation and instructions check out the <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates">templates repository</a>.',
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'providerVersion' => '0.3.*',
'variables' => [],
'scopes' => ['users.read']
],
+1 -1
View File
@@ -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.*",
Generated
+7 -7
View File
@@ -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": "bd45829c252971301370d62300be106d",
"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",
+39 -28
View File
@@ -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;
@@ -0,0 +1,24 @@
<?php
namespace Tests\Unit\Utopia\Request\Filters;
use Appwrite\Utopia\Request\Filter;
/**
* Test fixture: a filter that always throws, with a configurable code.
* Used to assert how Request::getParams() reacts to filter exceptions.
*/
class ThrowingFilter extends Filter
{
public int $calls = 0;
public function __construct(private int $code, private string $reason)
{
}
public function parse(array $content, string $model): array
{
$this->calls++;
throw new \Exception($this->reason, $this->code);
}
}
+105
View File
@@ -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.
*/