1use anyhow::{anyhow, Result};
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::sync::Arc;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
39pub enum DirectiveLocation {
40 Object,
42 FieldDefinition,
44 Interface,
46 Union,
48 Enum,
50 EnumValue,
52 InputObject,
54 InputFieldDefinition,
56 Scalar,
58 ArgumentDefinition,
60 Query,
62 Mutation,
64 Subscription,
66 FragmentDefinition,
68 FragmentSpread,
70 InlineFragment,
72 VariableDefinition,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct DirectiveArgument {
79 pub name: String,
81 pub value: DirectiveValue,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(untagged)]
88pub enum DirectiveValue {
89 String(String),
90 Int(i64),
91 Float(f64),
92 Boolean(bool),
93 List(Vec<DirectiveValue>),
94 Object(HashMap<String, DirectiveValue>),
95 Null,
96}
97
98impl DirectiveValue {
99 pub fn as_string(&self) -> Option<&str> {
100 match self {
101 DirectiveValue::String(s) => Some(s),
102 _ => None,
103 }
104 }
105
106 pub fn as_int(&self) -> Option<i64> {
107 match self {
108 DirectiveValue::Int(i) => Some(*i),
109 _ => None,
110 }
111 }
112
113 pub fn as_float(&self) -> Option<f64> {
114 match self {
115 DirectiveValue::Float(f) => Some(*f),
116 _ => None,
117 }
118 }
119
120 pub fn as_bool(&self) -> Option<bool> {
121 match self {
122 DirectiveValue::Boolean(b) => Some(*b),
123 _ => None,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct DirectiveDefinition {
131 pub name: String,
133 pub description: Option<String>,
135 pub locations: Vec<DirectiveLocation>,
137 pub repeatable: bool,
139 pub arguments: Vec<DirectiveArgumentDefinition>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct DirectiveArgumentDefinition {
146 pub name: String,
148 pub arg_type: String,
150 pub required: bool,
152 pub default_value: Option<DirectiveValue>,
154 pub description: Option<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AppliedDirective {
161 pub name: String,
163 pub arguments: Vec<DirectiveArgument>,
165 pub location: DirectiveLocation,
167}
168
169impl AppliedDirective {
170 pub fn new(name: String, location: DirectiveLocation) -> Self {
171 Self {
172 name,
173 arguments: Vec::new(),
174 location,
175 }
176 }
177
178 pub fn with_argument(mut self, name: String, value: DirectiveValue) -> Self {
179 self.arguments.push(DirectiveArgument { name, value });
180 self
181 }
182
183 pub fn get_argument(&self, name: &str) -> Option<&DirectiveValue> {
184 self.arguments
185 .iter()
186 .find(|arg| arg.name == name)
187 .map(|arg| &arg.value)
188 }
189}
190
191#[derive(Debug, Clone)]
193pub struct DirectiveContext {
194 pub field_name: Option<String>,
196 pub type_name: Option<String>,
198 pub user_context: Option<HashMap<String, String>>,
200 pub field_value: Option<DirectiveValue>,
202}
203
204impl DirectiveContext {
205 pub fn new() -> Self {
206 Self {
207 field_name: None,
208 type_name: None,
209 user_context: None,
210 field_value: None,
211 }
212 }
213
214 pub fn with_field(mut self, field_name: String) -> Self {
215 self.field_name = Some(field_name);
216 self
217 }
218
219 pub fn with_type(mut self, type_name: String) -> Self {
220 self.type_name = Some(type_name);
221 self
222 }
223
224 pub fn with_user_context(mut self, user_context: HashMap<String, String>) -> Self {
225 self.user_context = Some(user_context);
226 self
227 }
228
229 pub fn with_value(mut self, value: DirectiveValue) -> Self {
230 self.field_value = Some(value);
231 self
232 }
233}
234
235impl Default for DirectiveContext {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241pub trait DirectiveHandler: Send + Sync {
243 fn execute(
245 &self,
246 directive: &AppliedDirective,
247 context: &DirectiveContext,
248 ) -> Result<DirectiveValue>;
249
250 fn validate(&self, directive: &AppliedDirective) -> Result<()> {
252 let _ = directive;
253 Ok(())
254 }
255}
256
257pub struct AuthDirectiveHandler;
259
260impl DirectiveHandler for AuthDirectiveHandler {
261 fn execute(
262 &self,
263 directive: &AppliedDirective,
264 context: &DirectiveContext,
265 ) -> Result<DirectiveValue> {
266 if context.user_context.is_none() {
268 return Err(anyhow!("Authentication required"));
269 }
270
271 if let Some(requires) = directive.get_argument("requires") {
273 if let Some(required_role) = requires.as_string() {
274 let user_context = context
275 .user_context
276 .as_ref()
277 .expect("user_context should be set when authentication is present");
278 let user_role = user_context.get("role");
279
280 if user_role.map(|r| r.as_str()) != Some(required_role) {
281 return Err(anyhow!(
282 "Insufficient permissions. Required role: {}",
283 required_role
284 ));
285 }
286 }
287 }
288
289 Ok(context.field_value.clone().unwrap_or(DirectiveValue::Null))
291 }
292}
293
294pub struct HasRoleDirectiveHandler;
296
297impl DirectiveHandler for HasRoleDirectiveHandler {
298 fn execute(
299 &self,
300 directive: &AppliedDirective,
301 context: &DirectiveContext,
302 ) -> Result<DirectiveValue> {
303 let required_role = directive
304 .get_argument("role")
305 .and_then(|v| v.as_string())
306 .ok_or_else(|| anyhow!("@hasRole directive requires 'role' argument"))?;
307
308 let user_context = context
309 .user_context
310 .as_ref()
311 .ok_or_else(|| anyhow!("User context not available"))?;
312
313 let user_roles = user_context
314 .get("roles")
315 .map(|r| r.split(',').collect::<Vec<_>>())
316 .unwrap_or_default();
317
318 if !user_roles.contains(&required_role) {
319 return Err(anyhow!(
320 "User does not have required role: {}",
321 required_role
322 ));
323 }
324
325 Ok(context.field_value.clone().unwrap_or(DirectiveValue::Null))
326 }
327}
328
329pub struct UppercaseDirectiveHandler;
331
332impl DirectiveHandler for UppercaseDirectiveHandler {
333 fn execute(
334 &self,
335 _directive: &AppliedDirective,
336 context: &DirectiveContext,
337 ) -> Result<DirectiveValue> {
338 if let Some(DirectiveValue::String(s)) = &context.field_value {
339 Ok(DirectiveValue::String(s.to_uppercase()))
340 } else {
341 Ok(context.field_value.clone().unwrap_or(DirectiveValue::Null))
342 }
343 }
344}
345
346pub struct LowercaseDirectiveHandler;
348
349impl DirectiveHandler for LowercaseDirectiveHandler {
350 fn execute(
351 &self,
352 _directive: &AppliedDirective,
353 context: &DirectiveContext,
354 ) -> Result<DirectiveValue> {
355 if let Some(DirectiveValue::String(s)) = &context.field_value {
356 Ok(DirectiveValue::String(s.to_lowercase()))
357 } else {
358 Ok(context.field_value.clone().unwrap_or(DirectiveValue::Null))
359 }
360 }
361}
362
363pub struct ConstraintDirectiveHandler;
365
366impl DirectiveHandler for ConstraintDirectiveHandler {
367 fn execute(
368 &self,
369 directive: &AppliedDirective,
370 context: &DirectiveContext,
371 ) -> Result<DirectiveValue> {
372 let value = context
373 .field_value
374 .as_ref()
375 .ok_or_else(|| anyhow!("No value to validate"))?;
376
377 if let Some(val) = value.as_int() {
379 if let Some(min) = directive.get_argument("min").and_then(|v| v.as_int()) {
380 if val < min {
381 return Err(anyhow!("Value {} is less than minimum {}", val, min));
382 }
383 }
384 if let Some(max) = directive.get_argument("max").and_then(|v| v.as_int()) {
385 if val > max {
386 return Err(anyhow!("Value {} is greater than maximum {}", val, max));
387 }
388 }
389 }
390
391 if let Some(s) = value.as_string() {
393 if let Some(min_len) = directive.get_argument("minLength").and_then(|v| v.as_int()) {
394 if (s.len() as i64) < min_len {
395 return Err(anyhow!(
396 "String length {} is less than minimum {}",
397 s.len(),
398 min_len
399 ));
400 }
401 }
402 if let Some(max_len) = directive.get_argument("maxLength").and_then(|v| v.as_int()) {
403 if (s.len() as i64) > max_len {
404 return Err(anyhow!(
405 "String length {} is greater than maximum {}",
406 s.len(),
407 max_len
408 ));
409 }
410 }
411 }
412
413 Ok(value.clone())
414 }
415}
416
417pub struct CacheControlDirectiveHandler {
419 cache_hints: Arc<tokio::sync::RwLock<HashMap<String, CacheHint>>>,
420}
421
422#[derive(Debug, Clone)]
423pub struct CacheHint {
424 pub max_age: u32,
425 pub scope: CacheScope,
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
429pub enum CacheScope {
430 Public,
431 Private,
432}
433
434impl CacheControlDirectiveHandler {
435 pub fn new() -> Self {
436 Self {
437 cache_hints: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
438 }
439 }
440
441 pub async fn get_cache_hint(&self, key: &str) -> Option<CacheHint> {
442 let hints = self.cache_hints.read().await;
443 hints.get(key).cloned()
444 }
445}
446
447impl DirectiveHandler for CacheControlDirectiveHandler {
448 fn execute(
449 &self,
450 directive: &AppliedDirective,
451 context: &DirectiveContext,
452 ) -> Result<DirectiveValue> {
453 let max_age = directive
454 .get_argument("maxAge")
455 .and_then(|v| v.as_int())
456 .unwrap_or(0) as u32;
457
458 let scope = directive
459 .get_argument("scope")
460 .and_then(|v| v.as_string())
461 .map(|s| {
462 if s.eq_ignore_ascii_case("PRIVATE") {
463 CacheScope::Private
464 } else {
465 CacheScope::Public
466 }
467 })
468 .unwrap_or(CacheScope::Public);
469
470 let key = format!(
471 "{}:{}",
472 context.type_name.as_ref().unwrap_or(&"unknown".to_string()),
473 context
474 .field_name
475 .as_ref()
476 .unwrap_or(&"unknown".to_string())
477 );
478
479 let cache_hints = self.cache_hints.clone();
481 let key_clone = key.clone();
482 tokio::spawn(async move {
483 let mut hints = cache_hints.write().await;
484 hints.insert(key_clone, CacheHint { max_age, scope });
485 });
486
487 Ok(context.field_value.clone().unwrap_or(DirectiveValue::Null))
488 }
489}
490
491impl Default for CacheControlDirectiveHandler {
492 fn default() -> Self {
493 Self::new()
494 }
495}
496
497pub struct DirectiveRegistry {
499 definitions: Arc<tokio::sync::RwLock<HashMap<String, DirectiveDefinition>>>,
501 handlers: Arc<tokio::sync::RwLock<HashMap<String, Arc<dyn DirectiveHandler>>>>,
503}
504
505impl DirectiveRegistry {
506 pub fn new_empty() -> Self {
508 Self {
509 definitions: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
510 handlers: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
511 }
512 }
513
514 pub async fn new() -> Self {
516 let registry = Self::new_empty();
517 let _ = registry.register_builtin_directives().await;
518 registry
519 }
520
521 async fn register_builtin_directives(&self) -> Result<()> {
522 self.register_directive(
524 DirectiveDefinition {
525 name: "auth".to_string(),
526 description: Some("Requires authentication and optional role".to_string()),
527 locations: vec![
528 DirectiveLocation::Object,
529 DirectiveLocation::FieldDefinition,
530 ],
531 repeatable: false,
532 arguments: vec![DirectiveArgumentDefinition {
533 name: "requires".to_string(),
534 arg_type: "String".to_string(),
535 required: false,
536 default_value: None,
537 description: Some("Required role".to_string()),
538 }],
539 },
540 Arc::new(AuthDirectiveHandler),
541 )
542 .await?;
543
544 self.register_directive(
546 DirectiveDefinition {
547 name: "hasRole".to_string(),
548 description: Some("Requires specific role".to_string()),
549 locations: vec![DirectiveLocation::FieldDefinition],
550 repeatable: false,
551 arguments: vec![DirectiveArgumentDefinition {
552 name: "role".to_string(),
553 arg_type: "String!".to_string(),
554 required: true,
555 default_value: None,
556 description: Some("Required role name".to_string()),
557 }],
558 },
559 Arc::new(HasRoleDirectiveHandler),
560 )
561 .await?;
562
563 self.register_directive(
565 DirectiveDefinition {
566 name: "uppercase".to_string(),
567 description: Some("Converts string to uppercase".to_string()),
568 locations: vec![DirectiveLocation::FieldDefinition],
569 repeatable: false,
570 arguments: vec![],
571 },
572 Arc::new(UppercaseDirectiveHandler),
573 )
574 .await?;
575
576 self.register_directive(
578 DirectiveDefinition {
579 name: "lowercase".to_string(),
580 description: Some("Converts string to lowercase".to_string()),
581 locations: vec![DirectiveLocation::FieldDefinition],
582 repeatable: false,
583 arguments: vec![],
584 },
585 Arc::new(LowercaseDirectiveHandler),
586 )
587 .await?;
588
589 self.register_directive(
591 DirectiveDefinition {
592 name: "constraint".to_string(),
593 description: Some("Validates field value constraints".to_string()),
594 locations: vec![
595 DirectiveLocation::FieldDefinition,
596 DirectiveLocation::ArgumentDefinition,
597 ],
598 repeatable: false,
599 arguments: vec![
600 DirectiveArgumentDefinition {
601 name: "min".to_string(),
602 arg_type: "Int".to_string(),
603 required: false,
604 default_value: None,
605 description: Some("Minimum value".to_string()),
606 },
607 DirectiveArgumentDefinition {
608 name: "max".to_string(),
609 arg_type: "Int".to_string(),
610 required: false,
611 default_value: None,
612 description: Some("Maximum value".to_string()),
613 },
614 DirectiveArgumentDefinition {
615 name: "minLength".to_string(),
616 arg_type: "Int".to_string(),
617 required: false,
618 default_value: None,
619 description: Some("Minimum string length".to_string()),
620 },
621 DirectiveArgumentDefinition {
622 name: "maxLength".to_string(),
623 arg_type: "Int".to_string(),
624 required: false,
625 default_value: None,
626 description: Some("Maximum string length".to_string()),
627 },
628 ],
629 },
630 Arc::new(ConstraintDirectiveHandler),
631 )
632 .await?;
633
634 self.register_directive(
636 DirectiveDefinition {
637 name: "cacheControl".to_string(),
638 description: Some("Cache control hints".to_string()),
639 locations: vec![
640 DirectiveLocation::Object,
641 DirectiveLocation::FieldDefinition,
642 ],
643 repeatable: false,
644 arguments: vec![
645 DirectiveArgumentDefinition {
646 name: "maxAge".to_string(),
647 arg_type: "Int".to_string(),
648 required: false,
649 default_value: Some(DirectiveValue::Int(0)),
650 description: Some("Max age in seconds".to_string()),
651 },
652 DirectiveArgumentDefinition {
653 name: "scope".to_string(),
654 arg_type: "String".to_string(),
655 required: false,
656 default_value: Some(DirectiveValue::String("PUBLIC".to_string())),
657 description: Some("Cache scope (PUBLIC or PRIVATE)".to_string()),
658 },
659 ],
660 },
661 Arc::new(CacheControlDirectiveHandler::new()),
662 )
663 .await?;
664
665 Ok(())
666 }
667
668 pub async fn register_directive(
670 &self,
671 definition: DirectiveDefinition,
672 handler: Arc<dyn DirectiveHandler>,
673 ) -> Result<()> {
674 let mut definitions = self.definitions.write().await;
675 let mut handlers = self.handlers.write().await;
676
677 definitions.insert(definition.name.clone(), definition.clone());
678 handlers.insert(definition.name, handler);
679
680 Ok(())
681 }
682
683 pub async fn get_definition(&self, name: &str) -> Option<DirectiveDefinition> {
685 let definitions = self.definitions.read().await;
686 definitions.get(name).cloned()
687 }
688
689 pub async fn execute_directive(
691 &self,
692 directive: &AppliedDirective,
693 context: &DirectiveContext,
694 ) -> Result<DirectiveValue> {
695 let handlers = self.handlers.read().await;
696
697 if let Some(handler) = handlers.get(&directive.name) {
698 handler.execute(directive, context)
699 } else {
700 Err(anyhow!("Unknown directive: @{}", directive.name))
701 }
702 }
703
704 pub async fn validate_directive(&self, directive: &AppliedDirective) -> Result<()> {
706 let handlers = self.handlers.read().await;
707
708 if let Some(handler) = handlers.get(&directive.name) {
709 handler.validate(directive)
710 } else {
711 Err(anyhow!("Unknown directive: @{}", directive.name))
712 }
713 }
714
715 pub async fn get_directive_names(&self) -> Vec<String> {
717 let definitions = self.definitions.read().await;
718 definitions.keys().cloned().collect()
719 }
720}
721
722#[cfg(test)]
726mod tests {
727 use super::*;
728
729 #[test]
730 fn test_directive_value_types() {
731 let string_val = DirectiveValue::String("test".to_string());
732 assert_eq!(string_val.as_string(), Some("test"));
733
734 let int_val = DirectiveValue::Int(42);
735 assert_eq!(int_val.as_int(), Some(42));
736
737 let bool_val = DirectiveValue::Boolean(true);
738 assert_eq!(bool_val.as_bool(), Some(true));
739 }
740
741 #[test]
742 fn test_applied_directive_creation() {
743 let directive =
744 AppliedDirective::new("auth".to_string(), DirectiveLocation::FieldDefinition)
745 .with_argument(
746 "requires".to_string(),
747 DirectiveValue::String("ADMIN".to_string()),
748 );
749
750 assert_eq!(directive.name, "auth");
751 assert_eq!(directive.arguments.len(), 1);
752 assert_eq!(
753 directive
754 .get_argument("requires")
755 .and_then(|v| v.as_string()),
756 Some("ADMIN")
757 );
758 }
759
760 #[test]
761 fn test_directive_context() {
762 let context = DirectiveContext::new()
763 .with_field("email".to_string())
764 .with_type("User".to_string());
765
766 assert_eq!(context.field_name, Some("email".to_string()));
767 assert_eq!(context.type_name, Some("User".to_string()));
768 }
769
770 #[tokio::test]
771 async fn test_directive_registry_creation() {
772 let registry = DirectiveRegistry::new().await;
773 let names = registry.get_directive_names().await;
774
775 assert!(names.contains(&"auth".to_string()));
777 assert!(names.contains(&"uppercase".to_string()));
778 assert!(names.contains(&"constraint".to_string()));
779 }
780
781 #[tokio::test]
782 async fn test_uppercase_directive() {
783 let handler = UppercaseDirectiveHandler;
784 let directive =
785 AppliedDirective::new("uppercase".to_string(), DirectiveLocation::FieldDefinition);
786 let context =
787 DirectiveContext::new().with_value(DirectiveValue::String("hello".to_string()));
788
789 let result = handler
790 .execute(&directive, &context)
791 .expect("should succeed");
792 assert_eq!(result.as_string(), Some("HELLO"));
793 }
794
795 #[tokio::test]
796 async fn test_lowercase_directive() {
797 let handler = LowercaseDirectiveHandler;
798 let directive =
799 AppliedDirective::new("lowercase".to_string(), DirectiveLocation::FieldDefinition);
800 let context =
801 DirectiveContext::new().with_value(DirectiveValue::String("HELLO".to_string()));
802
803 let result = handler
804 .execute(&directive, &context)
805 .expect("should succeed");
806 assert_eq!(result.as_string(), Some("hello"));
807 }
808
809 #[tokio::test]
810 async fn test_constraint_directive_min_max() {
811 let handler = ConstraintDirectiveHandler;
812 let directive =
813 AppliedDirective::new("constraint".to_string(), DirectiveLocation::FieldDefinition)
814 .with_argument("min".to_string(), DirectiveValue::Int(0))
815 .with_argument("max".to_string(), DirectiveValue::Int(100));
816
817 let context = DirectiveContext::new().with_value(DirectiveValue::Int(50));
819 assert!(handler.execute(&directive, &context).is_ok());
820
821 let context = DirectiveContext::new().with_value(DirectiveValue::Int(-5));
823 assert!(handler.execute(&directive, &context).is_err());
824
825 let context = DirectiveContext::new().with_value(DirectiveValue::Int(150));
827 assert!(handler.execute(&directive, &context).is_err());
828 }
829
830 #[tokio::test]
831 async fn test_auth_directive_no_context() {
832 let handler = AuthDirectiveHandler;
833 let directive =
834 AppliedDirective::new("auth".to_string(), DirectiveLocation::FieldDefinition);
835 let context = DirectiveContext::new();
836
837 let result = handler.execute(&directive, &context);
838 assert!(result.is_err());
839 assert!(result
840 .unwrap_err()
841 .to_string()
842 .contains("Authentication required"));
843 }
844
845 #[tokio::test]
846 async fn test_auth_directive_with_role() {
847 let handler = AuthDirectiveHandler;
848 let directive =
849 AppliedDirective::new("auth".to_string(), DirectiveLocation::FieldDefinition)
850 .with_argument(
851 "requires".to_string(),
852 DirectiveValue::String("ADMIN".to_string()),
853 );
854
855 let mut user_context = HashMap::new();
856 user_context.insert("role".to_string(), "USER".to_string());
857
858 let context = DirectiveContext::new().with_user_context(user_context);
859
860 let result = handler.execute(&directive, &context);
861 assert!(result.is_err());
862 assert!(result
863 .unwrap_err()
864 .to_string()
865 .contains("Insufficient permissions"));
866 }
867
868 #[tokio::test]
869 async fn test_has_role_directive() {
870 let handler = HasRoleDirectiveHandler;
871 let directive =
872 AppliedDirective::new("hasRole".to_string(), DirectiveLocation::FieldDefinition)
873 .with_argument(
874 "role".to_string(),
875 DirectiveValue::String("admin".to_string()),
876 );
877
878 let mut user_context = HashMap::new();
879 user_context.insert("roles".to_string(), "user,admin,moderator".to_string());
880
881 let context = DirectiveContext::new().with_user_context(user_context);
882
883 let result = handler.execute(&directive, &context);
884 assert!(result.is_ok());
885 }
886}