1use serde::{Deserialize, Serialize};
19use std::fmt;
20use thiserror::Error;
21
22#[derive(Debug, Error)]
24pub enum SchemaError {
25 #[error("Schema building failed: {0}")]
27 BuildingFailed(String),
28
29 #[error("Configuration validation failed: {0}")]
31 ValidationError(String),
32
33 #[error("Query complexity limit exceeded: limit={limit}, actual={actual}")]
35 ComplexityLimitExceeded {
36 limit: usize,
38 actual: usize,
40 },
41
42 #[error("Query depth limit exceeded: limit={limit}, actual={actual}")]
44 DepthLimitExceeded {
45 limit: usize,
47 actual: usize,
49 },
50}
51
52pub type SchemaResult<T> = Result<T, SchemaError>;
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SchemaConfig {
61 pub introspection_enabled: bool,
63 pub complexity_limit: Option<usize>,
65 pub depth_limit: Option<usize>,
67}
68
69impl Default for SchemaConfig {
70 fn default() -> Self {
71 Self {
72 introspection_enabled: true,
73 complexity_limit: None,
74 depth_limit: None,
75 }
76 }
77}
78
79impl SchemaConfig {
80 #[must_use]
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub const fn set_introspection_enabled(&mut self, enabled: bool) -> &mut Self {
88 self.introspection_enabled = enabled;
89 self
90 }
91
92 pub const fn set_complexity_limit(&mut self, limit: usize) -> &mut Self {
94 self.complexity_limit = if limit > 0 { Some(limit) } else { None };
95 self
96 }
97
98 pub const fn set_depth_limit(&mut self, limit: usize) -> &mut Self {
100 self.depth_limit = if limit > 0 { Some(limit) } else { None };
101 self
102 }
103
104 pub const fn validate(&self) -> SchemaResult<()> {
110 Ok(())
113 }
114}
115
116pub struct SchemaBuilder<Query, Mutation, Subscription> {
139 config: SchemaConfig,
140 _query: std::marker::PhantomData<Query>,
141 _mutation: std::marker::PhantomData<Mutation>,
142 _subscription: std::marker::PhantomData<Subscription>,
143}
144
145impl<Query, Mutation, Subscription> Clone for SchemaBuilder<Query, Mutation, Subscription> {
146 fn clone(&self) -> Self {
147 Self {
148 config: self.config.clone(),
149 _query: std::marker::PhantomData,
150 _mutation: std::marker::PhantomData,
151 _subscription: std::marker::PhantomData,
152 }
153 }
154}
155
156impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription> {
157 #[must_use]
175 pub fn new(_query: Query, _mutation: Mutation, _subscription: Subscription) -> Self {
176 Self {
177 config: SchemaConfig::default(),
178 _query: std::marker::PhantomData,
179 _mutation: std::marker::PhantomData,
180 _subscription: std::marker::PhantomData,
181 }
182 }
183
184 #[must_use]
197 pub const fn enable_introspection(mut self, enable: bool) -> Self {
198 self.config.introspection_enabled = enable;
199 self
200 }
201
202 #[must_use]
215 pub const fn complexity_limit(mut self, limit: usize) -> Self {
216 self.config.set_complexity_limit(limit);
217 self
218 }
219
220 #[must_use]
233 pub const fn depth_limit(mut self, limit: usize) -> Self {
234 self.config.set_depth_limit(limit);
235 self
236 }
237
238 #[must_use]
244 pub const fn is_introspection_enabled(&self) -> bool {
245 self.config.introspection_enabled
246 }
247
248 #[must_use]
254 pub const fn get_complexity_limit(&self) -> Option<usize> {
255 self.config.complexity_limit
256 }
257
258 #[must_use]
264 pub const fn get_depth_limit(&self) -> Option<usize> {
265 self.config.depth_limit
266 }
267
268 #[must_use]
274 pub const fn config(&self) -> &SchemaConfig {
275 &self.config
276 }
277
278 #[must_use]
287 pub const fn finish(self) -> SchemaConfig {
288 self.config
289 }
290}
291
292impl<Query, Mutation, Subscription> fmt::Debug for SchemaBuilder<Query, Mutation, Subscription> {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 f.debug_struct("SchemaBuilder").field("config", &self.config).finish()
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct QueryOnlyConfig {
301 pub introspection_enabled: bool,
303 pub complexity_limit: Option<usize>,
305 pub depth_limit: Option<usize>,
307}
308
309impl Default for QueryOnlyConfig {
310 fn default() -> Self {
311 Self {
312 introspection_enabled: true,
313 complexity_limit: None,
314 depth_limit: None,
315 }
316 }
317}
318
319#[must_use]
327pub fn schema_query_only() -> QueryOnlyConfig {
328 QueryOnlyConfig::default()
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct QueryMutationConfig {
334 pub introspection_enabled: bool,
336 pub complexity_limit: Option<usize>,
338 pub depth_limit: Option<usize>,
340}
341
342impl Default for QueryMutationConfig {
343 fn default() -> Self {
344 Self {
345 introspection_enabled: true,
346 complexity_limit: None,
347 depth_limit: None,
348 }
349 }
350}
351
352#[must_use]
360pub fn schema_query_mutation() -> QueryMutationConfig {
361 QueryMutationConfig::default()
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct FullSchemaConfig {
367 pub introspection_enabled: bool,
369 pub complexity_limit: Option<usize>,
371 pub depth_limit: Option<usize>,
373}
374
375impl Default for FullSchemaConfig {
376 fn default() -> Self {
377 Self {
378 introspection_enabled: true,
379 complexity_limit: None,
380 depth_limit: None,
381 }
382 }
383}
384
385#[must_use]
393pub fn schema_full() -> FullSchemaConfig {
394 FullSchemaConfig::default()
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_schema_config_creation() {
403 let config = SchemaConfig::new();
404 assert!(config.introspection_enabled);
405 assert_eq!(config.complexity_limit, None);
406 assert_eq!(config.depth_limit, None);
407 }
408
409 #[test]
410 fn test_schema_config_validation() {
411 let config = SchemaConfig::new();
412 assert!(config.validate().is_ok());
413 }
414
415 #[test]
416 fn test_schema_builder_creation() {
417 struct DummyQuery;
418 struct DummyMutation;
419 struct DummySubscription;
420
421 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription);
422 assert!(builder.is_introspection_enabled());
423 assert_eq!(builder.get_complexity_limit(), None);
424 assert_eq!(builder.get_depth_limit(), None);
425 }
426
427 #[test]
428 fn test_schema_builder_disable_introspection() {
429 struct DummyQuery;
430 struct DummyMutation;
431 struct DummySubscription;
432
433 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription).enable_introspection(false);
434 assert!(!builder.is_introspection_enabled());
435 }
436
437 #[test]
438 fn test_schema_builder_complexity_limit() {
439 struct DummyQuery;
440 struct DummyMutation;
441 struct DummySubscription;
442
443 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription).complexity_limit(5000);
444 assert_eq!(builder.get_complexity_limit(), Some(5000));
445 }
446
447 #[test]
448 fn test_schema_builder_depth_limit() {
449 struct DummyQuery;
450 struct DummyMutation;
451 struct DummySubscription;
452
453 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription).depth_limit(50);
454 assert_eq!(builder.get_depth_limit(), Some(50));
455 }
456
457 #[test]
458 fn test_schema_builder_chaining() {
459 struct DummyQuery;
460 struct DummyMutation;
461 struct DummySubscription;
462
463 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription)
464 .enable_introspection(false)
465 .complexity_limit(3000)
466 .depth_limit(100);
467
468 assert!(!builder.is_introspection_enabled());
469 assert_eq!(builder.get_complexity_limit(), Some(3000));
470 assert_eq!(builder.get_depth_limit(), Some(100));
471 }
472
473 #[test]
474 fn test_schema_builder_zero_limits_are_ignored() {
475 struct DummyQuery;
476 struct DummyMutation;
477 struct DummySubscription;
478
479 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription)
480 .complexity_limit(0)
481 .depth_limit(0);
482
483 assert_eq!(builder.get_complexity_limit(), None);
484 assert_eq!(builder.get_depth_limit(), None);
485 }
486
487 #[test]
488 fn test_schema_builder_debug() {
489 struct DummyQuery;
490 struct DummyMutation;
491 struct DummySubscription;
492
493 let builder = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription)
494 .complexity_limit(5000)
495 .depth_limit(50);
496
497 let debug_str = format!("{builder:?}");
498 assert!(debug_str.contains("SchemaBuilder"));
499 assert!(debug_str.contains("config"));
500 }
501
502 #[test]
503 fn test_schema_builder_finish() {
504 struct DummyQuery;
505 struct DummyMutation;
506 struct DummySubscription;
507
508 let config = SchemaBuilder::new(DummyQuery, DummyMutation, DummySubscription)
509 .complexity_limit(5000)
510 .depth_limit(50)
511 .finish();
512
513 assert!(config.introspection_enabled);
514 assert_eq!(config.complexity_limit, Some(5000));
515 assert_eq!(config.depth_limit, Some(50));
516 }
517
518 #[test]
519 fn test_query_only_config() {
520 let config = schema_query_only();
521 assert!(config.introspection_enabled);
522 assert_eq!(config.complexity_limit, None);
523 }
524
525 #[test]
526 fn test_query_mutation_config() {
527 let config = schema_query_mutation();
528 assert!(config.introspection_enabled);
529 assert_eq!(config.complexity_limit, None);
530 }
531
532 #[test]
533 fn test_full_schema_config() {
534 let config = schema_full();
535 assert!(config.introspection_enabled);
536 assert_eq!(config.complexity_limit, None);
537 }
538
539 #[test]
540 fn test_schema_error_display() {
541 let err = SchemaError::ComplexityLimitExceeded {
542 limit: 5000,
543 actual: 6000,
544 };
545 let msg = err.to_string();
546 assert!(msg.contains("5000"));
547 assert!(msg.contains("6000"));
548 }
549}