1
0
Fork 0
mirror of synced 2024-09-30 01:08:13 +13:00

WIP input file handling

This commit is contained in:
Jake Barnby 2022-07-06 20:56:58 +12:00
parent 81265ab458
commit 67807f8c6f
9 changed files with 125 additions and 30 deletions

View file

@ -11,8 +11,10 @@ use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use Swoole\Coroutine\WaitGroup;
use Utopia\App;
use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;
use Utopia\Storage\Validator\File;
App::get('/v1/graphql')
->desc('GraphQL Endpoint')
@ -55,6 +57,9 @@ App::post('/v1/graphql')
->param('query', '', new Text(1024), 'The query to execute. Max 1024 chars.', true)
->param('operationName', null, new Text(256), 'Name of the operation to execute', true)
->param('variables', [], new JSON(), 'Variables to use in the operation', true)
->param('operations', '', new Text(1024), 'Variables to use in the operation', true)
->param('map', '', new Text(1024), 'Variables to use in the operation', true)
->param('files', [], new ArrayList(new File()), 'Files to upload. Use a path that is relative to the current directory.', true)
->inject('request')
->inject('response')
->inject('promiseAdapter')
@ -69,6 +74,9 @@ function graphqlRequest(
string $query,
?string $operationName,
?array $variables,
?string $operations,
?string $map,
?array $files,
Appwrite\Utopia\Request $request,
Appwrite\Utopia\Response $response,
CoroutinePromiseAdapter $promiseAdapter,
@ -97,6 +105,23 @@ function graphqlRequest(
$debugFlags = DebugFlag::NONE;
}
$map = \json_decode($map, true);
$result = \json_decode($operations, true);
if ($request->getHeader('content-type') === 'multipart/form-data') {
foreach ($map as $fileKey => $locations) {
foreach ($locations as $location) {
$items = &$result;
foreach (explode('.', $location) as $key) {
if (!isset($items[$key]) || !is_array($items[$key])) {
$items[$key] = [];
}
$items = &$items[$key];
}
$items = $request->getFiles($fileKey);
}
}
}
$promise = GraphQL::promiseToExecute(
$promiseAdapter,
$gqlSchema,

View file

@ -2,7 +2,8 @@
namespace Appwrite\GraphQL;
use Appwrite\GraphQL\Types\JsonType;
use Appwrite\GraphQL\Types\InputFile;
use Appwrite\GraphQL\Types\Json;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
@ -23,7 +24,8 @@ use function Co\go;
class Builder
{
protected static ?JsonType $jsonParser = null;
protected static ?Json $jsonType = null;
protected static ?InputFile $inputFile = null;
protected static array $typeMapping = [];
protected static array $defaultDocumentArgs = [];
@ -90,17 +92,20 @@ class Builder
];
}
/**
* Create a singleton for $jsonParser
*
* @return JsonType
*/
public static function json(): JsonType
public static function json(): Json
{
if (is_null(self::$jsonParser)) {
self::$jsonParser = new JsonType();
if (is_null(self::$jsonType)) {
self::$jsonType = new Json();
}
return self::$jsonParser;
return self::$jsonType;
}
public static function inputFile(): InputFile
{
if (is_null(self::$inputFile)) {
self::$inputFile = new InputFile();
}
return self::$inputFile;
}
/**
@ -199,7 +204,6 @@ class Builder
case 'Appwrite\Network\Validator\URL':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
case 'Appwrite\Storage\Validator\File':
case 'Utopia\Database\Validator\Key':
case 'Utopia\Database\Validator\CustomId':
case 'Utopia\Database\Validator\UID':
@ -238,6 +242,9 @@ class Builder
case 'Utopia\Validator\JSON':
$type = self::json();
break;
case 'Appwrite\Storage\Validator\File':
$type = self::inputFile();
break;
default:
$type = Type::string();
break;

View file

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Appwrite\GraphQL\Types;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
use Psr\Http\Message\UploadedFileInterface;
use UnexpectedValueException;
class InputFile extends ScalarType
{
/**
* @var string
*/
public $name = 'Upload';
/**
* @var string
*/
public $description
= 'The `Upload` special type represents a file to be uploaded in the same HTTP request as specified by
[graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).';
/**
* Serializes an internal value to include in a response.
*
* @param mixed $value
*
* @return mixed
*/
public function serialize($value)
{
throw new InvariantViolation('`Upload` cannot be serialized');
}
/**
* Parses an externally provided value (query variable) to use as an input.
*
* @param mixed $value
**/
public function parseValue($value)
{
return $value;
}
/**
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
*
* @param \GraphQL\Language\AST\Node $valueNode
*
* @return mixed
*/
public function parseLiteral($valueNode, ?array $variables = null)
{
throw new Error('`Upload` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification. Instead got: ' . $valueNode->kind, $valueNode);
}
}

View file

@ -12,7 +12,7 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\ScalarType;
// https://github.com/webonyx/graphql-php/issues/129#issuecomment-309366803
class JsonType extends ScalarType
class Json extends ScalarType
{
public $name = 'Json';
public $description =
@ -40,25 +40,24 @@ class JsonType extends ScalarType
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
switch ($valueNode) {
case ($valueNode instanceof StringValueNode):
case ($valueNode instanceof BooleanValueNode):
case $valueNode instanceof StringValueNode:
case $valueNode instanceof BooleanValueNode:
return $valueNode->value;
case ($valueNode instanceof IntValueNode):
case ($valueNode instanceof FloatValueNode):
case $valueNode instanceof IntValueNode:
case $valueNode instanceof FloatValueNode:
return floatval($valueNode->value);
case ($valueNode instanceof ObjectValueNode): {
case $valueNode instanceof ObjectValueNode:
$value = [];
foreach ($valueNode->fields as $field) {
$value[$field->name->value] = $this->parseLiteral($field->value);
$value[$field->name->value] =
$this->parseLiteral($field->value);
}
return $value;
}
case ($valueNode instanceof ListValueNode):
return array_map([$this, 'parseLiteral'], $valueNode->values);
default:
return null;
}
}
private function identity($value)

View file

@ -550,9 +550,10 @@ trait GraphQLBase
}
}';
case self::$UPDATE_USER_PHONE:
return 'mutation updateUserPhone($userId: String!, number: String!){
usersUpdatePhone(userId: $userId, number: number) {
return 'mutation updateUserPhone($userId: String!, $number: String!){
usersUpdatePhone(userId: $userId, number: $number) {
name
phone
email
}
}';
@ -747,7 +748,7 @@ trait GraphQLBase
}
}';
case self::$UPDATE_ACCOUNT_PHONE:
return 'mutation updateAccountPhone($number: Json!, $password: String!){
return 'mutation updateAccountPhone($number: String!, $password: String!){
accountUpdatePhone(number: $number, password: $password) {
_id
name
@ -1099,7 +1100,7 @@ trait GraphQLBase
functionsDelete(functionId: $functionId)
}';
case self::$CREATE_DEPLOYMENT:
return 'mutation createDeployment($functionId: String!, $entrypoint: String!, $code: String!, $activate: Boolean!) {
return 'mutation createDeployment($functionId: String!, $entrypoint: String!, $code: InputFile!, $activate: Boolean!) {
functionsCreateDeployment(functionId: $functionId, entrypoint: $entrypoint, code: $code, activate: $activate) {
_id
entrypoint

View file

@ -447,8 +447,6 @@ class GraphQLDatabaseServerTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
\var_dump($actor);
$this->assertArrayNotHasKey('errors', $actor['body']);
$this->assertIsArray($actor['body']['data']);
$this->assertIsArray($actor['body']['data']['actorsCreate']);

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\GraphQL;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@ -53,12 +54,13 @@ class GraphQLFunctionsClientTest extends Scope
{
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
$code = realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz";
$gqlPayload = [
'query' => $query,
'variables' => [
'functionId' => $function['_id'],
'entrypoint' => 'main.rb',
'code' => realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz",
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
'activate' => true,
]
];

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\GraphQL;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@ -53,12 +54,13 @@ class GraphQLFunctionsServerTest extends Scope
{
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
$code = realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz";
$gqlPayload = [
'query' => $query,
'variables' => [
'functionId' => $function['_id'],
'entrypoint' => 'main.rb',
'code' => realpath(__DIR__ . '/../../../resources/functions') . "/ruby/code.tar.gz",
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
'activate' => true,
]
];

View file

@ -257,6 +257,6 @@ class GraphQLTeamsServerTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(204, $team['headers']['status-code']);
$this->assertEquals(200, $team['headers']['status-code']);
}
}