logic_mesh/base/block/
mod.rs1pub mod connect;
8pub mod desc;
9pub mod props;
10
11use anyhow::Result;
12pub use connect::BlockConnect;
13pub use desc::{BlockDesc, BlockPin, BlockStaticDesc};
14use libhaystack::{
15 encoding::zinc,
16 val::{kind::HaystackKind, Bool, Number, Str, Value},
17};
18pub use props::BlockProps;
19
20#[derive(Default, Debug, Clone, Copy, PartialEq)]
22pub enum BlockState {
23 #[default]
24 Stopped,
25 Running,
26 Fault,
27 Terminated,
28}
29
30pub trait Block: BlockConnect {
31 #[allow(async_fn_in_trait)]
32 async fn execute(&mut self);
33}
34
35pub fn convert_value(expect: &Value, actual: Value) -> Result<Value> {
45 let to_kind = HaystackKind::from(&actual);
46 convert_value_kind(actual, HaystackKind::from(expect), to_kind)
47}
48
49pub fn convert_value_kind(
59 val: Value,
60 expected: HaystackKind,
61 actual: HaystackKind,
62) -> Result<Value> {
63 if expected == actual || actual == HaystackKind::Null {
64 return Ok(val);
65 }
66
67 match (expected, actual) {
68 (HaystackKind::Bool, HaystackKind::Bool) => Ok(val),
69 (HaystackKind::Bool, HaystackKind::Number) => {
70 let val = Number::try_from(&val).map_err(|err| anyhow::anyhow!(err))?;
71
72 Ok((val.value != 0.0).into())
73 }
74 (HaystackKind::Bool, HaystackKind::Str) => {
75 let val = Str::try_from(&val).map_err(|err| anyhow::anyhow!(err))?;
76
77 if val.value == "true" || val.value == "false" {
78 return Ok(val.value.parse::<bool>()?.into());
79 }
80
81 let num = zinc::decode::from_str(&val.value)?;
82 match num {
83 Value::Number(Number { value, unit: _ }) => Ok((value != 0.0).into()),
84 Value::Bool(Bool { value }) => Ok(value.into()),
85 _ => Err(anyhow::anyhow!("Expected a bool value, but got {:?}", val)),
86 }
87 }
88
89 (HaystackKind::Number, HaystackKind::Number) => Ok(val),
90 (HaystackKind::Number, HaystackKind::Bool) => {
91 let val = Bool::try_from(&val).map_err(|err| anyhow::anyhow!(err))?;
92
93 Ok((if val.value { 1 } else { 0 }).into())
94 }
95 (HaystackKind::Number, HaystackKind::Str) => {
96 let val = Str::try_from(&val).map_err(|err| anyhow::anyhow!(err))?;
97
98 let num = zinc::decode::from_str(&val.value)?;
99 if num.is_number() {
100 Ok(num)
101 } else {
102 Err(anyhow::anyhow!(
103 "Expected a number value, but got {:?}",
104 val
105 ))
106 }
107 }
108
109 (HaystackKind::Str, HaystackKind::Str) => Ok(val),
110 (HaystackKind::Str, HaystackKind::Bool) => Ok(val.to_string().as_str().into()),
111 (HaystackKind::Str, HaystackKind::Number) => {
112 let str = zinc::encode::to_zinc_string(&val)?;
113 Ok(str.as_str().into())
114 }
115
116 (HaystackKind::Str, _) => {
117 let str = zinc::encode::to_zinc_string(&val)?;
118 Ok(str.as_str().into())
119 }
120
121 _ => Err(anyhow::anyhow!(
122 "Cannot convert {:?} to {:?}",
123 actual,
124 expected
125 )),
126 }
127}
128
129#[cfg(test)]
130pub(crate) mod test_utils;
131#[cfg(test)]
132mod test {
133 use uuid::Uuid;
134
135 use crate::base::{
136 block::{Block, BlockDesc, BlockProps, BlockState},
137 input::{Input, InputProps},
138 output::Output,
139 };
140
141 use super::test_utils::mock::{InputImpl, OutputImpl};
142
143 use libhaystack::val::{kind::HaystackKind, Value};
144
145 #[block]
146 #[derive(BlockProps, Debug)]
147 #[dis = "Test long name"]
148 #[library = "test"]
149 #[category = "test"]
150 #[input(kind = "Number", count = 16)]
151 struct Test {
152 #[input(kind = "Number")]
153 user_defined: InputImpl,
154 #[output(kind = "Number")]
155 out: OutputImpl,
156 }
157
158 impl Block for Test {
159 async fn execute(&mut self) {
160 self.out.value = Value::make_int(42);
161 }
162 }
163
164 #[test]
165 fn test_block_props_declared_inputs() {
166 let test_block = &Test::new() as &dyn BlockProps<Reader = String, Writer = String>;
167
168 assert_eq!(test_block.desc().name, "Test");
169 assert_eq!(test_block.desc().dis, "Test long name");
170 assert_eq!(test_block.desc().library, "test");
171 assert_eq!(test_block.state(), BlockState::Stopped);
172 assert_eq!(test_block.inputs().len(), 17);
173 assert_eq!(test_block.outputs().len(), 1);
174
175 assert_eq!(
176 test_block
177 .inputs()
178 .iter()
179 .filter(|input| input.name().starts_with("in"))
180 .count(),
181 16
182 );
183
184 assert!(test_block
185 .inputs()
186 .iter()
187 .filter(|input| input.name().starts_with("in"))
188 .enumerate()
189 .all(|(i, input)| input.name() == format!("in{}", i)));
190
191 assert!(test_block
192 .inputs()
193 .iter()
194 .all(|i| i.kind() == &HaystackKind::Number));
195
196 assert!(test_block.outputs()[0].desc().name == "out");
197 assert!(test_block.outputs()[0].desc().kind == HaystackKind::Number);
198 assert!(!test_block.outputs()[0].is_connected());
199 }
200
201 #[test]
202 fn test_block_outputs() {
203 let test_block = &Test::new() as &dyn BlockProps<Reader = String, Writer = String>;
204
205 assert_eq!(test_block.outputs().len(), 1);
206 assert_eq!(test_block.outputs()[0].desc().name, "out");
207 assert_eq!(test_block.outputs()[0].desc().kind, HaystackKind::Number);
208 assert!(!test_block.outputs()[0].is_connected());
209 }
210
211 #[test]
212 fn convert_value_num_to_bool_test() {
213 let val = Value::make_bool(true);
214 let converted =
215 super::convert_value_kind(val, HaystackKind::Number, HaystackKind::Bool).unwrap();
216 assert_eq!(converted, Value::make_int(1));
217 }
218
219 #[test]
220 fn convert_value_str_to_num_test() {
221 let val = Value::make_str("42");
222 let converted =
223 super::convert_value_kind(val, HaystackKind::Number, HaystackKind::Str).unwrap();
224 assert_eq!(converted, Value::make_int(42));
225 }
226}