-
Notifications
You must be signed in to change notification settings - Fork 426
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
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. 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 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 It might work with adding a lifetime parameter to |
I doubt it does, but does the latest master with #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:
|
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
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:
But crashes horribly when handling the request through Rocket (although it could be down to Rocket's |
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 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...). |
Yeah it's fine in this case because the context is guaranteed to be alive for the duration of 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:
Taking the explanation above, we can see that it's absurd that the In my case, I initially have this following piece of code, the Context is pub struct Persistence<'a, T>
where
T: Serialize + Deserialize<'a>,
{
db: mongodb::Database,
collection_name: String,
phantom: PhantomData<T>
} And the implementation for this 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 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. |
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: I'm pretty new to Rust, so if anyone have any suggestions on a better way to do this, i'm all ears! |
Right now my context has a lifetime
But when I try to specify the same lifetime in a resolver then it gives me this error,
The text was updated successfully, but these errors were encountered: