Skip to main content

ocre/runtime/
graphql.rs

1use async_graphql::{ObjectType, Schema, SubscriptionType};
2use axum::{Router, body::Bytes, extract::State, http::header, routing::get};
3
4use super::Ctx;
5use crate::graphql::{graphiql, respond};
6
7/// GraphiQL loads React and its editor from unpkg.com and starts with an
8/// inline script: its page gets this policy instead of the app's
9/// `ocre::security::ContentSecurityPolicy` (a handler's header wins).
10const GRAPHIQL_CSP: &str = "default-src 'self'; script-src 'self' https://unpkg.com 'unsafe-inline'; \
11                            style-src 'self' https://unpkg.com 'unsafe-inline'; img-src 'self' data: https:; \
12                            font-src 'self' data: https://unpkg.com; connect-src 'self'";
13
14/// Returns a router serving GraphiQL on `GET /graphql` and queries on `POST /graphql`.
15///
16/// `schema` returns a schema built once per Worker instance (a `static
17/// LazyLock`), because building it costs CPU (part of the 20-60 ms a new
18/// instance spends with GraphQL, against the free plan's 10 ms per request).
19/// `POST` runs [`respond`](crate::graphql::respond) with the request's
20/// [`Ctx`](crate::Ctx) as resolver data, so resolvers reach D1 and the other
21/// bindings through `ctx.data::<ocre::Ctx>()?`. `GET` serves
22/// [`graphiql`](crate::graphql::graphiql). Merge it into the app's router.
23///
24/// # Examples
25///
26/// ```
27/// use std::sync::LazyLock;
28/// use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema};
29/// use axum::Router;
30/// use ocre::Ctx;
31///
32/// struct Query;
33///
34/// #[Object]
35/// impl Query {
36///     async fn version(&self) -> &'static str {
37///         "1"
38///     }
39/// }
40///
41/// static SCHEMA: LazyLock<Schema<Query, EmptyMutation, EmptySubscription>> =
42///     LazyLock::new(|| Schema::new(Query, EmptyMutation, EmptySubscription));
43///
44/// let _app: Router<Ctx> = Router::new().merge(ocre::graphql::routes(|| &*SCHEMA));
45/// ```
46pub fn routes<Q, M, S>(schema: fn() -> &'static Schema<Q, M, S>) -> Router<Ctx>
47where
48    Q: ObjectType + 'static,
49    M: ObjectType + 'static,
50    S: SubscriptionType + 'static,
51{
52    Router::new().route(
53        "/graphql",
54        get(|| async { ([(header::CONTENT_SECURITY_POLICY, GRAPHIQL_CSP)], graphiql()) })
55            .post(move |State(ctx): State<Ctx>, body: Bytes| async move { respond(schema(), &body, ctx).await }),
56    )
57}