fraiseql_server/routes/api/
federation.rs1use axum::{
8 Json,
9 extract::{Query, State},
10};
11use fraiseql_core::db::traits::DatabaseAdapter;
12use serde::{Deserialize, Serialize};
13
14use crate::routes::{
15 api::types::{ApiError, ApiResponse},
16 graphql::AppState,
17};
18
19#[derive(Debug, Serialize)]
21pub struct SubgraphsResponse {
22 pub subgraphs: Vec<SubgraphInfo>,
24}
25
26#[derive(Debug, Serialize, Clone)]
28pub struct SubgraphInfo {
29 pub name: String,
31 pub url: String,
33 pub entities: Vec<String>,
35 pub healthy: bool,
37}
38
39#[derive(Debug, Serialize)]
41pub struct GraphResponse {
42 pub format: String,
44 pub content: String,
46}
47
48#[derive(Debug, Deserialize)]
50pub struct GraphFormatQuery {
51 #[serde(default = "default_format")]
53 pub format: String,
54}
55
56fn default_format() -> String {
57 "json".to_string()
58}
59
60pub async fn subgraphs_handler<A: DatabaseAdapter>(
65 State(_state): State<AppState<A>>,
66) -> Result<Json<ApiResponse<SubgraphsResponse>>, ApiError> {
67 let response = SubgraphsResponse { subgraphs: vec![] };
74
75 Ok(Json(ApiResponse {
76 status: "success".to_string(),
77 data: response,
78 }))
79}
80
81pub async fn graph_handler<A: DatabaseAdapter>(
93 State(_state): State<AppState<A>>,
94 Query(query): Query<GraphFormatQuery>,
95) -> Result<Json<ApiResponse<GraphResponse>>, ApiError> {
96 let format = match query.format.as_str() {
98 "json" | "dot" | "mermaid" => query.format,
99 _ => return Err(ApiError::validation_error("format must be 'json', 'dot', or 'mermaid'")),
100 };
101
102 let content = generate_federation_graph(&format);
104
105 let response = GraphResponse {
106 format: format.clone(),
107 content,
108 };
109
110 Ok(Json(ApiResponse {
111 status: "success".to_string(),
112 data: response,
113 }))
114}
115
116fn generate_federation_graph(format: &str) -> String {
123 match format {
124 "json" => generate_json_graph(),
125 "dot" => generate_dot_graph(),
126 "mermaid" => generate_mermaid_graph(),
127 _ => "{}".to_string(),
128 }
129}
130
131fn generate_json_graph() -> String {
133 r#"{
134 "subgraphs": [],
135 "edges": []
136}"#
137 .to_string()
138}
139
140fn generate_dot_graph() -> String {
142 r#"digraph federation {
143 rankdir=LR;
144 node [shape=box, style=rounded];
145
146 // Subgraphs would be added here
147 // Example:
148 // users [label="users\n[User, Query]"];
149 // posts [label="posts\n[Post]"];
150 // users -> posts [label="User", style=dashed];
151}"#
152 .to_string()
153}
154
155fn generate_mermaid_graph() -> String {
157 r#"graph LR
158 %% Federation subgraphs
159 %% Example:
160 %% users["users<br/>[User, Query]"]
161 %% posts["posts<br/>[Post]"]
162 %% users -->|User| posts"#
163 .to_string()
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_generate_json_graph() {
172 let json = generate_json_graph();
173 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
174
175 assert!(parsed["subgraphs"].is_array());
176 assert!(parsed["edges"].is_array());
177 }
178
179 #[test]
180 fn test_generate_dot_graph() {
181 let dot = generate_dot_graph();
182
183 assert!(dot.contains("digraph"));
184 assert!(dot.contains("rankdir"));
185 }
186
187 #[test]
188 fn test_generate_mermaid_graph() {
189 let mermaid = generate_mermaid_graph();
190
191 assert!(mermaid.contains("graph LR"));
192 }
193
194 #[test]
195 fn test_default_format() {
196 assert_eq!(default_format(), "json");
197 }
198
199 #[test]
200 fn test_subgraph_info_creation() {
201 let info = SubgraphInfo {
202 name: "test".to_string(),
203 url: "http://test.local".to_string(),
204 entities: vec!["Entity1".to_string()],
205 healthy: true,
206 };
207
208 assert_eq!(info.name, "test");
209 assert!(info.healthy);
210 }
211
212 #[test]
213 fn test_subgraphs_response_creation() {
214 let response = SubgraphsResponse { subgraphs: vec![] };
215
216 assert!(response.subgraphs.is_empty());
217 }
218
219 #[test]
220 fn test_graph_response_creation() {
221 let response = GraphResponse {
222 format: "json".to_string(),
223 content: "{}".to_string(),
224 };
225
226 assert_eq!(response.format, "json");
227 }
228}