1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
//! GraphQL support for [`anyhow::Error`].
//!
//! # Example
//!
//! ```rust
//! # use std::backtrace::Backtrace;
//! use anyhow::anyhow;
//! # use juniper::graphql_object;
//!
//! struct Root;
//!
//! #[graphql_object]
//! impl Root {
//! fn err() -> anyhow::Result<i32> {
//! Err(anyhow!("errored!"))
//! }
//! }
//! ```
//!
//! # Backtrace
//!
//! Backtrace is supported in the same way as [`anyhow`] crate does:
//! > If using the nightly channel, or stable with `features = ["backtrace"]`, a backtrace is
//! > captured and printed with the error if the underlying error type does not already provide its
//! > own. In order to see backtraces, they must be enabled through the environment variables
//! > described in [`std::backtrace`]:
//! > - If you want panics and errors to both have backtraces, set `RUST_BACKTRACE=1`;
//! > - If you want only errors to have backtraces, set `RUST_LIB_BACKTRACE=1`;
//! > - If you want only panics to have backtraces, set `RUST_BACKTRACE=1` and
//! > `RUST_LIB_BACKTRACE=0`.
use crate::{FieldError, IntoFieldError, ScalarValue, Value};
impl<S: ScalarValue> IntoFieldError<S> for anyhow::Error {
fn into_field_error(self) -> FieldError<S> {
#[cfg(any(nightly, feature = "backtrace"))]
let extensions = {
let backtrace = self.backtrace().to_string();
if backtrace == "disabled backtrace" {
Value::Null
} else {
let mut obj = crate::value::Object::with_capacity(1);
_ = obj.add_field(
"backtrace",
Value::List(
backtrace
.split('\n')
.map(|line| Value::Scalar(line.to_owned().into()))
.collect(),
),
);
Value::Object(obj)
}
};
#[cfg(not(any(nightly, feature = "backtrace")))]
let extensions = Value::Null;
FieldError::new(self, extensions)
}
}
#[cfg(test)]
mod test {
use std::env;
use anyhow::anyhow;
use serial_test::serial;
use crate::{
execute, graphql_object, graphql_value, graphql_vars, parser::SourcePosition,
EmptyMutation, EmptySubscription, RootNode,
};
#[tokio::test]
#[serial]
async fn simple() {
struct Root;
#[graphql_object]
impl Root {
fn err() -> anyhow::Result<i32> {
Err(anyhow!("errored!"))
}
}
let prev_env = env::var("RUST_BACKTRACE").ok();
env::set_var("RUST_BACKTRACE", "1");
const DOC: &str = r#"{
err
}"#;
let schema = RootNode::new(
Root,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
let res = execute(DOC, None, &schema, &graphql_vars! {}, &()).await;
assert!(res.is_ok(), "failed: {:?}", res.unwrap_err());
let (val, errs) = res.unwrap();
assert_eq!(val, graphql_value!(null));
assert_eq!(errs.len(), 1, "too many errors: {errs:?}");
let err = errs.first().unwrap();
assert_eq!(*err.location(), SourcePosition::new(14, 1, 12));
assert_eq!(err.path(), &["err"]);
let err = err.error();
assert_eq!(err.message(), "errored!");
#[cfg(not(any(nightly, feature = "backtrace")))]
assert_eq!(err.extensions(), &graphql_value!(null));
#[cfg(any(nightly, feature = "backtrace"))]
assert_eq!(
err.extensions()
.as_object_value()
.map(|ext| ext.contains_field("backtrace")),
Some(true),
"no `backtrace` in extensions: {err:?}",
);
if let Some(val) = prev_env {
env::set_var("RUST_BACKTRACE", val);
}
}
}