Juniper: Write docs for using Context with a lifetime

Created on 13 Feb 2018  路  17Comments  路  Source: graphql-rust/juniper

Right now my context has a lifetime

use juniper::Context as JuniperContext;
use postgres::transaction::Transaction;

pub struct Context<'a> {
    pub tx: Transaction<'a>,
}

impl<'a> JuniperContext for Context<'a> {}

impl<'a> Context<'a> {
    pub fn new(tx: Transaction<'a>) -> Context<'a> {
        Context { tx: tx }
    }
}

But when I try to specify the same lifetime in a resolver then it gives me this error,

error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
 --> src/schema/query.rs:7:18
  |
7 | graphql_object!(<'a> QueryRoot: Context<'a> as "Query" |&self| {
  |                  ^^ unconstrained lifetime parameter
use models::user::User;
use schema::context::Context;

pub struct QueryRoot;

graphql_object!(<'a> QueryRoot: Context<'a> as "Query" |&self| {
    field users(&executor) -> FieldResult<Vec<User>> {
      Ok(vec![])
    }
});
docs

All 17 comments

Did you ever figure this out? I'm struggling with the same issue.

Nope. Its go something to do with the macro implementation and the executor. Basically the lifetime parameter needs to be specified at the resolver level context also. But its not being done.
https://github.com/graphql-rust/juniper/blob/master/juniper_codegen/src/derive_object.rs#L174
I don't know how we would be able to do that.
One Other solution I can think of is trying to implement the impl ::juniper::GraphQLType for #ident trait yourself for the type and seeing if it works.

I have decided to not go with this approach and create a transaction only when needed in the mutation. So my context doesn't require a lifetime parameter anymore.

Hello, and sorry for the late answer.

As far as I know, this is not possible in Rust since the context is an associated type on the underlying GraphQLType trait. The graphql_object! macro expands to something like this:

graphql_object!(<'a> QueryRoot: Context<'a> as "Query" |&self| {
    field users(&executor) -> FieldResult<Vec<User>> {
      Ok(vec![])
    }
});

// Expands into:

impl<'a> GraphQLType for QueryRoot { // <- no constraint on 'a here
    type Context = Context<'a>;

    // ...
}

As you can see, the 'a parameter is unconstrained in the impl line, which is an error in Rust.

It _might_ work with adding a lifetime parameter to QueryRoot, by using e.g. PhantomData, but I would advise against it since it's might cause trouble when we introduce asynchronous resolvers.

I doubt it does, but does the latest master with https://github.com/graphql-rust/juniper/pull/226 help here?

@LegNeato nah, those are for the derives, not the declarative macros.

I'm hitting the same issue. I'm using an embedded Graph DB, so context needs to be defined as: struct Context<'a> { graph: &'a Graph } but I can't due to this issue. As a workaround I can something horrible like this:

type GraphPtr = *const Graph;

impl<'a> From<&'a Graph> for GraphPtr {
    fn from(graph: &Graph) -> Self {
        graph as GraphPtr
    }
}
impl<'a> Into<&'a Graph> for GraphPtr {
    fn into(self) -> &'a Graph {
        unsafe { &*self }
    }
}

struct Context {
    graphPtr: GraphPtr,
}

I haven't touched this area of Juniper but I have some time tomorrow and will try to take a crack at it

Same issue, any "not-so-ugly" solution?

// context.rs
pub struct GraphQLContext<'a> {
    pub language: Language,
    pub db_pool: DbPool,
    pub cookies: Cookies<'a>
}

impl <'a>Context for GraphQLContext<'a> {}

// mutation.rs is similar as query.rs
// query.rs
graphql_object!(<'a> &'a Query: GraphQLContext<'a> as "Query" |&self| {
    field recipe(&executor, id: RecipeId) -> FieldResult<RecipeGql> {
        get_recipe(&id, executor)
    }

    field recipePreparationStep(&executor, id: RecipePreparationStepId) -> FieldResult<RecipePreparationStepGql> {
       get_rps(&id, executor)
    }
});

//schema.rs
pub type Schema = RootNode<'static,  Query, Mutation>;

// router.rs
#[post("/", data = "<request>")]
fn post_graphql_handler(
    language_guard: LanguageGuard,
    ctx: State<Context>,
    request: juniper_rocket::GraphQLRequest,
    schema: State<Schema>,
    mut cookies: Cookies
) -> juniper_rocket::GraphQLResponse {
    let gql_ctx = GraphQLContext::new(
        language_guard.language,
        ctx.db_pool.clone(),
        cookies
    );
    request.execute(&schema, &gql_ctx)
}

