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, }
331pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
333
334impl Metadata for () {}
335
336pub trait CuMsgMetadataTrait {
338 fn process_time(&self) -> PartialCuTimeRange;
340
341 fn status_txt(&self) -> &CuCompactString;
343}
344
345pub trait ErasedCuStampedData {
347 fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
348 fn tov(&self) -> Tov;
349 fn metadata(&self) -> &dyn CuMsgMetadataTrait;
350}
351
352pub trait ErasedCuStampedDataSet {
355 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
356}
357
358pub trait CuPayloadRawBytes {
360 fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
363}
364
365pub trait MatchingTasks {
367 fn get_all_task_ids() -> &'static [&'static str];
368}
369
370pub trait PayloadSchemas {
379 fn get_payload_schemas() -> Vec<(&'static str, String)> {
384 Vec::new()
385 }
386}
387
388pub trait CopperListTuple:
390 bincode::Encode
391 + bincode::Decode<()>
392 + Debug
393 + Serialize
394 + ErasedCuStampedDataSet
395 + MatchingTasks
396 + Default
397{
398} impl<T> CopperListTuple for T where
402 T: bincode::Encode
403 + bincode::Decode<()>
404 + Debug
405 + Serialize
406 + ErasedCuStampedDataSet
407 + MatchingTasks
408 + Default
409{
410}
411
412pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
416
417#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
418pub struct CuCompactString(pub CompactString);
419
420impl Encode for CuCompactString {
421 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
422 let CuCompactString(compact_string) = self;
423 let bytes = &compact_string.as_bytes();
424 bytes.encode(encoder)
425 }
426}
427
428impl Debug for CuCompactString {
429 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
430 if self.0.is_empty() {
431 return write!(f, "CuCompactString(Empty)");
432 }
433 write!(f, "CuCompactString({})", self.0)
434 }
435}
436
437impl<Context> Decode<Context> for CuCompactString {
438 fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
439 let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
441 CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
442 Ok(CuCompactString(compact_string))
443 }
444}
445
446impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
447 fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
448 CuCompactString::decode(decoder)
449 }
450}
451
452#[cfg(feature = "defmt")]
453impl defmt::Format for CuError {
454 fn format(&self, f: defmt::Formatter) {
455 match &self.cause {
456 Some(c) => {
457 let cause_str = c.to_string();
458 defmt::write!(
459 f,
460 "CuError {{ message: {}, cause: {} }}",
461 defmt::Display2Format(&self.message),
462 defmt::Display2Format(&cause_str),
463 )
464 }
465 None => defmt::write!(
466 f,
467 "CuError {{ message: {}, cause: None }}",
468 defmt::Display2Format(&self.message),
469 ),
470 }
471 }
472}
473
474#[cfg(feature = "defmt")]
475impl defmt::Format for CuCompactString {
476 fn format(&self, f: defmt::Formatter) {
477 if self.0.is_empty() {
478 defmt::write!(f, "CuCompactString(Empty)");
479 } else {
480 defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
481 }
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use crate::CuCompactString;
488 use bincode::{config, decode_from_slice, encode_to_vec};
489 use compact_str::CompactString;
490
491 #[test]
492 fn test_cucompactstr_encode_decode_empty() {
493 let cstr = CuCompactString(CompactString::from(""));
494 let config = config::standard();
495 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
496 assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
498 decode_from_slice(&encoded, config).expect("Decoding failed");
499 assert_eq!(cstr.0, decoded.0);
500 }
501
502 #[test]
503 fn test_cucompactstr_encode_decode_small() {
504 let cstr = CuCompactString(CompactString::from("test"));
505 let config = config::standard();
506 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
507 assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
509 decode_from_slice(&encoded, config).expect("Decoding failed");
510 assert_eq!(cstr.0, decoded.0);
511 }
512}
513
514#[cfg(all(test, feature = "std"))]
516mod std_tests {
517 use crate::{CuError, with_cause};
518
519 #[test]
520 fn test_cuerror_from_str() {
521 let err = CuError::from("test error");
522 assert_eq!(err.message(), "test error");
523 assert!(err.cause().is_none());
524 }
525
526 #[test]
527 fn test_cuerror_from_string() {
528 let err = CuError::from(String::from("test error"));
529 assert_eq!(err.message(), "test error");
530 assert!(err.cause().is_none());
531 }
532
533 #[test]
534 fn test_cuerror_new_index() {
535 let err = CuError::new(42);
536 assert_eq!(err.message(), "[interned:42]");
537 assert!(err.cause().is_none());
538 }
539
540 #[test]
541 fn test_cuerror_new_with_cause() {
542 let io_err = std::io::Error::other("io error");
543 let err = CuError::new_with_cause("wrapped error", io_err);
544 assert_eq!(err.message(), "wrapped error");
545 assert!(err.cause().is_some());
546 assert!(err.cause().unwrap().to_string().contains("io error"));
547 }
548
549 #[test]
550 fn test_cuerror_add_cause() {
551 let err = CuError::from("base error").add_cause("additional context");
552 assert_eq!(err.message(), "base error");
553 assert!(err.cause().is_some());
554 assert_eq!(err.cause().unwrap().to_string(), "additional context");
555 }
556
557 #[test]
558 fn test_cuerror_with_cause_method() {
559 let io_err = std::io::Error::other("io error");
560 let err = CuError::from("base error").with_cause(io_err);
561 assert_eq!(err.message(), "base error");
562 assert!(err.cause().is_some());
563 }
564
565 #[test]
566 fn test_cuerror_with_cause_free_function() {
567 let io_err = std::io::Error::other("io error");
568 let err = with_cause("wrapped", io_err);
569 assert_eq!(err.message(), "wrapped");
570 assert!(err.cause().is_some());
571 }
572
573 #[test]
574 fn test_cuerror_clone() {
575 let io_err = std::io::Error::other("io error");
576 let err = CuError::new_with_cause("test", io_err);
577 let cloned = err.clone();
578 assert_eq!(err.message(), cloned.message());
579 assert_eq!(
581 err.cause().map(|c| c.to_string()),
582 cloned.cause().map(|c| c.to_string())
583 );
584 }
585
586 #[test]
587 fn test_cuerror_serialize_deserialize_json() {
588 let io_err = std::io::Error::other("io error");
589 let err = CuError::new_with_cause("test", io_err);
590
591 let serialized = serde_json::to_string(&err).unwrap();
592 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
593
594 assert_eq!(err.message(), deserialized.message());
595 assert!(deserialized.cause().is_some());
597 }
598
599 #[test]
600 fn test_cuerror_serialize_deserialize_no_cause() {
601 let err = CuError::from("simple error");
602
603 let serialized = serde_json::to_string(&err).unwrap();
604 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
605
606 assert_eq!(err.message(), deserialized.message());
607 assert!(deserialized.cause().is_none());
608 }
609
610 #[test]
611 fn test_cuerror_display() {
612 let err = CuError::from("test error").add_cause("some context");
613 let display = format!("{}", err);
614 assert!(display.contains("test error"));
615 assert!(display.contains("some context"));
616 }
617
618 #[test]
619 fn test_cuerror_debug() {
620 let err = CuError::from("test error").add_cause("some context");
621 let debug = format!("{:?}", err);
622 assert!(debug.contains("test error"));
623 assert!(debug.contains("some context"));
624 }
625}