diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 49a1957c6..5d6178a20 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -4,6 +4,14 @@ - Support raw identifiers in field and argument names. (#[object] macro) +- Most error types now implement `std::error::Error`: + - `GraphQLError` + - `LexerError` + - `ParseError` + - `RuleError` + +See [#419](https://github.com/graphql-rust/juniper/pull/419). + ## Breaking Changes - remove old `graphql_object!` macro, rename `object` proc macro to `graphql_object` diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 093bb6601..c5209a7f2 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -1,4 +1,10 @@ -use std::{borrow::Cow, cmp::Ordering, collections::HashMap, fmt::Display, sync::RwLock}; +use std::{ + borrow::Cow, + cmp::Ordering, + collections::HashMap, + fmt::{self, Debug, Display}, + sync::RwLock, +}; use fnv::FnvHashMap; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index e0c858664..5a1b9d433 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -157,6 +157,7 @@ use crate::{ parser::{parse_document_source, ParseError, Spanning}, validation::{validate_input_values, visit_all_rules, ValidatorContext}, }; +use std::fmt; pub use crate::{ ast::{FromInputValue, InputValue, Selection, ToInputValue, Type}, @@ -193,6 +194,26 @@ pub enum GraphQLError<'a> { IsSubscription, } +impl<'a> fmt::Display for GraphQLError<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + GraphQLError::ParseError(error) => write!(f, "{}", error), + GraphQLError::ValidationError(errors) => { + for error in errors { + writeln!(f, "{}", error)?; + } + Ok(()) + } + GraphQLError::NoOperationProvided => write!(f, "No operation provided"), + GraphQLError::MultipleOperationsProvided => write!(f, "Multiple operations provided"), + GraphQLError::UnknownOperationName => write!(f, "Unknown operation name"), + GraphQLError::IsSubscription => write!(f, "Subscription are not currently supported"), + } + } +} + +impl<'a> std::error::Error for GraphQLError<'a> {} + /// Execute a query in a provided schema pub fn execute<'a, S, CtxT, QueryT, MutationT>( document_source: &'a str, diff --git a/juniper/src/parser/lexer.rs b/juniper/src/parser/lexer.rs index 5d8854e9c..4a18afee4 100644 --- a/juniper/src/parser/lexer.rs +++ b/juniper/src/parser/lexer.rs @@ -538,3 +538,5 @@ impl fmt::Display for LexerError { } } } + +impl std::error::Error for LexerError {} diff --git a/juniper/src/parser/parser.rs b/juniper/src/parser/parser.rs index 095fc1e7e..198c24322 100644 --- a/juniper/src/parser/parser.rs +++ b/juniper/src/parser/parser.rs @@ -203,3 +203,5 @@ impl<'a> fmt::Display for ParseError<'a> { } } } + +impl<'a> std::error::Error for ParseError<'a> {} diff --git a/juniper/src/parser/utils.rs b/juniper/src/parser/utils.rs index d5bd2c915..5ea12817c 100644 --- a/juniper/src/parser/utils.rs +++ b/juniper/src/parser/utils.rs @@ -90,6 +90,14 @@ impl<T> Spanning<T> { } } +impl<T: fmt::Display> fmt::Display for Spanning<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}. At {}", self.item, self.start) + } +} + +impl<T: std::error::Error> std::error::Error for Spanning<T> {} + impl SourcePosition { #[doc(hidden)] pub fn new(index: usize, line: usize, col: usize) -> SourcePosition { @@ -142,3 +150,9 @@ impl SourcePosition { self.col } } + +impl fmt::Display for SourcePosition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.line, self.col) + } +} diff --git a/juniper/src/validation/context.rs b/juniper/src/validation/context.rs index 9498bbdae..9a02ae15e 100644 --- a/juniper/src/validation/context.rs +++ b/juniper/src/validation/context.rs @@ -1,4 +1,7 @@ -use std::{collections::HashSet, fmt::Debug}; +use std::{ + collections::HashSet, + fmt::{self, Debug}, +}; use crate::ast::{Definition, Document, Type}; @@ -48,6 +51,21 @@ impl RuleError { } } +impl fmt::Display for RuleError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // this is fine since all `RuleError`s should have at least one source position + let locations = self + .locations + .iter() + .map(|location| format!("{}", location)) + .collect::<Vec<_>>() + .join(", "); + write!(f, "{}. At {}", self.message, locations) + } +} + +impl std::error::Error for RuleError {} + impl<'a, S: Debug> ValidatorContext<'a, S> { #[doc(hidden)] pub fn new(schema: &'a SchemaType<S>, document: &Document<'a, S>) -> ValidatorContext<'a, S> { diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index ab7015abe..6e29b069c 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -2,6 +2,7 @@ use crate::{ ast::{InputValue, ToInputValue}, parser::Spanning, }; +use std::fmt::{self, Display, Formatter}; mod object; mod scalar; @@ -185,6 +186,46 @@ impl<S: ScalarValue> ToInputValue<S> for Value<S> { } } +impl<S: ScalarValue> Display for Value<S> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Value::Null => write!(f, "null"), + Value::Scalar(s) => { + if let Some(string) = s.as_string() { + write!(f, "\"{}\"", string) + } else { + write!(f, "{}", s) + } + } + Value::List(list) => { + write!(f, "[")?; + for (idx, item) in list.iter().enumerate() { + write!(f, "{}", item)?; + if idx < list.len() - 1 { + write!(f, ", ")?; + } + } + write!(f, "]")?; + + Ok(()) + } + Value::Object(obj) => { + write!(f, "{{")?; + for (idx, (key, value)) in obj.iter().enumerate() { + write!(f, "\"{}\": {}", key, value)?; + + if idx < obj.field_count() - 1 { + write!(f, ", ")?; + } + } + write!(f, "}}")?; + + Ok(()) + } + } + } +} + impl<S, T> From<Option<T>> for Value<S> where S: ScalarValue, @@ -354,4 +395,82 @@ mod tests { ) ); } + + #[test] + fn display_null() { + let s: Value<DefaultScalarValue> = graphql_value!(None); + assert_eq!("null", format!("{}", s)); + } + + #[test] + fn display_int() { + let s: Value<DefaultScalarValue> = graphql_value!(123); + assert_eq!("123", format!("{}", s)); + } + + #[test] + fn display_float() { + let s: Value<DefaultScalarValue> = graphql_value!(123.456); + assert_eq!("123.456", format!("{}", s)); + } + + #[test] + fn display_string() { + let s: Value<DefaultScalarValue> = graphql_value!("foo"); + assert_eq!("\"foo\"", format!("{}", s)); + } + + #[test] + fn display_bool() { + let s: Value<DefaultScalarValue> = graphql_value!(false); + assert_eq!("false", format!("{}", s)); + + let s: Value<DefaultScalarValue> = graphql_value!(true); + assert_eq!("true", format!("{}", s)); + } + + #[test] + fn display_list() { + let s: Value<DefaultScalarValue> = graphql_value!([1, None, "foo"]); + assert_eq!("[1, null, \"foo\"]", format!("{}", s)); + } + + #[test] + fn display_list_one_element() { + let s: Value<DefaultScalarValue> = graphql_value!([1]); + assert_eq!("[1]", format!("{}", s)); + } + + #[test] + fn display_list_empty() { + let s: Value<DefaultScalarValue> = graphql_value!([]); + assert_eq!("[]", format!("{}", s)); + } + + #[test] + fn display_object() { + let s: Value<DefaultScalarValue> = graphql_value!({ + "int": 1, + "null": None, + "string": "foo", + }); + assert_eq!( + r#"{"int": 1, "null": null, "string": "foo"}"#, + format!("{}", s) + ); + } + + #[test] + fn display_object_one_field() { + let s: Value<DefaultScalarValue> = graphql_value!({ + "int": 1, + }); + assert_eq!(r#"{"int": 1}"#, format!("{}", s)); + } + + #[test] + fn display_object_empty() { + let s = Value::<DefaultScalarValue>::object(Object::with_capacity(0)); + assert_eq!(r#"{}"#, format!("{}", s)); + } }