1use anyhow::{anyhow, Result};
28use serde::{Deserialize, Serialize};
29use std::collections::{HashMap, HashSet};
30use std::sync::Arc;
31use tokio::sync::RwLock;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum NodeType {
36 ObjectType,
38 InterfaceType,
40 InputType,
42 EnumType,
44 UnionType,
46 ScalarType,
48 Directive,
50}
51
52impl std::fmt::Display for NodeType {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 NodeType::ObjectType => write!(f, "type"),
56 NodeType::InterfaceType => write!(f, "interface"),
57 NodeType::InputType => write!(f, "input"),
58 NodeType::EnumType => write!(f, "enum"),
59 NodeType::UnionType => write!(f, "union"),
60 NodeType::ScalarType => write!(f, "scalar"),
61 NodeType::Directive => write!(f, "directive"),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68pub enum EdgeType {
69 Field,
71 Implements,
73 UnionMember,
75 Argument,
77 DirectiveUse,
79}
80
81#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
83pub struct Position {
84 pub x: f64,
86 pub y: f64,
88}
89
90impl Position {
91 pub fn new(x: f64, y: f64) -> Self {
93 Self { x, y }
94 }
95
96 pub fn distance_to(&self, other: &Position) -> f64 {
98 let dx = self.x - other.x;
99 let dy = self.y - other.y;
100 (dx * dx + dy * dy).sqrt()
101 }
102}
103
104#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
106pub struct Size {
107 pub width: f64,
109 pub height: f64,
111}
112
113impl Default for Size {
114 fn default() -> Self {
115 Self {
116 width: 200.0,
117 height: 100.0,
118 }
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SchemaNode {
125 pub id: String,
127 pub name: String,
129 pub node_type: NodeType,
131 pub description: Option<String>,
133 pub fields: Vec<FieldDefinition>,
135 pub enum_values: Vec<EnumValue>,
137 pub union_members: Vec<String>,
139 pub implements: Vec<String>,
141 pub directives: Vec<DirectiveUse>,
143 pub position: Position,
145 pub size: Size,
147 pub collapsed: bool,
149 pub selected: bool,
151 pub metadata: HashMap<String, String>,
153}
154
155impl SchemaNode {
156 pub fn new(id: &str, name: &str, node_type: NodeType) -> Self {
158 Self {
159 id: id.to_string(),
160 name: name.to_string(),
161 node_type,
162 description: None,
163 fields: Vec::new(),
164 enum_values: Vec::new(),
165 union_members: Vec::new(),
166 implements: Vec::new(),
167 directives: Vec::new(),
168 position: Position::default(),
169 size: Size::default(),
170 collapsed: false,
171 selected: false,
172 metadata: HashMap::new(),
173 }
174 }
175
176 pub fn with_description(mut self, description: &str) -> Self {
178 self.description = Some(description.to_string());
179 self
180 }
181
182 pub fn with_field(mut self, field: FieldDefinition) -> Self {
184 self.fields.push(field);
185 self
186 }
187
188 pub fn with_position(mut self, x: f64, y: f64) -> Self {
190 self.position = Position::new(x, y);
191 self
192 }
193
194 pub fn calculate_height(&self) -> f64 {
196 let base_height = 40.0; let field_height = 24.0;
198 let padding = 16.0;
199
200 if self.collapsed {
201 return base_height;
202 }
203
204 let content_height = match self.node_type {
205 NodeType::ObjectType | NodeType::InterfaceType | NodeType::InputType => {
206 self.fields.len() as f64 * field_height
207 }
208 NodeType::EnumType => self.enum_values.len() as f64 * field_height,
209 NodeType::UnionType => self.union_members.len() as f64 * field_height,
210 _ => 0.0,
211 };
212
213 base_height + content_height + padding
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct FieldDefinition {
220 pub name: String,
222 pub field_type: String,
224 pub nullable: bool,
226 pub is_list: bool,
228 pub description: Option<String>,
230 pub arguments: Vec<ArgumentDefinition>,
232 pub directives: Vec<DirectiveUse>,
234 pub default_value: Option<String>,
236 pub deprecated: bool,
238 pub deprecation_reason: Option<String>,
240}
241
242impl FieldDefinition {
243 pub fn new(name: &str, field_type: &str) -> Self {
245 Self {
246 name: name.to_string(),
247 field_type: field_type.to_string(),
248 nullable: true,
249 is_list: false,
250 description: None,
251 arguments: Vec::new(),
252 directives: Vec::new(),
253 default_value: None,
254 deprecated: false,
255 deprecation_reason: None,
256 }
257 }
258
259 pub fn non_null(mut self) -> Self {
261 self.nullable = false;
262 self
263 }
264
265 pub fn list(mut self) -> Self {
267 self.is_list = true;
268 self
269 }
270
271 pub fn with_description(mut self, description: &str) -> Self {
273 self.description = Some(description.to_string());
274 self
275 }
276
277 pub fn type_string(&self) -> String {
279 let mut s = self.field_type.clone();
280 if self.is_list {
281 s = format!("[{}]", s);
282 }
283 if !self.nullable {
284 s = format!("{}!", s);
285 }
286 s
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ArgumentDefinition {
293 pub name: String,
295 pub arg_type: String,
297 pub nullable: bool,
299 pub default_value: Option<String>,
301 pub description: Option<String>,
303}
304
305impl ArgumentDefinition {
306 pub fn new(name: &str, arg_type: &str) -> Self {
308 Self {
309 name: name.to_string(),
310 arg_type: arg_type.to_string(),
311 nullable: true,
312 default_value: None,
313 description: None,
314 }
315 }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct EnumValue {
321 pub name: String,
323 pub description: Option<String>,
325 pub deprecated: bool,
327 pub deprecation_reason: Option<String>,
329}
330
331impl EnumValue {
332 pub fn new(name: &str) -> Self {
334 Self {
335 name: name.to_string(),
336 description: None,
337 deprecated: false,
338 deprecation_reason: None,
339 }
340 }
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct DirectiveUse {
346 pub name: String,
348 pub arguments: HashMap<String, String>,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct SchemaEdge {
355 pub id: String,
357 pub source: String,
359 pub target: String,
361 pub source_field: Option<String>,
363 pub edge_type: EdgeType,
365 pub label: Option<String>,
367 pub highlighted: bool,
369}
370
371impl SchemaEdge {
372 pub fn new(id: &str, source: &str, target: &str, edge_type: EdgeType) -> Self {
374 Self {
375 id: id.to_string(),
376 source: source.to_string(),
377 target: target.to_string(),
378 source_field: None,
379 edge_type,
380 label: None,
381 highlighted: false,
382 }
383 }
384
385 pub fn with_source_field(mut self, field: &str) -> Self {
387 self.source_field = Some(field.to_string());
388 self
389 }
390
391 pub fn with_label(mut self, label: &str) -> Self {
393 self.label = Some(label.to_string());
394 self
395 }
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct SchemaGraph {
401 pub nodes: Vec<SchemaNode>,
403 pub edges: Vec<SchemaEdge>,
405 pub width: f64,
407 pub height: f64,
409 pub zoom: f64,
411 pub pan: Position,
413}
414
415impl Default for SchemaGraph {
416 fn default() -> Self {
417 Self {
418 nodes: Vec::new(),
419 edges: Vec::new(),
420 width: 1000.0,
421 height: 800.0,
422 zoom: 1.0,
423 pan: Position::default(),
424 }
425 }
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
430pub enum LayoutAlgorithm {
431 #[default]
433 ForceDirected,
434 Hierarchical,
436 Circular,
438 Grid,
440 Manual,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct LayoutOptions {
447 pub algorithm: LayoutAlgorithm,
449 pub node_spacing: f64,
451 pub layer_spacing: f64,
453 pub iterations: u32,
455 pub animation_duration: u64,
457 pub group_by_type: bool,
459}
460
461impl Default for LayoutOptions {
462 fn default() -> Self {
463 Self {
464 algorithm: LayoutAlgorithm::default(),
465 node_spacing: 50.0,
466 layer_spacing: 100.0,
467 iterations: 100,
468 animation_duration: 300,
469 group_by_type: true,
470 }
471 }
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
476pub enum EditOperation {
477 AddNode { node: SchemaNode },
479 RemoveNode { node_id: String },
481 UpdateNode { node: SchemaNode },
483 AddField {
485 node_id: String,
486 field: FieldDefinition,
487 },
488 RemoveField { node_id: String, field_name: String },
490 UpdateField {
492 node_id: String,
493 field: FieldDefinition,
494 },
495 AddEdge { edge: SchemaEdge },
497 RemoveEdge { edge_id: String },
499 MoveNode { node_id: String, position: Position },
501 Batch { operations: Vec<EditOperation> },
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct ValidationResult {
508 pub valid: bool,
510 pub errors: Vec<ValidationError>,
512 pub warnings: Vec<ValidationWarning>,
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct ValidationError {
519 pub code: String,
521 pub message: String,
523 pub node_id: Option<String>,
525 pub field_name: Option<String>,
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ValidationWarning {
532 pub code: String,
534 pub message: String,
536 pub node_id: Option<String>,
538}
539
540struct DesignerState {
542 graph: SchemaGraph,
544 node_index: HashMap<String, usize>,
546 edge_index: HashMap<String, usize>,
548 history: Vec<EditOperation>,
550 history_position: usize,
552 layout_options: LayoutOptions,
554}
555
556impl DesignerState {
557 fn new() -> Self {
558 Self {
559 graph: SchemaGraph::default(),
560 node_index: HashMap::new(),
561 edge_index: HashMap::new(),
562 history: Vec::new(),
563 history_position: 0,
564 layout_options: LayoutOptions::default(),
565 }
566 }
567
568 fn rebuild_indices(&mut self) {
569 self.node_index.clear();
570 for (i, node) in self.graph.nodes.iter().enumerate() {
571 self.node_index.insert(node.id.clone(), i);
572 }
573
574 self.edge_index.clear();
575 for (i, edge) in self.graph.edges.iter().enumerate() {
576 self.edge_index.insert(edge.id.clone(), i);
577 }
578 }
579}
580
581pub struct SchemaDesigner {
586 state: Arc<RwLock<DesignerState>>,
588}
589
590impl SchemaDesigner {
591 pub fn new() -> Self {
593 Self {
594 state: Arc::new(RwLock::new(DesignerState::new())),
595 }
596 }
597
598 pub async fn get_visualization(&self) -> SchemaGraph {
600 let state = self.state.read().await;
601 state.graph.clone()
602 }
603
604 pub async fn add_node(&self, mut node: SchemaNode) -> Result<String> {
606 let node_id = if node.id.is_empty() {
607 uuid::Uuid::new_v4().to_string()
608 } else {
609 node.id.clone()
610 };
611 node.id = node_id.clone();
612
613 let mut state = self.state.write().await;
614
615 if state.graph.nodes.iter().any(|n| n.name == node.name) {
617 return Err(anyhow!("Type '{}' already exists", node.name));
618 }
619
620 node.size.height = node.calculate_height();
622
623 state.graph.nodes.push(node.clone());
624 let node_index_value = state.graph.nodes.len() - 1;
625 state.node_index.insert(node_id.clone(), node_index_value);
626
627 let history_pos = state.history_position;
629 state.history.truncate(history_pos);
630 state.history.push(EditOperation::AddNode { node });
631 state.history_position = state.history.len();
632
633 Ok(node_id)
634 }
635
636 pub async fn remove_node(&self, node_id: &str) -> Result<()> {
638 let mut state = self.state.write().await;
639
640 let Some(&index) = state.node_index.get(node_id) else {
641 return Err(anyhow!("Node '{}' not found", node_id));
642 };
643
644 let _node = state.graph.nodes.remove(index);
645
646 state
648 .graph
649 .edges
650 .retain(|e| e.source != node_id && e.target != node_id);
651
652 state.rebuild_indices();
654
655 let history_pos = state.history_position;
657 state.history.truncate(history_pos);
658 state.history.push(EditOperation::RemoveNode {
659 node_id: node_id.to_string(),
660 });
661 state.history_position = state.history.len();
662
663 Ok(())
664 }
665
666 pub async fn update_node(&self, node: SchemaNode) -> Result<()> {
668 let mut state = self.state.write().await;
669
670 let Some(&index) = state.node_index.get(&node.id) else {
671 return Err(anyhow!("Node '{}' not found", node.id));
672 };
673
674 let mut updated_node = node.clone();
675 updated_node.size.height = updated_node.calculate_height();
676
677 state.graph.nodes[index] = updated_node;
678
679 let history_pos = state.history_position;
681 state.history.truncate(history_pos);
682 state.history.push(EditOperation::UpdateNode { node });
683 state.history_position = state.history.len();
684
685 Ok(())
686 }
687
688 pub async fn add_field(&self, node_id: &str, field: FieldDefinition) -> Result<()> {
690 let mut state = self.state.write().await;
691
692 let Some(&index) = state.node_index.get(node_id) else {
693 return Err(anyhow!("Node '{}' not found", node_id));
694 };
695
696 if state.graph.nodes[index]
698 .fields
699 .iter()
700 .any(|f| f.name == field.name)
701 {
702 return Err(anyhow!(
703 "Field '{}' already exists in '{}'",
704 field.name,
705 node_id
706 ));
707 }
708
709 state.graph.nodes[index].fields.push(field.clone());
710 state.graph.nodes[index].size.height = state.graph.nodes[index].calculate_height();
711
712 let target_type = field
714 .field_type
715 .trim_matches(|c| c == '[' || c == ']' || c == '!');
716 if let Some(target_node) = state.graph.nodes.iter().find(|n| n.name == target_type) {
717 let edge = SchemaEdge::new(
718 &uuid::Uuid::new_v4().to_string(),
719 node_id,
720 &target_node.id,
721 EdgeType::Field,
722 )
723 .with_source_field(&field.name);
724 state.graph.edges.push(edge);
725 state.rebuild_indices();
726 }
727
728 let history_pos = state.history_position;
730 state.history.truncate(history_pos);
731 state.history.push(EditOperation::AddField {
732 node_id: node_id.to_string(),
733 field,
734 });
735 state.history_position = state.history.len();
736
737 Ok(())
738 }
739
740 pub async fn remove_field(&self, node_id: &str, field_name: &str) -> Result<()> {
742 let mut state = self.state.write().await;
743
744 let Some(&index) = state.node_index.get(node_id) else {
745 return Err(anyhow!("Node '{}' not found", node_id));
746 };
747
748 state.graph.nodes[index]
749 .fields
750 .retain(|f| f.name != field_name);
751 state.graph.nodes[index].size.height = state.graph.nodes[index].calculate_height();
752
753 state
755 .graph
756 .edges
757 .retain(|e| e.source != node_id || e.source_field.as_deref() != Some(field_name));
758 state.rebuild_indices();
759
760 let history_pos = state.history_position;
762 state.history.truncate(history_pos);
763 state.history.push(EditOperation::RemoveField {
764 node_id: node_id.to_string(),
765 field_name: field_name.to_string(),
766 });
767 state.history_position = state.history.len();
768
769 Ok(())
770 }
771
772 pub async fn move_node(&self, node_id: &str, position: Position) -> Result<()> {
774 let mut state = self.state.write().await;
775
776 let Some(&index) = state.node_index.get(node_id) else {
777 return Err(anyhow!("Node '{}' not found", node_id));
778 };
779
780 state.graph.nodes[index].position = position;
781
782 Ok(())
783 }
784
785 pub async fn apply_layout(&self) -> Result<()> {
787 let mut state = self.state.write().await;
788
789 match state.layout_options.algorithm {
790 LayoutAlgorithm::ForceDirected => {
791 self.apply_force_directed_layout(&mut state)?;
792 }
793 LayoutAlgorithm::Hierarchical => {
794 self.apply_hierarchical_layout(&mut state)?;
795 }
796 LayoutAlgorithm::Circular => {
797 self.apply_circular_layout(&mut state)?;
798 }
799 LayoutAlgorithm::Grid => {
800 self.apply_grid_layout(&mut state)?;
801 }
802 LayoutAlgorithm::Manual => {
803 }
805 }
806
807 Ok(())
808 }
809
810 fn apply_force_directed_layout(&self, state: &mut DesignerState) -> Result<()> {
811 let node_count = state.graph.nodes.len();
812 if node_count == 0 {
813 return Ok(());
814 }
815
816 for node in &mut state.graph.nodes {
818 if node.position.x == 0.0 && node.position.y == 0.0 {
819 node.position.x = fastrand::f64() * 800.0;
820 node.position.y = fastrand::f64() * 600.0;
821 }
822 }
823
824 let spacing = state.layout_options.node_spacing;
825 let iterations = state.layout_options.iterations;
826
827 for _ in 0..iterations {
829 let mut forces: Vec<Position> = vec![Position::default(); node_count];
830
831 for i in 0..node_count {
833 for j in (i + 1)..node_count {
834 let dx = state.graph.nodes[j].position.x - state.graph.nodes[i].position.x;
835 let dy = state.graph.nodes[j].position.y - state.graph.nodes[i].position.y;
836 let dist = (dx * dx + dy * dy).sqrt().max(1.0);
837 let repulsion = spacing * spacing / dist;
838
839 let fx = repulsion * dx / dist;
840 let fy = repulsion * dy / dist;
841
842 forces[i].x -= fx;
843 forces[i].y -= fy;
844 forces[j].x += fx;
845 forces[j].y += fy;
846 }
847 }
848
849 for edge in &state.graph.edges {
851 let Some(&source_idx) = state.node_index.get(&edge.source) else {
852 continue;
853 };
854 let Some(&target_idx) = state.node_index.get(&edge.target) else {
855 continue;
856 };
857
858 let dx = state.graph.nodes[target_idx].position.x
859 - state.graph.nodes[source_idx].position.x;
860 let dy = state.graph.nodes[target_idx].position.y
861 - state.graph.nodes[source_idx].position.y;
862 let dist = (dx * dx + dy * dy).sqrt();
863
864 let attraction = dist / spacing;
865 let fx = attraction * dx / dist.max(1.0);
866 let fy = attraction * dy / dist.max(1.0);
867
868 forces[source_idx].x += fx * 0.1;
869 forces[source_idx].y += fy * 0.1;
870 forces[target_idx].x -= fx * 0.1;
871 forces[target_idx].y -= fy * 0.1;
872 }
873
874 let damping = 0.85;
876 for (i, node) in state.graph.nodes.iter_mut().enumerate() {
877 node.position.x += forces[i].x * damping;
878 node.position.y += forces[i].y * damping;
879
880 node.position.x = node.position.x.max(50.0).min(state.graph.width - 50.0);
882 node.position.y = node.position.y.max(50.0).min(state.graph.height - 50.0);
883 }
884 }
885
886 Ok(())
887 }
888
889 fn apply_hierarchical_layout(&self, state: &mut DesignerState) -> Result<()> {
890 if state.graph.nodes.is_empty() {
891 return Ok(());
892 }
893
894 let spacing = state.layout_options.node_spacing;
895 let layer_spacing = state.layout_options.layer_spacing;
896
897 let groups: Vec<Vec<usize>> = if state.layout_options.group_by_type {
899 let mut type_groups: HashMap<NodeType, Vec<usize>> = HashMap::new();
900 for (i, node) in state.graph.nodes.iter().enumerate() {
901 type_groups.entry(node.node_type).or_default().push(i);
902 }
903 type_groups.into_values().collect()
904 } else {
905 vec![(0..state.graph.nodes.len()).collect()]
906 };
907
908 let mut y = 50.0;
909 for group in groups {
910 let mut x = 50.0;
911 for idx in group {
912 state.graph.nodes[idx].position.x = x;
913 state.graph.nodes[idx].position.y = y;
914 x += state.graph.nodes[idx].size.width + spacing;
915 }
916 y += layer_spacing;
917 }
918
919 Ok(())
920 }
921
922 fn apply_circular_layout(&self, state: &mut DesignerState) -> Result<()> {
923 let node_count = state.graph.nodes.len();
924 if node_count == 0 {
925 return Ok(());
926 }
927
928 let center_x = state.graph.width / 2.0;
929 let center_y = state.graph.height / 2.0;
930 let radius = (state.graph.width.min(state.graph.height) / 2.0) - 150.0;
931
932 for (i, node) in state.graph.nodes.iter_mut().enumerate() {
933 let angle = 2.0 * std::f64::consts::PI * (i as f64) / (node_count as f64);
934 node.position.x = center_x + radius * angle.cos();
935 node.position.y = center_y + radius * angle.sin();
936 }
937
938 Ok(())
939 }
940
941 fn apply_grid_layout(&self, state: &mut DesignerState) -> Result<()> {
942 let node_count = state.graph.nodes.len();
943 if node_count == 0 {
944 return Ok(());
945 }
946
947 let cols = (node_count as f64).sqrt().ceil() as usize;
948 let spacing = state.layout_options.node_spacing;
949
950 for (i, node) in state.graph.nodes.iter_mut().enumerate() {
951 let row = i / cols;
952 let col = i % cols;
953 node.position.x = 50.0 + (col as f64) * (node.size.width + spacing);
954 node.position.y = 50.0 + (row as f64) * (node.size.height + spacing);
955 }
956
957 Ok(())
958 }
959
960 pub async fn set_layout_options(&self, options: LayoutOptions) {
962 let mut state = self.state.write().await;
963 state.layout_options = options;
964 }
965
966 pub async fn validate(&self) -> ValidationResult {
968 let state = self.state.read().await;
969
970 let mut errors = Vec::new();
971 let mut warnings = Vec::new();
972
973 if !state.graph.nodes.iter().any(|n| n.name == "Query") {
975 errors.push(ValidationError {
976 code: "MISSING_QUERY".to_string(),
977 message: "Schema must have a Query type".to_string(),
978 node_id: None,
979 field_name: None,
980 });
981 }
982
983 let type_names: HashSet<_> = state.graph.nodes.iter().map(|n| n.name.as_str()).collect();
985 let builtin_types: HashSet<&str> = ["String", "Int", "Float", "Boolean", "ID"]
986 .iter()
987 .copied()
988 .collect();
989
990 for node in &state.graph.nodes {
991 for field in &node.fields {
992 let field_type = field
993 .field_type
994 .trim_matches(|c| c == '[' || c == ']' || c == '!');
995 if !type_names.contains(field_type) && !builtin_types.contains(field_type) {
996 errors.push(ValidationError {
997 code: "UNRESOLVED_TYPE".to_string(),
998 message: format!("Unknown type '{}' in field '{}'", field_type, field.name),
999 node_id: Some(node.id.clone()),
1000 field_name: Some(field.name.clone()),
1001 });
1002 }
1003 }
1004
1005 if matches!(
1007 node.node_type,
1008 NodeType::ObjectType | NodeType::InterfaceType | NodeType::InputType
1009 ) && node.fields.is_empty()
1010 {
1011 warnings.push(ValidationWarning {
1012 code: "EMPTY_TYPE".to_string(),
1013 message: format!("Type '{}' has no fields", node.name),
1014 node_id: Some(node.id.clone()),
1015 });
1016 }
1017
1018 if node.node_type == NodeType::EnumType && node.enum_values.is_empty() {
1020 errors.push(ValidationError {
1021 code: "EMPTY_ENUM".to_string(),
1022 message: format!("Enum '{}' has no values", node.name),
1023 node_id: Some(node.id.clone()),
1024 field_name: None,
1025 });
1026 }
1027 }
1028
1029 ValidationResult {
1030 valid: errors.is_empty(),
1031 errors,
1032 warnings,
1033 }
1034 }
1035
1036 pub async fn export_sdl(&self) -> String {
1038 let state = self.state.read().await;
1039 let mut sdl = String::new();
1040
1041 for node in &state.graph.nodes {
1042 if let Some(desc) = &node.description {
1044 sdl.push_str(&format!("\"\"\"{}\"\"\"\n", desc));
1045 }
1046
1047 match node.node_type {
1048 NodeType::ObjectType => {
1049 let implements = if !node.implements.is_empty() {
1050 format!(" implements {}", node.implements.join(" & "))
1051 } else {
1052 String::new()
1053 };
1054 sdl.push_str(&format!("type {}{} {{\n", node.name, implements));
1055 for field in &node.fields {
1056 if let Some(desc) = &field.description {
1057 sdl.push_str(&format!(" \"\"\"{}\"\"\"\n", desc));
1058 }
1059 sdl.push_str(&format!(" {}: {}\n", field.name, field.type_string()));
1060 }
1061 sdl.push_str("}\n\n");
1062 }
1063 NodeType::InterfaceType => {
1064 sdl.push_str(&format!("interface {} {{\n", node.name));
1065 for field in &node.fields {
1066 sdl.push_str(&format!(" {}: {}\n", field.name, field.type_string()));
1067 }
1068 sdl.push_str("}\n\n");
1069 }
1070 NodeType::InputType => {
1071 sdl.push_str(&format!("input {} {{\n", node.name));
1072 for field in &node.fields {
1073 sdl.push_str(&format!(" {}: {}\n", field.name, field.type_string()));
1074 }
1075 sdl.push_str("}\n\n");
1076 }
1077 NodeType::EnumType => {
1078 sdl.push_str(&format!("enum {} {{\n", node.name));
1079 for value in &node.enum_values {
1080 sdl.push_str(&format!(" {}\n", value.name));
1081 }
1082 sdl.push_str("}\n\n");
1083 }
1084 NodeType::UnionType => {
1085 sdl.push_str(&format!(
1086 "union {} = {}\n\n",
1087 node.name,
1088 node.union_members.join(" | ")
1089 ));
1090 }
1091 NodeType::ScalarType => {
1092 sdl.push_str(&format!("scalar {}\n\n", node.name));
1093 }
1094 NodeType::Directive => {
1095 }
1097 }
1098 }
1099
1100 sdl
1101 }
1102
1103 pub async fn import_sdl(&self, sdl: &str) -> Result<()> {
1105 let mut state = self.state.write().await;
1107 state.graph.nodes.clear();
1108 state.graph.edges.clear();
1109
1110 let lines: Vec<&str> = sdl.lines().collect();
1111 let mut current_node: Option<SchemaNode> = None;
1112 let mut in_type = false;
1113
1114 for line in lines {
1115 let line = line.trim();
1116
1117 if line.starts_with("type ")
1118 || line.starts_with("interface ")
1119 || line.starts_with("input ")
1120 || line.starts_with("enum ")
1121 {
1122 if let Some(node) = current_node.take() {
1124 state.graph.nodes.push(node);
1125 }
1126
1127 let (kind, rest) = if let Some(rest) = line.strip_prefix("type ") {
1128 (NodeType::ObjectType, rest)
1129 } else if let Some(rest) = line.strip_prefix("interface ") {
1130 (NodeType::InterfaceType, rest)
1131 } else if let Some(rest) = line.strip_prefix("input ") {
1132 (NodeType::InputType, rest)
1133 } else if let Some(rest) = line.strip_prefix("enum ") {
1134 (NodeType::EnumType, rest)
1135 } else {
1136 continue;
1137 };
1138
1139 let name = rest
1140 .split(|c: char| c.is_whitespace() || c == '{')
1141 .next()
1142 .unwrap_or("");
1143 current_node = Some(SchemaNode::new(
1144 &uuid::Uuid::new_v4().to_string(),
1145 name,
1146 kind,
1147 ));
1148 in_type = true;
1149 } else if line == "}" {
1150 if let Some(node) = current_node.take() {
1151 state.graph.nodes.push(node);
1152 }
1153 in_type = false;
1154 } else if in_type && !line.is_empty() && !line.starts_with("\"\"\"") {
1155 if let Some(ref mut node) = current_node {
1156 if let Some((name, type_part)) = line.split_once(':') {
1158 let name = name.trim();
1159 let type_str = type_part.trim().trim_end_matches(',');
1160 let field = FieldDefinition::new(name, type_str);
1161 node.fields.push(field);
1162 } else if node.node_type == NodeType::EnumType {
1163 node.enum_values.push(EnumValue::new(line));
1165 }
1166 }
1167 } else if let Some(rest) = line.strip_prefix("scalar ") {
1168 let name = rest.trim();
1169 let node = SchemaNode::new(
1170 &uuid::Uuid::new_v4().to_string(),
1171 name,
1172 NodeType::ScalarType,
1173 );
1174 state.graph.nodes.push(node);
1175 } else if let Some(rest) = line.strip_prefix("union ") {
1176 if let Some((name, members)) = rest.split_once('=') {
1177 let name = name.trim();
1178 let members: Vec<String> =
1179 members.split('|').map(|s| s.trim().to_string()).collect();
1180 let mut node = SchemaNode::new(
1181 &uuid::Uuid::new_v4().to_string(),
1182 name,
1183 NodeType::UnionType,
1184 );
1185 node.union_members = members;
1186 state.graph.nodes.push(node);
1187 }
1188 }
1189 }
1190
1191 if let Some(node) = current_node {
1193 state.graph.nodes.push(node);
1194 }
1195
1196 state.rebuild_indices();
1198
1199 self.create_edges_from_fields(&mut state);
1201
1202 drop(state);
1204 self.apply_layout().await?;
1205
1206 Ok(())
1207 }
1208
1209 fn create_edges_from_fields(&self, state: &mut DesignerState) {
1210 let type_map: HashMap<String, String> = state
1211 .graph
1212 .nodes
1213 .iter()
1214 .map(|n| (n.name.clone(), n.id.clone()))
1215 .collect();
1216
1217 let builtin_types: HashSet<&str> = ["String", "Int", "Float", "Boolean", "ID"]
1218 .iter()
1219 .copied()
1220 .collect();
1221
1222 let mut edges = Vec::new();
1223
1224 for node in &state.graph.nodes {
1225 for field in &node.fields {
1226 let field_type = field
1227 .field_type
1228 .trim_matches(|c| c == '[' || c == ']' || c == '!');
1229
1230 if builtin_types.contains(field_type) {
1231 continue;
1232 }
1233
1234 if let Some(target_id) = type_map.get(field_type) {
1235 let edge = SchemaEdge::new(
1236 &uuid::Uuid::new_v4().to_string(),
1237 &node.id,
1238 target_id,
1239 EdgeType::Field,
1240 )
1241 .with_source_field(&field.name);
1242 edges.push(edge);
1243 }
1244 }
1245
1246 for interface in &node.implements {
1248 if let Some(target_id) = type_map.get(interface) {
1249 let edge = SchemaEdge::new(
1250 &uuid::Uuid::new_v4().to_string(),
1251 &node.id,
1252 target_id,
1253 EdgeType::Implements,
1254 );
1255 edges.push(edge);
1256 }
1257 }
1258
1259 for member in &node.union_members {
1261 if let Some(target_id) = type_map.get(member) {
1262 let edge = SchemaEdge::new(
1263 &uuid::Uuid::new_v4().to_string(),
1264 &node.id,
1265 target_id,
1266 EdgeType::UnionMember,
1267 );
1268 edges.push(edge);
1269 }
1270 }
1271 }
1272
1273 state.graph.edges = edges;
1274 state.rebuild_indices();
1275 }
1276
1277 pub async fn can_undo(&self) -> bool {
1279 let state = self.state.read().await;
1280 state.history_position > 0
1281 }
1282
1283 pub async fn undo(&self) -> Result<()> {
1285 let mut state = self.state.write().await;
1286 if state.history_position == 0 {
1287 return Err(anyhow!("Nothing to undo"));
1288 }
1289 state.history_position -= 1;
1290 Ok(())
1292 }
1293
1294 pub async fn can_redo(&self) -> bool {
1296 let state = self.state.read().await;
1297 state.history_position < state.history.len()
1298 }
1299
1300 pub async fn redo(&self) -> Result<()> {
1302 let mut state = self.state.write().await;
1303 if state.history_position >= state.history.len() {
1304 return Err(anyhow!("Nothing to redo"));
1305 }
1306 state.history_position += 1;
1307 Ok(())
1309 }
1310}
1311
1312impl Default for SchemaDesigner {
1313 fn default() -> Self {
1314 Self::new()
1315 }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320 use super::*;
1321
1322 #[tokio::test]
1323 async fn test_designer_creation() {
1324 let designer = SchemaDesigner::new();
1325 let graph = designer.get_visualization().await;
1326 assert!(graph.nodes.is_empty());
1327 }
1328
1329 #[tokio::test]
1330 async fn test_add_node() {
1331 let designer = SchemaDesigner::new();
1332 let node = SchemaNode::new("", "User", NodeType::ObjectType);
1333 let node_id = designer.add_node(node).await.expect("should succeed");
1334
1335 let graph = designer.get_visualization().await;
1336 assert_eq!(graph.nodes.len(), 1);
1337 assert_eq!(graph.nodes[0].id, node_id);
1338 }
1339
1340 #[tokio::test]
1341 async fn test_add_field() {
1342 let designer = SchemaDesigner::new();
1343 let node = SchemaNode::new("", "User", NodeType::ObjectType);
1344 let node_id = designer.add_node(node).await.expect("should succeed");
1345
1346 let field = FieldDefinition::new("name", "String").non_null();
1347 designer
1348 .add_field(&node_id, field)
1349 .await
1350 .expect("should succeed");
1351
1352 let graph = designer.get_visualization().await;
1353 assert_eq!(graph.nodes[0].fields.len(), 1);
1354 assert_eq!(graph.nodes[0].fields[0].name, "name");
1355 }
1356
1357 #[tokio::test]
1358 async fn test_validation() {
1359 let designer = SchemaDesigner::new();
1360
1361 let result = designer.validate().await;
1363 assert!(!result.valid);
1364 assert!(result.errors.iter().any(|e| e.code == "MISSING_QUERY"));
1365
1366 let query = SchemaNode::new("", "Query", NodeType::ObjectType)
1368 .with_field(FieldDefinition::new("hello", "String"));
1369 designer.add_node(query).await.expect("should succeed");
1370
1371 let result = designer.validate().await;
1372 assert!(result.valid);
1373 }
1374
1375 #[tokio::test]
1376 async fn test_export_sdl() {
1377 let designer = SchemaDesigner::new();
1378
1379 let user = SchemaNode::new("", "User", NodeType::ObjectType)
1380 .with_field(FieldDefinition::new("id", "ID").non_null())
1381 .with_field(FieldDefinition::new("name", "String"));
1382 designer.add_node(user).await.expect("should succeed");
1383
1384 let sdl = designer.export_sdl().await;
1385 assert!(sdl.contains("type User"));
1386 assert!(sdl.contains("id: ID!"));
1387 assert!(sdl.contains("name: String"));
1388 }
1389
1390 #[tokio::test]
1391 async fn test_import_sdl() {
1392 let designer = SchemaDesigner::new();
1393
1394 let sdl = r#"
1395 type Query {
1396 users: [User]
1397 }
1398
1399 type User {
1400 id: ID!
1401 name: String
1402 }
1403 "#;
1404
1405 designer.import_sdl(sdl).await.expect("should succeed");
1406
1407 let graph = designer.get_visualization().await;
1408 assert_eq!(graph.nodes.len(), 2);
1409 }
1410
1411 #[tokio::test]
1412 async fn test_layout_algorithms() {
1413 let designer = SchemaDesigner::new();
1414
1415 for i in 0..5 {
1417 let node = SchemaNode::new("", &format!("Type{}", i), NodeType::ObjectType);
1418 designer.add_node(node).await.expect("should succeed");
1419 }
1420
1421 designer
1423 .set_layout_options(LayoutOptions {
1424 algorithm: LayoutAlgorithm::ForceDirected,
1425 ..Default::default()
1426 })
1427 .await;
1428 designer.apply_layout().await.expect("should succeed");
1429
1430 designer
1432 .set_layout_options(LayoutOptions {
1433 algorithm: LayoutAlgorithm::Circular,
1434 ..Default::default()
1435 })
1436 .await;
1437 designer.apply_layout().await.expect("should succeed");
1438
1439 designer
1441 .set_layout_options(LayoutOptions {
1442 algorithm: LayoutAlgorithm::Grid,
1443 ..Default::default()
1444 })
1445 .await;
1446 designer.apply_layout().await.expect("should succeed");
1447
1448 let graph = designer.get_visualization().await;
1449 for node in &graph.nodes {
1451 assert!(node.position.x > 0.0 || node.position.y > 0.0);
1452 }
1453 }
1454
1455 #[tokio::test]
1456 async fn test_remove_node() {
1457 let designer = SchemaDesigner::new();
1458
1459 let node = SchemaNode::new("", "User", NodeType::ObjectType);
1460 let node_id = designer.add_node(node).await.expect("should succeed");
1461
1462 designer
1463 .remove_node(&node_id)
1464 .await
1465 .expect("should succeed");
1466
1467 let graph = designer.get_visualization().await;
1468 assert!(graph.nodes.is_empty());
1469 }
1470
1471 #[tokio::test]
1472 async fn test_field_type_string() {
1473 let field = FieldDefinition::new("items", "Item").list().non_null();
1474 assert_eq!(field.type_string(), "[Item]!");
1475 }
1476
1477 #[tokio::test]
1478 async fn test_position_distance() {
1479 let p1 = Position::new(0.0, 0.0);
1480 let p2 = Position::new(3.0, 4.0);
1481 assert!((p1.distance_to(&p2) - 5.0).abs() < 0.0001);
1482 }
1483}