Skip to content

GraphQL specification violation in how Juniper handled field error propagation inside of fragment spreads #1287

@cmtm

Description

@cmtm

Describe the bug
I believe Juniper isn't spec compliant in how it's handling field error propagation.
Relevant excerpt from the GraphQL spec about how to handle field errors on non-nullable fields.

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.

Juniper follows this behavior when the query doesn't have any fragments, but if the field error only occurs in a fragment, "null" won't propagate to the closest nullable ancestor field, but instead seems to just make the fields in the fragment null, while not affecting fields outside of the fragment.

My interpretation of the GraphQL spec is that the way a query is partitioned into fragments shouldn't affect the query response. I'm not completely sure of this, but I also can't find any mention of how fragments should change the way field errors are handled.

To Reproduce
Unit test demonstrating divergence in behavior

use juniper::{graphql_object, EmptyMutation, EmptySubscription, Variables};

struct MyObject;

#[graphql_object]
impl MyObject {
    fn erroring_field() -> Result<i32, &'static str> {
        Err("This field always errors")
    }
}

// Arbitrary context data.
struct Ctx;

impl juniper::Context for Ctx {}

struct Query;

#[graphql_object]
#[graphql(context = Ctx)]
impl Query {
    fn my_object() -> MyObject {
        MyObject {}
    }

    fn just_a_field() -> i32 {
        3
    }
}

type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>, EmptySubscription<Ctx>>;

fn main() {
    let execute_query = |query: &str| {
        let ctx = Ctx;
        juniper::execute_sync(
            query,
            None,
            &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
            &Variables::new(),
            &ctx,
        )
        .unwrap()
    };

    let (res, _) = execute_query("{ myObject { erroringField } justAField }");
    let (res_fragment, _) = execute_query(
        "query { myObject { ...MyObjectFragment } justAField } fragment MyObjectFragment on MyObject { erroringField }",
    );

    assert_eq!(res, res_fragment);
}

Expected behavior
Expect field errors that occur inside fragments to propagate nullability to their closest nullable ancestor, even if that ancestor is outside of the fragment itself.
equivalently:
Expect above code to not panic.

Additional context
N/A

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingk::designRelated to overall design and/or architecture

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions