ranvier_core/
schematic.rs1use crate::metadata::StepMetadata;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6pub const SCHEMA_VERSION: &str = "1.0";
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Schematic {
15 pub schema_version: String,
17 pub id: String,
19 pub name: String,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub description: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub generated_at: Option<DateTime<Utc>>,
27 pub nodes: Vec<Node>,
29 pub edges: Vec<Edge>,
31}
32
33impl Default for Schematic {
34 fn default() -> Self {
35 Self {
36 schema_version: SCHEMA_VERSION.to_string(),
37 id: Uuid::new_v4().to_string(),
38 name: String::new(),
39 description: None,
40 generated_at: Some(Utc::now()),
41 nodes: Vec::new(),
42 edges: Vec::new(),
43 }
44 }
45}
46
47impl Schematic {
48 pub fn new(name: impl Into<String>) -> Self {
49 Self {
50 name: name.into(),
51 ..Default::default()
52 }
53 }
54
55 pub fn with_id(name: impl Into<String>, id: impl Into<String>) -> Self {
57 Self {
58 id: id.into(),
59 name: name.into(),
60 ..Default::default()
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SourceLocation {
68 pub file: String,
70 pub line: u32,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub column: Option<u32>,
75}
76
77impl SourceLocation {
78 pub fn new(file: impl Into<String>, line: u32) -> Self {
79 Self {
80 file: file.into(),
81 line,
82 column: None,
83 }
84 }
85
86 pub fn with_column(file: impl Into<String>, line: u32, column: u32) -> Self {
87 Self {
88 file: file.into(),
89 line,
90 column: Some(column),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct Node {
97 pub id: String, pub kind: NodeKind,
99 pub label: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub description: Option<String>,
102 pub input_type: String,
103 pub output_type: String, pub resource_type: String,
105 pub metadata: StepMetadata,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub source_location: Option<SourceLocation>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub enum NodeKind {
113 Ingress, Atom, Synapse, Egress, Subgraph(Box<Schematic>), }
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub enum EdgeType {
122 Linear, Branch(String), Jump, Fault, }
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct Edge {
130 pub from: String,
131 pub to: String,
132 pub kind: EdgeType,
133 pub label: Option<String>,
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_schematic_default_has_version_and_id() {
142 let schematic = Schematic::new("Test Circuit");
143 assert_eq!(schematic.schema_version, SCHEMA_VERSION);
144 assert!(!schematic.id.is_empty());
145 assert!(schematic.generated_at.is_some());
146 }
147
148 #[test]
149 fn test_schematic_serialization_with_new_fields() {
150 let schematic = Schematic::new("Test");
151 let json = serde_json::to_string_pretty(&schematic).unwrap();
152
153 assert!(json.contains("schema_version"));
154 assert!(json.contains("\"1.0\""));
155 assert!(json.contains("generated_at"));
156 }
157
158 #[test]
159 fn test_source_location_optional_in_json() {
160 let schematic = Schematic::new("Test");
161 let json = serde_json::to_string(&schematic).unwrap();
162
163 assert!(!json.contains("description"));
165 }
166
167 #[test]
168 fn test_source_location_creation() {
169 let loc = SourceLocation::new("src/main.rs", 42);
170 assert_eq!(loc.file, "src/main.rs");
171 assert_eq!(loc.line, 42);
172 assert!(loc.column.is_none());
173
174 let loc_with_col = SourceLocation::with_column("src/lib.rs", 10, 5);
175 assert_eq!(loc_with_col.column, Some(5));
176 }
177}