use std::{future::Future, pin::Pin};
use super::ProtocolAdapter;
#[derive(Debug, Clone, PartialEq)]
pub enum OperationType {
Query,
Mutation,
}
#[derive(Debug, Clone)]
pub struct GraphQLOperation {
pub operation_type: OperationType,
pub name: String,
pub handler: String,
}
impl GraphQLOperation {
pub fn new(
operation_type: OperationType,
name: impl Into<String>,
handler: impl Into<String>,
) -> Self {
Self {
operation_type,
name: name.into(),
handler: handler.into(),
}
}
}
pub struct GraphQLAdapter {
operations: Vec<GraphQLOperation>,
}
impl GraphQLAdapter {
pub fn new() -> Self {
Self {
operations: Vec::new(),
}
}
pub fn query(&mut self, name: &str, handler: &str) -> &mut Self {
self.operations
.push(GraphQLOperation::new(OperationType::Query, name, handler));
self
}
pub fn mutation(&mut self, name: &str, handler: &str) -> &mut Self {
self.operations.push(GraphQLOperation::new(
OperationType::Mutation,
name,
handler,
));
self
}
pub fn match_operation(
&self,
operation_type: OperationType,
name: &str,
) -> Option<&GraphQLOperation> {
self.operations
.iter()
.find(|op| op.operation_type == operation_type && op.name == name)
}
pub fn parse_query(&self, query: &str) -> Result<(OperationType, String), String> {
let trimmed = query.trim();
if trimmed.is_empty() {
return Err("Empty GraphQL query".to_string());
}
if let Some(stripped) = trimmed.strip_prefix("query") {
let name = self.extract_operation_name(stripped)?;
Ok((OperationType::Query, name))
} else if let Some(stripped) = trimmed.strip_prefix("mutation") {
let name = self.extract_operation_name(stripped)?;
Ok((OperationType::Mutation, name))
} else if trimmed.starts_with('{') {
let name = self.extract_operation_name(trimmed)?;
Ok((OperationType::Query, name))
} else {
Err("Invalid GraphQL query format".to_string())
}
}
fn extract_operation_name(&self, query_body: &str) -> Result<String, String> {
let trimmed = query_body.trim();
if let Some(brace_pos) = trimmed.find('{') {
let content = &trimmed[brace_pos + 1..];
if let Some(close_pos) = content.find('}') {
let inner = content[..close_pos].trim();
if let Some(name) = inner.split_whitespace().next() {
let name = if let Some(paren_pos) = name.find('(') {
&name[..paren_pos]
} else {
name
};
return Ok(name.to_string());
}
}
}
Err("Could not extract operation name from query".to_string())
}
pub fn generate_schema(&self) -> String {
let mut schema = String::new();
let queries: Vec<&GraphQLOperation> = self
.operations
.iter()
.filter(|op| op.operation_type == OperationType::Query)
.collect();
if !queries.is_empty() {
schema.push_str("type Query {\n");
for query in &queries {
schema.push_str(&format!(" {}: String\n", query.name));
}
schema.push_str("}\n\n");
}
let mutations: Vec<&GraphQLOperation> = self
.operations
.iter()
.filter(|op| op.operation_type == OperationType::Mutation)
.collect();
if !mutations.is_empty() {
schema.push_str("type Mutation {\n");
for mutation in &mutations {
schema.push_str(&format!(" {}: String\n", mutation.name));
}
schema.push_str("}\n\n");
}
if !queries.is_empty() || !mutations.is_empty() {
schema.push_str("schema {\n");
if !queries.is_empty() {
schema.push_str(" query: Query\n");
}
if !mutations.is_empty() {
schema.push_str(" mutation: Mutation\n");
}
schema.push_str("}\n");
}
schema.trim().to_string()
}
}
impl Default for GraphQLAdapter {
fn default() -> Self {
Self::new()
}
}
impl ProtocolAdapter for GraphQLAdapter {
fn name(&self) -> &str {
"graphql"
}
fn handle(
&self,
request: &str,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + '_>> {
let parse_result = self.parse_query(request);
let operations = self.operations.clone();
Box::pin(async move {
let (operation_type, operation_name) = match parse_result {
Ok(parsed) => parsed,
Err(e) => {
let response = format!(r#"{{"errors":[{{"message":"{}"}}]}}"#, e);
return Ok(response);
}
};
let matched_operation = operations
.iter()
.find(|op| op.operation_type == operation_type && op.name == operation_name);
match matched_operation {
Some(operation) => {
let response = format!(
r#"{{"data":{{"{}":"{}"}},"extensions":{{"handler":"{}"}}}}"#,
operation.name, operation.name, operation.handler
);
Ok(response)
}
None => {
let error = format!(
r#"{{"errors":[{{"message":"Operation not found: {}"}}]}}"#,
operation_name
);
Ok(error)
}
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_graphql_adapter_creation() {
let adapter = GraphQLAdapter::new();
assert_eq!(adapter.name(), "graphql");
}
#[test]
fn test_operation_registration_query() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
assert_eq!(adapter.operations.len(), 1);
assert_eq!(adapter.operations[0].operation_type, OperationType::Query);
assert_eq!(adapter.operations[0].name, "user");
assert_eq!(adapter.operations[0].handler, "get_user");
}
#[test]
fn test_operation_registration_mutation() {
let mut adapter = GraphQLAdapter::new();
adapter.mutation("createUser", "create_user_handler");
assert_eq!(adapter.operations.len(), 1);
assert_eq!(
adapter.operations[0].operation_type,
OperationType::Mutation
);
assert_eq!(adapter.operations[0].name, "createUser");
assert_eq!(adapter.operations[0].handler, "create_user_handler");
}
#[test]
fn test_operation_registration_multiple() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
adapter.query("users", "list_users");
adapter.mutation("createUser", "create_user");
assert_eq!(adapter.operations.len(), 3);
}
#[test]
fn test_match_operation_query() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
let matched = adapter.match_operation(OperationType::Query, "user");
assert!(matched.is_some());
assert_eq!(matched.unwrap().handler, "get_user");
}
#[test]
fn test_match_operation_mutation() {
let mut adapter = GraphQLAdapter::new();
adapter.mutation("createUser", "create_user");
let matched = adapter.match_operation(OperationType::Mutation, "createUser");
assert!(matched.is_some());
assert_eq!(matched.unwrap().handler, "create_user");
}
#[test]
fn test_match_operation_not_found() {
let adapter = GraphQLAdapter::new();
let matched = adapter.match_operation(OperationType::Query, "nonexistent");
assert!(matched.is_none());
}
#[test]
fn test_match_operation_wrong_type() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
let matched = adapter.match_operation(OperationType::Mutation, "user");
assert!(matched.is_none());
}
#[test]
fn test_parse_query_named() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("query GetUser { user }");
assert!(result.is_ok());
let (op_type, name) = result.unwrap();
assert_eq!(op_type, OperationType::Query);
assert_eq!(name, "user");
}
#[test]
fn test_parse_query_shorthand() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("{ user }");
assert!(result.is_ok());
let (op_type, name) = result.unwrap();
assert_eq!(op_type, OperationType::Query);
assert_eq!(name, "user");
}
#[test]
fn test_parse_query_with_args() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("query { user(id: 42) }");
assert!(result.is_ok());
let (op_type, name) = result.unwrap();
assert_eq!(op_type, OperationType::Query);
assert_eq!(name, "user");
}
#[test]
fn test_parse_mutation_named() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("mutation CreateUser { createUser }");
assert!(result.is_ok());
let (op_type, name) = result.unwrap();
assert_eq!(op_type, OperationType::Mutation);
assert_eq!(name, "createUser");
}
#[test]
fn test_parse_mutation_with_args() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("mutation { createUser(name: \"John\") }");
assert!(result.is_ok());
let (op_type, name) = result.unwrap();
assert_eq!(op_type, OperationType::Mutation);
assert_eq!(name, "createUser");
}
#[test]
fn test_parse_query_empty() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Empty"));
}
#[test]
fn test_parse_query_invalid() {
let adapter = GraphQLAdapter::new();
let result = adapter.parse_query("invalid query");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid GraphQL query format"));
}
#[test]
fn test_schema_generation_empty() {
let adapter = GraphQLAdapter::new();
let schema = adapter.generate_schema();
assert_eq!(schema, "");
}
#[test]
fn test_schema_generation_with_queries() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
adapter.query("users", "list_users");
let schema = adapter.generate_schema();
assert!(schema.contains("type Query {"));
assert!(schema.contains("user: String"));
assert!(schema.contains("users: String"));
assert!(schema.contains("schema {"));
assert!(schema.contains("query: Query"));
}
#[test]
fn test_schema_generation_with_mutations() {
let mut adapter = GraphQLAdapter::new();
adapter.mutation("createUser", "create_user");
adapter.mutation("deleteUser", "delete_user");
let schema = adapter.generate_schema();
assert!(schema.contains("type Mutation {"));
assert!(schema.contains("createUser: String"));
assert!(schema.contains("deleteUser: String"));
assert!(schema.contains("schema {"));
assert!(schema.contains("mutation: Mutation"));
}
#[test]
fn test_schema_generation_with_both() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
adapter.mutation("createUser", "create_user");
let schema = adapter.generate_schema();
assert!(schema.contains("type Query {"));
assert!(schema.contains("type Mutation {"));
assert!(schema.contains("query: Query"));
assert!(schema.contains("mutation: Mutation"));
}
#[tokio::test]
async fn test_handle_query_success() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
let result = adapter.handle("query { user }").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains(r#""data""#));
assert!(response.contains("user"));
assert!(response.contains("get_user"));
}
#[tokio::test]
async fn test_handle_mutation_success() {
let mut adapter = GraphQLAdapter::new();
adapter.mutation("createUser", "create_user_handler");
let result = adapter.handle("mutation { createUser }").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains(r#""data""#));
assert!(response.contains("createUser"));
assert!(response.contains("create_user_handler"));
}
#[tokio::test]
async fn test_handle_operation_not_found() {
let adapter = GraphQLAdapter::new();
let result = adapter.handle("query { user }").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains(r#""errors""#));
assert!(response.contains("Operation not found"));
}
#[tokio::test]
async fn test_handle_invalid_query() {
let adapter = GraphQLAdapter::new();
let result = adapter.handle("invalid query").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains(r#""errors""#));
}
#[tokio::test]
async fn test_handle_shorthand_query() {
let mut adapter = GraphQLAdapter::new();
adapter.query("user", "get_user");
let result = adapter.handle("{ user }").await;
assert!(result.is_ok());
let response = result.unwrap();
assert!(response.contains(r#""data""#));
assert!(response.contains("user"));
}
#[test]
fn test_graphql_operation_new() {
let op = GraphQLOperation::new(OperationType::Query, "user", "get_user");
assert_eq!(op.operation_type, OperationType::Query);
assert_eq!(op.name, "user");
assert_eq!(op.handler, "get_user");
}
}