Provide anyhow crate integration (#1215, #988)

- implement `IntoFieldError` for `anyhow::Error`
- add `anyhow` and `backtrace` Cargo features
This commit is contained in:
Kai Ren 2023-11-15 19:29:59 +01:00 committed by GitHub
parent 2215cd0e0b
commit 316b1887b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 2 deletions

View file

@ -99,6 +99,8 @@ jobs:
matrix:
include:
- { feature: <none>, crate: juniper }
- { feature: anyhow, crate: juniper }
- { feature: "anyhow,backtrace", crate: juniper }
- { feature: bigdecimal, crate: juniper }
- { feature: bson, crate: juniper }
- { feature: chrono, crate: juniper }

View file

@ -69,6 +69,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- `js` [Cargo feature] enabling `js-sys` and `wasm-bindgen` support for `wasm32-unknown-unknown` target. ([#1118], [#1147])
- `LookAheadMethods::applies_for()` method. ([#1138], [#1145])
- `LookAheadMethods::field_original_name()` and `LookAheadMethods::field_alias()` methods. ([#1199])
- [`anyhow` crate] integration behind `anyhow` and `backtrace` [Cargo feature]s. ([#1215], [#988])
### Changed
@ -98,6 +99,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#979]: /../../pull/979
[#985]: /../../pull/985
[#987]: /../../pull/987
[#988]: /../../issues/988
[#996]: /../../pull/996
[#1000]: /../../issues/1000
[#1001]: /../../pull/1001
@ -137,6 +139,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1207]: /../../pull/1207
[#1208]: /../../pull/1208
[#1209]: /../../pull/1209
[#1215]: /../../pull/1215
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
[CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j
@ -150,6 +153,7 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.9/juniper/CHANGELOG.md).
[`anyhow` crate]: https://docs.rs/anyhow
[`bigdecimal` crate]: https://docs.rs/bigdecimal
[`bson` crate]: https://docs.rs/bson
[`chrono` crate]: https://docs.rs/chrono

View file

@ -31,6 +31,8 @@ default = [
"url",
"uuid",
]
anyhow = ["dep:anyhow"]
backtrace = ["anyhow?/backtrace"]
bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "dep:ryu"]
bson = ["dep:bson"]
chrono = ["dep:chrono"]
@ -46,7 +48,7 @@ url = ["dep:url"]
uuid = ["dep:uuid"]
[dependencies]
anyhow = { version = "1.0.47", default-features = false, optional = true }
anyhow = { version = "1.0.47", optional = true }
async-trait = "0.1.39"
bigdecimal = { version = "0.4", optional = true }
bson = { version = "2.4", features = ["chrono-0_4"], optional = true }
@ -81,6 +83,7 @@ bencher = "0.1.2"
chrono = { version = "0.4.30", features = ["alloc"], default-features = false }
pretty_assertions = "1.0.0"
serde_json = "1.0.18"
serial_test = "2.0"
tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] }
[[bench]]

View file

@ -0,0 +1,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);
}
}
}

View file

@ -1,5 +1,7 @@
//! Provides GraphQLType implementations for some external types
#[cfg(feature = "anyhow")]
pub mod anyhow;
#[cfg(feature = "bigdecimal")]
pub mod bigdecimal;
#[cfg(feature = "bson")]

View file

@ -332,7 +332,7 @@ mod fallible_method {
impl<S: ScalarValue> IntoFieldError<S> for CustomError {
fn into_field_error(self) -> FieldError<S> {
juniper::FieldError::new("Whatever", graphql_value!({"code": "some"}))
FieldError::new("Whatever", graphql_value!({"code": "some"}))
}
}