use std::fmt;
use std::pin::Pin;
use futures::Stream;
use heck::ToShoutySnakeCase;
pub use router_bridge::planner::Location;
use router_bridge::planner::PlanError;
use router_bridge::planner::PlanErrorExtensions;
use router_bridge::planner::PlannerError;
use router_bridge::planner::WorkerError;
use router_bridge::planner::WorkerGraphQLError;
use serde::Deserialize;
use serde::Serialize;
use serde_json_bytes::json;
use serde_json_bytes::ByteString;
use serde_json_bytes::Map as JsonMap;
use serde_json_bytes::Value;
use crate::error::FetchError;
use crate::json_ext::Object;
use crate::json_ext::Path;
pub use crate::json_ext::Path as JsonPath;
pub use crate::json_ext::PathElement as JsonPathElement;
pub use crate::request::Request;
pub use crate::response::IncrementalResponse;
pub use crate::response::Response;
pub type ResponseStream = Pin<Box<dyn Stream<Item = Response> + Send>>;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Error {
pub message: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub locations: Vec<Location>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<Path>,
#[serde(default, skip_serializing_if = "Object::is_empty")]
pub extensions: Object,
}
#[buildstructor::buildstructor]
impl Error {
#[builder(visibility = "pub")]
fn new<T: Into<String>>(
message: String,
locations: Vec<Location>,
path: Option<Path>,
extension_code: T,
mut extensions: JsonMap<ByteString, Value>,
) -> Self {
extensions
.entry("code")
.or_insert_with(|| extension_code.into().into());
Self {
message,
locations,
path,
extensions,
}
}
pub(crate) fn from_value(service_name: &str, value: Value) -> Result<Error, FetchError> {
let mut object =
ensure_object!(value).map_err(|error| FetchError::SubrequestMalformedResponse {
service: service_name.to_string(),
reason: error.to_string(),
})?;
let extensions =
extract_key_value_from_object!(object, "extensions", Value::Object(o) => o)
.map_err(|err| FetchError::SubrequestMalformedResponse {
service: service_name.to_string(),
reason: err.to_string(),
})?
.unwrap_or_default();
let message = extract_key_value_from_object!(object, "message", Value::String(s) => s)
.map_err(|err| FetchError::SubrequestMalformedResponse {
service: service_name.to_string(),
reason: err.to_string(),
})?
.map(|s| s.as_str().to_string())
.unwrap_or_default();
let locations = extract_key_value_from_object!(object, "locations")
.map(serde_json_bytes::from_value)
.transpose()
.map_err(|err| FetchError::SubrequestMalformedResponse {
service: service_name.to_string(),
reason: err.to_string(),
})?
.unwrap_or_default();
let path = extract_key_value_from_object!(object, "path")
.map(serde_json_bytes::from_value)
.transpose()
.map_err(|err| FetchError::SubrequestMalformedResponse {
service: service_name.to_string(),
reason: err.to_string(),
})?;
Ok(Error {
message,
locations,
path,
extensions,
})
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(f)
}
}
pub(crate) trait IntoGraphQLErrors
where
Self: Sized,
{
fn into_graphql_errors(self) -> Result<Vec<Error>, Self>;
}
pub(crate) trait ErrorExtension
where
Self: Sized,
{
fn extension_code(&self) -> String {
std::any::type_name::<Self>().to_shouty_snake_case()
}
fn custom_extension_details(&self) -> Option<Object> {
None
}
}
impl ErrorExtension for PlanError {}
impl From<PlanError> for Error {
fn from(err: PlanError) -> Self {
let extension_code = err.extension_code();
let extensions = err
.extensions
.map(convert_extensions_to_map)
.unwrap_or_else(move || {
let mut object = Object::new();
object.insert("code", extension_code.into());
object
});
Self {
message: err.message.unwrap_or_else(|| String::from("plan error")),
extensions,
..Default::default()
}
}
}
impl ErrorExtension for PlannerError {
fn extension_code(&self) -> String {
match self {
PlannerError::WorkerGraphQLError(worker_graphql_error) => worker_graphql_error
.extensions
.as_ref()
.map(|ext| ext.code.clone())
.unwrap_or_else(|| worker_graphql_error.extension_code()),
PlannerError::WorkerError(worker_error) => worker_error
.extensions
.as_ref()
.map(|ext| ext.code.clone())
.unwrap_or_else(|| worker_error.extension_code()),
}
}
}
impl From<PlannerError> for Error {
fn from(err: PlannerError) -> Self {
match err {
PlannerError::WorkerGraphQLError(err) => err.into(),
PlannerError::WorkerError(err) => err.into(),
}
}
}
impl ErrorExtension for WorkerError {}
impl From<WorkerError> for Error {
fn from(err: WorkerError) -> Self {
let extension_code = err.extension_code();
let mut extensions = err
.extensions
.map(convert_extensions_to_map)
.unwrap_or_default();
extensions.insert("code", extension_code.into());
Self {
message: err.message.unwrap_or_else(|| String::from("worker error")),
locations: err.locations.into_iter().map(Location::from).collect(),
extensions,
..Default::default()
}
}
}
impl ErrorExtension for WorkerGraphQLError {}
impl From<WorkerGraphQLError> for Error {
fn from(err: WorkerGraphQLError) -> Self {
let extension_code = err.extension_code();
let mut extensions = err
.extensions
.map(convert_extensions_to_map)
.unwrap_or_default();
extensions.insert("code", extension_code.into());
Self {
message: err.message,
locations: err.locations.into_iter().map(Location::from).collect(),
extensions,
..Default::default()
}
}
}
fn convert_extensions_to_map(ext: PlanErrorExtensions) -> Object {
let mut extensions = Object::new();
extensions.insert("code", ext.code.into());
if let Some(exception) = ext.exception {
extensions.insert(
"exception",
json!({
"stacktrace": serde_json_bytes::Value::from(exception.stacktrace)
}),
);
}
extensions
}