1use anyhow::{anyhow, Result};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use std::time::{Duration, SystemTime};
19use tokio::sync::RwLock;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub enum SourceType {
24 GraphQL,
26 REST,
28 OpenAPI,
30 SOAP,
32 GRPC,
34 OData,
36 Database,
38 JsonSchema,
40 Custom,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct DataSource {
47 pub id: String,
49 pub name: String,
51 pub source_type: SourceType,
53 pub endpoint: String,
55 pub schema: Option<String>,
57 pub auth: Option<AuthConfig>,
59 pub headers: HashMap<String, String>,
61 pub transforms: Vec<Transform>,
63 pub cache: Option<CacheConfig>,
65 pub health_check: Option<String>,
67 pub enabled: bool,
69 pub metadata: HashMap<String, String>,
71}
72
73impl DataSource {
74 pub fn new(id: &str, name: &str, source_type: SourceType, endpoint: &str) -> Self {
76 Self {
77 id: id.to_string(),
78 name: name.to_string(),
79 source_type,
80 endpoint: endpoint.to_string(),
81 schema: None,
82 auth: None,
83 headers: HashMap::new(),
84 transforms: Vec::new(),
85 cache: None,
86 health_check: None,
87 enabled: true,
88 metadata: HashMap::new(),
89 }
90 }
91
92 pub fn with_auth(mut self, auth: AuthConfig) -> Self {
94 self.auth = Some(auth);
95 self
96 }
97
98 pub fn with_header(mut self, key: &str, value: &str) -> Self {
100 self.headers.insert(key.to_string(), value.to_string());
101 self
102 }
103
104 pub fn with_transform(mut self, transform: Transform) -> Self {
106 self.transforms.push(transform);
107 self
108 }
109
110 pub fn with_cache(mut self, cache: CacheConfig) -> Self {
112 self.cache = Some(cache);
113 self
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub enum AuthConfig {
120 None,
122 Basic { username: String, password: String },
124 Bearer { token: String },
126 ApiKey { key: String, header: String },
128 OAuth2 {
130 client_id: String,
131 client_secret: String,
132 token_url: String,
133 scopes: Vec<String>,
134 },
135 Custom { config: HashMap<String, String> },
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub enum Transform {
142 Prefix { value: String },
144 Rename { from: String, to: String },
146 FilterTypes { include: Vec<String> },
148 FilterFields {
150 type_name: String,
151 include: Vec<String>,
152 },
153 AddField {
155 type_name: String,
156 field_name: String,
157 field_type: String,
158 },
159 Encapsulate { namespace: String },
161 Snapshot { name: String },
163 Custom {
165 name: String,
166 config: HashMap<String, String>,
167 },
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct CacheConfig {
173 pub ttl: Duration,
175 pub max_entries: usize,
177 pub key_strategy: CacheKeyStrategy,
179 pub invalidation_events: Vec<String>,
181}
182
183impl Default for CacheConfig {
184 fn default() -> Self {
185 Self {
186 ttl: Duration::from_secs(60),
187 max_entries: 1000,
188 key_strategy: CacheKeyStrategy::default(),
189 invalidation_events: Vec::new(),
190 }
191 }
192}
193
194#[derive(Debug, Clone, Default, Serialize, Deserialize)]
196pub enum CacheKeyStrategy {
197 #[default]
199 QueryHash,
200 FieldLevel,
202 Custom { template: String },
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct TypeMergeConfig {
209 pub type_name: String,
211 pub key_field: String,
213 pub sources: Vec<String>,
215 pub resolution: MergeResolution,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
221pub enum MergeResolution {
222 First,
224 Last,
226 MergeAll,
228 FailOnConflict,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct CrossSourceRelation {
235 pub source_type: String,
237 pub source_field: String,
239 pub source_id: String,
241 pub target_type: String,
243 pub target_key_field: String,
245 pub target_source_id: String,
247 pub relation_type: RelationType,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
253pub enum RelationType {
254 OneToOne,
256 OneToMany,
258 ManyToOne,
260 ManyToMany,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct MeshConfig {
267 pub name: String,
269 pub sources: Vec<DataSource>,
271 pub type_merges: Vec<TypeMergeConfig>,
273 pub relations: Vec<CrossSourceRelation>,
275 pub global_transforms: Vec<Transform>,
277 pub enable_introspection: bool,
279 pub serve: ServeConfig,
281}
282
283impl Default for MeshConfig {
284 fn default() -> Self {
285 Self {
286 name: "mesh".to_string(),
287 sources: Vec::new(),
288 type_merges: Vec::new(),
289 relations: Vec::new(),
290 global_transforms: Vec::new(),
291 enable_introspection: true,
292 serve: ServeConfig::default(),
293 }
294 }
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct ServeConfig {
300 pub host: String,
302 pub port: u16,
304 pub playground: bool,
306 pub cors: Vec<String>,
308}
309
310impl Default for ServeConfig {
311 fn default() -> Self {
312 Self {
313 host: "0.0.0.0".to_string(),
314 port: 4000,
315 playground: true,
316 cors: vec!["*".to_string()],
317 }
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct SourceHealth {
324 pub source_id: String,
326 pub healthy: bool,
328 pub last_check: SystemTime,
330 pub last_error: Option<String>,
332 pub latency_ms: u64,
334}
335
336struct MeshState {
338 config: MeshConfig,
340 source_health: HashMap<String, SourceHealth>,
342 merged_schema: Option<String>,
344 last_merge: Option<SystemTime>,
346}
347
348impl MeshState {
349 fn new(config: MeshConfig) -> Self {
350 Self {
351 config,
352 source_health: HashMap::new(),
353 merged_schema: None,
354 last_merge: None,
355 }
356 }
357}
358
359pub struct GraphQLMesh {
363 state: Arc<RwLock<MeshState>>,
365}
366
367impl GraphQLMesh {
368 pub fn new(config: MeshConfig) -> Self {
370 Self {
371 state: Arc::new(RwLock::new(MeshState::new(config))),
372 }
373 }
374
375 pub async fn add_source(&self, source: DataSource) -> Result<()> {
377 let mut state = self.state.write().await;
378
379 if state.config.sources.iter().any(|s| s.id == source.id) {
380 return Err(anyhow!("Source '{}' already exists", source.id));
381 }
382
383 state.config.sources.push(source);
384 state.merged_schema = None; Ok(())
387 }
388
389 pub async fn remove_source(&self, source_id: &str) -> Result<()> {
391 let mut state = self.state.write().await;
392
393 let initial_len = state.config.sources.len();
394 state.config.sources.retain(|s| s.id != source_id);
395
396 if state.config.sources.len() == initial_len {
397 return Err(anyhow!("Source '{}' not found", source_id));
398 }
399
400 state.source_health.remove(source_id);
401 state.merged_schema = None;
402
403 Ok(())
404 }
405
406 pub async fn get_sources(&self) -> Vec<DataSource> {
408 let state = self.state.read().await;
409 state.config.sources.clone()
410 }
411
412 pub async fn get_source(&self, source_id: &str) -> Option<DataSource> {
414 let state = self.state.read().await;
415 state
416 .config
417 .sources
418 .iter()
419 .find(|s| s.id == source_id)
420 .cloned()
421 }
422
423 pub async fn update_source_health(
425 &self,
426 source_id: &str,
427 healthy: bool,
428 error: Option<String>,
429 latency_ms: u64,
430 ) {
431 let mut state = self.state.write().await;
432
433 state.source_health.insert(
434 source_id.to_string(),
435 SourceHealth {
436 source_id: source_id.to_string(),
437 healthy,
438 last_check: SystemTime::now(),
439 last_error: error,
440 latency_ms,
441 },
442 );
443 }
444
445 pub async fn get_source_health(&self, source_id: &str) -> Option<SourceHealth> {
447 let state = self.state.read().await;
448 state.source_health.get(source_id).cloned()
449 }
450
451 pub async fn get_all_health(&self) -> Vec<SourceHealth> {
453 let state = self.state.read().await;
454 state.source_health.values().cloned().collect()
455 }
456
457 pub async fn add_type_merge(&self, merge: TypeMergeConfig) {
459 let mut state = self.state.write().await;
460 state.config.type_merges.push(merge);
461 state.merged_schema = None;
462 }
463
464 pub async fn add_relation(&self, relation: CrossSourceRelation) {
466 let mut state = self.state.write().await;
467 state.config.relations.push(relation);
468 state.merged_schema = None;
469 }
470
471 pub async fn build_schema(&self) -> Result<String> {
473 let mut state = self.state.write().await;
474
475 let mut sdl = String::new();
476
477 sdl.push_str("type Query {\n");
479
480 for source in &state.config.sources {
481 if !source.enabled {
482 continue;
483 }
484
485 let prefix = source
487 .transforms
488 .iter()
489 .find_map(|t| match t {
490 Transform::Prefix { value } => Some(value.clone()),
491 _ => None,
492 })
493 .unwrap_or_default();
494
495 sdl.push_str(&format!(
496 " # Source: {} ({})\n",
497 source.name,
498 source.source_type.name()
499 ));
500 sdl.push_str(&format!(
501 " {}health: Boolean @source(name: \"{}\")\n",
502 prefix.to_lowercase(),
503 source.id
504 ));
505 }
506
507 sdl.push_str("}\n\n");
508
509 sdl.push_str("directive @source(name: String!) on FIELD_DEFINITION\n");
511
512 state.merged_schema = Some(sdl.clone());
513 state.last_merge = Some(SystemTime::now());
514
515 Ok(sdl)
516 }
517
518 pub async fn get_merged_schema(&self) -> Option<String> {
520 let state = self.state.read().await;
521 state.merged_schema.clone()
522 }
523
524 pub async fn get_config(&self) -> MeshConfig {
526 let state = self.state.read().await;
527 state.config.clone()
528 }
529
530 pub async fn validate(&self) -> Vec<ValidationError> {
532 let state = self.state.read().await;
533 let mut errors = Vec::new();
534
535 let mut seen_ids = std::collections::HashSet::new();
537 for source in &state.config.sources {
538 if !seen_ids.insert(&source.id) {
539 errors.push(ValidationError {
540 path: format!("sources.{}", source.id),
541 message: "Duplicate source ID".to_string(),
542 });
543 }
544 }
545
546 for merge in &state.config.type_merges {
548 for source_id in &merge.sources {
549 if !state.config.sources.iter().any(|s| &s.id == source_id) {
550 errors.push(ValidationError {
551 path: format!("type_merges.{}.sources", merge.type_name),
552 message: format!("Unknown source: {}", source_id),
553 });
554 }
555 }
556 }
557
558 for relation in &state.config.relations {
560 if !state
561 .config
562 .sources
563 .iter()
564 .any(|s| s.id == relation.source_id)
565 {
566 errors.push(ValidationError {
567 path: format!("relations.{}.source_id", relation.source_field),
568 message: format!("Unknown source: {}", relation.source_id),
569 });
570 }
571 if !state
572 .config
573 .sources
574 .iter()
575 .any(|s| s.id == relation.target_source_id)
576 {
577 errors.push(ValidationError {
578 path: format!("relations.{}.target_source_id", relation.source_field),
579 message: format!("Unknown source: {}", relation.target_source_id),
580 });
581 }
582 }
583
584 errors
585 }
586
587 pub async fn export_yaml(&self) -> Result<String> {
589 let state = self.state.read().await;
590 let yaml = serde_json::to_string_pretty(&state.config)?;
591 Ok(yaml)
592 }
593}
594
595impl SourceType {
596 pub fn name(&self) -> &'static str {
598 match self {
599 SourceType::GraphQL => "GraphQL",
600 SourceType::REST => "REST",
601 SourceType::OpenAPI => "OpenAPI",
602 SourceType::SOAP => "SOAP",
603 SourceType::GRPC => "gRPC",
604 SourceType::OData => "OData",
605 SourceType::Database => "Database",
606 SourceType::JsonSchema => "JSON Schema",
607 SourceType::Custom => "Custom",
608 }
609 }
610}
611
612#[derive(Debug, Clone, Serialize, Deserialize)]
614pub struct ValidationError {
615 pub path: String,
617 pub message: String,
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 #[tokio::test]
626 async fn test_mesh_creation() {
627 let config = MeshConfig::default();
628 let mesh = GraphQLMesh::new(config);
629
630 let sources = mesh.get_sources().await;
631 assert!(sources.is_empty());
632 }
633
634 #[tokio::test]
635 async fn test_add_source() {
636 let mesh = GraphQLMesh::new(MeshConfig::default());
637
638 let source = DataSource::new(
639 "users",
640 "Users API",
641 SourceType::GraphQL,
642 "https://api.example.com/graphql",
643 );
644
645 mesh.add_source(source).await.expect("should succeed");
646
647 let sources = mesh.get_sources().await;
648 assert_eq!(sources.len(), 1);
649 assert_eq!(sources[0].id, "users");
650 }
651
652 #[tokio::test]
653 async fn test_duplicate_source() {
654 let mesh = GraphQLMesh::new(MeshConfig::default());
655
656 let source1 = DataSource::new(
657 "api",
658 "API 1",
659 SourceType::GraphQL,
660 "https://api1.example.com",
661 );
662 let source2 = DataSource::new(
663 "api",
664 "API 2",
665 SourceType::GraphQL,
666 "https://api2.example.com",
667 );
668
669 mesh.add_source(source1).await.expect("should succeed");
670 let result = mesh.add_source(source2).await;
671
672 assert!(result.is_err());
673 }
674
675 #[tokio::test]
676 async fn test_remove_source() {
677 let mesh = GraphQLMesh::new(MeshConfig::default());
678
679 let source = DataSource::new(
680 "users",
681 "Users",
682 SourceType::GraphQL,
683 "https://api.example.com",
684 );
685 mesh.add_source(source).await.expect("should succeed");
686
687 mesh.remove_source("users").await.expect("should succeed");
688
689 let sources = mesh.get_sources().await;
690 assert!(sources.is_empty());
691 }
692
693 #[tokio::test]
694 async fn test_source_health() {
695 let mesh = GraphQLMesh::new(MeshConfig::default());
696
697 let source = DataSource::new("api", "API", SourceType::GraphQL, "https://api.example.com");
698 mesh.add_source(source).await.expect("should succeed");
699
700 mesh.update_source_health("api", true, None, 50).await;
701
702 let health = mesh.get_source_health("api").await.expect("should succeed");
703 assert!(health.healthy);
704 assert_eq!(health.latency_ms, 50);
705 }
706
707 #[tokio::test]
708 async fn test_build_schema() {
709 let mesh = GraphQLMesh::new(MeshConfig::default());
710
711 let source = DataSource::new(
712 "users",
713 "Users",
714 SourceType::GraphQL,
715 "https://api.example.com",
716 )
717 .with_transform(Transform::Prefix {
718 value: "Users_".to_string(),
719 });
720
721 mesh.add_source(source).await.expect("should succeed");
722
723 let schema = mesh.build_schema().await.expect("should succeed");
724 assert!(schema.contains("type Query"));
725 assert!(schema.contains("users_health"));
726 }
727
728 #[tokio::test]
729 async fn test_validation() {
730 let mesh = GraphQLMesh::new(MeshConfig::default());
731
732 mesh.add_type_merge(TypeMergeConfig {
733 type_name: "User".to_string(),
734 key_field: "id".to_string(),
735 sources: vec!["nonexistent".to_string()],
736 resolution: MergeResolution::MergeAll,
737 })
738 .await;
739
740 let errors = mesh.validate().await;
741 assert!(!errors.is_empty());
742 }
743
744 #[test]
745 fn test_data_source_builder() {
746 let source = DataSource::new("api", "API", SourceType::REST, "https://api.example.com")
747 .with_auth(AuthConfig::Bearer {
748 token: "secret".to_string(),
749 })
750 .with_header("X-Custom", "value")
751 .with_cache(CacheConfig::default());
752
753 assert!(source.auth.is_some());
754 assert!(source.headers.contains_key("X-Custom"));
755 assert!(source.cache.is_some());
756 }
757
758 #[test]
759 fn test_source_type_name() {
760 assert_eq!(SourceType::GraphQL.name(), "GraphQL");
761 assert_eq!(SourceType::REST.name(), "REST");
762 assert_eq!(SourceType::GRPC.name(), "gRPC");
763 }
764}