#![allow(unused_variables, unused_imports, dead_code, unused_mut)] mod helpers; use assert_json_diff::assert_json_include; use helpers::StatsHash; use juniper::{EmptyMutation, Executor, FieldResult, ID}; use juniper_eager_loading::{ prelude::*, EagerLoading, HasManyThrough, HasOne, LoadChildrenOutput, LoadFrom, OptionHasOne, }; use juniper_from_schema::graphql_schema; use serde_json::{json, Value}; graphql_schema! { schema { query: Query } type Query { users: [User!]! @juniper(ownership: "owned") } type User { id: Int! parent: User! grandParent: User @juniper(ownership: "as_ref") } } mod models { use juniper_eager_loading::LoadFrom; #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] pub struct User { pub id: i32, pub parent_id: i32, pub grand_parent_id: Option, } impl LoadFrom for User { type Error = Box; type Context = super::Context; fn load(ids: &[i32], _: &(), ctx: &Self::Context) -> Result, Self::Error> { let models = ctx .db .users .all_values() .into_iter() .filter(|value| ids.contains(&value.id)) .cloned() .collect::>(); Ok(models) } } } pub struct Db { users: StatsHash, } pub struct Context { db: Db, } impl juniper::Context for Context {} pub struct Query; impl QueryFields for Query { fn field_users<'a>( &self, executor: &Executor<'a, Context>, trail: &QueryTrail<'a, User, Walked>, ) -> FieldResult> { let ctx = executor.context(); let mut user_models = ctx .db .users .all_values() .into_iter() .cloned() .collect::>(); user_models.sort_by_key(|user| user.id); let mut users = User::from_db_models(&user_models); User::eager_load_all_children_for_each(&mut users, &user_models, ctx, trail)?; Ok(users) } } #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, EagerLoading)] #[eager_loading(context = Context, error = Box)] pub struct User { user: models::User, #[has_one(root_model_field = user)] parent: HasOne>, #[option_has_one(root_model_field = user)] grand_parent: OptionHasOne>, } impl UserFields for User { fn field_id<'a>(&self, _: &Executor<'a, Context>) -> FieldResult<&i32> { Ok(&self.user.id) } fn field_parent<'a>( &self, executor: &Executor<'a, Context>, trail: &QueryTrail<'a, User, Walked>, ) -> FieldResult<&User> { Ok(self.parent.try_unwrap()?) } fn field_grand_parent<'a>( &self, executor: &Executor<'a, Context>, trail: &QueryTrail<'a, User, Walked>, ) -> FieldResult> { let grand_parent = self .grand_parent .try_unwrap()? .as_ref() .map(|boxed| &**boxed); Ok(grand_parent) } } #[test] fn loading_recursive_type() { let mut users = StatsHash::new("users"); users.insert( 1, models::User { id: 1, parent_id: 1, grand_parent_id: Some(1), }, ); let db = Db { users }; let (json, counts) = run_query( r#" query Test { users { id parent { id parent { id grandParent { id } } } } } "#, db, ); assert_json_include!( expected: json!({ "users": [ { "id": 1, "parent": { "id": 1, "parent": { "id": 1, "grandParent": { "id": 1, }, }, }, }, ] }), actual: json.clone(), ); } struct DbStats { user_reads: usize, } fn run_query(query: &str, db: Db) -> (Value, DbStats) { let ctx = Context { db }; let (result, errors) = juniper::execute( query, None, &Schema::new(Query, EmptyMutation::new()), &juniper::Variables::new(), &ctx, ) .unwrap(); if !errors.is_empty() { panic!( "GraphQL errors\n{}", serde_json::to_string_pretty(&errors).unwrap() ); } let json: Value = serde_json::from_str(&serde_json::to_string(&result).unwrap()).unwrap(); println!("{}", serde_json::to_string_pretty(&json).unwrap()); ( json, DbStats { user_reads: ctx.db.users.reads_count(), }, ) }