use crate::juniper_schema::{create_schema, GraphQLContext, Schema};
use crate::RdfStore;
use anyhow::Result;
use juniper::{execute, Variables};
use serde_json;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::{error, info};
#[derive(Debug, Clone)]
pub struct GraphQLServerConfig {
pub enable_graphiql: bool,
pub enable_playground: bool,
pub enable_introspection: bool,
pub max_query_depth: Option<usize>,
pub max_query_complexity: Option<usize>,
pub cors_enabled: bool,
pub cors_origins: Option<Vec<String>>,
}
impl Default for GraphQLServerConfig {
fn default() -> Self {
Self {
enable_graphiql: true,
enable_playground: true,
enable_introspection: true,
max_query_depth: Some(15),
max_query_complexity: Some(1000),
cors_enabled: true,
cors_origins: None, }
}
}
pub struct JuniperGraphQLServer {
schema: Arc<Schema>,
context: GraphQLContext,
config: GraphQLServerConfig,
}
impl JuniperGraphQLServer {
pub fn new(store: Arc<RdfStore>) -> Self {
let schema = Arc::new(create_schema());
let context = GraphQLContext { store };
let config = GraphQLServerConfig::default();
Self {
schema,
context,
config,
}
}
pub fn with_config(store: Arc<RdfStore>, config: GraphQLServerConfig) -> Self {
let schema = Arc::new(create_schema());
let context = GraphQLContext { store };
Self {
schema,
context,
config,
}
}
pub async fn start(&self, addr: SocketAddr) -> Result<()> {
info!("Starting simplified Juniper GraphQL server on {}", addr);
let listener = TcpListener::bind(addr).await?;
info!("GraphQL server listening on http://{}", addr);
info!("GraphQL endpoint: http://{}/graphql", addr);
if self.config.enable_graphiql {
info!("GraphiQL interface: http://{}/graphiql", addr);
}
if self.config.enable_playground {
info!("GraphQL Playground: http://{}/playground", addr);
}
loop {
match listener.accept().await {
Ok((stream, _)) => {
let schema = Arc::clone(&self.schema);
let context = self.context.clone();
let config = self.config.clone();
tokio::spawn(async move {
if let Err(e) =
Self::handle_connection(stream, schema, context, config).await
{
error!("Connection handling error: {}", e);
}
});
}
Err(e) => {
error!("Failed to accept connection: {}", e);
}
}
}
}
async fn handle_connection(
_stream: tokio::net::TcpStream,
_schema: Arc<Schema>,
_context: GraphQLContext,
_config: GraphQLServerConfig,
) -> Result<()> {
Ok(())
}
pub async fn execute_query(
&self,
query: &str,
variables: Variables,
) -> Result<serde_json::Value> {
let result = execute(query, None, &*self.schema, &variables, &self.context).await;
Ok(serde_json::to_value(result)?)
}
}
pub struct GraphQLServerBuilder {
config: GraphQLServerConfig,
}
impl GraphQLServerBuilder {
pub fn new() -> Self {
Self {
config: GraphQLServerConfig::default(),
}
}
pub fn enable_graphiql(mut self, enable: bool) -> Self {
self.config.enable_graphiql = enable;
self
}
pub fn enable_playground(mut self, enable: bool) -> Self {
self.config.enable_playground = enable;
self
}
pub fn enable_introspection(mut self, enable: bool) -> Self {
self.config.enable_introspection = enable;
self
}
pub fn max_query_depth(mut self, depth: Option<usize>) -> Self {
self.config.max_query_depth = depth;
self
}
pub fn max_query_complexity(mut self, complexity: Option<usize>) -> Self {
self.config.max_query_complexity = complexity;
self
}
pub fn cors_enabled(mut self, enabled: bool) -> Self {
self.config.cors_enabled = enabled;
self
}
pub fn cors_origins(mut self, origins: Vec<String>) -> Self {
self.config.cors_origins = Some(origins);
self
}
pub fn build(self, store: Arc<RdfStore>) -> JuniperGraphQLServer {
JuniperGraphQLServer::with_config(store, self.config)
}
}
impl Default for GraphQLServerBuilder {
fn default() -> Self {
Self::new()
}
}
pub async fn start_graphql_server(store: Arc<RdfStore>, addr: SocketAddr) -> Result<()> {
let server = JuniperGraphQLServer::new(store);
server.start(addr).await
}
pub async fn start_graphql_server_with_config(
store: Arc<RdfStore>,
addr: SocketAddr,
config: GraphQLServerConfig,
) -> Result<()> {
let server = JuniperGraphQLServer::with_config(store, config);
server.start(addr).await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_server_creation() {
let store = Arc::new(RdfStore::new().expect("Failed to create store"));
let server = JuniperGraphQLServer::new(store);
assert!(server.config.enable_graphiql);
assert!(server.config.enable_playground);
assert!(server.config.enable_introspection);
}
#[tokio::test]
async fn test_server_builder() {
let store = Arc::new(RdfStore::new().expect("Failed to create store"));
let server = GraphQLServerBuilder::new()
.enable_graphiql(false)
.enable_playground(true)
.enable_introspection(false)
.max_query_depth(Some(10))
.cors_enabled(true)
.build(store);
assert!(!server.config.enable_graphiql);
assert!(server.config.enable_playground);
assert!(!server.config.enable_introspection);
assert_eq!(server.config.max_query_depth, Some(10));
assert!(server.config.cors_enabled);
}
#[tokio::test]
async fn test_query_execution() {
let store = Arc::new(RdfStore::new().expect("Failed to create store"));
let server = JuniperGraphQLServer::new(store);
let query = "{ __schema { queryType { name } } }";
let variables = Variables::new();
let result = server.execute_query(query, variables).await;
assert!(result.is_ok());
}
}