错误处理

types/objects/error_handling.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

Rust 将错误组合成两个主要类别Result<T, E> 处理可恢复错误panic! 处理不可恢复错误。Juniper 对不可恢复错误不做处理;不可恢复错误将上溯到集成 Juniper的框架,然后错误在框架层次有希望得到处理。

对于可恢复错误,Juniper 能够完善地地处理内建的 Result 类型,你可以使用 ? 操作符或者 try! 宏(macro)来让程序按照预期设定工作:

# extern crate juniper;
use std::{
    str,
    path::PathBuf,
    fs::{File},
    io::{Read},
};
use juniper::FieldResult;

struct Example {
    filename: PathBuf,
}

#[juniper::object]
impl Example {
    fn contents() -> FieldResult<String> {
        let mut file = File::open(&self.filename)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }

    fn foo() -> FieldResult<Option<String>> {
      // 随意的无效字节值
      let invalid = vec![128, 223];

      match str::from_utf8(&invalid) {
        Ok(s) => Ok(Some(s.to_string())),
        Err(e) => Err(e)?,
      }
    }
}

# fn main() {}

FieldResult<T>Result<T, FieldError> 的别名,其是所有字段都必须返回的错误类型。通过使用 ? 操作符或者 try! 宏(macro),任何实现了 Display 特性的类型——这些既是当前绝大多数错误类型——这些错误会自动转换为 FieldError

当字段返回错误时,字段的结果将被 null 替换,然后在响应的顶层附加一个名为 errors 的对象,最后继续执行程序。例如,基于前述的示例代码,在 GraphQL 做如下查询:

{
  example {
    contents
    foo
  }
}

若果 str::from_utf8 导致了 std::str::Utf8Error 错误, 将返回以下内容:

!文件名 错误的可空字段的响应

{
  "data": {
    "example": {
      contents: "<Contents of the file>",
      foo: null,
    }
  },
  "errors": [
    "message": "invalid utf-8 sequence of 2 bytes from index 0",
    "locations": [{ "line": 2, "column": 4 }])
  ]
}

如果非空字段返回错误,如同上述示例代码,null 值将传播到第一个可空的父字段;如果没有可空字段,则传播到根(root)内名为 data 的对象。

举例,执行如下查询:

{
  example {
    contents
  }
}

若果上述代码中的 File::open() 导致 std::io::ErrorKind::PermissionDenied 错误,将返回以下内容:

!文件名 没有可空父子段的非空字段的响应

{
  "errors": [
    "message": "Permission denied (os error 13)",
    "locations": [{ "line": 2, "column": 4 }])
  ]
}

结构化错误

有些情况下,有必要向客户端返回附加的结构化错误信息。可以通过实现 IntoFieldError 来解决:

# #[macro_use] extern crate juniper;
enum CustomError {
    WhateverNotSet,
}

impl juniper::IntoFieldError for CustomError {
    fn into_field_error(self) -> juniper::FieldError {
        match self {
            CustomError::WhateverNotSet => juniper::FieldError::new(
                "不存在任何东东",
                graphql_value!({
                    "type": "NO_WHATEVER"
                }),
            ),
        }
    }
}

struct Example {
    whatever: Option<bool>,
}

#[juniper::object]
impl Example {
    fn whatever() -> Result<bool, CustomError> {
      if let Some(value) = self.whatever {
        return Ok(value);
      }
      Err(CustomError::WhateverNotSet)
    }
}

# fn main() {}

指定的结构化错误信息被包含在名为 extensions 的键值中:

{
  "errors": [
    "message": "不存在任何东东",
    "locations": [{ "line": 2, "column": 4 }]),
    "extensions": {
      "type": "NO_WHATEVER"
    }
  ]
}