Highly ambitious ATProtocol AppView service and sdks
at main 4.4 kB view raw
1//! GraphQL HTTP handler for Axum 2 3use async_graphql::dynamic::Schema; 4use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; 5use axum::{ 6 extract::{Query, State}, 7 http::{HeaderMap, StatusCode}, 8 response::Html, 9}; 10use serde::Deserialize; 11use std::sync::Arc; 12use tokio::sync::RwLock; 13 14use crate::errors::AppError; 15use crate::AppState; 16 17/// Global schema cache (one schema per slice) 18/// This prevents rebuilding the schema on every request 19type SchemaCache = Arc<RwLock<std::collections::HashMap<String, Schema>>>; 20 21lazy_static::lazy_static! { 22 static ref SCHEMA_CACHE: SchemaCache = Arc::new(RwLock::new(std::collections::HashMap::new())); 23} 24 25#[derive(Deserialize, Default)] 26pub struct GraphQLParams { 27 pub slice: Option<String>, 28} 29 30/// GraphQL query handler 31/// Accepts slice URI from either query parameter (?slice=...) or HTTP header (X-Slice-Uri) 32pub async fn graphql_handler( 33 State(state): State<AppState>, 34 Query(params): Query<GraphQLParams>, 35 headers: HeaderMap, 36 req: GraphQLRequest, 37) -> Result<GraphQLResponse, (StatusCode, String)> { 38 // Get slice URI from query param or header 39 let slice_uri = params 40 .slice 41 .or_else(|| { 42 headers 43 .get("x-slice-uri") 44 .and_then(|h| h.to_str().ok()) 45 .map(|s| s.to_string()) 46 }) 47 .ok_or_else(|| { 48 ( 49 StatusCode::BAD_REQUEST, 50 "Missing slice parameter. Provide either ?slice=... query parameter or X-Slice-Uri header".to_string(), 51 ) 52 })?; 53 54 let schema = match get_or_build_schema(&state, &slice_uri).await { 55 Ok(s) => s, 56 Err(e) => { 57 tracing::error!("Failed to get GraphQL schema: {:?}", e); 58 return Ok(async_graphql::Response::from_errors(vec![async_graphql::ServerError::new( 59 format!("Schema error: {:?}", e), 60 None, 61 )]) 62 .into()); 63 } 64 }; 65 66 Ok(schema.execute(req.into_inner()).await.into()) 67} 68 69/// GraphQL Playground UI handler 70/// Configures the playground with the slice URI in headers 71pub async fn graphql_playground( 72 Query(params): Query<GraphQLParams>, 73) -> Result<Html<String>, (StatusCode, String)> { 74 let slice_uri = params.slice.ok_or_else(|| { 75 ( 76 StatusCode::BAD_REQUEST, 77 "Missing slice parameter. Provide ?slice=... query parameter".to_string(), 78 ) 79 })?; 80 81 // Create playground with pre-configured headers 82 let playground_html = format!( 83 r#"<!DOCTYPE html> 84<html> 85<head> 86 <meta charset=utf-8 /> 87 <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui"> 88 <title>Slices GraphQL Playground</title> 89 <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/css/index.css" /> 90 <link rel="shortcut icon" href="//cdn.jsdelivr.net/npm/graphql-playground-react/build/favicon.png" /> 91 <script src="//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/js/middleware.js"></script> 92</head> 93<body> 94 <div id="root"></div> 95 <script> 96 window.addEventListener('load', function (event) {{ 97 GraphQLPlayground.init(document.getElementById('root'), {{ 98 endpoint: '/graphql', 99 settings: {{ 100 'request.credentials': 'omit', 101 }}, 102 tabs: [{{ 103 endpoint: '/graphql', 104 headers: {{ 105 'X-Slice-Uri': '{}' 106 }} 107 }}] 108 }}) 109 }}) 110 </script> 111</body> 112</html>"#, 113 slice_uri.replace("'", "\\'").replace("\"", "\\\"") 114 ); 115 116 Ok(Html(playground_html)) 117} 118 119/// Gets schema from cache or builds it if not cached 120async fn get_or_build_schema( 121 state: &AppState, 122 slice_uri: &str, 123) -> Result<Schema, AppError> { 124 // Check cache first 125 { 126 let cache = SCHEMA_CACHE.read().await; 127 if let Some(schema) = cache.get(slice_uri) { 128 return Ok(schema.clone()); 129 } 130 } 131 132 // Build schema 133 let schema = crate::graphql::build_graphql_schema( 134 state.database.clone(), 135 slice_uri.to_string(), 136 ) 137 .await 138 .map_err(|e| AppError::Internal(format!("Failed to build GraphQL schema: {}", e)))?; 139 140 // Cache it 141 { 142 let mut cache = SCHEMA_CACHE.write().await; 143 cache.insert(slice_uri.to_string(), schema.clone()); 144 } 145 146 Ok(schema) 147}