Top View


Author Junkins

Laravel + LighthouseでGraphQLのエンドポイントを作った時のエラーレスポンスのカスタマイズ方法

2018/12/21

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のエラーレスポンスをカスタマイズしましょう。

手順はこちら

  1. 独自のエラーハンドリングクラスを作成
  2. 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-&amp;gt;catchMessages as $catchMessage =&amp;gt; $errorCode) {
 if ($message == $catchMessage) {
 $errorProperties = [
 'message' =\> $message,
 'nodes' =\> null,
 'source' =\> null,
 'positions' =\> null,
 'path' =\> null,
 'previous' =\> null,
 'extensions' =\> [
 'extensions' =\> ['code' =&gt; $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

Junkins

JenkinsじゃないよJunkinsです。紛らわしくてすいません。 元々PHPerでしたが、最近Rubyistになりました。