Error handling

Rust provides two ways of dealing with errors: Result<T, E> for recoverable errors and panic! for unrecoverable errors. Juniper does not do anything about panicking; it will bubble up to the surrounding framework and hopefully be dealt with there.

For recoverable errors, Juniper works well with the built-in Result type, you can use the ? operator or the try! macro and things will generally just work as you expect them to:

use juniper::FieldResult;
use std::path::PathBuf;
use std::fs::File;
use std::io::Read;
use std::str;

struct Example {
    filename: PathBuf,
}

graphql_object!(Example: () |&self| {
    field contents() -> FieldResult<String> {
        let mut file = File::open(&self.filename)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }
    field foo() -> FieldResult<Option<String>> {
      // Some invalid bytes.
      let invalid = vec![128, 223];

      match str::from_utf8(&invalid) {
        Ok(s) => Ok(Some(s.to_string())),
        Err(e) => Err(e)?,
      }
    }
});

FieldResult<T> is an alias for Result<T, FieldError>, which is the error type all fields must return. By using the ? operator or try! macro, any type that implements the Display trait - which are most of the error types out there - those errors are automatically converted into FieldError.

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

{
  example {
    contents
    foo
  }
}

If str::from_utf8 resulted in a std::str::Utf8Error, the following would be returned:

Response for nullable field with error

{
  "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 }])
  ]
}

If an error is returned from a non-null field, such as the example above, the null value is propagated up to the first nullable parent field, or the root data object if there are no nullable fields.

For example, with the following query:

{
  example {
    contents
  }
}

If File::open() above resulted in std::io::ErrorKind::PermissionDenied, the following would be returned:

Response for non-null field with error and no nullable parent

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

Structured errors

Sometimes it is desirable to return additional structured error information to clients. This can be accomplished by implementing IntoFieldError:

use juniper::{FieldError, IntoFieldError};

enum CustomError {
    WhateverNotSet,
}

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

struct Example {
    whatever: Option<bool>,
}

graphql_object!(Example: () |&self| {
    field whatever() -> Result<bool, CustomError> {
      if let Some(value) = self.whatever {
        return Ok(value);
      }
      Err(CustomError::WhateverNotSet)
    }
});

The specified structured error information is included in the extensions key:

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

results matching ""

    No results matching ""