1#![cfg_attr(not(feature = "std"), no_std)]
23extern crate alloc;
24
25use bincode::de::{BorrowDecoder, Decoder};
26use bincode::enc::Encoder;
27use bincode::error::{DecodeError, EncodeError};
28use bincode::{BorrowDecode, Decode as dDecode, Decode, Encode, Encode as dEncode};
29use compact_str::CompactString;
30use cu29_clock::{PartialCuTimeRange, Tov};
31use serde::{Deserialize, Serialize};
32
33use alloc::boxed::Box;
34use alloc::format;
35use alloc::string::{String, ToString};
36use alloc::vec::Vec;
37#[cfg(not(feature = "std"))]
38use core::error::Error as CoreError;
39use core::fmt::{Debug, Display, Formatter};
40#[cfg(feature = "std")]
41use std::error::Error;
42
43#[cfg(feature = "std")]
45type DynError = dyn std::error::Error + Send + Sync + 'static;
46#[cfg(not(feature = "std"))]
47type DynError = dyn core::error::Error + Send + Sync + 'static;
48
49#[derive(Debug)]
52struct StringError(String);
53
54impl Display for StringError {
55 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
56 write!(f, "{}", self.0)
57 }
58}
59
60#[cfg(feature = "std")]
61impl std::error::Error for StringError {}
62
63#[cfg(not(feature = "std"))]
64impl core::error::Error for StringError {}
65
66pub struct CuError {
72 message: String,
73 cause: Option<Box<DynError>>,
74}
75
76impl Debug for CuError {
78 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
79 f.debug_struct("CuError")
80 .field("message", &self.message)
81 .field("cause", &self.cause.as_ref().map(|e| e.to_string()))
82 .finish()
83 }
84}
85
86impl Clone for CuError {
88 fn clone(&self) -> Self {
89 CuError {
90 message: self.message.clone(),
91 cause: self
92 .cause
93 .as_ref()
94 .map(|e| Box::new(StringError(e.to_string())) as Box<DynError>),
95 }
96 }
97}
98
99impl Serialize for CuError {
101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: serde::Serializer,
104 {
105 use serde::ser::SerializeStruct;
106 let mut state = serializer.serialize_struct("CuError", 2)?;
107 state.serialize_field("message", &self.message)?;
108 state.serialize_field("cause", &self.cause.as_ref().map(|e| e.to_string()))?;
109 state.end()
110 }
111}
112
113impl<'de> Deserialize<'de> for CuError {
115 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116 where
117 D: serde::Deserializer<'de>,
118 {
119 #[derive(Deserialize)]
120 struct CuErrorHelper {
121 message: String,
122 cause: Option<String>,
123 }
124
125 let helper = CuErrorHelper::deserialize(deserializer)?;
126 Ok(CuError {
127 message: helper.message,
128 cause: helper
129 .cause
130 .map(|s| Box::new(StringError(s)) as Box<DynError>),
131 })
132 }
133}
134
135impl Display for CuError {
136 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
137 let context_str = match &self.cause {
138 Some(c) => c.to_string(),
139 None => "None".to_string(),
140 };
141 write!(f, "{}\n context:{}", self.message, context_str)?;
142 Ok(())
143 }
144}
145
146#[cfg(not(feature = "std"))]
147impl CoreError for CuError {
148 fn source(&self) -> Option<&(dyn CoreError + 'static)> {
149 self.cause
150 .as_deref()
151 .map(|e| e as &(dyn CoreError + 'static))
152 }
153}
154
155#[cfg(feature = "std")]
156impl Error for CuError {
157 fn source(&self) -> Option<&(dyn Error + 'static)> {
158 self.cause.as_deref().map(|e| e as &(dyn Error + 'static))
159 }
160}
161
162impl From<&str> for CuError {
163 fn from(s: &str) -> CuError {
164 CuError {
165 message: s.to_string(),
166 cause: None,
167 }
168 }
169}
170
171impl From<String> for CuError {
172 fn from(s: String) -> CuError {
173 CuError {
174 message: s,
175 cause: None,
176 }
177 }
178}
179
180impl CuError {
181 pub fn new(message_index: usize) -> CuError {
187 CuError {
188 message: format!("[interned:{}]", message_index),
189 cause: None,
190 }
191 }
192
193 #[cfg(feature = "std")]
203 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
204 where
205 E: std::error::Error + Send + Sync + 'static,
206 {
207 CuError {
208 message: message.to_string(),
209 cause: Some(Box::new(cause)),
210 }
211 }
212
213 #[cfg(not(feature = "std"))]
215 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
216 where
217 E: core::error::Error + Send + Sync + 'static,
218 {
219 CuError {
220 message: message.to_string(),
221 cause: Some(Box::new(cause)),
222 }
223 }
224
225 pub fn add_cause(mut self, context: &str) -> CuError {
236 self.cause = Some(Box::new(StringError(context.to_string())));
237 self
238 }
239
240 #[cfg(feature = "std")]
250 pub fn with_cause<E>(mut self, cause: E) -> CuError
251 where
252 E: std::error::Error + Send + Sync + 'static,
253 {
254 self.cause = Some(Box::new(cause));
255 self
256 }
257
258 #[cfg(not(feature = "std"))]
260 pub fn with_cause<E>(mut self, cause: E) -> CuError
261 where
262 E: core::error::Error + Send + Sync + 'static,
263 {
264 self.cause = Some(Box::new(cause));
265 self
266 }
267
268 pub fn cause(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> {
270 self.cause.as_deref()
271 }
272
273 pub fn message(&self) -> &str {
275 &self.message
276 }
277}
278
279#[cfg(feature = "std")]
291pub fn with_cause<E>(message: &str, cause: E) -> CuError
292where
293 E: std::error::Error + Send + Sync + 'static,
294{
295 CuError::new_with_cause(message, cause)
296}
297
298#[cfg(not(feature = "std"))]
300pub fn with_cause<E>(message: &str, cause: E) -> CuError
301where
302 E: core::error::Error + Send + Sync + 'static,
303{
304 CuError::new_with_cause(message, cause)
305}
306
307pub type CuResult<T> = Result<T, CuError>;
309
310pub trait WriteStream<E: Encode>: Debug + Send + Sync {
312 fn log(&mut self, obj: &E) -> CuResult<()>;
313 fn flush(&mut self) -> CuResult<()> {
314 Ok(())
315 }
316 fn last_log_bytes(&self) -> Option<usize> {
318 None
319 }
320}
321
322#[derive(dEncode, dDecode, Copy, Clone, Debug, PartialEq)]
324pub enum UnifiedLogType {
325 Empty, StructuredLogLine, CopperList, FrozenTasks, LastEntry, RuntimeLifecycle, }
332pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
334
335impl Metadata for () {}
336
337pub trait CuMsgMetadataTrait {
339 fn process_time(&self) -> PartialCuTimeRange;
341
342 fn status_txt(&self) -> &CuCompactString;
344}
345
346pub trait ErasedCuStampedData {
348 fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
349 fn tov(&self) -> Tov;
350 fn metadata(&self) -> &dyn CuMsgMetadataTrait;
351}
352
353pub trait ErasedCuStampedDataSet {
356 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
357}
358
359pub trait CuPayloadRawBytes {
361 fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
364}
365
366pub trait MatchingTasks {
368 fn get_all_task_ids() -> &'static [&'static str];
369}
370
371pub trait PayloadSchemas {
380 fn get_payload_schemas() -> Vec<(&'static str, String)> {
385 Vec::new()
386 }
387}
388
389pub trait CopperListTuple:
391 bincode::Encode
392 + bincode::Decode<()>
393 + Debug
394 + Serialize
395 + ErasedCuStampedDataSet
396 + MatchingTasks
397 + Default
398{
399} impl<T> CopperListTuple for T where
403 T: bincode::Encode
404 + bincode::Decode<()>
405 + Debug
406 + Serialize
407 + ErasedCuStampedDataSet
408 + MatchingTasks
409 + Default
410{
411}
412
413pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
417
418#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
419pub struct CuCompactString(pub CompactString);
420
421impl Encode for CuCompactString {
422 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
423 let CuCompactString(compact_string) = self;
424 let bytes = &compact_string.as_bytes();
425 bytes.encode(encoder)
426 }
427}
428
429impl Debug for CuCompactString {
430 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
431 if self.0.is_empty() {
432 return write!(f, "CuCompactString(Empty)");
433 }
434 write!(f, "CuCompactString({})", self.0)
435 }
436}
437
438impl<Context> Decode<Context> for CuCompactString {
439 fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
440 let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
442 CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
443 Ok(CuCompactString(compact_string))
444 }
445}
446
447impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
448 fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
449 CuCompactString::decode(decoder)
450 }
451}
452
453#[cfg(feature = "defmt")]
454impl defmt::Format for CuError {
455 fn format(&self, f: defmt::Formatter) {
456 match &self.cause {
457 Some(c) => {
458 let cause_str = c.to_string();
459 defmt::write!(
460 f,
461 "CuError {{ message: {}, cause: {} }}",
462 defmt::Display2Format(&self.message),
463 defmt::Display2Format(&cause_str),
464 )
465 }
466 None => defmt::write!(
467 f,
468 "CuError {{ message: {}, cause: None }}",
469 defmt::Display2Format(&self.message),
470 ),
471 }
472 }
473}
474
475#[cfg(feature = "defmt")]
476impl defmt::Format for CuCompactString {
477 fn format(&self, f: defmt::Formatter) {
478 if self.0.is_empty() {
479 defmt::write!(f, "CuCompactString(Empty)");
480 } else {
481 defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use crate::CuCompactString;
489 use bincode::{config, decode_from_slice, encode_to_vec};
490 use compact_str::CompactString;
491
492 #[test]
493 fn test_cucompactstr_encode_decode_empty() {
494 let cstr = CuCompactString(CompactString::from(""));
495 let config = config::standard();
496 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
497 assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
499 decode_from_slice(&encoded, config).expect("Decoding failed");
500 assert_eq!(cstr.0, decoded.0);
501 }
502
503 #[test]
504 fn test_cucompactstr_encode_decode_small() {
505 let cstr = CuCompactString(CompactString::from("test"));
506 let config = config::standard();
507 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
508 assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
510 decode_from_slice(&encoded, config).expect("Decoding failed");
511 assert_eq!(cstr.0, decoded.0);
512 }
513}
514
515#[cfg(all(test, feature = "std"))]
517mod std_tests {
518 use crate::{CuError, with_cause};
519
520 #[test]
521 fn test_cuerror_from_str() {
522 let err = CuError::from("test error");
523 assert_eq!(err.message(), "test error");
524 assert!(err.cause().is_none());
525 }
526
527 #[test]
528 fn test_cuerror_from_string() {
529 let err = CuError::from(String::from("test error"));
530 assert_eq!(err.message(), "test error");
531 assert!(err.cause().is_none());
532 }
533
534 #[test]
535 fn test_cuerror_new_index() {
536 let err = CuError::new(42);
537 assert_eq!(err.message(), "[interned:42]");
538 assert!(err.cause().is_none());
539 }
540
541 #[test]
542 fn test_cuerror_new_with_cause() {
543 let io_err = std::io::Error::other("io error");
544 let err = CuError::new_with_cause("wrapped error", io_err);
545 assert_eq!(err.message(), "wrapped error");
546 assert!(err.cause().is_some());
547 assert!(err.cause().unwrap().to_string().contains("io error"));
548 }
549
550 #[test]
551 fn test_cuerror_add_cause() {
552 let err = CuError::from("base error").add_cause("additional context");
553 assert_eq!(err.message(), "base error");
554 assert!(err.cause().is_some());
555 assert_eq!(err.cause().unwrap().to_string(), "additional context");
556 }
557
558 #[test]
559 fn test_cuerror_with_cause_method() {
560 let io_err = std::io::Error::other("io error");
561 let err = CuError::from("base error").with_cause(io_err);
562 assert_eq!(err.message(), "base error");
563 assert!(err.cause().is_some());
564 }
565
566 #[test]
567 fn test_cuerror_with_cause_free_function() {
568 let io_err = std::io::Error::other("io error");
569 let err = with_cause("wrapped", io_err);
570 assert_eq!(err.message(), "wrapped");
571 assert!(err.cause().is_some());
572 }
573
574 #[test]
575 fn test_cuerror_clone() {
576 let io_err = std::io::Error::other("io error");
577 let err = CuError::new_with_cause("test", io_err);
578 let cloned = err.clone();
579 assert_eq!(err.message(), cloned.message());
580 assert_eq!(
582 err.cause().map(|c| c.to_string()),
583 cloned.cause().map(|c| c.to_string())
584 );
585 }
586
587 #[test]
588 fn test_cuerror_serialize_deserialize_json() {
589 let io_err = std::io::Error::other("io error");
590 let err = CuError::new_with_cause("test", io_err);
591
592 let serialized = serde_json::to_string(&err).unwrap();
593 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
594
595 assert_eq!(err.message(), deserialized.message());
596 assert!(deserialized.cause().is_some());
598 }
599
600 #[test]
601 fn test_cuerror_serialize_deserialize_no_cause() {
602 let err = CuError::from("simple error");
603
604 let serialized = serde_json::to_string(&err).unwrap();
605 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
606
607 assert_eq!(err.message(), deserialized.message());
608 assert!(deserialized.cause().is_none());
609 }
610
611 #[test]
612 fn test_cuerror_display() {
613 let err = CuError::from("test error").add_cause("some context");
614 let display = format!("{}", err);
615 assert!(display.contains("test error"));
616 assert!(display.contains("some context"));
617 }
618
619 #[test]
620 fn test_cuerror_debug() {
621 let err = CuError::from("test error").add_cause("some context");
622 let debug = format!("{:?}", err);
623 assert!(debug.contains("test error"));
624 assert!(debug.contains("some context"));
625 }
626}