1
0
Fork 0
mirror of synced 2024-09-28 15:31:43 +12:00

Fix collection type arg and type setting

This commit is contained in:
Jake Barnby 2022-04-08 02:02:48 +12:00
parent b1521a2b33
commit 40b04a3721
No known key found for this signature in database
GPG key ID: A4674EBC0E404657
4 changed files with 230 additions and 233 deletions

View file

@ -309,7 +309,7 @@ Things to remember when releasing SDKs
Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension or if you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection. Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension or if you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection.
First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php** and there change the IP address to your development machine's IP. Without the proper IP address debugger wont connect. And you also need to set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file. First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php** and there change the IP address to your development machine's IP. Without the proper IP address debugger will not connect. You will also need to set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
### VS Code Launch Configuration ### VS Code Launch Configuration
@ -321,13 +321,13 @@ First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** fil
"port": 9005, "port": 9005,
"pathMappings": { "pathMappings": {
"/usr/src/code": "${workspaceRoot}" "/usr/src/code": "${workspaceRoot}"
}, }
} }
``` ```
### PHPStorm Setup ### PHPStorm Setup
In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, there under **Xdebug** set the debug port to **9005** and enable **can accept external connections** checkbox. In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, there under **Xdebug** set the debug port to **9005** and enable the **can accept external connection** checkbox.
## Tests ## Tests
@ -349,7 +349,7 @@ To run end-2-end tests use:
docker-compose exec appwrite test /usr/src/code/tests/e2e docker-compose exec appwrite test /usr/src/code/tests/e2e
``` ```
To run end-2-end tests for a spcific service use: To run end-2-end tests for a specific service use:
```bash ```bash
docker-compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] docker-compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
@ -406,9 +406,11 @@ docker-compose exec appwrite /usr/src/code/vendor/bin/psalm
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials: From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
* [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md) * [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
* [Appwrite Environment Variables](./docs/tutorials/environment-variables.md) * [Appwrite Environment Variables](./docs/tutorials/add-environment-variable.md)
* [Running in Production](./docs/tutorials/running-in-production.md) * [Adding Support for a New Runtime](./docs/tutorials/add-runtime.md)
* [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md) * [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
* [Adding Translations](./docs/tutorials/add-translations.md)
* [Multi-Architecture Support](./docs/tutorials/multi-architecture-support.md)
## Other Ways to Help ## Other Ways to Help
@ -416,7 +418,7 @@ Pull requests are great, but there are many other areas where you can help Appwr
### Blogging & Speaking ### Blogging & Speaking
Blogging, speaking about, or creating tutorials about one of Appwrites many features. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub. Blogging, speaking about, or creating tutorials about one of Appwrites many features. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io), so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
### Presenting at Meetups ### Presenting at Meetups
@ -437,4 +439,3 @@ Submitting documentation updates, enhancements, designs, or bug fixes. Spelling
### Helping Someone ### Helping Someone
Searching for Appwrite on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Appwrite's repo! Searching for Appwrite on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Appwrite's repo!

View file

@ -65,7 +65,7 @@ App::post('/v1/graphql')
$apiSchema, $apiSchema,
$register, $register,
$dbForProject $dbForProject
);; );
$promise = GraphQL::promiseToExecute( $promise = GraphQL::promiseToExecute(
$promiseAdapter, $promiseAdapter,
@ -76,18 +76,18 @@ App::post('/v1/graphql')
validationRules: $validations validationRules: $validations
); );
// Blocking wait while queries resolve asynchronously. // Blocking wait while queries resolve asynchronously
$wg = new WaitGroup(); $wg = new WaitGroup();
$wg->add(); $wg->add();
$promise->then( $promise->then(
function ($result) use ($response, $debugFlags, $wg) { function ($result) use ($response, $debugFlags, $wg) {
$response->json(['data' => $result->toArray($debugFlags)]); $response->json($result->toArray($debugFlags));
$wg->done(); $wg->done();
}, },
function ($error) use ($response, $wg) { function ($error) use ($response, $wg) {
$response->json(['errors' => [\json_encode($error)]]); $response->text(\json_encode(['errors' => [\json_encode($error)]]));
$wg->done(); $wg->done();
} }
); );
$wg->wait(); $wg->wait(App::getEnv('_APP_GRAPHQL_REQUEST_TIMEOUT', 30));
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -3,6 +3,7 @@
namespace Appwrite\GraphQL; namespace Appwrite\GraphQL;
use Appwrite\GraphQL\Types\JsonType; use Appwrite\GraphQL\Types\JsonType;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response; use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response\Model;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -14,7 +15,6 @@ use Utopia\App;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Database\Database; use Utopia\Database\Database;
use Utopia\Database\Document; use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry; use Utopia\Registry\Registry;
@ -95,28 +95,34 @@ class Builder
foreach ($rules as $key => $props) { foreach ($rules as $key => $props) {
$escapedKey = str_replace('$', '_', $key); $escapedKey = str_replace('$', '_', $key);
if (isset(self::$typeMapping[$props['type']])) { $types = \is_array($props['type'])
$type = self::$typeMapping[$props['type']]; ? $props['type']
} else { : [$props['type']];
try {
$complexModel = $response->getModel($props['type']);
$type = self::getTypeMapping($complexModel, $response);
} catch (\Exception $e) {
Console::error("Could Not find model for : {$props['type']}");
}
}
if ($props['array']) { foreach ($types as $type) {
$type = Type::listOf($type); if (isset(self::$typeMapping[$type])) {
} $type = self::$typeMapping[$type];
} else {
$fields[$escapedKey] = [ try {
'type' => $type, $complexModel = $response->getModel($type);
'description' => $props['description'], $type = self::getTypeMapping($complexModel, $response);
'resolve' => function ($object, $args, $context, $info) use ($key) { } catch (\Exception $e) {
return $object[$key]; Console::error("Could not find model for : {$type}");
}
} }
];
if ($props['array']) {
$type = Type::listOf($type);
}
$fields[$escapedKey] = [
'type' => $type,
'description' => $props['description'],
'resolve' => function ($object, $args, $context, $info) use ($key) {
return $object[$key];
}
];
}
} }
$objectType = [ $objectType = [
'name' => $name, 'name' => $name,
@ -135,8 +141,9 @@ class Builder
* @param $utopia * @param $utopia
* @param $injections * @param $injections
* @return Type * @return Type
* @throws \Exception
*/ */
protected static function getArgType($validator, bool $required, $utopia, $injections): Type private static function getParameterArgType($validator, bool $required, $utopia, $injections): Type
{ {
$validator = \is_callable($validator) $validator = \is_callable($validator)
? \call_user_func_array($validator, $utopia->getResources($injections)) ? \call_user_func_array($validator, $utopia->getResources($injections))
@ -158,13 +165,15 @@ class Builder
$type = Type::boolean(); $type = Type::boolean();
break; break;
case 'Utopia\Validator\ArrayList': case 'Utopia\Validator\ArrayList':
$type = Type::listOf(self::json()); $nested = (fn() => $this->validator)->bindTo($validator, $validator)();
$type = Type::listOf(self::getParameterArgType($nested, $required, $utopia, $injections));
break; break;
case 'Utopia\Validator\Numeric': case 'Utopia\Validator\Numeric':
case 'Utopia\Validator\Range': case 'Utopia\Validator\Range':
$type = Type::int(); $type = Type::int();
break; break;
case 'Utopia\Validator\Assoc': case 'Utopia\Validator\Assoc':
case 'Utopia\Validator\JSON':
default: default:
$type = self::json(); $type = self::json();
break; break;
@ -177,6 +186,35 @@ class Builder
return $type; return $type;
} }
/**
* Function to map an attribute type to a valid GraphQL Type
*
* @param $validator
* @param bool $required
* @param $utopia
* @param $injections
* @return Type
* @throws \Exception
*/
private static function getAttributeArgType($type, $array, $required): Type
{
if ($array) {
return Type::listOf(self::getAttributeArgType($type, false, $required));
}
$type = match ($type) {
'boolean' => Type::boolean(),
'integer' => Type::int(),
'double' => Type::float(),
default => Type::string(),
};
if ($required) {
$type = Type::nonNull($type);
}
return $type;
}
/** /**
* @throws \Exception * @throws \Exception
*/ */
@ -186,7 +224,8 @@ class Builder
Database $dbForProject Database $dbForProject
): Schema ): Schema
{ {
Console::info("[INFO] Appending GraphQL Database Schema..."); Console::info("[INFO] Merging Schema...");
$start = microtime(true); $start = microtime(true);
$db = self::buildCollectionsSchema($register, $dbForProject); $db = self::buildCollectionsSchema($register, $dbForProject);
@ -211,39 +250,12 @@ class Builder
]); ]);
$time_elapsed_secs = microtime(true) - $start; $time_elapsed_secs = microtime(true) - $start;
Console::info("[INFO] Time Taken To Append Database to API Schema : ${time_elapsed_secs}s");
Console::info("[INFO] Time Taken To Merge Schema : ${time_elapsed_secs}s");
return $schema; return $schema;
} }
/**
* @throws \Exception
*/
public static function buildSchema($utopia, $response, $register, $dbForProject): Schema
{
$db = self::buildCollectionsSchema($register, $dbForProject);
$api = self::buildAPISchema($utopia, $response, $register);
$queryFields = \array_merge($api['query'], $db['query']);
$mutationFields = \array_merge($api['mutation'], $db['mutation']);
ksort($queryFields);
ksort($mutationFields);
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'description' => 'The root of all queries',
'fields' => $queryFields
]),
'mutation' => new ObjectType([
'name' => 'Mutation',
'description' => 'The root of all mutations',
'fields' => $mutationFields
])
]);
}
/** /**
* This function goes through all the project attributes and builds a * This function goes through all the project attributes and builds a
* GraphQL schema for all the collections they make up. * GraphQL schema for all the collections they make up.
@ -255,116 +267,91 @@ class Builder
*/ */
public static function buildCollectionsSchema(Registry &$register, Database $dbForProject): array public static function buildCollectionsSchema(Registry &$register, Database $dbForProject): array
{ {
Console::log("[INFO] Building GraphQL Database Schema..."); Console::info("[INFO] Building GraphQL Project Collection Schema...");
$start = microtime(true); $start = microtime(true);
$collections = []; $collections = [];
$queryFields = []; $queryFields = [];
$mutationFields = []; $mutationFields = [];
$limit = 50;
$offset = 0; $offset = 0;
$attrs = Authorization::skip(static fn() => $dbForProject->find('attributes', [new Query('collectionId', Query::TYPE_EQUAL, ['movies'])])); Authorization::skip(function () use (&$mutationFields, &$queryFields, &$collections, $register, $limit, $offset, $dbForProject) {
Authorization::skip(function () use ($mutationFields, $queryFields, $collections, $register, $offset, $dbForProject) {
while (!empty($attrs = $dbForProject->find( while (!empty($attrs = $dbForProject->find(
'attributes', 'attributes',
limit: $dbForProject->getAttributeLimit(), limit: $limit,
offset: $offset offset: $offset
))) { ))) {
go(function ($attrs, $dbForProject, $register, $collections, $queryFields, $mutationFields) { foreach ($attrs as $attr) {
foreach ($attrs as $attr) { $collectionId = $attr->getAttribute('collectionId');
go(function ($attr, &$collections) {
/** @var Document $attr */
$collectionId = $attr->getAttribute('collectionId'); if ($attr->getAttribute('status') !== 'available') {
return;
if (isset(self::$typeMapping[$collectionId])) {
return;
}
if ($attr->getAttribute('status') !== 'available') {
return;
}
$key = $attr->getAttribute('key');
$type = $attr->getAttribute('type');
$escapedKey = str_replace('$', '_', $key);
$collections[$collectionId][$escapedKey] = [
'type' => $type,
'resolve' => function ($object, $args, $context, $info) use ($key) {
return $object->getAttribute($key);
}
];
}, $attr, $collections);
} }
foreach ($collections as $collectionId => $attributes) { $key = $attr->getAttribute('key');
go(function ($collectionId, $attributes, $dbForProject, $register, &$queryFields, &$mutationFields) { $type = $attr->getAttribute('type');
if (isset(self::$typeMapping[$collectionId])) { $array = $attr->getAttribute('array');
return; $required = $attr->getAttribute('required');
}
$objectType = new ObjectType([ $escapedKey = str_replace('$', '_', $key);
'name' => \ucfirst($collectionId),
'fields' => $attributes
]);
self::$typeMapping[$collectionId] = $objectType; $collections[$collectionId][$escapedKey] = [
'type' => self::getAttributeArgType($type, $array, $required),
'resolve' => function ($object, $args, $context, $info) use ($key) {
return $object->getAttribute($key);
}
];
}
$mutateArgs = []; foreach ($collections as $collectionId => $attributes) {
$objectType = new ObjectType([
'name' => $collectionId,
'fields' => $attributes
]);
foreach ($attributes as $name => $attribute) { $idArgs = [
$mutateArgs[$name] = [ 'id' => [
'type' => $attribute['type'] 'type' => Type::string()
]; ]
} ];
$idArgs = [ $listArgs = [
'id' => [ 'limit' => [
'type' => Type::string() 'type' => Type::int(),
] 'defaultValue' => $limit,
]; ],
'offset' => [
'type' => Type::int(),
'defaultValue' => 0,
],
'cursor' => [
'type' => Type::string(),
'defaultValue' => null,
],
'orderAttributes' => [
'type' => Type::listOf(Type::string()),
'defaultValue' => [],
],
'orderType' => [
'types' => Type::listOf(Type::string()),
'defaultValue' => [],
]
];
$listArgs = [ self::createCollectionGetQuery($collectionId, $register, $dbForProject, $idArgs, $queryFields, $objectType);
'limit' => [ self::createCollectionListQuery($collectionId, $register, $dbForProject, $listArgs, $queryFields, $objectType);
'type' => Type::int() self::createCollectionCreateMutation($collectionId, $register, $dbForProject, $attributes, $mutationFields, $objectType);
], self::createCollectionUpdateMutation($collectionId, $register, $dbForProject, $attributes, $mutationFields, $objectType);
'offset' => [ self::createCollectionDeleteMutation($collectionId, $register, $dbForProject, $idArgs, $mutationFields, $objectType);
'type' => Type::int() }
],
'cursor' => [
'type' => Type::string()
],
'orderAttributes' => [
'type' => Type::listOf(Type::string())
],
'orderType' => [
'types' => Type::listOf(Type::string())
]
];
self::createCollectionGetQuery($collectionId, $register, $dbForProject, $idArgs, $queryFields); $offset += $limit;
self::createCollectionListQuery($collectionId, $register, $dbForProject, $listArgs, $queryFields);
self::createCollectionCreateMutation($collectionId, $register, $dbForProject, $mutateArgs, $mutationFields);
self::createCollectionUpdateMutation($collectionId, $register, $dbForProject, $mutateArgs, $mutationFields);
self::createCollectionDeleteMutation($collectionId, $register, $dbForProject, $idArgs, $mutationFields);
}, $collectionId, $attributes, $dbForProject, $register, $queryFields, $mutationFields);
}
}, $attrs, $dbForProject, $register, $collections, $queryFields, $mutationFields);
$offset += $dbForProject->getAttributeLimit();
} }
}); });
$time_elapsed_secs = microtime(true) - $start; $time_elapsed_secs = microtime(true) - $start;
Console::log("[INFO] Time Taken To Build Database Schema : ${time_elapsed_secs}s"); Console::info("[INFO] Time Taken To Build Project Collection Schema : ${time_elapsed_secs}s");
Console::info('[INFO] Schema : ' . json_encode([
'query' => $queryFields,
'mutation' => $mutationFields
]));
return [ return [
'query' => $queryFields, 'query' => $queryFields,
@ -372,102 +359,103 @@ class Builder
]; ];
} }
private static function createCollectionGetQuery($collectionId, $register, $dbForProject, $args, &$queryFields) private static function createCollectionGetQuery($collectionId, $register, $dbForProject, $args, &$queryFields, $objectType)
{ {
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) { $resolve = fn($type, $args, $context, $info) => new SwoolePromise(
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) { function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try { try {
$resolve($dbForProject->getDocument($collectionId, $args['id'])); $resolve($dbForProject->getDocument($collectionId, $args['id']));
} catch (\Throwable $e) { } catch (\Throwable $e) {
$reject($e); $reject($e);
} }
}); }
}; );
$get = [ $get = [
'type' => \ucfirst($collectionId), 'type' => $objectType,
'args' => $args, 'args' => $args,
'resolve' => $resolve 'resolve' => $resolve
]; ];
$queryFields['get' . \ucfirst($collectionId)] = $get; $queryFields[$collectionId . 'Get'] = $get;
} }
private static function createCollectionListQuery($collectionId, $register, $dbForProject, $args, &$queryFields) private static function createCollectionListQuery($collectionId, $register, $dbForProject, $args, &$queryFields, $objectType)
{ {
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) { $resolve = fn($type, $args, $context, $info) => new SwoolePromise(
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) { function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try { try {
$resolve($dbForProject->getCollection($collectionId)); $resolve($dbForProject->getCollection($collectionId));
} catch (\Throwable $e) { } catch (\Throwable $e) {
$reject($e); $reject($e);
} }
}); }
}; );
$list = [ $list = [
'type' => \ucfirst($collectionId), 'type' => $objectType,
'args' => $args, 'args' => $args,
'resolve' => $resolve 'resolve' => $resolve
]; ];
$queryFields['list' . \ucfirst($collectionId)] = $list; $queryFields[$collectionId . 'List'] = $list;
} }
private static function createCollectionCreateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields) private static function createCollectionCreateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields, $objectType)
{ {
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) { $resolve = fn($type, $args, $context, $info) => new SwoolePromise(
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) { function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try { try {
$resolve($dbForProject->createDocument($collectionId, new Document($args))); $resolve($dbForProject->createDocument($collectionId, new Document($args)));
} catch (\Throwable $e) { } catch (\Throwable $e) {
$reject($e); $reject($e);
} }
}); }
}; );
$create = [ $create = [
'type' => \ucfirst($collectionId), 'type' => $objectType,
'args' => $args, 'args' => $args,
'resolve' => $resolve 'resolve' => $resolve
]; ];
$mutationFields['create' . \ucfirst($collectionId)] = $create; $mutationFields[$collectionId . 'Create'] = $create;
} }
private static function createCollectionUpdateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields) private static function createCollectionUpdateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields, $objectType)
{ {
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) { $resolve = fn($type, $args, $context, $info) => new SwoolePromise(
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) { function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try { try {
$resolve($dbForProject->updateDocument($collectionId, $args['id'], new Document($args))); $resolve($dbForProject->updateDocument($collectionId, $args['id'], new Document($args)));
} catch (\Throwable $e) { } catch (\Throwable $e) {
$reject($e); $reject($e);
} }
}); }
}; );
$update = [ $update = [
'type' => \ucfirst($collectionId), 'type' => $objectType,
'args' => $args, 'args' => $args,
'resolve' => $resolve 'resolve' => $resolve
]; ];
$mutationFields['update' . \ucfirst($collectionId)] = $update; $mutationFields[$collectionId . 'Update'] = $update;
} }
private static function createCollectionDeleteMutation($collectionId, $register, $dbForProject, $args, &$mutationFields) private static function createCollectionDeleteMutation($collectionId, $register, $dbForProject, $args, &$mutationFields, $objectType)
{ {
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) { $resolve = fn($type, $args, $context, $info) => new SwoolePromise(
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) { function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try { try {
$resolve($dbForProject->deleteDocument($collectionId, $args['id'])); $resolve($dbForProject->deleteDocument($collectionId, $args['id']));
} catch (\Throwable $e) { } catch (\Throwable $e) {
$reject($e); $reject($e);
} }
}); }
}; );
$delete = [ $delete = [
'type' => \ucfirst($collectionId), 'type' => $objectType,
'args' => $args, 'args' => $args,
'resolve' => $resolve 'resolve' => $resolve
]; ];
$mutationFields['delete' . \ucfirst($collectionId)] = $delete; $mutationFields[$collectionId . 'Delete'] = $delete;
} }
/** /**
@ -475,14 +463,15 @@ class Builder
* GraphQL schema for all those routes whose response model is neither empty nor NONE * GraphQL schema for all those routes whose response model is neither empty nor NONE
* *
* @param App $utopia * @param App $utopia
* @param Request $request
* @param Response $response * @param Response $response
* @param Registry $register * @param Registry $register
* @return array * @return array
* @throws \Exception * @throws \Exception
*/ */
public static function buildAPISchema(App $utopia, Response $response, Registry $register): array public static function buildAPISchema(App $utopia, Request $request, Response $response, Registry $register): array
{ {
Console::log("[INFO] Building GraphQL API Schema..."); Console::info("[INFO] Building GraphQL REST API Schema...");
$start = microtime(true); $start = microtime(true);
self::init(); self::init();
@ -491,66 +480,73 @@ class Builder
foreach ($utopia->getRoutes() as $method => $routes) { foreach ($utopia->getRoutes() as $method => $routes) {
foreach ($routes as $route) { foreach ($routes as $route) {
if (str_starts_with($route->getPath(), '/v1/mock/')) {
continue;
}
$namespace = $route->getLabel('sdk.namespace', ''); $namespace = $route->getLabel('sdk.namespace', '');
$methodName = $namespace . \ucfirst($route->getLabel('sdk.method', '')); $methodName = $namespace . \ucfirst($route->getLabel('sdk.method', ''));
$responseModelName = $route->getLabel('sdk.response.model', "none"); $responseModelNames = $route->getLabel('sdk.response.model', "none");
Console::info("Namespace: $namespace"); if ($responseModelNames !== "none") {
Console::info("Method: $methodName"); $responseModels = \is_array($responseModelNames)
Console::info("Response Model: $responseModelName"); ? \array_map(static fn($m) => $response->getModel($m), $responseModelNames)
Console::info("Raw routes: " . \json_encode($routes)); : [$response->getModel($responseModelNames)];
Console::info("Raw route: " . \json_encode($route));
if ($responseModelName !== "none") { foreach ($responseModels as $responseModel) {
$responseModel = $response->getModel($responseModelName); $type = self::getTypeMapping($responseModel, $response);
$description = $route->getDesc();
$args = [];
/* Create a GraphQL type for the current response model */ foreach ($route->getParams() as $key => $value) {
$type = self::getTypeMapping($responseModel, $response); $args[$key] = [
/* Get a description for this type */ 'type' => self::getParameterArgType(
$description = $route->getDesc(); $value['validator'],
/* Create the args required for this type */ !$value['optional'],
$args = []; $utopia,
foreach ($route->getParams() as $key => $value) { $value['injections']
$args[$key] = [ ),
'type' => self::getArgType($value['validator'], !$value['optional'], $utopia, $value['injections']), 'description' => $value['description'],
'description' => $value['description'], 'defaultValue' => $value['default']
'defaultValue' => $value['default'] ];
]; }
}
/* Define a resolve function that defines how to fetch data for this type */
$resolve = function ($type, $args, $context, $info) use ($utopia, $response, &$register, $route) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($utopia, $response, &$register, $route, $args) {
$utopia->setRoute($route)->execute($route, $args);
$result = $response->getPayload();
if ($response->getCurrentModel() == Response::MODEL_ERROR_DEV) { /* Define a resolve function that defines how to fetch data for this type */
$reject(new ExceptionDev($result['message'], $result['code'], $result['version'], $result['file'], $result['line'], $result['trace'])); $resolve = fn($type, $args, $context, $info) => new SwoolePromise(
} else if ($response->getCurrentModel() == Response::MODEL_ERROR) { function (callable $resolve, callable $reject) use ($utopia, $request, $response, &$register, $route, $args) {
$reject(new \Exception($result['message'], $result['code'])); $utopia
->setRoute($route)
->execute($route, $request);
$result = $response->getPayload();
if ($response->getCurrentModel() == Response::MODEL_ERROR_DEV) {
$reject(new ExceptionDev($result['message'], $result['code'], $result['version'], $result['file'], $result['line'], $result['trace']));
} else if ($response->getCurrentModel() == Response::MODEL_ERROR) {
$reject(new \Exception($result['message'], $result['code']));
}
$resolve($result);
} }
);
$resolve($result); $field = [
}); 'type' => $type,
}; 'description' => $description,
'args' => $args,
'resolve' => $resolve
];
$field = [ if ($method == 'GET') {
'type' => $type, $queryFields[$methodName] = $field;
'description' => $description, } else if ($method == 'POST' || $method == 'PUT' || $method == 'PATCH' || $method == 'DELETE') {
'args' => $args, $mutationFields[$methodName] = $field;
'resolve' => $resolve }
];
if ($method == 'GET') {
$queryFields[$methodName] = $field;
} else if ($method == 'POST' || $method == 'PUT' || $method == 'PATCH' || $method == 'DELETE') {
$mutationFields[$methodName] = $field;
} }
} }
} }
} }
$time_elapsed_secs = microtime(true) - $start; $time_elapsed_secs = microtime(true) - $start;
Console::log("[INFO] Time Taken To Build API Schema : ${time_elapsed_secs}s"); Console::info("[INFO] Time Taken To Build REST API Schema : ${time_elapsed_secs}s");
return [ return [
'query' => $queryFields, 'query' => $queryFields,