diff --git a/src/error.rs b/src/error.rs index e42dacf7..3792b196 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ use crate::{Pos, QueryPathNode, Value}; -use std::fmt::{Debug, Display}; +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::ops::Deref; use thiserror::Error; /// An error in the format of an input value. @@ -382,6 +383,27 @@ pub struct RuleError { pub message: String, } +impl Display for RuleError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + for (idx, loc) in self.locations.iter().enumerate() { + if idx == 0 { + write!(f, "[")?; + } else { + write!(f, ", ")?; + } + + write!(f, "{}:{}", loc.line, loc.column)?; + + if idx == self.locations.len() - 1 { + write!(f, "] ")?; + } + } + + write!(f, "{}", self.message)?; + Ok(()) + } +} + /// An error serving a GraphQL query. #[derive(Debug, Error, PartialEq)] pub enum Error { @@ -403,9 +425,45 @@ pub enum Error { }, /// The query statement verification failed. - #[error("Rule error")] + #[error("Rule error:\n{errors}")] Rule { /// List of errors. - errors: Vec, + errors: RuleErrors, }, } + +/// A collection of RuleError. +#[derive(Debug, PartialEq)] +pub struct RuleErrors(Vec); + +impl From> for RuleErrors { + fn from(errors: Vec) -> Self { + Self(errors) + } +} + +impl Deref for RuleErrors { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl IntoIterator for RuleErrors { + type Item = RuleError; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Display for RuleErrors { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + for err in &self.0 { + writeln!(f, " {}", err)?; + } + Ok(()) + } +} diff --git a/src/extensions/logger.rs b/src/extensions/logger.rs index 2e078611..4aad23bb 100644 --- a/src/extensions/logger.rs +++ b/src/extensions/logger.rs @@ -94,7 +94,7 @@ impl Extension for Logger { } } Error::Rule { errors } => { - for error in errors { + for error in errors.iter() { let locations = error .locations .iter() diff --git a/src/extensions/tracing.rs b/src/extensions/tracing.rs index f6d9a25b..56ea2f82 100644 --- a/src/extensions/tracing.rs +++ b/src/extensions/tracing.rs @@ -46,69 +46,69 @@ impl Extension for Tracing { fn parse_end(&mut self, _document: &ExecutableDocument) { self.parse .take() - .unwrap() - .with_subscriber(|(id, d)| d.exit(id)); + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); } fn validation_start(&mut self) { - let parent = self.root.as_ref().unwrap(); - let validation_span = span!( - target: "async_graphql::graphql", - parent: parent, - Level::INFO, - "validation" - ); - validation_span.with_subscriber(|(id, d)| d.enter(id)); - self.validation.replace(validation_span); + if let Some(parent) = &self.root { + let validation_span = span!( + target: "async_graphql::graphql", + parent: parent, + Level::INFO, + "validation" + ); + validation_span.with_subscriber(|(id, d)| d.enter(id)); + self.validation.replace(validation_span); + } } fn validation_end(&mut self) { self.validation .take() - .unwrap() - .with_subscriber(|(id, d)| d.exit(id)); + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); } fn execution_start(&mut self) { - let parent = self.root.as_ref().unwrap(); - let execute_span = span!( - target: "async_graphql::graphql", - parent: parent, - Level::INFO, - "execute" - ); - execute_span.with_subscriber(|(id, d)| d.enter(id)); - self.execute.replace(execute_span); + if let Some(parent) = &self.root { + let execute_span = span!( + target: "async_graphql::graphql", + parent: parent, + Level::INFO, + "execute" + ); + execute_span.with_subscriber(|(id, d)| d.enter(id)); + self.execute.replace(execute_span); + } } fn execution_end(&mut self) { self.execute .take() - .unwrap() - .with_subscriber(|(id, d)| d.exit(id)); + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); self.root .take() - .unwrap() - .with_subscriber(|(id, d)| d.exit(id)); + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); } fn resolve_start(&mut self, info: &ResolveInfo<'_>) { let parent_span = match info.resolve_id.parent { - Some(parent_id) if parent_id > 0 => self.fields.get(&parent_id).unwrap(), - _ => self.execute.as_ref().unwrap(), + Some(parent_id) if parent_id > 0 => self.fields.get(&parent_id), + _ => self.execute.as_ref(), }; - let span = span!( - target: "async_graphql::graphql", - parent: parent_span, - Level::INFO, - "field", - id = %info.resolve_id.current, - path = %info.path_node - ); - span.with_subscriber(|(id, d)| d.enter(id)); - self.fields.insert(info.resolve_id.current, span); + if let Some(parent_span) = parent_span { + let span = span!( + target: "async_graphql::graphql", + parent: parent_span, + Level::INFO, + "field", + id = %info.resolve_id.current, + path = %info.path_node + ); + span.with_subscriber(|(id, d)| d.enter(id)); + self.fields.insert(info.resolve_id.current, span); + } } fn resolve_end(&mut self, info: &ResolveInfo<'_>) { @@ -118,6 +118,24 @@ impl Extension for Tracing { } fn error(&mut self, err: &Error) { - tracing::error!(target: "async_graphql::graphql", error = %err); + tracing::error!(target: "async_graphql::graphql", error = %err.to_string()); + + for span in self.fields.values() { + span.with_subscriber(|(id, d)| d.exit(id)); + } + self.fields.clear(); + + self.execute + .take() + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); + self.validation + .take() + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); + self.parse + .take() + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); + self.root + .take() + .and_then(|span| span.with_subscriber(|(id, d)| d.exit(id))); } } diff --git a/src/serialize_resp.rs b/src/serialize_resp.rs index 9a568d9f..a9c267ff 100644 --- a/src/serialize_resp.rs +++ b/src/serialize_resp.rs @@ -72,7 +72,7 @@ impl Serialize for Error { } Error::Rule { errors } => { let mut seq = serializer.serialize_seq(Some(errors.len()))?; - for error in errors { + for error in errors.iter() { seq.serialize_element(&serde_json::json!({ "message": error.message, "locations": error.locations.iter().map(|pos| serde_json::json!({"line": pos.line, "column": pos.column})).collect_vec(), diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 279ef13e..89e60082 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -89,7 +89,9 @@ pub fn check_rules( } if !ctx.errors.is_empty() { - return Err(Error::Rule { errors: ctx.errors }); + return Err(Error::Rule { + errors: ctx.errors.into(), + }); } Ok(CheckResult { cache_control, diff --git a/src/validation/test_harness.rs b/src/validation/test_harness.rs index 5f69c0c0..c4483ed9 100644 --- a/src/validation/test_harness.rs +++ b/src/validation/test_harness.rs @@ -355,7 +355,9 @@ where let mut visitor = factory(); visit(&mut visitor, &mut ctx, doc); if !ctx.errors.is_empty() { - return Err(Error::Rule { errors: ctx.errors }); + return Err(Error::Rule { + errors: ctx.errors.into(), + }); } Ok(()) } diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 3613b331..e8bb7048 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -73,6 +73,7 @@ pub async fn test_input_validator_string_min_length() { }), message: field_error_msg }) + .into() } ); @@ -90,6 +91,7 @@ pub async fn test_input_validator_string_min_length() { }), message: object_error_msg }) + .into() } ); } else { @@ -183,6 +185,7 @@ pub async fn test_input_validator_string_max_length() { }), message: field_error_msg }) + .into() } ); @@ -200,6 +203,7 @@ pub async fn test_input_validator_string_max_length() { }), message: object_error_msg }) + .into() } ); } else { @@ -320,6 +324,7 @@ pub async fn test_input_validator_string_email() { }), message: field_error_msg }) + .into() } ); @@ -338,6 +343,7 @@ pub async fn test_input_validator_string_email() { }), message: object_error_msg }) + .into() } ); } else { @@ -468,6 +474,7 @@ pub async fn test_input_validator_string_mac() { }), message: field_error_msg.clone() }) + .into() } ); @@ -486,6 +493,7 @@ pub async fn test_input_validator_string_mac() { }), message: object_error_msg.clone() }) + .into() } ); @@ -503,6 +511,7 @@ pub async fn test_input_validator_string_mac() { }), message: field_error_msg }) + .into() } ); @@ -521,6 +530,7 @@ pub async fn test_input_validator_string_mac() { }), message: object_error_msg }) + .into() } ); } @@ -578,6 +588,7 @@ pub async fn test_input_validator_string_mac() { }), message: field_error_msg }) + .into() } ); @@ -596,6 +607,7 @@ pub async fn test_input_validator_string_mac() { }), message: object_error_msg }) + .into() } ); } else { @@ -637,6 +649,7 @@ pub async fn test_input_validator_string_mac() { }), message: field_error_msg }) + .into() } ); @@ -655,6 +668,7 @@ pub async fn test_input_validator_string_mac() { }), message: object_error_msg }) + .into() } ); } @@ -715,6 +729,7 @@ pub async fn test_input_validator_int_range() { }), message: field_error_msg }) + .into() } ); @@ -732,6 +747,7 @@ pub async fn test_input_validator_int_range() { }), message: object_error_msg }) + .into() } ); } else { @@ -820,6 +836,7 @@ pub async fn test_input_validator_int_less_than() { }), message: field_error_msg }) + .into() } ); @@ -837,6 +854,7 @@ pub async fn test_input_validator_int_less_than() { }), message: object_error_msg }) + .into() } ); } else { @@ -927,6 +945,7 @@ pub async fn test_input_validator_int_greater_than() { }), message: field_error_msg }) + .into() } ); @@ -944,6 +963,7 @@ pub async fn test_input_validator_int_greater_than() { }), message: object_error_msg }) + .into() } ); } else { @@ -1027,6 +1047,7 @@ pub async fn test_input_validator_int_nonzero() { }), message: field_error_msg }) + .into() } ); @@ -1044,6 +1065,7 @@ pub async fn test_input_validator_int_nonzero() { }), message: object_error_msg }) + .into() } ); } else { @@ -1128,6 +1150,7 @@ pub async fn test_input_validator_int_equal() { }), message: field_error_msg }) + .into() } ); @@ -1145,6 +1168,7 @@ pub async fn test_input_validator_int_equal() { }), message: object_error_msg }) + .into() } ); } else { @@ -1244,6 +1268,7 @@ pub async fn test_input_validator_list_max_length() { }), message: field_error_msg }) + .into() } ); @@ -1261,6 +1286,7 @@ pub async fn test_input_validator_list_max_length() { }), message: object_error_msg }) + .into() } ); } else { @@ -1360,6 +1386,7 @@ pub async fn test_input_validator_list_min_length() { }), message: field_error_msg }) + .into() } ); @@ -1377,6 +1404,7 @@ pub async fn test_input_validator_list_min_length() { }), message: object_error_msg }) + .into() } ); } else { @@ -1484,6 +1512,7 @@ pub async fn test_input_validator_operator_or() { }), message: field_error_msg }) + .into() } ); @@ -1501,6 +1530,7 @@ pub async fn test_input_validator_operator_or() { }), message: object_error_msg }) + .into() } ); } else { @@ -1601,6 +1631,7 @@ pub async fn test_input_validator_operator_and() { }), message: field_error_msg }) + .into() } ); @@ -1618,6 +1649,7 @@ pub async fn test_input_validator_operator_and() { }), message: object_error_msg }) + .into() } ); } else { @@ -1724,6 +1756,7 @@ pub async fn test_input_validator_variable() { }), message: field_error_msg }) + .into() } ); @@ -1741,6 +1774,7 @@ pub async fn test_input_validator_variable() { }), message: object_error_msg }) + .into() } ); } else {