1pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum IssueSeverity {
20 #[serde(rename = "critical")]
22 Critical,
23 #[serde(rename = "warning")]
25 Warning,
26 #[serde(rename = "info")]
28 Info,
29}
30
31impl IssueSeverity {
32 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#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct FederationIssue {
45 pub severity: IssueSeverity,
47 pub message: String,
49 pub suggestion: String,
51 pub entity: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct CostWarning {
58 pub severity: IssueSeverity,
60 pub message: String,
62 pub suggestion: String,
64 pub worst_case_complexity: Option<u32>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct CacheIssue {
71 pub severity: IssueSeverity,
73 pub message: String,
75 pub suggestion: String,
77 pub affected: Option<String>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct AuthIssue {
84 pub severity: IssueSeverity,
86 pub message: String,
88 pub suggestion: String,
90 pub affected_field: Option<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct SchemaIssue {
97 pub severity: IssueSeverity,
99 pub message: String,
101 pub suggestion: String,
103 pub affected_type: Option<String>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct DesignAudit {
110 pub federation_issues: Vec<FederationIssue>,
112 pub cost_warnings: Vec<CostWarning>,
114 pub cache_issues: Vec<CacheIssue>,
116 pub auth_issues: Vec<AuthIssue>,
118 pub schema_issues: Vec<SchemaIssue>,
120}
121
122impl DesignAudit {
123 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 pub fn from_schema_json(json: &str) -> Result<Self, serde_json::Error> {
136 let schema: serde_json::Value = serde_json::from_str(json)?;
138
139 let mut audit = Self::new();
140
141 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 pub fn score(&self) -> u8 {
154 let mut score: f64 = 100.0;
156
157 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 let score = score.clamp(0.0, 100.0);
202 score as u8
203 }
204
205 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 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}