コンテンツにスキップするには Enter キーを押してください

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

本記事は、Fusic Advent Calendar 2018の20日目の記事になります。

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

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 // 独自のエラーハンドリングクラス
    ],

以上。

コメントする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です