- implement `IntoFieldError` for `anyhow::Error` - add `anyhow` and `backtrace` Cargo features
This commit is contained in:
parent
2215cd0e0b
commit
316b1887b2
6 changed files with 144 additions and 2 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -99,6 +99,8 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { feature: <none>, crate: juniper }
|
- { feature: <none>, crate: juniper }
|
||||||
|
- { feature: anyhow, crate: juniper }
|
||||||
|
- { feature: "anyhow,backtrace", crate: juniper }
|
||||||
- { feature: bigdecimal, crate: juniper }
|
- { feature: bigdecimal, crate: juniper }
|
||||||
- { feature: bson, crate: juniper }
|
- { feature: bson, crate: juniper }
|
||||||
- { feature: chrono, crate: juniper }
|
- { feature: chrono, crate: juniper }
|
||||||
|
|
|
@ -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])
|
- `js` [Cargo feature] enabling `js-sys` and `wasm-bindgen` support for `wasm32-unknown-unknown` target. ([#1118], [#1147])
|
||||||
- `LookAheadMethods::applies_for()` method. ([#1138], [#1145])
|
- `LookAheadMethods::applies_for()` method. ([#1138], [#1145])
|
||||||
- `LookAheadMethods::field_original_name()` and `LookAheadMethods::field_alias()` methods. ([#1199])
|
- `LookAheadMethods::field_original_name()` and `LookAheadMethods::field_alias()` methods. ([#1199])
|
||||||
|
- [`anyhow` crate] integration behind `anyhow` and `backtrace` [Cargo feature]s. ([#1215], [#988])
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
||||||
[#979]: /../../pull/979
|
[#979]: /../../pull/979
|
||||||
[#985]: /../../pull/985
|
[#985]: /../../pull/985
|
||||||
[#987]: /../../pull/987
|
[#987]: /../../pull/987
|
||||||
|
[#988]: /../../issues/988
|
||||||
[#996]: /../../pull/996
|
[#996]: /../../pull/996
|
||||||
[#1000]: /../../issues/1000
|
[#1000]: /../../issues/1000
|
||||||
[#1001]: /../../pull/1001
|
[#1001]: /../../pull/1001
|
||||||
|
@ -137,6 +139,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
||||||
[#1207]: /../../pull/1207
|
[#1207]: /../../pull/1207
|
||||||
[#1208]: /../../pull/1208
|
[#1208]: /../../pull/1208
|
||||||
[#1209]: /../../pull/1209
|
[#1209]: /../../pull/1209
|
||||||
|
[#1215]: /../../pull/1215
|
||||||
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
|
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
|
||||||
[CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j
|
[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
|
[`bigdecimal` crate]: https://docs.rs/bigdecimal
|
||||||
[`bson` crate]: https://docs.rs/bson
|
[`bson` crate]: https://docs.rs/bson
|
||||||
[`chrono` crate]: https://docs.rs/chrono
|
[`chrono` crate]: https://docs.rs/chrono
|
||||||
|
|
|
@ -31,6 +31,8 @@ default = [
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
anyhow = ["dep:anyhow"]
|
||||||
|
backtrace = ["anyhow?/backtrace"]
|
||||||
bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "dep:ryu"]
|
bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "dep:ryu"]
|
||||||
bson = ["dep:bson"]
|
bson = ["dep:bson"]
|
||||||
chrono = ["dep:chrono"]
|
chrono = ["dep:chrono"]
|
||||||
|
@ -46,7 +48,7 @@ url = ["dep:url"]
|
||||||
uuid = ["dep:uuid"]
|
uuid = ["dep:uuid"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.47", default-features = false, optional = true }
|
anyhow = { version = "1.0.47", optional = true }
|
||||||
async-trait = "0.1.39"
|
async-trait = "0.1.39"
|
||||||
bigdecimal = { version = "0.4", optional = true }
|
bigdecimal = { version = "0.4", optional = true }
|
||||||
bson = { version = "2.4", features = ["chrono-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 }
|
chrono = { version = "0.4.30", features = ["alloc"], default-features = false }
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
serde_json = "1.0.18"
|
serde_json = "1.0.18"
|
||||||
|
serial_test = "2.0"
|
||||||
tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|
131
juniper/src/integrations/anyhow.rs
Normal file
131
juniper/src/integrations/anyhow.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
//! Provides GraphQLType implementations for some external types
|
//! Provides GraphQLType implementations for some external types
|
||||||
|
|
||||||
|
#[cfg(feature = "anyhow")]
|
||||||
|
pub mod anyhow;
|
||||||
#[cfg(feature = "bigdecimal")]
|
#[cfg(feature = "bigdecimal")]
|
||||||
pub mod bigdecimal;
|
pub mod bigdecimal;
|
||||||
#[cfg(feature = "bson")]
|
#[cfg(feature = "bson")]
|
||||||
|
|
|
@ -332,7 +332,7 @@ mod fallible_method {
|
||||||
|
|
||||||
impl<S: ScalarValue> IntoFieldError<S> for CustomError {
|
impl<S: ScalarValue> IntoFieldError<S> for CustomError {
|
||||||
fn into_field_error(self) -> FieldError<S> {
|
fn into_field_error(self) -> FieldError<S> {
|
||||||
juniper::FieldError::new("Whatever", graphql_value!({"code": "some"}))
|
FieldError::new("Whatever", graphql_value!({"code": "some"}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue