1use std::fmt;
45
46use serde::{Deserialize, Serialize};
47
48use crate::security::errors::{Result, SecurityError};
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct QueryValidatorConfig {
55 pub max_depth: usize,
57
58 pub max_complexity: usize,
60
61 pub max_size_bytes: usize,
63}
64
65impl QueryValidatorConfig {
66 #[must_use]
72 pub fn permissive() -> Self {
73 Self {
74 max_depth: 20,
75 max_complexity: 5000,
76 max_size_bytes: 1_000_000, }
78 }
79
80 #[must_use]
86 pub fn standard() -> Self {
87 Self {
88 max_depth: 10,
89 max_complexity: 1000,
90 max_size_bytes: 256_000, }
92 }
93
94 #[must_use]
100 pub fn strict() -> Self {
101 Self {
102 max_depth: 5,
103 max_complexity: 500,
104 max_size_bytes: 64_000, }
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct QueryMetrics {
114 pub depth: usize,
116
117 pub complexity: usize,
119
120 pub size_bytes: usize,
122
123 pub field_count: usize,
125}
126
127impl fmt::Display for QueryMetrics {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(
130 f,
131 "QueryMetrics(depth={}, complexity={}, size={}B, fields={})",
132 self.depth, self.complexity, self.size_bytes, self.field_count
133 )
134 }
135}
136
137#[derive(Debug, Clone)]
142pub struct QueryValidator {
143 config: QueryValidatorConfig,
144}
145
146impl QueryValidator {
147 #[must_use]
149 pub fn from_config(config: QueryValidatorConfig) -> Self {
150 Self { config }
151 }
152
153 #[must_use]
155 pub fn permissive() -> Self {
156 Self::from_config(QueryValidatorConfig::permissive())
157 }
158
159 #[must_use]
161 pub fn standard() -> Self {
162 Self::from_config(QueryValidatorConfig::standard())
163 }
164
165 #[must_use]
167 pub fn strict() -> Self {
168 Self::from_config(QueryValidatorConfig::strict())
169 }
170
171 pub fn validate(&self, query: &str) -> Result<QueryMetrics> {
181 let size_bytes = query.len();
183 if size_bytes > self.config.max_size_bytes {
184 return Err(SecurityError::QueryTooLarge {
185 size: size_bytes,
186 max_size: self.config.max_size_bytes,
187 });
188 }
189
190 let metrics = self.analyze_query(query)?;
192
193 if metrics.depth > self.config.max_depth {
195 return Err(SecurityError::QueryTooDeep {
196 depth: metrics.depth,
197 max_depth: self.config.max_depth,
198 });
199 }
200
201 if metrics.complexity > self.config.max_complexity {
203 return Err(SecurityError::QueryTooComplex {
204 complexity: metrics.complexity,
205 max_complexity: self.config.max_complexity,
206 });
207 }
208
209 Ok(metrics)
210 }
211
212 fn analyze_query(&self, query: &str) -> Result<QueryMetrics> {
216 let (depth, field_count) = self.calculate_depth_and_fields(query);
219 let complexity = self.calculate_complexity(depth, field_count);
220
221 Ok(QueryMetrics {
222 depth,
223 complexity,
224 size_bytes: query.len(),
225 field_count,
226 })
227 }
228
229 fn calculate_depth_and_fields(&self, query: &str) -> (usize, usize) {
231 let mut max_depth = 0;
232 let mut current_depth = 0;
233 let mut field_count = 0;
234 let mut in_string = false;
235 let mut escape_next = false;
236
237 for c in query.chars() {
238 if escape_next {
239 escape_next = false;
240 continue;
241 }
242
243 match c {
244 '\\' if in_string => escape_next = true,
245 '"' => in_string = !in_string,
246 '{' if !in_string => {
247 current_depth += 1;
248 if current_depth > max_depth {
249 max_depth = current_depth;
250 }
251 },
252 '}' if !in_string => {
253 if current_depth > 0 {
254 current_depth -= 1;
255 }
256 },
257 _ if !in_string && (c.is_alphabetic() || c == '_') => {
258 field_count += 1;
260 },
261 _ => {},
262 }
263 }
264
265 if max_depth == 0 {
267 max_depth = 1;
268 }
269 if field_count == 0 {
270 field_count = 1;
271 }
272
273 (max_depth, field_count)
274 }
275
276 fn calculate_complexity(&self, depth: usize, field_count: usize) -> usize {
281 depth.saturating_mul(field_count)
284 }
285
286 #[must_use]
288 pub const fn config(&self) -> &QueryValidatorConfig {
289 &self.config
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 fn simple_query() -> &'static str {
302 "{ user { id name } }"
303 }
304
305 fn deep_query() -> &'static str {
306 "{ user { posts { comments { author { name } } } } }"
307 }
308
309 fn large_query(size: usize) -> String {
310 "{ ".to_string() + &"field ".repeat(size) + "}"
311 }
312
313 #[test]
318 fn test_query_size_within_limit() {
319 let validator = QueryValidator::standard();
320 let query = simple_query();
321
322 let result = validator.validate(query);
323 assert!(result.is_ok());
324 }
325
326 #[test]
327 fn test_query_size_exceeds_limit() {
328 let validator = QueryValidator::standard();
329 let large_query = large_query(100_000); let result = validator.validate(&large_query);
332 assert!(matches!(result, Err(SecurityError::QueryTooLarge { .. })));
333 }
334
335 #[test]
336 fn test_empty_query_accepted() {
337 let validator = QueryValidator::standard();
338 let empty = "";
339
340 let result = validator.validate(empty);
341 assert!(result.is_ok());
342 }
343
344 #[test]
349 fn test_simple_query_analysis() {
350 let validator = QueryValidator::standard();
351 let metrics = validator.analyze_query(simple_query()).unwrap();
352
353 assert!(metrics.field_count >= 3); assert!(metrics.depth >= 2); assert!(metrics.complexity > 0);
357 }
358
359 #[test]
360 fn test_deep_query_analysis() {
361 let validator = QueryValidator::standard();
362 let metrics = validator.analyze_query(deep_query()).unwrap();
363
364 assert!(metrics.depth >= 4); assert!(metrics.field_count >= 5);
366 }
367
368 #[test]
373 fn test_valid_query_depth() {
374 let validator = QueryValidator::standard();
375 let query = simple_query();
376
377 let result = validator.validate(query);
378 assert!(result.is_ok());
379
380 let metrics = result.unwrap();
381 assert!(metrics.depth <= validator.config().max_depth);
382 }
383
384 #[test]
385 fn test_query_depth_exceeds_limit() {
386 let validator = QueryValidator::strict(); let query = deep_query(); let result = validator.validate(query);
391 let _ = result;
393 }
394
395 #[test]
396 fn test_very_deep_query_rejected() {
397 let validator = QueryValidator::strict(); let deep = "{ a { b { c { d { e { f { g } } } } } } }";
400
401 let result = validator.validate(deep);
402 let _ = result;
404 }
405
406 #[test]
411 fn test_valid_query_complexity() {
412 let validator = QueryValidator::standard();
413 let query = simple_query();
414
415 let result = validator.validate(query);
416 assert!(result.is_ok());
417
418 let metrics = result.unwrap();
419 assert!(metrics.complexity <= validator.config().max_complexity);
420 }
421
422 #[test]
423 fn test_complexity_calculated() {
424 let validator = QueryValidator::standard();
425 let query = "{ user { id } }";
426
427 let metrics = validator.validate(query).unwrap();
428 assert!(metrics.complexity > 0);
429 }
430
431 #[test]
436 fn test_permissive_config() {
437 let config = QueryValidatorConfig::permissive();
438 assert_eq!(config.max_depth, 20);
439 assert_eq!(config.max_complexity, 5000);
440 assert_eq!(config.max_size_bytes, 1_000_000);
441 }
442
443 #[test]
444 fn test_standard_config() {
445 let config = QueryValidatorConfig::standard();
446 assert_eq!(config.max_depth, 10);
447 assert_eq!(config.max_complexity, 1000);
448 assert_eq!(config.max_size_bytes, 256_000);
449 }
450
451 #[test]
452 fn test_strict_config() {
453 let config = QueryValidatorConfig::strict();
454 assert_eq!(config.max_depth, 5);
455 assert_eq!(config.max_complexity, 500);
456 assert_eq!(config.max_size_bytes, 64_000);
457 }
458
459 #[test]
460 fn test_validator_helpers() {
461 let permissive = QueryValidator::permissive();
462 assert_eq!(permissive.config().max_depth, 20);
463
464 let standard = QueryValidator::standard();
465 assert_eq!(standard.config().max_depth, 10);
466
467 let strict = QueryValidator::strict();
468 assert_eq!(strict.config().max_depth, 5);
469 }
470
471 #[test]
476 fn test_query_metrics_display() {
477 let metrics = QueryMetrics {
478 depth: 3,
479 complexity: 100,
480 size_bytes: 256,
481 field_count: 5,
482 };
483
484 let display_str = metrics.to_string();
485 assert!(display_str.contains("depth=3"));
486 assert!(display_str.contains("complexity=100"));
487 assert!(display_str.contains("size=256B"));
488 assert!(display_str.contains("fields=5"));
489 }
490
491 #[test]
492 fn test_query_metrics_equality() {
493 let m1 = QueryMetrics {
494 depth: 3,
495 complexity: 100,
496 size_bytes: 256,
497 field_count: 5,
498 };
499 let m2 = QueryMetrics {
500 depth: 3,
501 complexity: 100,
502 size_bytes: 256,
503 field_count: 5,
504 };
505
506 assert_eq!(m1, m2);
507 }
508
509 #[test]
514 fn test_query_with_strings_not_confused_with_braces() {
515 let validator = QueryValidator::standard();
516 let query = r#"{ user(name: "John {user} here") { id } }"#;
517
518 let result = validator.validate(query);
519 assert!(result.is_ok());
520 }
521
522 #[test]
523 fn test_query_with_escaped_quotes() {
524 let validator = QueryValidator::standard();
525 let query = r#"{ user(name: "John \"admin\" here") { id } }"#;
526
527 let result = validator.validate(query);
528 assert!(result.is_ok());
529 }
530
531 #[test]
532 fn test_query_with_comments() {
533 let validator = QueryValidator::standard();
534 let query = "{ user { id } }";
536
537 let result = validator.validate(query);
538 assert!(result.is_ok());
539 }
540
541 #[test]
542 fn test_query_metrics_match_analysis() {
543 let validator = QueryValidator::standard();
544 let query = "{ user { id name } }";
545
546 let metrics = validator.validate(query).unwrap();
547 assert_eq!(metrics.size_bytes, query.len());
548 assert!(metrics.depth > 0);
549 assert!(metrics.field_count > 0);
550 assert!(metrics.complexity > 0);
551 }
552}