LoginSignup
0
0

More than 1 year has passed since last update.

Sangriaにおけるエラーハンドリング

Posted at

Scala 用の GraphQL ライブラリ Sangria の下記エラーハンドリングに関してです。

  • (a) resolve 処理で例外が発生した場合
  • (b) GraphQL クエリに問題があった場合

はじめに

今回は以下の環境で実行しました。

build.sbt
ThisBuild / scalaVersion := "3.2.1"

libraryDependencies ++= Seq(
  "org.sangria-graphql" %% "sangria" % "3.4.1",
  "org.sangria-graphql" %% "sangria-circe" % "1.3.2"
)

GraphQL の結果を JSON 文字列で出力するため sangria-circe を使っています。

(a) resolve 処理で例外が発生した場合

resolve 処理で何らかの例外が発生した場合、デフォルトでエラーメッセージが Internal server error となります。

例えば、以下のような処理を実行した場合

処理1
import sangria.schema.*
import sangria.execution.*
import sangria.macros.graphql
import sangria.marshalling.circe.*
import scala.concurrent.ExecutionContext

...

val QueryType = ObjectType("Query", fields[Unit, Unit](
  Field(
    "sample", 
    StringType, 
    arguments = Argument("input", StringType) :: Nil,
    resolve = c =>
      val input = c.args.arg[String]("input")

      if input.isBlank then
        throw Exception("input is blank")
      else
        s"ok-${input}"
  )
))

val schema = Schema(QueryType)

val q = graphql"""{ sample(input: "") }"""

given ExecutionContext = ExecutionContext.global

val r1 = Executor.execute(schema, q)
// r1 は Future(<not completed>)
r1.foreach(r => println(s"r1 result = ${r}"))

実行結果は次のようになります。
Future としては成功扱いとなる点に注意。

実行結果1
r1 result = {
  "data" : null,
  "errors" : [
    {
      "message" : "Internal server error",
      "path" : [
        "sample"
      ],
      "locations" : [
        {
          "line" : 1,
          "column" : 3
        }
      ]
    }
  ]
}

このような例外は ExceptionHandler を使ってハンドリングでき、
HandledException で GraphQL 形式のエラー内容を組み立てる事ができます。

処理2 - ExceptionHandler の適用例
// ExceptionHandler の実装
val exceptionHandler = ExceptionHandler {
  case (_, e) => {
    println(s"*** Error Handling: ${e}")
    // エラーメッセージを変更
    HandledException(e.getMessage)
  }
}

val r2 = Executor.execute(schema, q, exceptionHandler = exceptionHandler)
r2.foreach(r => println(s"r2 result = ${r}"))

ExceptionHandler の適用結果は次のようになりました。

実行結果2
*** Error Handling: java.lang.Exception: input is blank
r2 result = {
  "data" : null,
  "errors" : [
    {
      "message" : "input is blank",
      "path" : [
        "sample"
      ],
      "locations" : [
        {
          "line" : 1,
          "column" : 3
        }
      ]
    }
  ]
}

(b) GraphQL クエリに問題があった場合

次に、以下のように GraphQL クエリ自体に問題があった場合、ExceptionHandler ではハンドリングされず、Future(Failure(sangria.execution.ValidationError: Query does not pass validation. Violations: ・・・ のように Future 自体が失敗扱いとなります。

GraphQL クエリに問題がある場合
// 問題のあるクエリ(input 引数が未指定)
val q2 = graphql"{ sample }"

val r3 = Executor.execute(schema, q2, exceptionHandler = exceptionHandler)
// r3 が Future(Failure(sangria.execution.ValidationError)) となり foreach の処理は実行されない
r3.foreach(r => println(s"r3 result1 = ${r}"))

そのため、Future の recover でエラーハンドリングするしか無さそうです。

エラー自体は ValidationError の他にもいくつか用意されているようですが、どのエラーも基本的に ErrorWithResolver トレイトを実装しており、その resolveError メソッドを使う事で GraphQL 形式のエラー結果 {・・・, "errors": [・・・]} を得られます。

処理3 - クエリエラーのハンドリング例
...

r3.recover {
  case e: ErrorWithResolver => e.resolveError
}.foreach(r => println(s"r3 result2 = ${r}"))
実行結果3
r3 result2 = {
  "data" : null,
  "errors" : [
    {
      "message" : "Field 'sample' argument 'input' of type 'String!' is required but not provided. (line 1, column 3):\n{ sample }\n  ^",
      "locations" : [
        {
          "line" : 1,
          "column" : 3
        }
      ]
    }
  ]
}

また、queryValidator を設定してクエリの検証をスキップする事も可能でしたが、ExceptionHandler ではハンドリングできないようですし、基本的には使わない方が良さそうです。

処理4 - クエリの検証をスキップ
val r4 = Executor.execute(schema, q2, queryValidator = QueryValidator.empty)
// r4 は Future(<not completed>)
r4.foreach(r => println(s"r4 result = ${r}"))
実行結果4
r4 result = {
  "data" : null,
  "errors" : [
    {
      "message" : "Null value was provided for the NotNull Type 'String!' at path 'input'. (line 1, column 3):\n{ sample }\n  ^",
      "path" : [
        "sample"
      ],
      "locations" : [
        {
          "line" : 1,
          "column" : 3
        }
      ]
    }
  ]
}

今回のサンプルコードは https://github.com/fits/try_samples/tree/master/blog/20230107/sangria_error_handling

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0