From bee925050f72febefbb99e8b08b2fa9ee242d56e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 10 Nov 2025 18:30:00 +0530 Subject: [PATCH] Migrate DNS validator to Utopia DNS package This change migrates from the local DNS validator implementation to the one provided by the Utopia DNS package. This consolidates DNS validation logic and removes duplicate code. Changes: - Remove local Appwrite\Network\Validator\DNS class - Update all imports to use Utopia\DNS\Validator\DNS - Update GraphQL type mapper to reference the new validator class - Remove associated unit tests that are now covered by the Utopia DNS package --- src/Appwrite/GraphQL/Types/Mapper.php | 2 +- src/Appwrite/Network/Validator/DNS.php | 116 ------------------ .../Modules/Proxy/Http/Rules/API/Create.php | 2 +- .../Proxy/Http/Rules/Function/Create.php | 2 +- .../Proxy/Http/Rules/Redirect/Create.php | 2 +- .../Modules/Proxy/Http/Rules/Site/Create.php | 2 +- .../Proxy/Http/Rules/Verification/Update.php | 2 +- .../Platform/Workers/Certificates.php | 2 +- tests/unit/Network/Validators/DNSTest.php | 98 --------------- tests/unit/Network/Validators/EmailTest.php | 69 ----------- tests/unit/Network/Validators/OriginTest.php | 113 ----------------- 11 files changed, 7 insertions(+), 403 deletions(-) delete mode 100644 src/Appwrite/Network/Validator/DNS.php delete mode 100644 tests/unit/Network/Validators/DNSTest.php delete mode 100755 tests/unit/Network/Validators/EmailTest.php delete mode 100644 tests/unit/Network/Validators/OriginTest.php diff --git a/src/Appwrite/GraphQL/Types/Mapper.php b/src/Appwrite/GraphQL/Types/Mapper.php index c9ae84f1c3..5af70a72a4 100644 --- a/src/Appwrite/GraphQL/Types/Mapper.php +++ b/src/Appwrite/GraphQL/Types/Mapper.php @@ -275,7 +275,7 @@ class Mapper case 'Appwrite\Network\Validator\CNAME': case 'Appwrite\Network\Validator\Email': case 'Appwrite\Network\Validator\Redirect': - case 'Appwrite\Network\Validator\DNS': + case 'Utopia\DNS\Validator\DNS': case 'Appwrite\Network\Validator\Origin': case 'Appwrite\Task\Validator\Cron': case 'Appwrite\Utopia\Database\Validator\CustomId': diff --git a/src/Appwrite/Network/Validator/DNS.php b/src/Appwrite/Network/Validator/DNS.php deleted file mode 100644 index e3c1d38015..0000000000 --- a/src/Appwrite/Network/Validator/DNS.php +++ /dev/null @@ -1,116 +0,0 @@ -server = $server ?: System::getEnv('_APP_DNS', '8.8.8.8'); - } - - public function getDescription(): string - { - return 'Invalid DNS record.'; - } - - public function isValid($value): bool - { - if (!is_string($value) || trim($value) === '') { - return false; - } - - $client = new Client($this->server); - try { - $response = $client->query(Message::query( - new Question($value, $this->type) - )); - } catch (\Throwable) { - return false; - } - - $typeMatches = array_filter( - $response->answers, - fn (Record $record) => $record->type === $this->type - ); - - if (empty($typeMatches)) { - if ($this->type === Record::TYPE_CAA) { - return $this->validateParentCAA($value); - } - - return false; - } - - foreach ($typeMatches as $record) { - if ($this->type === Record::TYPE_CAA) { - $valuePart = $this->extractCAAValue($record->rdata); - if ($valuePart !== '' && $valuePart === $this->target) { - return true; - } - } - - if ($record->rdata === $this->target) { - return true; - } - } - - return false; - } - - private function validateParentCAA(string $domain): bool - { - try { - $domainInfo = new Domain($domain); - } catch (\Throwable) { - return false; - } - - if ($domainInfo->get() === $domainInfo->getApex()) { - return true; - } - - $parts = explode('.', $domainInfo->get()); - array_shift($parts); - $parent = implode('.', $parts); - - if ($parent === '') { - return false; - } - - $validator = new self($this->target, Record::TYPE_CAA, $this->server); - return $validator->isValid($parent); - } - - private function extractCAAValue(string $rdata): string - { - $parts = explode(' ', $rdata, 3); - if (count($parts) < 3) { - return ''; - } - - $value = trim($parts[2], '"'); - return explode(';', $value)[0] ?? ''; - } - - public function isArray(): bool - { - return false; - } - - public function getType(): string - { - return self::TYPE_STRING; - } -} diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php index ff92b3a408..6aadeb1285 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -15,6 +14,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\DNS\Message\Record; +use Utopia\DNS\Validator\DNS; use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php index 6e6d9905a8..01c445040d 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -16,6 +15,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; use Utopia\DNS\Message\Record; +use Utopia\DNS\Validator\DNS; use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php index e2cc51d91f..38dd3b19b4 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -16,6 +15,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; use Utopia\DNS\Message\Record; +use Utopia\DNS\Validator\DNS; use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php index 5154a82e16..151740c370 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -16,6 +15,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; use Utopia\DNS\Message\Record; +use Utopia\DNS\Validator\DNS; use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php index af61f25f05..4e755a905a 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Verification; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -14,6 +13,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\UID; use Utopia\DNS\Message\Record; +use Utopia\DNS\Validator\DNS; use Utopia\Domains\Domain; use Utopia\Logger\Log; use Utopia\Platform\Action; diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php index ac3deb31af..d8a7a180ca 100644 --- a/src/Appwrite/Platform/Workers/Certificates.php +++ b/src/Appwrite/Platform/Workers/Certificates.php @@ -8,7 +8,6 @@ use Appwrite\Event\Func; use Appwrite\Event\Mail; use Appwrite\Event\Realtime; use Appwrite\Event\Webhook; -use Appwrite\Network\Validator\DNS; use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model\Rule; use Exception; @@ -23,6 +22,7 @@ use Utopia\Database\Exception\Structure; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\DNS\Message\Record; +use Utopia\DNS\Validator\DNS; use Utopia\Domains\Domain; use Utopia\Locale\Locale; use Utopia\Logger\Log; diff --git a/tests/unit/Network/Validators/DNSTest.php b/tests/unit/Network/Validators/DNSTest.php deleted file mode 100644 index 9f8928b87f..0000000000 --- a/tests/unit/Network/Validators/DNSTest.php +++ /dev/null @@ -1,98 +0,0 @@ -assertEquals($validator->isValid(''), false); - $this->assertEquals($validator->isValid(null), false); - $this->assertEquals($validator->isValid(false), false); - $this->assertEquals($validator->isValid('cname-unit-test.appwrite.org'), true); - $this->assertEquals($validator->isValid('test1.appwrite.org'), false); - } - - public function testA(): void - { - // IPv4 for documentation purposes - $validator = new DNS('203.0.113.1', Record::TYPE_A); - $this->assertEquals($validator->isValid(''), false); - $this->assertEquals($validator->isValid(null), false); - $this->assertEquals($validator->isValid(false), false); - $this->assertEquals($validator->isValid('a-unit-test.appwrite.org'), true); - $this->assertEquals($validator->isValid('test1.appwrite.org'), false); - } - - public function testAAAA(): void - { - // IPv6 for documentation purposes - $validator = new DNS('2001:db8::1', Record::TYPE_AAAA); - $this->assertEquals($validator->isValid(''), false); - $this->assertEquals($validator->isValid(null), false); - $this->assertEquals($validator->isValid(false), false); - $this->assertEquals($validator->isValid('aaaa-unit-test.appwrite.org'), true); - $this->assertEquals($validator->isValid('test1.appwrite.org'), false); - } - - #[Retry(count: 5)] - public function testCAA(): void - { - $certainly = new DNS('certainly.com', Record::TYPE_CAA, 'ns1.digitalocean.com'); - $letsencrypt = new DNS('letsencrypt.org', Record::TYPE_CAA, 'ns1.digitalocean.com'); - - // No CAA record succeeds on main domain & subdomains for any issuer - $this->assertEquals($certainly->isValid('caa.appwrite.org'), true); - $this->assertEquals($certainly->isValid('sub.caa.appwrite.org'), true); - $this->assertEquals($certainly->isValid('sub.sub.caa.appwrite.org'), true); - - $this->assertEquals($letsencrypt->isValid('caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.sub.caa.appwrite.org'), true); - - // Custom flags and tag is allowed, but only for Certainly - $this->assertEquals($certainly->isValid('certainly-full.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('certainly-full.caa.appwrite.org'), false); - - // Custom flags&tag are not allowed if validator includes specific flags&tag - $certainlyFull = new DNS('0 issue "certainly.com"', Record::TYPE_CAA); - $this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), false); - - // Custom flags&tag still allows if they match exactly - $certainlyFull = new DNS('128 issuewild "certainly.com;account=123456;validationmethods=dns-01"', Record::TYPE_CAA); - $this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), true); - - // Certainly CAA allows Certainly, but not LetsEncrypt; Same for subdomains - $this->assertEquals($certainly->isValid('certainly.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('certainly.caa.appwrite.org'), false); - - $this->assertEquals($certainly->isValid('sub.certainly.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.certainly.caa.appwrite.org'), false); - - $this->assertEquals($certainly->isValid('sub.sub.certainly.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.sub.certainly.caa.appwrite.org'), false); - - // LetsEncrypt CAA on subdomain with parent allowing Certainly. Only LetsEncrypt is allowed; Same for subdomains - $this->assertEquals($certainly->isValid('letsencrypt.certainly.caa.appwrite.org'), false); - $this->assertEquals($letsencrypt->isValid('letsencrypt.certainly.caa.appwrite.org'), true); - - $this->assertEquals($certainly->isValid('sub.letsencrypt.certainly.caa.appwrite.org'), false); - $this->assertEquals($letsencrypt->isValid('sub.letsencrypt.certainly.caa.appwrite.org'), true); - - $this->assertEquals($certainly->isValid('sub.sub.letsencrypt.certainly.caa.appwrite.org'), false); - $this->assertEquals($letsencrypt->isValid('sub.sub.letsencrypt.certainly.caa.appwrite.org'), true); - } -} diff --git a/tests/unit/Network/Validators/EmailTest.php b/tests/unit/Network/Validators/EmailTest.php deleted file mode 100755 index f629ed6ddc..0000000000 --- a/tests/unit/Network/Validators/EmailTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * @version 1.0 RC4 - * @license The MIT License (MIT) - */ - -namespace Tests\Unit\Network\Validators; - -use Appwrite\Network\Validator\Email; -use PHPUnit\Framework\TestCase; - -class EmailTest extends TestCase -{ - protected ?Email $email = null; - - public function setUp(): void - { - $this->email = new Email(); - } - - public function tearDown(): void - { - $this->email = null; - } - - public function testIsValid(): void - { - $this->assertEquals(true, $this->email->isValid('email@domain.com')); - $this->assertEquals(true, $this->email->isValid('firstname.lastname@domain.com')); - $this->assertEquals(true, $this->email->isValid('email@subdomain.domain.com')); - $this->assertEquals(true, $this->email->isValid('firstname+lastname@domain.com')); - $this->assertEquals(true, $this->email->isValid('email@[123.123.123.123]')); - $this->assertEquals(true, $this->email->isValid('"email"@domain.com')); - $this->assertEquals(true, $this->email->isValid('1234567890@domain.com')); - $this->assertEquals(true, $this->email->isValid('email@domain-one.com')); - $this->assertEquals(true, $this->email->isValid('_______@domain.com')); - $this->assertEquals(true, $this->email->isValid('email@domain.name')); - $this->assertEquals(true, $this->email->isValid('email@domain.co.jp')); - $this->assertEquals(true, $this->email->isValid('firstname-lastname@domain.com')); - $this->assertEquals(false, $this->email->isValid(false)); - $this->assertEquals(false, $this->email->isValid(['string', 'string'])); - $this->assertEquals(false, $this->email->isValid(1)); - $this->assertEquals(false, $this->email->isValid(1.2)); - $this->assertEquals(false, $this->email->isValid('plainaddress')); // Missing @ sign and domain - $this->assertEquals(false, $this->email->isValid('@domain.com')); // Missing username - $this->assertEquals(false, $this->email->isValid('#@%^%#$@#$@#.com')); // Garbage - $this->assertEquals(false, $this->email->isValid('Joe Smith ')); // Encoded html within email is invalid - $this->assertEquals(false, $this->email->isValid('email.domain.com')); // Missing @ - $this->assertEquals(false, $this->email->isValid('email@domain@domain.com')); // Two @ sign - $this->assertEquals(false, $this->email->isValid('.email@domain.com')); // Leading dot in address is not allowed - $this->assertEquals(false, $this->email->isValid('email.@domain.com')); // Trailing dot in address is not allowed - $this->assertEquals(false, $this->email->isValid('email..email@domain.com')); // Multiple dots - $this->assertEquals(false, $this->email->isValid('あいうえお@domain.com')); // Unicode char as address - $this->assertEquals(false, $this->email->isValid('email@domain.com (Joe Smith)')); // Text followed email is not allowed - $this->assertEquals(false, $this->email->isValid('email@domain')); // Missing top level domain (.com/.net/.org/etc) - $this->assertEquals(false, $this->email->isValid('email@-domain.com')); // Leading dash in front of domain is invalid - $this->assertEquals(false, $this->email->isValid('email@111.222.333.44444')); // Invalid IP format - $this->assertEquals(false, $this->email->isValid('email@domain..com')); // Multiple dot in the domain portion is invalid - $this->assertEquals($this->email->getType(), 'string'); - } -} diff --git a/tests/unit/Network/Validators/OriginTest.php b/tests/unit/Network/Validators/OriginTest.php deleted file mode 100644 index d312f8c5a5..0000000000 --- a/tests/unit/Network/Validators/OriginTest.php +++ /dev/null @@ -1,113 +0,0 @@ - ID::custom('platforms'), - 'name' => 'Production', - 'type' => Platform::TYPE_WEB, - 'hostname' => 'appwrite.io', - ], - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Development', - 'type' => Platform::TYPE_WEB, - 'hostname' => 'appwrite.test', - ], - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Localhost', - 'type' => Platform::TYPE_WEB, - 'hostname' => 'localhost', - ], - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Flutter', - 'type' => Platform::TYPE_FLUTTER_WEB, - 'hostname' => 'appwrite.flutter', - ], - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Expo', - 'type' => Platform::TYPE_SCHEME, - 'key' => 'exp', - ], - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Appwrite Callback', - 'type' => Platform::TYPE_SCHEME, - 'key' => 'appwrite-callback-123', - ], - ]); - - $this->assertEquals(false, $validator->isValid('')); - $this->assertEquals(false, $validator->isValid('/')); - - $this->assertEquals(true, $validator->isValid('https://localhost')); - $this->assertEquals(true, $validator->isValid('http://localhost')); - $this->assertEquals(true, $validator->isValid('http://localhost:80')); - - $this->assertEquals(true, $validator->isValid('https://appwrite.io')); - $this->assertEquals(true, $validator->isValid('http://appwrite.io')); - $this->assertEquals(true, $validator->isValid('http://appwrite.io:80')); - - $this->assertEquals(true, $validator->isValid('https://appwrite.test')); - $this->assertEquals(true, $validator->isValid('http://appwrite.test')); - $this->assertEquals(true, $validator->isValid('http://appwrite.test:80')); - - $this->assertEquals(true, $validator->isValid('https://appwrite.flutter')); - $this->assertEquals(true, $validator->isValid('http://appwrite.flutter')); - $this->assertEquals(true, $validator->isValid('http://appwrite.flutter:80')); - - $this->assertEquals(false, $validator->isValid('https://example.com')); - $this->assertEquals(false, $validator->isValid('http://example.com')); - $this->assertEquals(false, $validator->isValid('http://example.com:80')); - - $this->assertEquals(true, $validator->isValid('exp://')); - $this->assertEquals(true, $validator->isValid('exp:///')); - $this->assertEquals(true, $validator->isValid('exp://index')); - - $this->assertEquals(true, $validator->isValid('appwrite-callback-123://')); - $this->assertEquals(false, $validator->isValid('appwrite-callback-456://')); - - $this->assertEquals(false, $validator->isValid('appwrite-ios://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new iOS platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('appwrite-android://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Android platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('appwrite-macos://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new macOS platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('appwrite-linux://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Linux platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('appwrite-windows://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Windows platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('chrome-extension://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Chrome Extension) platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('moz-extension://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Firefox Extension) platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('safari-web-extension://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Safari Extension) platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('ms-browser-extension://com.company.appname')); - $this->assertEquals('Invalid Origin. Register your new client (com.company.appname) as a new Web (Edge Extension) platform on your project console dashboard', $validator->getDescription()); - - $this->assertEquals(false, $validator->isValid('random-scheme://localhost')); - $this->assertEquals('Invalid Scheme. The scheme used (random-scheme) in the Origin (random-scheme://localhost) is not supported. If you are using a custom scheme, please change it to `appwrite-callback-`', $validator->getDescription()); - } -}