use std::sync::Arc;
use displaydoc::Display;
use lazy_static::__Deref;
use miette::Diagnostic;
use miette::NamedSource;
use miette::Report;
use miette::SourceSpan;
use router_bridge::introspect::IntrospectionError;
use router_bridge::planner::PlannerError;
use router_bridge::planner::UsageReporting;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
use tokio::task::JoinError;
use tracing::level_filters::LevelFilter;
pub(crate) use crate::configuration::ConfigurationError;
pub(crate) use crate::graphql::Error;
use crate::graphql::ErrorExtension;
use crate::graphql::IntoGraphQLErrors;
use crate::graphql::Response;
use crate::json_ext::Path;
use crate::json_ext::Value;
use crate::spec::SpecError;
#[derive(Error, Display, Debug, Clone, Serialize)]
#[serde(untagged)]
#[ignore_extra_doc_attributes]
#[non_exhaustive]
#[allow(missing_docs)] pub(crate) enum FetchError {
ValidationInvalidTypeVariable {
name: String,
},
ValidationPlanningError {
reason: String,
},
SubrequestMalformedResponse {
service: String,
reason: String,
},
SubrequestUnexpectedPatchResponse {
service: String,
},
SubrequestHttpError {
service: String,
reason: String,
},
ExecutionFieldNotFound {
field: String,
},
#[cfg(test)]
ExecutionInvalidContent { reason: String },
ExecutionPathNotFound { reason: String },
CompressionError {
service: String,
reason: String,
},
}
impl FetchError {
pub(crate) fn to_graphql_error(&self, path: Option<Path>) -> Error {
let mut value: Value = serde_json::to_value(self).unwrap_or_default().into();
if let Some(extensions) = value.as_object_mut() {
extensions
.entry("code")
.or_insert_with(|| self.extension_code().into());
match self {
FetchError::SubrequestMalformedResponse { service, .. }
| FetchError::SubrequestUnexpectedPatchResponse { service }
| FetchError::SubrequestHttpError { service, .. }
| FetchError::CompressionError { service, .. } => {
extensions
.entry("service")
.or_insert_with(|| service.clone().into());
}
FetchError::ExecutionFieldNotFound { field, .. } => {
extensions
.entry("field")
.or_insert_with(|| field.clone().into());
}
FetchError::ValidationInvalidTypeVariable { name } => {
extensions
.entry("name")
.or_insert_with(|| name.clone().into());
}
_ => (),
}
}
Error {
message: self.to_string(),
locations: Default::default(),
path,
extensions: value.as_object().unwrap().to_owned(),
}
}
pub(crate) fn to_response(&self) -> Response {
Response {
errors: vec![self.to_graphql_error(None)],
..Response::default()
}
}
}
impl ErrorExtension for FetchError {
fn extension_code(&self) -> String {
match self {
FetchError::ValidationInvalidTypeVariable { .. } => "VALIDATION_INVALID_TYPE_VARIABLE",
FetchError::ValidationPlanningError { .. } => "VALIDATION_PLANNING_ERROR",
FetchError::SubrequestMalformedResponse { .. } => "SUBREQUEST_MALFORMED_RESPONSE",
FetchError::SubrequestUnexpectedPatchResponse { .. } => {
"SUBREQUEST_UNEXPECTED_PATCH_RESPONSE"
}
FetchError::SubrequestHttpError { .. } => "SUBREQUEST_HTTP_ERROR",
FetchError::ExecutionFieldNotFound { .. } => "EXECUTION_FIELD_NOT_FOUND",
FetchError::ExecutionPathNotFound { .. } => "EXECUTION_PATH_NOT_FOUND",
FetchError::CompressionError { .. } => "COMPRESSION_ERROR",
#[cfg(test)]
FetchError::ExecutionInvalidContent { .. } => "EXECUTION_INVALID_CONTENT",
}
.to_string()
}
}
impl From<QueryPlannerError> for FetchError {
fn from(err: QueryPlannerError) -> Self {
FetchError::ValidationPlanningError {
reason: err.to_string(),
}
}
}
#[derive(Error, Debug, Display, Clone, Serialize, Deserialize)]
pub(crate) enum CacheResolverError {
RetrievalError(Arc<QueryPlannerError>),
}
impl IntoGraphQLErrors for CacheResolverError {
fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
let CacheResolverError::RetrievalError(retrieval_error) = self;
retrieval_error
.deref()
.clone()
.into_graphql_errors()
.map_err(|_err| CacheResolverError::RetrievalError(retrieval_error))
}
}
impl From<QueryPlannerError> for CacheResolverError {
fn from(qp_err: QueryPlannerError) -> Self {
Self::RetrievalError(Arc::new(qp_err))
}
}
#[derive(Error, Debug, Display, Clone)]
pub(crate) enum ServiceBuildError {
QueryPlannerError(QueryPlannerError),
}
#[derive(Error, Debug, Display, Clone, Serialize, Deserialize)]
pub(crate) enum QueryPlannerError {
SchemaValidationErrors(PlannerErrors),
PlanningErrors(PlanErrors),
JoinError(String),
CacheResolverError(Arc<CacheResolverError>),
EmptyPlan(UsageReporting),
UnhandledPlannerResult,
RouterBridgeError(router_bridge::error::Error),
SpecError(SpecError),
Introspection(IntrospectionError),
}
impl IntoGraphQLErrors for QueryPlannerError {
fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
match self {
QueryPlannerError::SpecError(err) => {
let gql_err = match err.custom_extension_details() {
Some(extension_details) => Error::builder()
.message(err.to_string())
.extension_code(err.extension_code())
.extensions(extension_details)
.build(),
None => Error::builder()
.message(err.to_string())
.extension_code(err.extension_code())
.build(),
};
Ok(vec![gql_err])
}
QueryPlannerError::SchemaValidationErrors(errs) => errs
.into_graphql_errors()
.map_err(QueryPlannerError::SchemaValidationErrors),
QueryPlannerError::PlanningErrors(planning_errors) => Ok(planning_errors
.errors
.iter()
.map(|p_err| Error::from(p_err.clone()))
.collect()),
err => Err(err),
}
}
}
impl ErrorExtension for QueryPlannerError {
fn extension_code(&self) -> String {
match self {
QueryPlannerError::SchemaValidationErrors(_) => "SCHEMA_VALIDATION_ERRORS",
QueryPlannerError::PlanningErrors(_) => "PLANNING_ERRORS",
QueryPlannerError::JoinError(_) => "JOIN_ERROR",
QueryPlannerError::CacheResolverError(_) => "CACHE_RESOLVER_ERROR",
QueryPlannerError::EmptyPlan(_) => "EMPTY_PLAN",
QueryPlannerError::UnhandledPlannerResult => "UNHANDLED_PLANNER_RESULT",
QueryPlannerError::RouterBridgeError(_) => "ROUTER_BRIDGE_ERROR",
QueryPlannerError::SpecError(_) => "SPEC_ERROR",
QueryPlannerError::Introspection(_) => "INTROSPECTION",
}
.to_string()
}
}
#[derive(Clone, Debug, Error, Serialize, Deserialize)]
pub(crate) struct PlannerErrors(Arc<Vec<PlannerError>>);
impl IntoGraphQLErrors for PlannerErrors {
fn into_graphql_errors(self) -> Result<Vec<Error>, Self> {
let errors = self.0.iter().map(|e| Error::from(e.clone())).collect();
Ok(errors)
}
}
impl std::fmt::Display for PlannerErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"schema validation errors: {}",
self.0
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(", ")
))
}
}
impl From<Vec<PlannerError>> for QueryPlannerError {
fn from(errors: Vec<PlannerError>) -> Self {
QueryPlannerError::SchemaValidationErrors(PlannerErrors(Arc::new(errors)))
}
}
impl From<router_bridge::planner::PlanErrors> for QueryPlannerError {
fn from(errors: router_bridge::planner::PlanErrors) -> Self {
QueryPlannerError::PlanningErrors(errors.into())
}
}
impl From<PlanErrors> for QueryPlannerError {
fn from(errors: PlanErrors) -> Self {
QueryPlannerError::PlanningErrors(errors)
}
}
impl From<JoinError> for QueryPlannerError {
fn from(err: JoinError) -> Self {
QueryPlannerError::JoinError(err.to_string())
}
}
impl From<CacheResolverError> for QueryPlannerError {
fn from(err: CacheResolverError) -> Self {
QueryPlannerError::CacheResolverError(Arc::new(err))
}
}
impl From<SpecError> for QueryPlannerError {
fn from(err: SpecError) -> Self {
QueryPlannerError::SpecError(err)
}
}
impl From<QueryPlannerError> for Response {
fn from(err: QueryPlannerError) -> Self {
FetchError::from(err).to_response()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct PlanErrors {
pub(crate) errors: Arc<Vec<router_bridge::planner::PlanError>>,
pub(crate) usage_reporting: UsageReporting,
}
impl From<router_bridge::planner::PlanErrors> for PlanErrors {
fn from(
router_bridge::planner::PlanErrors {
errors,
usage_reporting,
}: router_bridge::planner::PlanErrors,
) -> Self {
PlanErrors {
errors,
usage_reporting,
}
}
}
impl std::fmt::Display for PlanErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"query validation errors: {}",
self.errors
.iter()
.map(|e| e
.message
.clone()
.unwrap_or_else(|| "UNKNWON ERROR".to_string()))
.collect::<Vec<String>>()
.join(", ")
))
}
}
#[derive(Debug, Error, Display)]
#[non_exhaustive]
pub(crate) enum SchemaError {
UrlParse(String, http::uri::InvalidUri),
MissingSubgraphUrl(String),
Parse(ParseErrors),
Api(String),
}
#[derive(Debug)]
pub(crate) struct ParseErrors {
pub(crate) raw_schema: String,
pub(crate) errors: Vec<apollo_parser::Error>,
}
#[derive(Error, Debug, Diagnostic)]
#[error("{}", self.ty)]
#[diagnostic(code("apollo-parser parsing error."))]
struct ParserError {
ty: String,
#[source_code]
src: NamedSource,
#[label("{}", self.ty)]
span: SourceSpan,
}
impl ParseErrors {
#[allow(clippy::needless_return)]
pub(crate) fn print(&self) {
if LevelFilter::current() == LevelFilter::OFF {
return;
} else if atty::is(atty::Stream::Stdout) {
self.errors.iter().for_each(|err| {
let report = Report::new(ParserError {
src: NamedSource::new("supergraph_schema", self.raw_schema.clone()),
span: (err.index(), err.data().len()).into(),
ty: err.message().into(),
});
println!("{:?}", report);
});
} else {
self.errors.iter().for_each(|r| {
println!("{:#?}", r);
});
};
}
}
#[derive(Error, Display, Debug, Clone, Serialize, Deserialize)]
pub(crate) enum LicenseError {
MissingGraphReference,
MissingKey,
}