Skip to content

Commit

Permalink
FEATURE: Improved error handling
Browse files Browse the repository at this point in the history
* Custom `GraphQlView` that converts any exceptions hiding the original exception message
  and adding some useful details like `_statusCode` and `_referenceCode`.

Note: The underscores indicate that those properties are not part of the official
GraphQL specification (see http://facebook.github.io/graphql/#sec-Errors)
  • Loading branch information
bwaidelich committed Jul 26, 2016
1 parent 67f5db7 commit 2c17334
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 11 deletions.
23 changes: 12 additions & 11 deletions Classes/Controller/StandardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Mvc\Controller\ActionController;
use Wwwision\GraphQL\TypeResolver;
use Wwwision\GraphQL\View\GraphQlView;

/**
* Default controller serving a GraphiQL interface as well as the GraphQL endpoint
Expand All @@ -24,6 +25,11 @@ class StandardController extends ActionController
*/
protected $supportedMediaTypes = ['application/json', 'text/html'];

/**
* @var array
*/
protected $viewFormatToObjectNameMap = ['json' => GraphQlView::class];

/**
* @param string $endpoint The GraphQL endpoint, to allow for providing multiple APIs (this value is set from the routing usually)
* @return void
Expand All @@ -46,18 +52,13 @@ public function queryAction($endpoint, $query, $variables = null, $operationName
{
$this->verifySettings($endpoint);
$decodedVariables = json_decode($variables, true);
try {

$querySchema = $this->typeResolver->get($this->settings['endpoints'][$endpoint]['querySchema']);
$mutationSchema = isset($this->settings['endpoints'][$endpoint]['mutationSchema']) ? $this->typeResolver->get($this->settings['endpoints'][$endpoint]['mutationSchema']) : null;
$subscriptionSchema = isset($this->settings['endpoints'][$endpoint]['subscriptionSchema']) ? $this->typeResolver->get($this->settings['endpoints'][$endpoint]['subscriptionSchema']) : null;
$schema = new Schema($querySchema, $mutationSchema, $subscriptionSchema);
$result = GraphQL::execute($schema, $query, null, $decodedVariables, $operationName);
} catch (\Exception $exception) {
$result = ['errors' => [['message' => $exception->getMessage()]]];
}
header('Content-Type: application/json');
return json_encode($result);
$querySchema = $this->typeResolver->get($this->settings['endpoints'][$endpoint]['querySchema']);
$mutationSchema = isset($this->settings['endpoints'][$endpoint]['mutationSchema']) ? $this->typeResolver->get($this->settings['endpoints'][$endpoint]['mutationSchema']) : null;
$subscriptionSchema = isset($this->settings['endpoints'][$endpoint]['subscriptionSchema']) ? $this->typeResolver->get($this->settings['endpoints'][$endpoint]['subscriptionSchema']) : null;
$schema = new Schema($querySchema, $mutationSchema, $subscriptionSchema);
$result = GraphQL::executeAndReturnResult($schema, $query, null, $decodedVariables, $operationName);
$this->view->assign('result', $result);
}

/**
Expand Down
67 changes: 67 additions & 0 deletions Classes/View/GraphQlView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
namespace Wwwision\GraphQL\View;

use GraphQL\Error;
use GraphQL\Executor\ExecutionResult;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Http\Response as HttpResponse;
use TYPO3\Flow\Mvc\View\AbstractView;
use TYPO3\Flow\Exception as FlowException;

class GraphQlView extends AbstractView
{

/**
* @return string The rendered view
* @throws FlowException
*/
public function render()
{
if (!isset($this->variables['result'])) {
throw new FlowException(sprintf('The GraphQlView expects a variable "result" of type "%s", non given!', ExecutionResult::class), 1469545196);
}
$result = $this->variables['result'];
if (!$result instanceof ExecutionResult) {
throw new FlowException(sprintf('The GraphQlView expects a variable "result" of type "%s", "%s" given!', ExecutionResult::class, is_object($result) ? get_class($result) : gettype($result)), 1469545198);
}

/** @var HttpResponse $response */
$response = $this->controllerContext->getResponse();
$response->setHeader('Content-Type', 'application/json');

return json_encode($this->formatResult($result));
}

/**
* Formats the result of the GraphQL execution, converting Flow exceptions by hiding the original exception message
* and adding status- and referenceCode.
*
* @param ExecutionResult $executionResult
* @return array
*/
private function formatResult(ExecutionResult $executionResult)
{
$convertedResult = [
'data' => $executionResult->data,
];
if (!empty($executionResult->errors)) {
$convertedResult['errors'] = array_map(function(Error $error) {
$errorResult = [
'message' => $error->message,
'locations' => $error->getLocations()
];
$exception = $error->getPrevious();
if ($exception instanceof FlowException) {
$errorResult['message'] = HttpResponse::getStatusMessageByCode($exception->getStatusCode());
$errorResult['_statusCode'] = $exception->getStatusCode();
$errorResult['_referenceCode'] = $exception->getReferenceCode();
}
return $errorResult;
}, $executionResult->errors);
}
if (!empty($executionResult->extensions)) {
$convertedResult['extensions'] = (array)$executionResult->extensions;
}
return $convertedResult;
}
}

0 comments on commit 2c17334

Please sign in to comment.