Implement std::error::Error for all error types (#419)

* Implement `std::error::Error` for all error types

* Fix copy-paste

* Implement `Display` for `Value`

This is required for implementing `Display` for `FieldError`

* Implement `std::error::Error` for `FieldError`

This required removing `impl From<T> for FieldError where T: Display`
because it would otherwise cause a conflicting implementation. That is
because `impl From<T> for T` already exists.

Instead I added `impl From<String> for FieldError` and `impl From<&str>
for FieldError` which should cover most use cases of the previous
  `impl`. I also added `FieldError::from_error` so users can convert
  from any error they may have.

* Bring back `impl<T: Display, S> From<T> for FieldError<S>`

We cannot have this and `impl<S> std::error::Error for FieldError<S>` so
we agreed this is more valuable. More context https://github.com/graphql-rust/juniper/pull/419

* Write errors without allocations
This commit is contained in:
David Pedersen 2020-02-21 06:31:58 +01:00 committed by GitHub
parent db68dd7697
commit ca28e90f7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 192 additions and 2 deletions

View file

@ -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`

View file

@ -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;

View file

@ -162,6 +162,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},
@ -198,6 +199,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,

View file

@ -538,3 +538,5 @@ impl fmt::Display for LexerError {
}
}
}
impl std::error::Error for LexerError {}

View file

@ -203,3 +203,5 @@ impl<'a> fmt::Display for ParseError<'a> {
}
}
}
impl<'a> std::error::Error for ParseError<'a> {}

View file

@ -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)
}
}

View file

@ -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> {

View file

@ -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));
}
}