gives me

  --> src/router/graphql.rs:9:1
   |
9  | / fn post_graphql_handler(
10 | |     language_guard: LanguageGuard,
11 | |     ctx: State<Context>,
12 | |     request: juniper_rocket::GraphQLRequest,
...  |
21 | |     request.execute(&schema, &gql_ctx)
22 | | }
   | |_^ the trait `juniper::GraphQLType` is not implemented for `graphql::query::Query`
   |
   = help: the following implementations were found:
             <&'a graphql::query::Query as juniper::GraphQLType>
   = note: required by `juniper::RootNode`

error[E0277]: the trait bound `graphql::mutation::Mutation: juniper::GraphQLType` is not satisfied
  --> src/router/graphql.rs:9:1
   |
9  | / fn post_graphql_handler(
10 | |     language_guard: LanguageGuard,
11 | |     ctx: State<Context>,
12 | |     request: juniper_rocket::GraphQLRequest,
...  |
21 | |     request.execute(&schema, &gql_ctx)
22 | | }
   | |_^ the trait `juniper::GraphQLType` is not implemented for `graphql::mutation::Mutation`
   |
   = help: the following implementations were found:
             <&'a graphql::mutation::Mutation as juniper::GraphQLType>
   = note: required by `juniper::RootNode`

I really need those cookies in context. Or is there better way how to pass them?

Don't mind me. I've just wanted to set cookies in login resolver, and that's probably not possible, dunno. Classic REST route will be better for this purpose.

@mrceperka Did you find a solution for this? I'm trying to do the exact same thing (pass cookies into the context with Rocket) and ran into the same issue.

Nope I did not. I've continued without cookies... (cos I just wanted to do something and I did not want to be stuck on this cookie thing :D) But one day, I will have to fix it anyways.

I'm doing something like this which compiles:

pub struct Query<'a> {
  _marker: PhantomData<&'a ()>,
}

graphql_object!(<'a> Query<'a>: Context<'a> as "Query" |&self| {
  field getUser(&executor, id: Uuid) -> FieldResult<User> {
    Ok(User::read(&executor.context().db.connect()?, &id)?)
  }
});

pub struct Mutation<'a> {
  _marker: PhantomData<&'a ()>,
}

graphql_object!(<'a> Mutation<'a>: Context<'a> as "Mutation" |&self| {
  field createUser(&executor, user: UserCreate) -> FieldResult<String> {
    Ok(User::create(
      &executor.context().db.connect()?,
      executor.context().hasher,
      executor.context().tokeniser,
      &user
    )?)
  }

  field updateUser(&executor, user: UserEdit) -> FieldResult<bool> {
    Ok(User::update(
      &executor.context().db.connect()?,
      executor.context().hasher,
      &user
    )?)
  }

  field deleteUser(&executor, id: Uuid) -> FieldResult<bool> {
    Ok(User::delete(&executor.context().db.connect()?, &id)?)
  }

  field login(&executor, user: UserLogin) -> FieldResult<String> {
    Ok(User::login(
      &executor.context().db.connect()?,
      *executor.context().hash_verify,
      executor.context().tokeniser,
      &user
    )?)
  }
});

pub type Schema<'a> = RootNode<'static, Query<'a>, Mutation<'a>>;

pub fn new<'a>() -> Schema<'a> {
  Schema::new(Query { _marker: PhantomData }, Mutation { _marker: PhantomData })
}

But crashes horribly when handling the request through Rocket (although it could be down to Rocket's State for me, I've got a question here regarding: https://stackoverflow.com/questions/55884872/rockets-state-errors-with-attempted-to-retrieve-unmanaged-state-when-using-an)

This is an example that works with the new object macro.

THis should be added to the book and to the macro doc comment.

pub struct DataStore;

pub struct Context<'a> {
    pub datastore: &'a DataStore
}

impl<'a> juniper::Context for Context<'a> {}

pub struct Query<'a> {
  marker: std::marker::PhantomData<&'a ()>,   
}

#[juniper::object(context = Context<'a>)]
impl<'a> Query<'a> {

    fn datastore_version(ctx: &Context<'a>) -> String {
        "lala".into()
    }
}


fn main() {
  let query = Query{ marker: std::marker::PhantomData };
}

Yeah, I've done something similar. This is the workaround I ended up creating:

#[derive(Debug)]
pub struct QueryRoot<'a>(PhantomData<&'a ()>);

#[derive(Debug)]
pub struct MutationRoot<'a>(PhantomData<&'a ()>);

