Skip to main content

fraiseql_core/design/
mod.rs

1//! Design Quality Analysis Engine
2//!
3//! Provides linting and quality enforcement for GraphQL schema architecture.
4//! Detects anti-patterns and provides actionable recommendations aligned with
5//! FraiseQL's compilation model.
6
7pub mod authorization;
8pub mod cache;
9pub mod compilation;
10pub mod cost;
11pub mod federation;
12pub mod schema_patterns;
13
14use serde::{Deserialize, Serialize};
15
16/// Severity level for design issues
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum IssueSeverity {
20    /// Critical issues that may cause performance problems or bugs
21    #[serde(rename = "critical")]
22    Critical,
23    /// Warning issues that should be addressed
24    #[serde(rename = "warning")]
25    Warning,
26    /// Informational suggestions for improvement
27    #[serde(rename = "info")]
28    Info,
29}
30
31impl IssueSeverity {
32    /// Get numeric weight for scoring (critical=3, warning=2, info=1)
33    pub fn weight(&self) -> u32 {
34        match self {
35            IssueSeverity::Critical => 3,
36            IssueSeverity::Warning => 2,
37            IssueSeverity::Info => 1,
38        }
39    }
40}
41
42/// Federation-related design issue
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct FederationIssue {
45    /// Severity level of the issue
46    pub severity:   IssueSeverity,
47    /// Clear message describing the issue
48    pub message:    String,
49    /// Actionable suggestion for fixing the issue
50    pub suggestion: String,
51    /// Affected entity or component (if applicable)
52    pub entity:     Option<String>,
53}
54
55/// Cost analysis warning
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct CostWarning {
58    /// Severity level of the warning
59    pub severity:              IssueSeverity,
60    /// Clear message describing the issue
61    pub message:               String,
62    /// Actionable suggestion for fixing the issue
63    pub suggestion:            String,
64    /// Worst-case complexity score if applicable
65    pub worst_case_complexity: Option<u32>,
66}
67
68/// Cache coherency issue
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct CacheIssue {
71    /// Severity level of the issue
72    pub severity:   IssueSeverity,
73    /// Clear message describing the issue
74    pub message:    String,
75    /// Actionable suggestion for fixing the issue
76    pub suggestion: String,
77    /// Affected entity or field (if applicable)
78    pub affected:   Option<String>,
79}
80
81/// Authorization boundary issue
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct AuthIssue {
84    /// Severity level of the issue
85    pub severity:       IssueSeverity,
86    /// Clear message describing the issue
87    pub message:        String,
88    /// Actionable suggestion for fixing the issue
89    pub suggestion:     String,
90    /// Affected field or scope (if applicable)
91    pub affected_field: Option<String>,
92}
93
94/// Schema design issue
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct SchemaIssue {
97    /// Severity level of the issue
98    pub severity:      IssueSeverity,
99    /// Clear message describing the issue
100    pub message:       String,
101    /// Actionable suggestion for fixing the issue
102    pub suggestion:    String,
103    /// Affected type or pattern (if applicable)
104    pub affected_type: Option<String>,
105}
106
107/// Complete design quality audit
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct DesignAudit {
110    /// Federation-related issues
111    pub federation_issues: Vec<FederationIssue>,
112    /// Cost analysis warnings
113    pub cost_warnings:     Vec<CostWarning>,
114    /// Cache coherency issues
115    pub cache_issues:      Vec<CacheIssue>,
116    /// Authorization boundary issues
117    pub auth_issues:       Vec<AuthIssue>,
118    /// Schema design issues
119    pub schema_issues:     Vec<SchemaIssue>,
120}
121
122impl DesignAudit {
123    /// Create a new empty audit
124    pub fn new() -> Self {
125        Self {
126            federation_issues: Vec::new(),
127            cost_warnings:     Vec::new(),
128            cache_issues:      Vec::new(),
129            auth_issues:       Vec::new(),
130            schema_issues:     Vec::new(),
131        }
132    }
133
134    /// Analyze a schema from JSON string
135    pub fn from_schema_json(json: &str) -> Result<Self, serde_json::Error> {
136        // Parse the schema JSON
137        let schema: serde_json::Value = serde_json::from_str(json)?;
138
139        let mut audit = Self::new();
140
141        // Run all analysis engines
142        federation::analyze(&schema, &mut audit);
143        cost::analyze(&schema, &mut audit);
144        cache::analyze(&schema, &mut audit);
145        authorization::analyze(&schema, &mut audit);
146        schema_patterns::analyze(&schema, &mut audit);
147        compilation::analyze(&schema, &mut audit);
148
149        Ok(audit)
150    }
151
152    /// Calculate overall design quality score (0-100)
153    pub fn score(&self) -> u8 {
154        // Base score
155        let mut score: f64 = 100.0;
156
157        // Deduct points for each issue based on severity
158        // Critical issues are heavily penalized
159        for issue in &self.federation_issues {
160            let penalty = match issue.severity {
161                IssueSeverity::Critical => 25.0,
162                IssueSeverity::Warning => 15.0,
163                IssueSeverity::Info => 3.0,
164            };
165            score -= penalty;
166        }
167        for warning in &self.cost_warnings {
168            let penalty = match warning.severity {
169                IssueSeverity::Critical => 20.0,
170                IssueSeverity::Warning => 8.0,
171                IssueSeverity::Info => 2.0,
172            };
173            score -= penalty;
174        }
175        for issue in &self.cache_issues {
176            let penalty = match issue.severity {
177                IssueSeverity::Critical => 15.0,
178                IssueSeverity::Warning => 6.0,
179                IssueSeverity::Info => 1.0,
180            };
181            score -= penalty;
182        }
183        for issue in &self.auth_issues {
184            let penalty = match issue.severity {
185                IssueSeverity::Critical => 25.0,
186                IssueSeverity::Warning => 12.0,
187                IssueSeverity::Info => 2.0,
188            };
189            score -= penalty;
190        }
191        for issue in &self.schema_issues {
192            let penalty = match issue.severity {
193                IssueSeverity::Critical => 15.0,
194                IssueSeverity::Warning => 5.0,
195                IssueSeverity::Info => 1.0,
196            };
197            score -= penalty;
198        }
199
200        // Clamp to 0-100
201        let score = score.clamp(0.0, 100.0);
202        score as u8
203    }
204
205    /// Count issues by severity level
206    pub fn severity_count(&self, severity: IssueSeverity) -> usize {
207        let fed_count = self.federation_issues.iter().filter(|i| i.severity == severity).count();
208        let cost_count = self.cost_warnings.iter().filter(|w| w.severity == severity).count();
209        let cache_count = self.cache_issues.iter().filter(|i| i.severity == severity).count();
210        let auth_count = self.auth_issues.iter().filter(|i| i.severity == severity).count();
211        let schema_count = self.schema_issues.iter().filter(|i| i.severity == severity).count();
212
213        fed_count + cost_count + cache_count + auth_count + schema_count
214    }
215
216    /// Get all issues as a flat list
217    pub fn all_issues(&self) -> Vec<String> {
218        let mut issues = Vec::new();
219
220        for issue in &self.federation_issues {
221            issues.push(format!("{:?}: {}", issue.severity, issue.message));
222        }
223        for warning in &self.cost_warnings {
224            issues.push(format!("{:?}: {}", warning.severity, warning.message));
225        }
226        for issue in &self.cache_issues {
227            issues.push(format!("{:?}: {}", issue.severity, issue.message));
228        }
229        for issue in &self.auth_issues {
230            issues.push(format!("{:?}: {}", issue.severity, issue.message));
231        }
232        for issue in &self.schema_issues {
233            issues.push(format!("{:?}: {}", issue.severity, issue.message));
234        }
235
236        issues
237    }
238}
239
240impl Default for DesignAudit {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_issue_severity_weight() {
252        assert_eq!(IssueSeverity::Critical.weight(), 3);
253        assert_eq!(IssueSeverity::Warning.weight(), 2);
254        assert_eq!(IssueSeverity::Info.weight(), 1);
255    }
256
257    #[test]
258    fn test_empty_audit_score() {
259        let audit = DesignAudit::new();
260        assert_eq!(audit.score(), 100);
261    }
262
263    #[test]
264    fn test_severity_count_empty() {
265        let audit = DesignAudit::new();
266        assert_eq!(audit.severity_count(IssueSeverity::Critical), 0);
267        assert_eq!(audit.severity_count(IssueSeverity::Warning), 0);
268        assert_eq!(audit.severity_count(IssueSeverity::Info), 0);
269    }
270}