use acton_service::graphql::{GraphQLContextExt, VersionedGraphQLBuilder};
use acton_service::prelude::*;
use async_graphql::{Context, EmptyMutation, EmptySubscription, Object, Schema};
#[cfg(feature = "graphql-cedar")]
use acton_service::graphql::CedarResolverCheck;
struct QueryV1;
#[Object]
impl QueryV1 {
async fn hello(&self) -> &'static str {
"Hello from GraphQL V1!"
}
async fn whoami(&self, ctx: &Context<'_>) -> String {
ctx.claims()
.map(|c| c.sub.clone())
.unwrap_or_else(|| "anonymous".to_string())
}
}
struct QueryV2;
#[Object]
impl QueryV2 {
async fn me(&self, ctx: &Context<'_>) -> async_graphql::Result<Me> {
let claims = ctx.require_claims()?;
Ok(Me {
sub: claims.sub.clone(),
roles: claims.roles.clone(),
})
}
async fn document(
&self,
_ctx: &Context<'_>,
id: String,
) -> async_graphql::Result<String> {
#[cfg(feature = "graphql-cedar")]
CedarResolverCheck::for_context(_ctx)?
.with_action("readDocument")
.with_resource_type("Document")
.with_resource_id(&id)
.authorize()
.await
.map_err(|e| async_graphql::Error::new(e.to_string()))?;
Ok(format!("Document {} contents", id))
}
}
#[derive(async_graphql::SimpleObject)]
struct Me {
sub: String,
roles: Vec<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let routes = VersionedApiBuilder::new()
.with_base_path("/api")
.add_version(ApiVersion::V1, |r| r)
.add_version(ApiVersion::V2, |r| r)
.build_routes();
let schema_v1 = Schema::build(QueryV1, EmptyMutation, EmptySubscription).finish();
let schema_v2 = Schema::build(QueryV2, EmptyMutation, EmptySubscription).finish();
let graphql = VersionedGraphQLBuilder::new()
.with_base_path("/api")
.add_version(ApiVersion::V1, schema_v1)
.add_version(ApiVersion::V2, schema_v2)
.build();
ServiceBuilder::new()
.with_routes(routes)
.with_versioned_graphql(graphql)
.build()
.serve()
.await?;
Ok(())
}