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