Skip to main content

fraiseql_cli/
output_schemas.rs

1//! Output schemas for CLI commands
2//!
3//! Provides JSON Schema definitions for the output of each command,
4//! enabling AI agents to understand and validate command responses.
5
6use serde_json::{Value, json};
7
8use crate::output::OutputSchema;
9
10/// Get the output schema for a specific command
11pub fn get_output_schema(command: &str) -> Option<OutputSchema> {
12    let (success, error) = match command {
13        "compile" => (compile_success_schema(), error_schema()),
14        "validate" => (validate_success_schema(), validation_error_schema()),
15        "lint" => (lint_success_schema(), error_schema()),
16        "analyze" => (analyze_success_schema(), error_schema()),
17        "explain" => (explain_success_schema(), error_schema()),
18        "cost" => (cost_success_schema(), error_schema()),
19        "dependency-graph" => (dependency_graph_success_schema(), error_schema()),
20        _ => return None,
21    };
22
23    Some(OutputSchema {
24        command: command.to_string(),
25        schema_version: "1.0".to_string(),
26        format: "json".to_string(),
27        success,
28        error,
29    })
30}
31
32/// List all commands that have output schemas
33pub fn list_schema_commands() -> Vec<&'static str> {
34    vec![
35        "compile",
36        "validate",
37        "lint",
38        "analyze",
39        "explain",
40        "cost",
41        "dependency-graph",
42    ]
43}
44
45fn compile_success_schema() -> Value {
46    json!({
47        "type": "object",
48        "required": ["status", "command", "data"],
49        "properties": {
50            "status": {
51                "type": "string",
52                "const": "success"
53            },
54            "command": {
55                "type": "string",
56                "const": "compile"
57            },
58            "data": {
59                "type": "object",
60                "properties": {
61                    "output_file": {
62                        "type": "string",
63                        "description": "Path to the generated schema.compiled.json"
64                    },
65                    "types_count": {
66                        "type": "integer",
67                        "description": "Number of types compiled"
68                    },
69                    "queries_count": {
70                        "type": "integer",
71                        "description": "Number of queries compiled"
72                    },
73                    "mutations_count": {
74                        "type": "integer",
75                        "description": "Number of mutations compiled"
76                    }
77                }
78            },
79            "warnings": {
80                "type": "array",
81                "items": { "type": "string" }
82            }
83        }
84    })
85}
86
87fn validate_success_schema() -> Value {
88    json!({
89        "type": "object",
90        "required": ["status", "command", "data"],
91        "properties": {
92            "status": {
93                "type": "string",
94                "enum": ["success", "validation-failed"]
95            },
96            "command": {
97                "type": "string",
98                "const": "validate"
99            },
100            "data": {
101                "type": "object",
102                "properties": {
103                    "types_validated": {
104                        "type": "integer"
105                    },
106                    "cycles_detected": {
107                        "type": "array",
108                        "items": {
109                            "type": "array",
110                            "items": { "type": "string" }
111                        }
112                    },
113                    "unused_types": {
114                        "type": "array",
115                        "items": { "type": "string" }
116                    }
117                }
118            },
119            "errors": {
120                "type": "array",
121                "items": { "type": "string" },
122                "description": "Validation errors (when status is validation-failed)"
123            },
124            "warnings": {
125                "type": "array",
126                "items": { "type": "string" }
127            }
128        }
129    })
130}
131
132fn validation_error_schema() -> Value {
133    json!({
134        "type": "object",
135        "required": ["status", "command", "errors"],
136        "properties": {
137            "status": {
138                "type": "string",
139                "const": "validation-failed"
140            },
141            "command": {
142                "type": "string"
143            },
144            "errors": {
145                "type": "array",
146                "items": { "type": "string" },
147                "minItems": 1
148            }
149        }
150    })
151}
152
153fn lint_success_schema() -> Value {
154    json!({
155        "type": "object",
156        "required": ["status", "command", "data"],
157        "properties": {
158            "status": {
159                "type": "string",
160                "const": "success"
161            },
162            "command": {
163                "type": "string",
164                "const": "lint"
165            },
166            "data": {
167                "type": "object",
168                "properties": {
169                    "audits": {
170                        "type": "object",
171                        "additionalProperties": {
172                            "type": "object",
173                            "properties": {
174                                "issues": {
175                                    "type": "array",
176                                    "items": {
177                                        "type": "object",
178                                        "properties": {
179                                            "severity": { "type": "string", "enum": ["critical", "warning", "info"] },
180                                            "message": { "type": "string" },
181                                            "location": { "type": "string" }
182                                        }
183                                    }
184                                }
185                            }
186                        }
187                    },
188                    "summary": {
189                        "type": "object",
190                        "properties": {
191                            "critical": { "type": "integer" },
192                            "warning": { "type": "integer" },
193                            "info": { "type": "integer" }
194                        }
195                    }
196                }
197            }
198        }
199    })
200}
201
202fn analyze_success_schema() -> Value {
203    json!({
204        "type": "object",
205        "required": ["status", "command", "data"],
206        "properties": {
207            "status": {
208                "type": "string",
209                "const": "success"
210            },
211            "command": {
212                "type": "string",
213                "const": "analyze"
214            },
215            "data": {
216                "type": "object",
217                "properties": {
218                    "recommendations": {
219                        "type": "array",
220                        "items": {
221                            "type": "object",
222                            "properties": {
223                                "category": {
224                                    "type": "string",
225                                    "enum": ["performance", "security", "federation", "complexity", "caching", "indexing"]
226                                },
227                                "severity": { "type": "string" },
228                                "message": { "type": "string" },
229                                "suggestion": { "type": "string" }
230                            }
231                        }
232                    }
233                }
234            }
235        }
236    })
237}
238
239fn explain_success_schema() -> Value {
240    json!({
241        "type": "object",
242        "required": ["status", "command", "data"],
243        "properties": {
244            "status": {
245                "type": "string",
246                "const": "success"
247            },
248            "command": {
249                "type": "string",
250                "const": "explain"
251            },
252            "data": {
253                "type": "object",
254                "properties": {
255                    "query": { "type": "string" },
256                    "execution_plan": {
257                        "type": "object",
258                        "properties": {
259                            "steps": {
260                                "type": "array",
261                                "items": { "type": "string" }
262                            }
263                        }
264                    },
265                    "sql": { "type": "string" },
266                    "complexity": {
267                        "type": "object",
268                        "properties": {
269                            "depth": { "type": "integer" },
270                            "field_count": { "type": "integer" },
271                            "score": { "type": "integer" }
272                        }
273                    }
274                }
275            }
276        }
277    })
278}
279
280fn cost_success_schema() -> Value {
281    json!({
282        "type": "object",
283        "required": ["status", "command", "data"],
284        "properties": {
285            "status": {
286                "type": "string",
287                "const": "success"
288            },
289            "command": {
290                "type": "string",
291                "const": "cost"
292            },
293            "data": {
294                "type": "object",
295                "required": ["depth", "field_count", "score"],
296                "properties": {
297                    "depth": {
298                        "type": "integer",
299                        "description": "Maximum nesting depth of the query"
300                    },
301                    "field_count": {
302                        "type": "integer",
303                        "description": "Total number of fields requested"
304                    },
305                    "score": {
306                        "type": "integer",
307                        "description": "Calculated complexity score"
308                    }
309                }
310            }
311        }
312    })
313}
314
315fn dependency_graph_success_schema() -> Value {
316    json!({
317        "type": "object",
318        "required": ["status", "command", "data"],
319        "properties": {
320            "status": {
321                "type": "string",
322                "const": "success"
323            },
324            "command": {
325                "type": "string",
326                "const": "dependency-graph"
327            },
328            "data": {
329                "type": "object",
330                "properties": {
331                    "format": {
332                        "type": "string",
333                        "enum": ["json", "dot", "mermaid", "d2", "console"]
334                    },
335                    "graph": {
336                        "type": "object",
337                        "description": "Graph data (format depends on output format)",
338                        "properties": {
339                            "nodes": {
340                                "type": "array",
341                                "items": {
342                                    "type": "object",
343                                    "properties": {
344                                        "name": { "type": "string" },
345                                        "type": { "type": "string" }
346                                    }
347                                }
348                            },
349                            "edges": {
350                                "type": "array",
351                                "items": {
352                                    "type": "object",
353                                    "properties": {
354                                        "from": { "type": "string" },
355                                        "to": { "type": "string" },
356                                        "relationship": { "type": "string" }
357                                    }
358                                }
359                            }
360                        }
361                    },
362                    "cycles": {
363                        "type": "array",
364                        "items": {
365                            "type": "array",
366                            "items": { "type": "string" }
367                        }
368                    },
369                    "unused_types": {
370                        "type": "array",
371                        "items": { "type": "string" }
372                    }
373                }
374            }
375        }
376    })
377}
378
379fn error_schema() -> Value {
380    json!({
381        "type": "object",
382        "required": ["status", "command", "message", "code"],
383        "properties": {
384            "status": {
385                "type": "string",
386                "const": "error"
387            },
388            "command": {
389                "type": "string"
390            },
391            "message": {
392                "type": "string",
393                "description": "Human-readable error message"
394            },
395            "code": {
396                "type": "string",
397                "description": "Machine-readable error code"
398            }
399        }
400    })
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[test]
408    fn test_get_output_schema_compile() {
409        let schema = get_output_schema("compile");
410        assert!(schema.is_some());
411        let schema = schema.unwrap();
412        assert_eq!(schema.command, "compile");
413        assert_eq!(schema.format, "json");
414    }
415
416    #[test]
417    fn test_get_output_schema_unknown() {
418        let schema = get_output_schema("unknown-command");
419        assert!(schema.is_none());
420    }
421
422    #[test]
423    fn test_list_schema_commands() {
424        let commands = list_schema_commands();
425        assert!(commands.contains(&"compile"));
426        assert!(commands.contains(&"validate"));
427        assert!(commands.contains(&"lint"));
428    }
429
430    #[test]
431    fn test_success_schema_structure() {
432        let schema = get_output_schema("cost").unwrap();
433        let success = &schema.success;
434
435        assert_eq!(success["type"], "object");
436        assert!(success["required"].is_array());
437        assert!(success["properties"].is_object());
438    }
439
440    #[test]
441    fn test_error_schema_structure() {
442        let schema = get_output_schema("compile").unwrap();
443        let error = &schema.error;
444
445        assert_eq!(error["type"], "object");
446        assert!(error["properties"]["message"].is_object());
447        assert!(error["properties"]["code"].is_object());
448    }
449}