# 错误扩展

引用 graphql-spec

GraphQL 服务可以通过扩展提供错误的附加条目。 该条目(如果设置)必须是一个映射作为其值,用于附加错误的其它信息。

示例

我建议您查看此 错误扩展示例 作为快速入门。

一般概念

Async-graphql中,所有面向用户的错误都强制转换为Error类型,默认情况下会提供 由std:::fmt::Display暴露的错误消息。但是,Error实际上提供了一个额外的可以扩展错误的信息。

Resolver 函数类似这样:

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
struct Query;
#[Object]
impl Query {
async fn parse_with_extensions(&self) -> Result<i32, Error> {
    Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
}
}
}

然后可以返回如下响应:

{
  "errors": [
    {
      "message": "MyMessage",
      "locations": [ ... ],
      "path": [ ... ],
      "extensions": {
        "details": "CAN_NOT_FETCH",
      }
    }
  ]
}

ErrorExtensions

手动构造新的Error很麻烦。这就是为什么Async-graphql提供 两个方便特性,可将您的错误转换为适当的Error扩展。

扩展任何错误的最简单方法是对错误调用extend_with。 这将把任何错误转换为具有给定扩展信息的Error

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
struct Query;
use std::num::ParseIntError;
#[Object]
impl Query {
async fn parse_with_extensions(&self) -> Result<i32> {
     Ok("234a"
         .parse()
         .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
}
}
}

为自定义错误实现 ErrorExtensions

你也可以给自己的错误类型实现ErrorExtensions:

#![allow(unused)]
fn main() {
extern crate async_graphql;
extern crate thiserror;
use async_graphql::*;
#[derive(Debug, thiserror::Error)]
pub enum MyError {
    #[error("Could not find resource")]
    NotFound,

    #[error("ServerError")]
    ServerError(String),

    #[error("No Extensions")]
    ErrorWithoutExtensions,
}

impl ErrorExtensions for MyError {
    // lets define our base extensions
    fn extend(&self) -> Error {
        Error::new(format!("{}", self)).extend_with(|err, e| 
            match self {
              MyError::NotFound => e.set("code", "NOT_FOUND"),
              MyError::ServerError(reason) => e.set("reason", reason.clone()),
              MyError::ErrorWithoutExtensions => {}
          })
    }
}
}

您只需要对错误调用extend即可将错误与其提供的扩展信息一起传递,或者通过extend_with进一步扩展错误信息。

#![allow(unused)]
fn main() {
extern crate async_graphql;
extern crate thiserror;
use async_graphql::*;
#[derive(Debug, thiserror::Error)]
pub enum MyError {
    #[error("Could not find resource")]
    NotFound,

    #[error("ServerError")]
    ServerError(String),

    #[error("No Extensions")]
    ErrorWithoutExtensions,
}
struct Query;
#[Object]
impl Query {
async fn parse_with_extensions_result(&self) -> Result<i32> {
    // Err(MyError::NotFound.extend())
    // OR
    Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
}
}
}
{
  "errors": [
    {
      "message": "NotFound",
      "locations": [ ... ],
      "path": [ ... ],
      "extensions": {
        "code": "NOT_FOUND",
        "on_the_fly": "some_more_info"
      }
    }
  ]
}

ResultExt

这个特质使您可以直接在结果上调用extend_err。因此上面的代码不再那么冗长。

// @todo figure out why this example does not compile!
extern crate async_graphql;
use async_graphql::*;
struct Query;
#[Object]
impl Query {
async fn parse_with_extensions(&self) -> Result<i32> {
     Ok("234a"
         .parse()
         .extend_err(|_, e| e.set("code", 404))?)
}
}

链式调用

由于对所有&E where E: std::fmt::Display实现了ErrorExtensionsResultsExt,我们可以将扩展链接在一起。

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
struct Query;
#[Object]
impl Query {
async fn parse_with_extensions(&self) -> Result<i32> {
    match "234a".parse() {
        Ok(n) => Ok(n),
        Err(e) => Err(e
            .extend_with(|_, e| e.set("code", 404))
            .extend_with(|_, e| e.set("details", "some more info.."))
            // keys may also overwrite previous keys...
            .extend_with(|_, e| e.set("code", 500))),
    }
}
}
}

响应:

{
  "errors": [
    {
      "message": "MyMessage",
      "locations": [ ... ],
      "path": [ ... ],
      "extensions": {
      	"details": "some more info...",
        "code": 500,
      }
    }
  ]
}

缺陷

Rust 的稳定版本还未提供特化功能,这就是为什么ErrorExtensions&E where E: std::fmt::Display实现,代替E:std::fmt::Display通过提供一些特化功能。

Autoref-based stable specialization.

缺点是下面的代码不能编译:

async fn parse_with_extensions_result(&self) -> Result<i32> {
    // the trait `error::ErrorExtensions` is not implemented
    // for `std::num::ParseIntError`
    "234a".parse().extend_err(|_, e| e.set("code", 404))
}

但这可以通过编译:

async fn parse_with_extensions_result(&self) -> Result<i32> {
    // does work because ErrorExtensions is implemented for &ParseIntError
    "234a"
      .parse()
      .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
}