Repository: graphql-rust/juniper (language: rust)

Issue:
Panic with certain nested fragments
I'm using juniper with juniper-from-schema for my project's API. I have been
getting a panic when executing certain queries with nested fragments. The panic
is coming from
`LookAheadSelection::build_from_selection(...).expect("a child selection")` at
https://github.com/graphql-rust/juniper/blob/juniper-0.14.2/juniper/src/executor/mod.rs#L552-L557.

Here is a reduced panicking example using Rust 1.40.0 with juniper 0.14.2 and
juniper-from-schema 0.5.1.

## Example

```rust
juniper_from_schema::graphql_schema! {
    schema {
        query: Query
    }

    type Query {
        parent: Parent @juniper(ownership: "owned", infallible: true)
    }

    type Parent {
        child: Child! @juniper(ownership: "owned", infallible: true)
    }

    type Child {
        id: ID! @juniper(ownership: "owned", infallible: true)
    }
}

pub struct Context;
impl juniper::Context for Context {}

pub struct Query;
pub struct Parent;
pub struct Child;

impl QueryFields for Query {
    fn field_parent(
        &self,
        _executor: &juniper::Executor<'_, Context>,
        _trail: &QueryTrail<'_, Parent, Walked>,
    ) -> Option<Parent> {
        Some(Parent)
    }
}

impl ParentFields for Parent {
    fn field_child(
        &self,
        _executor: &juniper::Executor<'_, Context>,
        _trail: &QueryTrail<'_, Child, Walked>,
    ) -> Child {
        Child
    }
}

impl ChildFields for Child {
    fn field_id(&self, _executor: &juniper::Executor<'_, Context>) -> juniper::ID {
        "example-id".to_string().into()
    }
}

fn main() {
    let schema = Schema::new(Query {}, juniper::EmptyMutation::new());

    let query = r#"
        fragment ChildFragment on Child {
            id
        }

        fragment ParentFragment on Parent {
            child {
                ...ChildFragment
            }
        }

        query {
            parent {
                ...ParentFragment
            }
        }
    "#;

    juniper::execute(query, None, &schema, &juniper::Variables::new(), &Context).unwrap();
}
```

## Expected Result

The query would execute successfully, or if I've made a mistake, I would expect this to fail sooner, during compilation or initialization, instead of only when the query is executed.

## Actual Output

