cu29_traits/
lib.rs

1use bincode::de::{BorrowDecoder, Decoder};
2use bincode::enc::Encoder;
3use bincode::error::{DecodeError, EncodeError};
4use bincode::{BorrowDecode, Decode as dDecode, Decode, Encode, Encode as dEncode};
5use compact_str::CompactString;
6use cu29_clock::{PartialCuTimeRange, Tov};
7use serde::{Deserialize, Serialize};
8use std::error::Error;
9use std::fmt::{Debug, Display, Formatter};
10
11/// Common copper Error type.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CuError {
14    message: String,
15    cause: Option<String>,
16}
17
18impl Display for CuError {
19    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
20        let context_str = match &self.cause {
21            Some(c) => c.to_string(),
22            None => "None".to_string(),
23        };
24        write!(f, "{}\n   context:{}", self.message, context_str)?;
25        Ok(())
26    }
27}
28
29impl Error for CuError {}
30
31impl From<&str> for CuError {
32    fn from(s: &str) -> CuError {
33        CuError {
34            message: s.to_string(),
35            cause: None,
36        }
37    }
38}
39
40impl From<String> for CuError {
41    fn from(s: String) -> CuError {
42        CuError {
43            message: s,
44            cause: None,
45        }
46    }
47}
48
49impl CuError {
50    pub fn new_with_cause(message: &str, cause: impl Error) -> CuError {
51        CuError {
52            message: message.to_string(),
53            cause: Some(cause.to_string()),
54        }
55    }
56
57    pub fn add_cause(mut self, context: &str) -> CuError {
58        self.cause = Some(context.into());
59        self
60    }
61}
62
63// Generic Result type for copper.
64pub type CuResult<T> = Result<T, CuError>;
65
66/// Defines a basic write, append only stream trait to be able to log or send serializable objects.
67pub trait WriteStream<E: Encode>: Sync + Send + Debug {
68    fn log(&mut self, obj: &E) -> CuResult<()>;
69    fn flush(&mut self) -> CuResult<()> {
70        Ok(())
71    }
72}
73
74/// Defines the types of what can be logged in the unified logger.
75#[derive(dEncode, dDecode, Copy, Clone, Debug, PartialEq)]
76pub enum UnifiedLogType {
77    Empty,             // Dummy default used as a debug marker
78    StructuredLogLine, // This is for the structured logs (ie. debug! etc..)
79    CopperList,        // This is the actual data log storing activities between tasks.
80    FrozenTasks,       // Log of all frozen state of the tasks.
81    LastEntry,         // This is a special entry that is used to signal the end of the log.
82}
83/// Represent the minimum set of traits to be usable as Metadata in Copper.
84pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
85
86impl Metadata for () {}
87
88/// Key metadata piece attached to every message in Copper.
89pub trait CuMsgMetadataTrait {
90    /// The time range used for the processing of this message
91    fn process_time(&self) -> PartialCuTimeRange;
92
93    /// Small status text for user UI to get the realtime state of task (max 24 chrs)
94    fn status_txt(&self) -> &CuCompactString;
95}
96
97/// A generic trait to expose the generated CuStampedDataSet from the task graph.
98pub trait ErasedCuStampedData {
99    fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
100    fn tov(&self) -> Tov;
101    fn metadata(&self) -> &dyn CuMsgMetadataTrait;
102}
103
104/// Trait to get a vector of type-erased CuStampedDataSet
105/// This is used for generic serialization of the copperlists
106pub trait ErasedCuStampedDataSet {
107    fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
108}
109
110/// Trait to trace back from the CopperList the origin of the messages
111pub trait MatchingTasks {
112    fn get_all_task_ids() -> &'static [&'static str];
113}
114
115/// A CopperListTuple needs to be encodable, decodable and fixed size in memory.
116pub trait CopperListTuple:
117    bincode::Encode
118    + bincode::Decode<()>
119    + Debug
120    + Serialize
121    + ErasedCuStampedDataSet
122    + MatchingTasks
123    + Default
124{
125} // Decode forces Sized already
126
127// Also anything that follows this contract can be a payload (blanket implementation)
128impl<T> CopperListTuple for T where
129    T: bincode::Encode
130        + bincode::Decode<()>
131        + Debug
132        + Serialize
133        + ErasedCuStampedDataSet
134        + MatchingTasks
135        + Default
136{
137}
138
139// We use this type to convey very small status messages.
140// MAX_SIZE from their repr module is not accessible so we need to copy paste their definition for 24
141// which is the maximum size for inline allocation (no heap)
142pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
143
144#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
145pub struct CuCompactString(pub CompactString);
146
147impl Encode for CuCompactString {
148    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
149        let CuCompactString(ref compact_string) = self;
150        let bytes = &compact_string.as_bytes();
151        bytes.encode(encoder)
152    }
153}
154
155impl Debug for CuCompactString {
156    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
157        if self.0.is_empty() {
158            return write!(f, "CuCompactString(Empty)");
159        }
160        write!(f, "CuCompactString({})", self.0)
161    }
162}
163
164impl<Context> Decode<Context> for CuCompactString {
165    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
166        let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; // Decode into a byte buffer
167        let compact_string =
168            CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
169        Ok(CuCompactString(compact_string))
170    }
171}
172
173impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
174    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
175        CuCompactString::decode(decoder)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use crate::CuCompactString;
182    use bincode::{config, decode_from_slice, encode_to_vec};
183    use compact_str::CompactString;
184
185    #[test]
186    fn test_cucompactstr_encode_decode_empty() {
187        let cstr = CuCompactString(CompactString::from(""));
188        let config = config::standard();
189        let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
190        assert_eq!(encoded.len(), 1); // This encodes the usize 0 in variable encoding so 1 byte which is 0.
191        let (decoded, _): (CuCompactString, usize) =
192            decode_from_slice(&encoded, config).expect("Decoding failed");
193        assert_eq!(cstr.0, decoded.0);
194    }
195
196    #[test]
197    fn test_cucompactstr_encode_decode_small() {
198        let cstr = CuCompactString(CompactString::from("test"));
199        let config = config::standard();
200        let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
201        assert_eq!(encoded.len(), 5); // This encodes a 4-byte string "test" plus 1 byte for the length prefix.
202        let (decoded, _): (CuCompactString, usize) =
203            decode_from_slice(&encoded, config).expect("Decoding failed");
204        assert_eq!(cstr.0, decoded.0);
205    }
206}