#[derive(Debug)]
pub struct Schema(RootNode<'static, QueryRoot<'static>, MutationRoot<'static>>);

impl Schema {
  // Workaround for https://github.com/graphql-rust/juniper/issues/143
  //
  // The only reason we have this lifetime on `QueryRoot`/`MutationRoot` is so we can use it on
  // associated `Context` type so we can pass context items with lifetimes.
  //
  // At the same time, we need `Schema` to use `'static` lifetime internally since we create it
  // once and store globally in `Arc`.
  pub fn with_lifetime<'a>(&self) -> &RootNode<'a, QueryRoot<'a>, MutationRoot<'a>> {
    let res: &RootNode<'static, QueryRoot<'static>, MutationRoot<'static>> = &self.0;
    let res: &RootNode<'static, QueryRoot<'a>, MutationRoot<'a>> =
      unsafe { std::mem::transmute(res) };
    res
  }
}

One constraint in my case is that building a new schema is quite expensive, so I have to create it once and store in Rocket State (and hence it has to have a lifetime of 'static).

Still, this is not a great solution as it uses unsafe to erase lifetimes (which seems to be fine in my particular case, but safe unsafe is no unsafe at all...).

which seems to be fine in my particular case

Yeah it's fine in this case because the context is guaranteed to be alive for the duration of execute, but it's obviously unfortunate that this is needed.

Out of curiosity, is your schema so big that construction time is noticable?

Our schema is defined through metadata which we read from the database and/or files. And it's big, yes. Takes seconds to build a schema (in debug, in release it is fraction of a second, but still some significant amount of time).

Hey, I'm glad I found this issue, but I'm still struggling to get this to work...

use crate::{
    models::recipe::Recipe as RecipeModel,
    graphql::{
        Context,
        resolvers::{
            RecipeIngredient,
        }
    }
};

pub struct Recipe<'a> {
    marker: std::marker::PhantomData<&'a ()>,
    record: RecipeModel,
}

#[juniper::graphql_object(Context = Context<'a>)]
impl<'a> Recipe<'a> {
    fn id(&self) -> i32 {
        self.record.id
    }

    fn name(&self) -> &String {
        &self.record.name
    }

    async fn ingredients(&self, context: &Context<'a>) -> Vec<RecipeIngredient> {
        let records = context.recipe_ingredient_loader.load(self.record.id).await;
        records
            .into_iter()
            .map(|r| RecipeIngredient { marker: std::marker::PhantomData, record: r })
            .collect()
    }
}

This is one resolver that's not on the top level. When i compile this, i get the following error:

error[E0308]: mismatched types
  --> src/graphql/resolvers/recipe.rs:16:1
   |
16 | #[juniper::graphql_object(Context = Context<'a>)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected type `std::marker::Sync`
              found type `std::marker::Sync`
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

Any Idea what's happening here?

I actually solved this problem by removing the lifetime parameter from the Context object.

If you think about it, the context shouldn't need an annotated lifetime, because it's suppose to live as long as the program. In other words, the fields within the Context object should also live as long too.

Let's take a look at the OP's code:

use juniper::Context as JuniperContext;
use postgres::transaction::Transaction;

pub struct Context<'a> {
    pub tx: Transaction<'a>,
}

Taking the explanation above, we can see that it's absurd that the tx object live as long as the Context, since tx should only live as long as the lifetime of a client request, while Context live until the program dies.

In my case, I initially have this following piece of code, the Context is Persistence:

pub struct Persistence<'a, T>
where
    T: Serialize + Deserialize<'a>,
{
    db: mongodb::Database,
    collection_name: String,
    phantom: PhantomData<T>
}

And the implementation for this Persistence struct contains function that deserialize JSON data. Then I realised something horribly wrong, if the lifetime of the deserialized object lives as long as the context, doesn't that means that memory consumption will be ever-increasing since the context lives until the program is terminated?

To solve this problem, where the Context and the deserialized object have different lifetime, I used Higher Kinded Trait Bounds, which allows you to introduce lifetime parameter from nowhere (kinda like creating a lamba).

pub struct Persistence<T>
where
    T: Serialize + for<'a> Deserialize<'a>,
{
    db: mongodb::Database,
    collection_name: String,
    phantom: PhantomData<T>
}

And voila the lifetime parameter is eliminated from the Persistence struct (and so this issue become irrelevant).

P/S: I'm still very new to Rust, the explanation above is solely based on my speculations. I'll be more than happy if you could correct my mistakes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

librelois picture librelois  路  3Comments

pyros2097 picture pyros2097  路  3Comments

snnsnn picture snnsnn  路  4Comments

tomhoule picture tomhoule  路  4Comments

davidpdrsn picture davidpdrsn  路  5Comments