```
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/example`
thread 'main' panicked at 'a child selection', src/libcore/option.rs:1185:5
stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src/libstd/sys_common/backtrace.rs:77
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
             at src/libstd/sys_common/backtrace.rs:61
   4: core::fmt::write
             at src/libcore/fmt/mod.rs:1028
   5: std::io::Write::write_fmt
             at src/libstd/io/mod.rs:1412
   6: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:65
   7: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:50
   8: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:188
   9: std::panicking::default_hook
             at src/libstd/panicking.rs:205
  10: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:464
  11: std::panicking::continue_panic_fmt
             at src/libstd/panicking.rs:373
  12: rust_begin_unwind
             at src/libstd/panicking.rs:302
  13: core::panicking::panic_fmt
             at src/libcore/panicking.rs:139
  14: core::option::expect_failed
             at src/libcore/option.rs:1185
  15: core::option::Option<T>::expect
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/option.rs:345
  16: juniper::executor::Executor<CtxT,S>::look_ahead::{{closure}}::{{closure}}::{{closure}}
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:552
  17: core::iter::adapters::map_fold::{{closure}}
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/adapters/mod.rs:694
  18: core::iter::traits::iterator::Iterator::fold::ok::{{closure}}
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/traits/iterator.rs:1828
  19: core::iter::traits::iterator::Iterator::try_fold
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/traits/iterator.rs:1709
  20: core::iter::traits::iterator::Iterator::fold
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/traits/iterator.rs:1831
  21: <core::iter::adapters::Map<I,F> as core::iter::traits::iterator::Iterator>::fold
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/adapters/mod.rs:727
  22: core::iter::traits::iterator::Iterator::for_each
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/traits/iterator.rs:631
  23: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::spec_extend
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/liballoc/vec.rs:2031
  24: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::from_iter
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/liballoc/vec.rs:2014
  25: <alloc::vec::Vec<T> as core::iter::traits::collect::FromIterator<T>>::from_iter
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/liballoc/vec.rs:1901
  26: core::iter::traits::iterator::Iterator::collect
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/iter/traits/iterator.rs:1493
  27: juniper::executor::Executor<CtxT,S>::look_ahead::{{closure}}::{{closure}}
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:550
  28: core::option::Option<T>::map
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/option.rs:447
  29: juniper::executor::Executor<CtxT,S>::look_ahead::{{closure}}
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:547
  30: core::option::Option<T>::unwrap_or_else
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/option.rs:419
  31: juniper::executor::Executor<CtxT,S>::look_ahead
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:521
  32: <example::Parent as juniper::types::base::GraphQLType>::resolve_field::{{closure}}
             at src/main.rs:1
  33: <example::Parent as juniper::types::base::GraphQLType>::resolve_field
             at ./<::juniper::macros::object::graphql_object macros>:71
  34: juniper::types::base::resolve_selection_set_into
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/types/base.rs:405
  35: juniper::types::base::resolve_selection_set_into
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/types/base.rs:447
  36: juniper::types::base::GraphQLType::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/types/base.rs:335
  37: juniper::executor::Executor<CtxT,S>::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:373
  38: juniper::executor::Executor<CtxT,S>::resolve_into_value
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:383
  39: juniper::types::containers::<impl juniper::types::base::GraphQLType<S> for core::option::Option<T>>::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/types/containers.rs:40
  40: juniper::executor::Executor<CtxT,S>::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:373
  41: juniper::executor::Executor<CtxT,S>::resolve_with_ctx
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:364
  42: <example::Query as juniper::types::base::GraphQLType>::resolve_field::{{closure}}
             at ./<::juniper::macros::object::graphql_object macros>:89
  43: core::result::Result<T,E>::and_then
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/result.rs:691
  44: <example::Query as juniper::types::base::GraphQLType>::resolve_field
             at ./<::juniper::macros::object::graphql_object macros>:81
  45: juniper::schema::schema::<impl juniper::types::base::GraphQLType<S> for juniper::schema::model::RootNode<QueryT,MutationT,S>>::resolve_field
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/schema/schema.rs:55
  46: juniper::types::base::resolve_selection_set_into
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/types/base.rs:405
  47: juniper::schema::schema::<impl juniper::types::base::GraphQLType<S> for juniper::schema::model::RootNode<QueryT,MutationT,S>>::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/schema/schema.rs:68
  48: juniper::types::pointers::<impl juniper::types::base::GraphQLType<S> for &T>::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/types/pointers.rs:135
  49: juniper::executor::Executor<CtxT,S>::resolve
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:373
  50: juniper::executor::Executor<CtxT,S>::resolve_into_value
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:383
  51: juniper::executor::execute_validated_query
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/executor/mod.rs:688
  52: juniper::execute
             at /home/jeremy/.cargo/registry/src/github.com-1ecc6299db9ec823/juniper-0.14.2/src/lib.rs:227
  53: example::main
             at src/main.rs:73
  54: std::rt::lang_start::{{closure}}
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/rt.rs:61
  55: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:48
  56: std::panicking::try::do_call
             at src/libstd/panicking.rs:287
  57: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:78
  58: std::panicking::try
             at src/libstd/panicking.rs:265
  59: std::panic::catch_unwind
             at src/libstd/panic.rs:396
  60: std::rt::lang_start_internal
             at src/libstd/rt.rs:47
  61: std::rt::lang_start
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/rt.rs:61
  62: main
  63: __libc_start_main
  64: _start
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
```



Discussion / hints:
Thanks for the bug report! I hope to have time next week to take a look but anyone else should feel free to poke at it in the meantime.
@LegNeato I just ran into this issue myself. Did you get to look into it at all? If not, I can take a peek.

Fix patch:
diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md
index f5bc77f8c..25cf215b9 100644
--- a/juniper/CHANGELOG.md
+++ b/juniper/CHANGELOG.md
@@ -84,6 +84,8 @@
 
 - When enabled, the optional `bson` integration now requires `bson-1.0.0`. ([#678](https://github.com/graphql-rust/juniper/pull/678))
 
+- Fixed panic on `executor.look_ahead()` for nested fragments ([#500](https://github.com/graphql-rust/juniper/issues/500))
+
 ## Breaking Changes
 
 - `GraphQLType` trait was split into 2 traits: ([#685](https://github.com/graphql-rust/juniper/pull/685))
diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs
index 3fede022e..682610ebc 100644
--- a/juniper/src/executor/look_ahead.rs
+++ b/juniper/src/executor/look_ahead.rs
@@ -190,7 +190,7 @@ where
         Self::build_from_selection_with_parent(s, None, vars, fragments)
     }
 
-    fn build_from_selection_with_parent(
+    pub(super) fn build_from_selection_with_parent(
         s: &'a Selection<'a, S>,
         parent: Option<&mut Self>,
         vars: &'a Variables<S>,
diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs
index 8a8000969..9a4588fa3 100644
--- a/juniper/src/executor/mod.rs
+++ b/juniper/src/executor/mod.rs
@@ -654,6 +654,7 @@ where
         };
         self.parent_selection_set
             .map(|p| {
+                // Search the parent's fields to find this field within the set
                 let found_field = p.iter().find(|&x| {
                     match *x {
                         Selection::Field(ref field) => {
@@ -672,31 +673,30 @@ where
                     None
                 }
             })
-            .filter(|s| s.is_some())
+            .flatten()
             .unwrap_or_else(|| {
-                Some(LookAheadSelection {
-                    name: self.current_type.innermost_concrete().name().unwrap_or(""),
+                // We didn't find a field in the parent's selection matching
+                // this field, which means we're inside a FragmentSpread
+                let mut ret = LookAheadSelection {
+                    name: field_name,
                     alias: None,
                     arguments: Vec::new(),
-                    children: self
-                        .current_selection_set
-                        .map(|s| {
-                            s.iter()
-                                .map(|s| ChildSelection {
-                                    inner: LookAheadSelection::build_from_selection(
-                                        &s,
-                                        self.variables,
-                                        self.fragments,
-                                    )
-                                    .expect("a child selection"),
-                                    applies_for: Applies::All,
-                                })
-                                .collect()
-                        })
-                        .unwrap_or_else(Vec::new),
-                })
+                    children: Vec::new(),
+                };
+
+                // Add in all the children - this will mutate `ret`
+                if let Some(selection_set) = self.current_selection_set {
+                    for c in selection_set {
+                        LookAheadSelection::build_from_selection_with_parent(
+                            c,
+                            Some(&mut ret),
+                            self.variables,
+                            self.fragments,
+                        );
+                    }
+                }
+                ret
             })
-            .unwrap_or_default()
     }
 
     /// Create new `OwnedExecutor` and clone all current data


Test patch:
diff --git a/integration_tests/juniper_tests/src/issue_500.rs b/integration_tests/juniper_tests/src/issue_500.rs
new file mode 100644
index 000000000..7dbb595fd
--- /dev/null
+++ b/integration_tests/juniper_tests/src/issue_500.rs
@@ -0,0 +1,92 @@
+use juniper::*;
+
+struct Query;
+
+#[juniper::graphql_object]
+impl Query {
+    fn users(executor: &Executor) -> Vec<User> {
+        executor.look_ahead();
+
+        vec![User {
+            city: City {
+                country: Country { id: 1 },
+            },
+        }]
+    }
+}
+
+struct User {
+    city: City,
+}
+
+#[juniper::graphql_object]
+impl User {
+    fn city(&self, executor: &Executor) -> &City {
+        executor.look_ahead();
+        &self.city
+    }
+}
+
+struct City {
+    country: Country,
+}
+
+#[juniper::graphql_object]
+impl City {
+    fn country(&self, executor: &Executor) -> &Country {
+        executor.look_ahead();
+        &self.country
+    }
+}
+
+struct Country {
+    id: i32,
+}
+
+#[juniper::graphql_object]
+impl Country {
+    fn id(&self) -> i32 {
+        self.id
+    }
+}
+
+type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
+
+#[tokio::test]
+async fn test_nested_fragments() {
+    let query = r#"
+        query Query {
+            users {
+                ...UserFragment
+            }
+        }
+
+        fragment UserFragment on User {
+            city {
+                ...CityFragment
+            }
+        }
+
+        fragment CityFragment on City {
+            country {
+                ...CountryFragment
+            }
+        }
+
+        fragment CountryFragment on Country {
+            id
+        }
+    "#;
+
+    let (_, errors) = juniper::execute(
+        query,
+        None,
+        &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
+        &Variables::new(),
+        &(),
+    )
+    .await
+    .unwrap();
+
+    assert_eq!(errors.len(), 0);
+}
diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs
index 642e428fa..19c07da47 100644
--- a/integration_tests/juniper_tests/src/lib.rs
+++ b/integration_tests/juniper_tests/src/lib.rs
@@ -12,3 +12,5 @@ mod infallible_as_field_error;
 mod issue_371;
 #[cfg(test)]
 mod issue_398;
+#[cfg(test)]
+mod issue_500;