Skip to content

Commit ca28e90

Browse files
authored
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 #419 * Write errors without allocations
1 parent db68dd7 commit ca28e90

File tree

8 files changed

+192
-2
lines changed

8 files changed

+192
-2
lines changed

juniper/CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
- Support raw identifiers in field and argument names. (#[object] macro)
66

7+
- Most error types now implement `std::error::Error`:
8+
- `GraphQLError`
9+
- `LexerError`
10+
- `ParseError`
11+
- `RuleError`
12+
13+
See [#419](https://github.com/graphql-rust/juniper/pull/419).
14+
715
## Breaking Changes
816

917
- remove old `graphql_object!` macro, rename `object` proc macro to `graphql_object`

juniper/src/executor/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
use std::{borrow::Cow, cmp::Ordering, collections::HashMap, fmt::Display, sync::RwLock};
1+
use std::{
2+
borrow::Cow,
3+
cmp::Ordering,
4+
collections::HashMap,
5+
fmt::{self, Debug, Display},
6+
sync::RwLock,
7+
};
28

39
use fnv::FnvHashMap;
410

juniper/src/lib.rs

+21
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ use crate::{
162162
parser::{parse_document_source, ParseError, Spanning},
163163
validation::{validate_input_values, visit_all_rules, ValidatorContext},
164164
};
165+
use std::fmt;
165166

166167
pub use crate::{
167168
ast::{FromInputValue, InputValue, Selection, ToInputValue, Type},
@@ -198,6 +199,26 @@ pub enum GraphQLError<'a> {
198199
IsSubscription,
199200
}
200201

202+
impl<'a> fmt::Display for GraphQLError<'a> {
203+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204+
match self {
205+
GraphQLError::ParseError(error) => write!(f, "{}", error),
206+
GraphQLError::ValidationError(errors) => {
207+
for error in errors {
208+
writeln!(f, "{}", error)?;
209+
}
210+
Ok(())
211+
}
212+
GraphQLError::NoOperationProvided => write!(f, "No operation provided"),
213+
GraphQLError::MultipleOperationsProvided => write!(f, "Multiple operations provided"),
214+
GraphQLError::UnknownOperationName => write!(f, "Unknown operation name"),
215+
GraphQLError::IsSubscription => write!(f, "Subscription are not currently supported"),
216+
}
217+
}
218+
}
219+
220+
impl<'a> std::error::Error for GraphQLError<'a> {}
221+
201222
/// Execute a query in a provided schema
202223
pub fn execute<'a, S, CtxT, QueryT, MutationT>(
203224
document_source: &'a str,

juniper/src/parser/lexer.rs

+2
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,5 @@ impl fmt::Display for LexerError {
538538
}
539539
}
540540
}
541+
542+
impl std::error::Error for LexerError {}

juniper/src/parser/parser.rs

+2
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,5 @@ impl<'a> fmt::Display for ParseError<'a> {
203203
}
204204
}
205205
}
206+
207+
impl<'a> std::error::Error for ParseError<'a> {}

juniper/src/parser/utils.rs

+14
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ impl<T> Spanning<T> {
9090
}
9191
}
9292

93+
impl<T: fmt::Display> fmt::Display for Spanning<T> {
94+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95+
write!(f, "{}. At {}", self.item, self.start)
96+
}
97+
}
98+
99+
impl<T: std::error::Error> std::error::Error for Spanning<T> {}
100+
93101
impl SourcePosition {
94102
#[doc(hidden)]
95103
pub fn new(index: usize, line: usize, col: usize) -> SourcePosition {
@@ -142,3 +150,9 @@ impl SourcePosition {
142150
self.col
143151
}
144152
}
153+
154+
impl fmt::Display for SourcePosition {
155+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156+
write!(f, "{}:{}", self.line, self.col)
157+
}
158+
}

juniper/src/validation/context.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::{collections::HashSet, fmt::Debug};
1+
use std::{
2+
collections::HashSet,
3+
fmt::{self, Debug},
4+
};
25

36
use crate::ast::{Definition, Document, Type};
47

@@ -48,6 +51,21 @@ impl RuleError {
4851
}
4952
}
5053

