fraiseql_cli/commands/
cost.rs1use anyhow::Result;
6use fraiseql_core::graphql::{complexity::RequestValidator, parse_query};
7use serde::Serialize;
8
9use crate::output::CommandResult;
10
11#[derive(Debug, Serialize)]
13pub struct CostResponse {
14 pub query: String,
16 pub complexity_score: usize,
18 pub estimated_cost: usize,
20 pub depth: usize,
22 pub alias_count: usize,
24}
25
26pub fn run(query: &str) -> Result<CommandResult> {
33 let _parsed = parse_query(query)?;
35
36 let validator = RequestValidator::default();
38 let metrics = validator.analyze(query)?;
39
40 let response = CostResponse {
41 query: query.to_string(),
42 complexity_score: metrics.complexity,
43 estimated_cost: metrics.depth * 25, depth: metrics.depth,
45 alias_count: metrics.alias_count,
46 };
47
48 Ok(CommandResult::success("cost", serde_json::to_value(&response)?))
49}
50
51#[allow(clippy::unwrap_used)] #[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn test_cost_simple_query() {
58 let query = "query { users { id } }";
59 let result = run(query);
60
61 let cmd_result = result.unwrap_or_else(|e| panic!("expected Ok for simple query: {e}"));
62 assert_eq!(cmd_result.status, "success");
63 }
64
65 #[test]
66 fn test_cost_invalid_query_fails() {
67 let query = "query { invalid {";
68 let result = run(query);
69
70 assert!(result.is_err(), "expected Err for invalid query, got: {result:?}");
71 }
72
73 #[test]
74 fn test_cost_provides_score() {
75 let query = "query { users { id name } }";
76 let result = run(query);
77
78 let cmd_result = result.unwrap_or_else(|e| panic!("expected Ok for score query: {e}"));
79 if let Some(data) = cmd_result.data {
80 assert!(data["complexity_score"].is_number());
81 }
82 }
83
84 #[test]
85 fn test_cost_more_fields_higher_score() {
86 let few_fields = run("query { users { id } }").unwrap();
87 let many_fields = run("query { users { id name email phone address } }").unwrap();
88
89 let few_score = few_fields
90 .data
91 .as_ref()
92 .and_then(|d| d["complexity_score"].as_u64())
93 .unwrap_or(0);
94 let many_score = many_fields
95 .data
96 .as_ref()
97 .and_then(|d| d["complexity_score"].as_u64())
98 .unwrap_or(0);
99
100 assert!(many_score >= few_score);
101 }
102
103 #[test]
104 fn test_cost_nested_has_higher_score() {
105 let shallow = run("query { users { id } }").unwrap();
106 let deep = run("query { users { posts { comments { author } } } }").unwrap();
107
108 let shallow_score =
109 shallow.data.as_ref().and_then(|d| d["complexity_score"].as_u64()).unwrap_or(0);
110 let deep_score =
111 deep.data.as_ref().and_then(|d| d["complexity_score"].as_u64()).unwrap_or(0);
112
113 assert!(deep_score > shallow_score);
114 }
115}