Laravel + LighthouseでGraphQLのエンドポイントを作った時のエラーレスポンスのカスタマイズ方法
2018/12/21
Table of Contents
LaravelでGraphQLのエンドポイントを作る
Lighthouseは、graphql-phpをベースとしたLaravelのGraphQLのエンドポイント作成用のライブラリです。
[参考]
– Lighthouse
– LaravelでGraphQLを使い倒してみた
エラーレスポンスのカスタマイズが必要になった経緯
Lighthouseのエラーレスポンス
Lighthouseはgraphql-phpを利用しています。
graphql-phpの文章を確認すると下記の文章が記載されています。
Internal errors
As of version 0.10.0, all exceptions thrown in resolvers are reported with generic message “Internal server error”. This > is done to avoid information leak in production environments (e.g. database connection errors, file access errors, etc).
[Google翻訳]
Internal errors
バージョン0.10.0以降、リゾルバでスローされたすべての例外は、汎用メッセージ “Internal server error”で報告されます。
これは、運用環境での情報漏洩(データベース接続エラー、ファイルアクセスエラーなど)を回避するために行われます。
つまりセキュリテイのために全部「Internal server error」と表示されるわけです。
認証エラーが発生した際のレスポンスを下記に記載します。(スタータスコードは200系です。)
{
[
{
'debugMessage': 'Unauthenticated.',
'message': 'Internal server error',
'category': 'internal',
'locations': [
{
'line': 2,
'column': 3
}
],
'path': [
'users'
]
}
Unauthenticatedなのに「Internal server error」だと。。。。
クライアント側(Apollo)のエラーハンドリングの都合
クライアント側ではエラーレスポンスによって処理を変更する必要があります。
例)認証エラーならログイン画面、500系エラーならエラーページの表示
メッセージが「Internal server error」だと非常にめんどくさいですよね。
ApolloはGraphQLを利用するためのクライアント側のライブラリです。
Apolloのエラーハンドリングの記事では下記のようなエラーのレスポンスのサンプルが紹介されています。
{
'error': {
'graphQLErrors': [
{
'message': 'forbidden',
'locations': [],
'path': [
'protectedAction'
],
'extensions': {
'code': 'UNAUTHENTICATED',
}
}
],
'networkError': null,
'message': 'GraphQL error: You must be logged in'
}
}
codeをちゃんと記載することでエラーの判別を容易にしています。
[参考]
– graphql-php
– graphql-php doc
– Apollo
– Full Stack Error Handling with GraphQL and Apollo
エラーレスポンスをカスタマイズしよう
やっと本題ですね。
Laravel + Lighthouseのエラーレスポンスをカスタマイズしましょう。
手順はこちら
- 独自のエラーハンドリングクラスを作成
- Lighthouseの設定ファイルを修正してエラーハンドリングクラスに独自のエラーハンドリングクラスを追加
1. 独自のエラーハンドリングクラスを作成
まず独自のエラーハンドリングクラスを作成します。
\<?php
namespace App\Exceptions;
use GraphQL\Error\Error;
use App\Error\ApolloError;
class GraphQLErrorHandler implements \Nuwave\Lighthouse\Execution\ErrorHandler
{
public static function handle(Error $error, \Closure $next): array
{
$error = new ApolloError(
$error-\>message,
$error-\>getNodes(),
$error-\>getSource(),
$error-\>getPositions(),
$error-\>getPath(),
$error-\>getPrevious()
);
return $next($error);
}
}
こんな感じでメッセージとエラーコードを紐つけています。
\<?php
namespace App\Error;
use GraphQL\Error\Error;
use GraphQL\Language\Source;
class ApolloError extends Error
{
// エラーコードの定義
const CODE\_UNAUTHENTICATED = 'UNAUTHENTICATED'; // 認証エラー
const CODE\_SCHEMAERROR = 'SCHEMAERROR'; // スキーマエラー
const CODE\_VALIDATIONERROR = 'VALIDATIONERROR'; // バリデーションエラー
// エラーメッセージとエラーコードの対応付け
private $catchMessages = [
'Token not provided' =\> self::CODE\_UNAUTHENTICATED,
'Wrong number of segments' =\> self::CODE\_UNAUTHENTICATED,
'Unauthenticated' =\> self::CODE\_UNAUTHENTICATED
];
/\*\*
\* \_\_construct
\* @author ito
\*/
public function \_\_construct(
$message,
$nodes = null,
Source $source = null,
$positions = null,
$path = null,
$previous = null,
array $extensions = []
){
// messageに従ってエラー内容を修正
$errorProperties = $this-\>getErrorProperties($message, $nodes, $source, $positions, $path, $previous, $extensions);
parent::\_\_construct(
$errorProperties['message'],
$errorProperties['nodes'],
$errorProperties['source'],
$errorProperties['positions'],
$errorProperties['path'],
$errorProperties['previous'],
$errorProperties['extensions']
);
}
/\*\*
\* getErrorProperties
\* @author ito
\*/
private function getErrorProperties(
$message,
$nodes,
Source $source,
$positions,
$path,
$previous,
$extensions
){
$errorProperties = null;
foreach ($this-&gt;catchMessages as $catchMessage =&gt; $errorCode) {
if ($message == $catchMessage) {
$errorProperties = [
'message' =\> $message,
'nodes' =\> null,
'source' =\> null,
'positions' =\> null,
'path' =\> null,
'previous' =\> null,
'extensions' =\> [
'extensions' =\> ['code' => $errorCode]
]
];
break;
}
}
$originalErrorProperties = [
'message' =\> $message,
'nodes' =\> $nodes,
'source' =\> $source,
'positions' =\> $positions,
'path' =\> $path,
'previous' =\> $previous,
'extensions' =\> $extensions
];
return (!empty($errorProperties))? $errorProperties : $originalErrorProperties;
}
}
2. Lighthouseの設定ファイルを修正してエラーハンドリングクラスに独自のエラーハンドリングクラスを追加
/config/lighthouse.php
'error\_handlers' =\> [
\Nuwave\Lighthouse\Execution\ExtensionErrorHandler::class,
\App\Exceptions\GraphQLErrorHandler::class // 独自のエラーハンドリングクラス
],
以上。
Junkins
JenkinsじゃないよJunkinsです。紛らわしくてすいません。 元々PHPerでしたが、最近Rubyistになりました。