Field errors

Rust provides two ways of dealing with errors:

Juniper does not do anything about panicking, it naturally bubbles up to the surrounding code/framework and can be dealt with there.

For recoverable errors, Juniper works well with the built-in Result type. You can use the ? operator and things will work as you expect them to:

extern crate juniper;
use std::{fs::File, io::Read, path::PathBuf, str};
use juniper::{graphql_object, FieldResult};

struct Example {
    filename: PathBuf,
}

#[graphql_object]
impl Example {
    fn contents(&self) -> FieldResult<String> {
        let mut file = File::open(&self.filename)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }

    fn foo() -> FieldResult<Option<String>> {
        // Some invalid bytes.
        let invalid = vec![128, 223];

        Ok(Some(str::from_utf8(&invalid)?.to_string()))
    }
}

fn main() {}

FieldResult<T> is an alias for Result<T, FieldError>, which is the error type all fallible fields must return. By using the ? operator, any type that implements the Display trait (which most of the error types out there do) can be automatically converted into a FieldError.

TIP: If a custom conversion into a FieldError is needed (to fill up extensions, for example), the IntoFieldError trait should be implemented.

NOTE: FieldErrors are GraphQL field errors and are not visible in a GraphQL schema in any way.

Error payloads, null, and partial errors

Juniper's error behavior conforms to the GraphQL specification.

When a field returns an error, the field's result is replaced by null, and an additional errors object is created at the top level of the response, and the execution is resumed.

Let's run the following query against the previous example:

{
  example {
    contents
    foo
  }
}

If str::from_utf8 results in a std::str::Utf8Error, then the following will be returned:

{
  "data": {
    "example": {
      "contents": "<Contents of the file>",
      "foo": null
    }
  },
  "errors": [{
    "message": "invalid utf-8 sequence of 2 bytes from index 0",
    "locations": [{"line": 2, "column": 4}]
  }]
}

Since Non-Null type fields cannot be null, field errors are propagated to be handled by the parent field. If the parent field may be null then it resolves to null, otherwise if it is a Non-Null type, the field error is further propagated to its parent field.

For example, with the following query:

{
  example {
    contents
  }
}

If the File::open() above results in a std::io::ErrorKind::PermissionDenied, the following ill be returned:

{
  "data": null,
  "errors": [{
    "message": "Permission denied (os error 13)",
    "locations": [{"line": 2, "column": 4}]
  }]
}

Additional information

Sometimes it's desirable to return additional structured error information to clients. This can be accomplished by implementing the IntoFieldError trait:

#[macro_use] extern crate juniper;
use juniper::{graphql_object, FieldError, IntoFieldError, ScalarValue};

enum CustomError {
    WhateverNotSet,
}

impl<S: ScalarValue> IntoFieldError<S> for CustomError {
    fn into_field_error(self) -> FieldError<S> {
        match self {
            Self::WhateverNotSet => FieldError::new(
                "Whatever does not exist",
                graphql_value!({
                    "type": "NO_WHATEVER"
                }),
            ),
        }
    }
}

struct Example {
    whatever: Option<bool>,
}

#[graphql_object]
impl Example {
    fn whatever(&self) -> Result<bool, CustomError> {
        if let Some(value) = self.whatever {
            return Ok(value);
        }
        Err(CustomError::WhateverNotSet)
    }
}

fn main() {}

And the specified structured error information will be included into the error's extensions:

{
  "errors": [{
    "message": "Whatever does not exist",
    "locations": [{"line": 2, "column": 4}],
    "extensions": {
      "type": "NO_WHATEVER"
    }
  }]
}

NOTE: This pattern is particularly useful when it comes to instrumentation of returned field errors with custom error codes or additional diagnostics (like stack traces or tracing IDs).