Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write docs for using Context with a lifetime #143

Open
pyrossh opened this issue Feb 13, 2018 · 18 comments
Open

Write docs for using Context with a lifetime #143

pyrossh opened this issue Feb 13, 2018 · 18 comments
Assignees
Labels
k::documentation Related to project documentation

Comments

@pyrossh
Copy link

pyrossh commented Feb 13, 2018

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![])
    }
});
@carlosdp
Copy link
Contributor

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

@pyrossh
Copy link
Author

pyrossh commented Mar 11, 2018

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.

@mhallin
Copy link
Member

mhallin commented Mar 11, 2018

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.

@LegNeato
Copy link
Member

I doubt it does, but does the latest master with #226 help here?

@carlosdp
Copy link
Contributor

carlosdp commented Sep 5, 2018

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

@nitingupta910
Copy link

nitingupta910 commented Sep 19, 2018

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,
}

@LegNeato
Copy link
Member

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

@mrceperka
Copy link

mrceperka commented Jan 5, 2019

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.

@LucasPickering
Copy link
Contributor

@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.

@mrceperka
Copy link

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.

@WillSquire
Copy link

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)

@theduke theduke added the k::documentation Related to project documentation label May 21, 2019
@theduke theduke changed the title How to specify lifetimes for context in a resolver? Write docs for using Context with a lifetime May 21, 2019
@theduke theduke self-assigned this May 21, 2019
@theduke
Copy link
Member

theduke commented May 21, 2019

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 };
}

@idubrov
Copy link
Contributor

idubrov commented May 21, 2019

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...).

@theduke
Copy link
Member

theduke commented May 21, 2019

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?

@idubrov
Copy link
Contributor

idubrov commented May 21, 2019

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).

@repomaa
Copy link

repomaa commented Jul 10, 2020

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?

@wongjiahau
Copy link

wongjiahau commented Oct 11, 2020

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.

@HendrylGodoi
Copy link

I believe this can be fixed by using an Arc (or Rc, if you're not doing anything async) to wrap your data source, database, http client or anything else.

This way you can tell Rocket to manage this object, and on the FromRequest implementation you can always create a brand new Context, but instead of having to instantiate all it's dependencies, you would be cloning them from the Arc pointer, which is not an actual expensive clone, but only getting a reference to the initial instance, only adding to the Arc refs counter...

Something like this:

Here we create our UserAddressService wrapped by an Arc and tell Rocket to manage it, so we can ask for it later in the FromRequest trait implementation. This could be your database or anything else you might need in your context.

// Settings

let settings = rocket_builder.state::<Settings>().unwrap();

// Http Client

let http_client = Arc::new(
    Client::builder()
        .build(HttpsConnector::new())
);
let vtex_settings = settings.vtex_api.clone().unwrap();
let base_uri = vtex_settings.base_uri.clone().unwrap();
let app_key = vtex_settings.app_key.clone().unwrap();
let app_token = vtex_settings.app_token.clone().unwrap();

// Data Sources

let vtex_master_data_api = Arc::new(VtexMasterDataApi::new(
    http_client.clone(),
    base_uri.to_string(),
    app_key.to_string(),
    app_token.to_string(),
));

// Services

let user_address_service = Arc::new(UserAddressService::new(
    vtex_master_data_api.clone(),
));

rocket_builder
    .manage(user_address_service)

Here we ask Rocket for our reference and return a new context containing it.

pub struct RequestContext {
	pub user_address_service: Arc<UserAddressService>,
}

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

#[rocket::async_trait]
impl<'r> FromRequest<'r> for RequestContext {
	type Error = ();

	async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {

		let user_address_service = request
			.rocket()
			.state::<Arc<UserAddressService>>()
			.unwrap();

		Outcome::Success(RequestContext {
			// We don't need to worry about cloning here, 
			// since we're using Arc, we're only getting a shared reference!
			user_address_service: user_address_service.clone(),
		})
	}
}

This way our dependency instance will be kept alive for the lifetime of our application (or at least while Rocket is running) and every time we need it inside a resolver we'll be getting a new reference without having to worry about managing lifetimes ourselves.

Another thing you may need to do if you intend to use mutable state inside your shared object, is using a Mutex or RwLock to make it thread-safe. Along the lines of:
Arc::new(RwLock::new(YourInMemoryDatabase::new()))

I'm pretty new to Rust, so if anyone have any suggestions on a better way to do this, i'm all ears!
Anyways, I hope this helps anyone...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
k::documentation Related to project documentation
Projects
None yet
Development

No branches or pull requests