54+
impl fmt::Display for RuleError {
55+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56+
// this is fine since all `RuleError`s should have at least one source position
57+
let locations = self
58+
.locations
59+
.iter()
60+
.map(|location| format!("{}", location))
61+
.collect::<Vec<_>>()
62+
.join(", ");
63+
write!(f, "{}. At {}", self.message, locations)
64+
}
65+
}
66+
67+
impl std::error::Error for RuleError {}
68+
5169
impl<'a, S: Debug> ValidatorContext<'a, S> {
5270
#[doc(hidden)]
5371
pub fn new(schema: &'a SchemaType<S>, document: &Document<'a, S>) -> ValidatorContext<'a, S> {

juniper/src/value/mod.rs

+119
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{
22
ast::{InputValue, ToInputValue},
33
parser::Spanning,
44
};
5+
use std::fmt::{self, Display, Formatter};
56
mod object;
67
mod scalar;
78

@@ -185,6 +186,46 @@ impl<S: ScalarValue> ToInputValue<S> for Value<S> {
185186
}
186187
}
187188

189+
impl<S: ScalarValue> Display for Value<S> {
190+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
191+
match self {
192+
Value::Null => write!(f, "null"),
193+
Value::Scalar(s) => {
194+
if let Some(string) = s.as_string() {
195+
write!(f, "\"{}\"", string)
196+
} else {
197+
write!(f, "{}", s)
198+
}
199+
}
200+
Value::List(list) => {
201+
write!(f, "[")?;
202+
for (idx, item) in list.iter().enumerate() {
203+
write!(f, "{}", item)?;
204+
if idx < list.len() - 1 {
205+
write!(f, ", ")?;
206+
}
207+
}
208+
write!(f, "]")?;
209+
210+
Ok(())
211+
}
212+
Value::Object(obj) => {
213+
write!(f, "{{")?;
214+
for (idx, (key, value)) in obj.iter().enumerate() {
215+
write!(f, "\"{}\": {}", key, value)?;
216+
217+
if idx < obj.field_count() - 1 {
218+
write!(f, ", ")?;
219+
}
220+
}
221+
write!(f, "}}")?;
222+
223+
Ok(())
224+
}
225+
}
226+
}
227+
}
228+
188229
impl<S, T> From<Option<T>> for Value<S>
189230
where
190231
S: ScalarValue,
@@ -354,4 +395,82 @@ mod tests {
354395
)
355396
);
356397
}
398+
399+
#[test]
400+
fn display_null() {
401+
let s: Value<DefaultScalarValue> = graphql_value!(None);
402+
assert_eq!("null", format!("{}", s));
403+
}
404+
405+
#[test]
406+
fn display_int() {
407+
let s: Value<DefaultScalarValue> = graphql_value!(123);
408+
assert_eq!("123", format!("{}", s));
409+
}
410+
411+
#[test]
412+
fn display_float() {
413+
let s: Value<DefaultScalarValue> = graphql_value!(123.456);
414+
assert_eq!("123.456", format!("{}", s));
415+
}
416+
417+
#[test]
418+
fn display_string() {
419+
let s: Value<DefaultScalarValue> = graphql_value!("foo");
420+
assert_eq!("\"foo\"", format!("{}", s));
421+
}
422+
423+
#[test]
424+
fn display_bool() {
425+
let s: Value<DefaultScalarValue> = graphql_value!(false);
426+
assert_eq!("false", format!("{}", s));
427+
428+
let s: Value<DefaultScalarValue> = graphql_value!(true);
429+
assert_eq!("true", format!("{}", s));
430+
}
431+
432+
#[test]
433+
fn display_list() {
434+
let s: Value<DefaultScalarValue> = graphql_value!([1, None, "foo"]);
435+
assert_eq!("[1, null, \"foo\"]", format!("{}", s));
436+
}
437+
438+
#[test]
439+
fn display_list_one_element() {
440+
let s: Value<DefaultScalarValue> = graphql_value!([1]);
441+
assert_eq!("[1]", format!("{}", s));
442+
}
443+
444+
#[test]
445+
fn display_list_empty() {
446+
let s: Value<DefaultScalarValue> = graphql_value!([]);
447+
assert_eq!("[]", format!("{}", s));
448+
}
449+
450+
#[test]
451+
fn display_object() {
452+
let s: Value<DefaultScalarValue> = graphql_value!({
453+
"int": 1,
454+
"null": None,
455+
"string": "foo",
456+
});
457+
assert_eq!(
458+
r#"{"int": 1, "null": null, "string": "foo"}"#,
459+
format!("{}", s)
460+
);
461+
}
462+
463+
#[test]
464+
fn display_object_one_field() {
465+
let s: Value<DefaultScalarValue> = graphql_value!({
466+
"int": 1,
467+
});
468+
assert_eq!(r#"{"int": 1}"#, format!("{}", s));
469+
}
470+
471+
#[test]
472+
fn display_object_empty() {
473+
let s = Value::<DefaultScalarValue>::object(Object::with_capacity(0));
474+
assert_eq!(r#"{}"#, format!("{}", s));
475+
}
357476
}

0 commit comments

Comments
 (0)