logic_mesh/base/block/
mod.rs

1// Copyright (c) 2022-2023, Radu Racariu.
2
3//!
4//! Defines the block trait and associated types
5//!
6
7pub 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/// Determines the state a block is in
21#[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
35/// Converts the actual value to the expected type expected value.
36///
37/// # Arguments
38/// - `expect` The expected value, this is used to determine the expected type
39/// - `actual` The actual value to convert
40///
41/// # Returns
42/// The converted value if the conversion was successful.
43/// If the conversion was not successful, an error is returned.
44pub 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
49/// Converts a value from one kind to another.
50///
51/// # Arguments
52/// - `val` The value to convert
53/// - `expected` The expected kind of the value
54/// - `actual` The actual kind of the value
55///
56/// # Returns
57/// The converted value if the conversion was successful.
58pub 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}