juniper_warp

Function make_graphql_filter

Source
pub fn make_graphql_filter<S, Query, Mutation, Subscription, CtxT, CtxErr>(
    schema: impl Into<Arc<RootNode<'static, Query, Mutation, Subscription, S>>>,
    context_extractor: impl Filter<Extract = (CtxT,), Error = CtxErr> + Send + Sync + 'static,
) -> impl Filter<Extract = (Response,), Error = Rejection> + Clone + Send
where Query: GraphQLTypeAsync<S, Context = CtxT> + Send + 'static, Query::TypeInfo: Send + Sync, Mutation: GraphQLTypeAsync<S, Context = CtxT> + Send + 'static, Mutation::TypeInfo: Send + Sync, Subscription: GraphQLSubscriptionType<S, Context = CtxT> + Send + 'static, Subscription::TypeInfo: Send + Sync, CtxT: Send + Sync + 'static, CtxErr: Into<Rejection>, S: ScalarValue + Send + Sync + 'static,
Expand description

Makes a Filter for handling GraphQL queries/mutations.

The schema argument is your juniper schema.

The context_extractor argument should be a Filter that provides the GraphQL context, required by the schema.

§Example

type UserId = String;
struct AppState(Vec<i64>);
struct ExampleContext(Arc<AppState>, UserId);

struct QueryRoot;

#[graphql_object(context = ExampleContext)]
impl QueryRoot {
    fn say_hello(context: &ExampleContext) -> String {
        format!(
            "good morning {}, the app state is {:?}",
            context.1,
            context.0,
        )
    }
}

let schema = RootNode::new(QueryRoot, EmptyMutation::new(), EmptySubscription::new());

let app_state = Arc::new(AppState(vec![3, 4, 5]));
let app_state = warp::any().map(move || app_state.clone());

let context_extractor = warp::any()
    .and(warp::header::<String>("authorization"))
    .and(app_state)
    .map(|auth_header: String, app_state: Arc<AppState>| {
        let user_id = auth_header; // we believe them
        ExampleContext(app_state, user_id)
    });

let graphql_endpoint = warp::path("graphql")
    .and(make_graphql_filter(schema, context_extractor));

§Fallible context_extractor

WARNING: In case the context_extractor is fallible (e.g. implements Filter<Error = Rejection>), it’s error should be handled via Filter::recover() to fails fast and avoid switching to other Filters branches, because Rejection doesn’t mean to abort the whole request, but rather to say that a Filter couldn’t fulfill its preconditions.

#[derive(Clone, Copy, Debug)]
struct NotAuthorized;

impl warp::reject::Reject for NotAuthorized {}

impl warp::Reply for NotAuthorized {
    fn into_response(self) -> warp::reply::Response {
        http::StatusCode::FORBIDDEN.into_response()
    }
}

let schema = RootNode::new(QueryRoot, EmptyMutation::new(), EmptySubscription::new());

let app_state = Arc::new(AppState(vec![3, 4, 5]));
let app_state = warp::any().map(move || app_state.clone());

let context_extractor = warp::any()
    .and(warp::header::<String>("authorization"))
    .and(app_state)
    .and_then(|auth_header: String, app_state: Arc<AppState>| async move {
        if auth_header == "correct" {
            Ok(ExampleContext(app_state, auth_header))
        } else {
            Err(warp::reject::custom(NotAuthorized))
        }
    });

let graphql_endpoint = warp::path("graphql")
    .and(make_graphql_filter(schema, context_extractor))
    .recover(|rejection: warp::reject::Rejection| async move {
        rejection
            .find::<NotAuthorized>()
            .map(|e| e.into_response())
            .ok_or(rejection)
    });