Context

Context is a feature in Juniper that lets field resolvers access global data, most commonly database connections or authentication information.

Let's say that we have a simple Users database in a HashMap:

#![allow(dead_code)]
use std::collections::HashMap;

struct Database {
    users: HashMap<i32, User>,
}

struct User {
    id: i32,
    name: String,
    friend_ids: Vec<i32>,
}

fn main() {}

We would like to define a friends field on User that returns a list of User objects. In order to write such a field we need to query a Database. To accomplish this we must first mark the Database as a valid context type and then assign it to the User object. To gain access to the context in the friends field, we need to specify an argument with the same type as the specified context:

extern crate juniper;
use std::collections::HashMap;
use juniper::graphql_object;

struct Database {
    users: HashMap<i32, User>,
}

// Mark the `Database` as a valid context type for Juniper.
impl juniper::Context for Database {}

struct User {
    id: i32,
    name: String,
    friend_ids: Vec<i32>,
}

#[graphql_object]
#[graphql(context = Database)] // assign `Database` as the context type
impl User {
    // Inject the `Database` context by specifying an argument with the 
    // context type: 
    // - the type must be a reference;
    // - the name of the argument SHOULD be `context` (or `ctx`).
    fn friends<'db>(&self, context: &'db Database) -> Vec<&'db User> {
        //                 ^^^^^^^ or `ctx`, up to your preference
        self.friend_ids.iter()
            .map(|id| {
                context.users.get(&id).expect("could not find `User` with ID")
            })
            .collect()
    }

    fn friend<'db>(
        &self, 
        id: i32, 
        // Alternatively, the context argument may be marked with an attribute,
        // and thus, named arbitrary.
        #[graphql(context)] db: &'db Database,
        //        ^^^^^^^ or `ctx`, up to your preference
    ) -> Option<&'db User> {
        self.friend_ids.contains(&id).then(|| {
            db.users.get(&id).expect("could not find `User` with ID")
        })
    }

    fn name(&self) -> &str { 
        self.name.as_str() 
    }

    fn id(&self) -> i32 { 
        self.id 
    }
}

fn main() {}

Mutating and mutable references

Context cannot be a mutable reference as fields may be resolved concurrently. If something in the context requires a mutable reference, the context type should leverage the interior mutability pattern (e.g. use RwLock, RefCell or similar).

For example, when using async runtime with work stealing (like tokio), which obviously requires thread safety in addition, we will need to use a corresponding async version of RwLock:

extern crate juniper;
extern crate tokio;
use std::collections::HashMap;
use juniper::graphql_object;
use tokio::sync::RwLock;

struct Database {
    requested_count: HashMap<i32, i32>,
}

// Since we cannot directly implement `juniper::Context`
// for `RwLock`, we use the newtype idiom.
struct DatabaseContext(RwLock<Database>);

impl juniper::Context for DatabaseContext {}

struct User {
    id: i32,
    name: String
}

#[graphql_object]
#[graphql(context = DatabaseContext)]
impl User {
    async fn times_requested<'db>(&self, ctx: &'db DatabaseContext) -> i32 {
        // Acquire a mutable reference and `.await` if async `RwLock` is used,
        // which is necessary if context consists of async operations like 
        // querying remote databases.
        
        // Obtain base type.
        let DatabaseContext(db) = ctx;
        // If context is immutable use `.read()` on `RwLock` instead.
        let mut db = db.write().await;
        
        // Perform a mutable operation.
        db.requested_count
            .entry(self.id)
            .and_modify(|e| *e += 1)
            .or_insert(1)
            .clone()
    }

    fn name(&self) -> &str { 
        self.name.as_str() 
    }

    fn id(&self) -> i32 { 
        self.id 
    }
}

fn main() {}

TIP: Replace tokio::sync::RwLock with std::sync::RwLock (or similar) if you don't intend to use async resolving.