use std::{fmt::Display, fs, str::FromStr};
use anyhow::Result;
use serde::Serialize;
use crate::output::CommandResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum GraphFormat {
Json,
Dot,
Mermaid,
}
impl FromStr for GraphFormat {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"json" => Ok(GraphFormat::Json),
"dot" => Ok(GraphFormat::Dot),
"mermaid" => Ok(GraphFormat::Mermaid),
other => Err(format!("Unknown format: {other}. Use json, dot, or mermaid")),
}
}
}
impl Display for GraphFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GraphFormat::Json => write!(f, "json"),
GraphFormat::Dot => write!(f, "dot"),
GraphFormat::Mermaid => write!(f, "mermaid"),
}
}
}
#[derive(Debug, Serialize)]
pub struct FederationGraph {
pub subgraphs: Vec<Subgraph>,
pub edges: Vec<Edge>,
}
#[derive(Debug, Serialize)]
pub struct Subgraph {
pub name: String,
pub url: String,
pub entities: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct Edge {
pub from: String,
pub to: String,
pub entity: String,
}
pub fn run(schema_path: &str, format: GraphFormat) -> Result<CommandResult> {
let schema_content = fs::read_to_string(schema_path)?;
let _schema: serde_json::Value = serde_json::from_str(&schema_content)?;
let graph = FederationGraph {
subgraphs: vec![
Subgraph {
name: "users".to_string(),
url: "http://users.service/graphql".to_string(),
entities: vec!["User".to_string()],
},
Subgraph {
name: "posts".to_string(),
url: "http://posts.service/graphql".to_string(),
entities: vec!["Post".to_string()],
},
Subgraph {
name: "comments".to_string(),
url: "http://comments.service/graphql".to_string(),
entities: vec!["Comment".to_string()],
},
],
edges: vec![
Edge {
from: "users".to_string(),
to: "posts".to_string(),
entity: "User".to_string(),
},
Edge {
from: "posts".to_string(),
to: "comments".to_string(),
entity: "Post".to_string(),
},
],
};
let output = match format {
GraphFormat::Json => serde_json::to_value(&graph)?,
GraphFormat::Dot => serde_json::Value::String(to_dot(&graph)),
GraphFormat::Mermaid => serde_json::Value::String(to_mermaid(&graph)),
};
Ok(CommandResult::success("federation/graph", output))
}
pub(crate) fn to_dot(graph: &FederationGraph) -> String {
let mut dot = String::from("digraph federation {\n");
for subgraph in &graph.subgraphs {
let entities = subgraph.entities.join(", ");
dot.push_str(&format!(
" {} [label=\"{}\\n[{}]\"];\n",
subgraph.name, subgraph.name, entities
));
}
for edge in &graph.edges {
dot.push_str(&format!(" {} -> {} [label=\"{}\"];\n", edge.from, edge.to, edge.entity));
}
dot.push_str("}\n");
dot
}
pub(crate) fn to_mermaid(graph: &FederationGraph) -> String {
let mut mermaid = String::from("graph LR\n");
for subgraph in &graph.subgraphs {
let entities = subgraph.entities.join("<br/>");
mermaid.push_str(&format!(
" {}[\"{}\\n[{}\\n]\"]\n",
subgraph.name, subgraph.name, entities
));
}
for edge in &graph.edges {
mermaid.push_str(&format!(" {} -->|{}| {}\n", edge.from, edge.entity, edge.to));
}
mermaid
}