1use serde::{Deserialize, Serialize};
4
5use frp_domain::{AtomKind, BlockSchema, Port, port::PortDirection};
6use frp_plexus::{BlockId, PortId, TypeSig};
7
8use crate::archetype::Archetype;
9use crate::error::WeaveError;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct PortDef {
14 pub name: String,
16 pub direction: String,
18 pub type_sig: String,
20}
21
22impl PortDef {
23 pub fn to_port(&self, id: PortId) -> Result<Port, WeaveError> {
27 let type_sig = parse_type_sig(&self.type_sig)?;
28 match self.direction.to_lowercase().as_str() {
29 "input" => Ok(Port::new_input(id, self.name.clone(), type_sig)),
30 "output" => Ok(Port::new_output(id, self.name.clone(), type_sig)),
31 other => Err(WeaveError::TemplateError(format!(
32 "unknown direction: '{other}'"
33 ))),
34 }
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct BlockTemplate {
42 pub name: String,
44 pub version: u32,
46 pub ports: Vec<PortDef>,
48 pub required_atoms: Vec<String>,
50}
51
52impl BlockTemplate {
53 pub fn to_archetype(&self) -> Result<Archetype, WeaveError> {
57 let mut inputs = Vec::new();
58 let mut outputs = Vec::new();
59
60 for (i, def) in self.ports.iter().enumerate() {
61 let port = def.to_port(PortId::new((i + 1) as u64))?;
62 match port.direction {
63 PortDirection::Input => inputs.push(port),
64 PortDirection::Output => outputs.push(port),
65 }
66 }
67
68 let schema = BlockSchema::new(inputs, outputs);
69 schema
70 .validate()
71 .map_err(|e| WeaveError::ValidationFailed(e.to_string()))?;
72
73 let required = self
74 .required_atoms
75 .iter()
76 .map(|s| parse_atom_kind(s))
77 .collect::<Result<Vec<_>, _>>()?;
78
79 Ok(Archetype::new(
80 self.name.clone(),
81 self.version,
82 schema,
83 required,
84 ))
85 }
86
87 pub fn instantiate(&self, block_id: BlockId) -> Result<frp_domain::Block, WeaveError> {
89 self.to_archetype()?.instantiate(block_id)
90 }
91}
92
93fn parse_type_sig(s: &str) -> Result<TypeSig, WeaveError> {
96 match s.to_lowercase().as_str() {
97 "any" => Ok(TypeSig::Any),
98 "null" => Ok(TypeSig::Null),
99 "bool" => Ok(TypeSig::Bool),
100 "int" => Ok(TypeSig::Int),
101 "float" => Ok(TypeSig::Float),
102 "string" | "str" => Ok(TypeSig::String),
103 "bytes" => Ok(TypeSig::Bytes),
104 other => Err(WeaveError::TemplateError(format!(
105 "unknown type sig: '{other}'"
106 ))),
107 }
108}
109
110fn parse_atom_kind(s: &str) -> Result<AtomKind, WeaveError> {
111 match s.to_lowercase().as_str() {
112 "source" => Ok(AtomKind::Source),
113 "sink" => Ok(AtomKind::Sink),
114 "transform" => Ok(AtomKind::Transform),
115 "state" => Ok(AtomKind::State),
116 "trigger" => Ok(AtomKind::Trigger),
117 other => Err(WeaveError::TemplateError(format!(
118 "unknown atom kind: '{other}'"
119 ))),
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use frp_plexus::BlockId;
127
128 fn basic_template() -> BlockTemplate {
129 BlockTemplate {
130 name: "my_block".to_string(),
131 version: 1,
132 ports: vec![
133 PortDef {
134 name: "in".to_string(),
135 direction: "input".to_string(),
136 type_sig: "Any".to_string(),
137 },
138 PortDef {
139 name: "out".to_string(),
140 direction: "output".to_string(),
141 type_sig: "Int".to_string(),
142 },
143 ],
144 required_atoms: vec!["Transform".to_string()],
145 }
146 }
147
148 #[test]
149 fn to_archetype_success() {
150 let arch = basic_template().to_archetype().unwrap();
151 assert_eq!(arch.id, "my_block");
152 assert_eq!(arch.version, 1);
153 assert_eq!(arch.required_atoms, vec![AtomKind::Transform]);
154 }
155
156 #[test]
157 fn instantiate_success() {
158 let block = basic_template().instantiate(BlockId::new(5)).unwrap();
159 assert_eq!(block.id, BlockId::new(5));
160 }
161
162 #[test]
163 fn unknown_direction_fails() {
164 let mut t = basic_template();
165 t.ports[0].direction = "sideways".to_string();
166 let err = t.to_archetype().unwrap_err();
167 assert!(matches!(err, WeaveError::TemplateError(_)));
168 }
169
170 #[test]
171 fn unknown_type_sig_fails() {
172 let mut t = basic_template();
173 t.ports[0].type_sig = "Turbo".to_string();
174 let err = t.to_archetype().unwrap_err();
175 assert!(matches!(err, WeaveError::TemplateError(_)));
176 }
177
178 #[test]
179 fn unknown_atom_kind_fails() {
180 let mut t = basic_template();
181 t.required_atoms = vec!["Gadget".to_string()];
182 let err = t.to_archetype().unwrap_err();
183 assert!(matches!(err, WeaveError::TemplateError(_)));
184 }
185
186 #[test]
187 fn round_trip_serde() {
188 let t = basic_template();
189 let json = serde_json::to_string(&t).unwrap();
190 let back: BlockTemplate = serde_json::from_str(&json).unwrap();
191 assert_eq!(back.name, t.name);
192 assert_eq!(back.ports.len(), t.ports.len());
193 }
194}