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::enc::write::Writer;
30use bincode::error::{DecodeError, EncodeError};
31use bincode::{BorrowDecode, Decode as dDecode, Decode, Encode, Encode as dEncode};
32use compact_str::CompactString;
33use cu29_clock::{PartialCuTimeRange, Tov};
34use serde::{Deserialize, Serialize};
35
36use alloc::boxed::Box;
37use alloc::format;
38use alloc::string::{String, ToString};
39use alloc::vec::Vec;
40#[cfg(feature = "std")]
41use core::cell::Cell;
42#[cfg(not(feature = "std"))]
43use core::error::Error as CoreError;
44use core::fmt::{Debug, Display, Formatter};
45#[cfg(feature = "std")]
46use std::error::Error;
47
48#[cfg(not(feature = "std"))]
49use spin::Mutex as SpinMutex;
50
51#[cfg(feature = "std")]
53type DynError = dyn std::error::Error + Send + Sync + 'static;
54#[cfg(not(feature = "std"))]
55type DynError = dyn core::error::Error + Send + Sync + 'static;
56
57#[derive(Debug)]
60struct StringError(String);
61
62impl Display for StringError {
63 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
64 write!(f, "{}", self.0)
65 }
66}
67
68#[cfg(feature = "std")]
69impl std::error::Error for StringError {}
70
71#[cfg(not(feature = "std"))]
72impl core::error::Error for StringError {}
73
74pub struct CuError {
80 message: String,
81 cause: Option<Box<DynError>>,
82}
83
84impl Debug for CuError {
86 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
87 f.debug_struct("CuError")
88 .field("message", &self.message)
89 .field("cause", &self.cause.as_ref().map(|e| e.to_string()))
90 .finish()
91 }
92}
93
94impl Clone for CuError {
96 fn clone(&self) -> Self {
97 CuError {
98 message: self.message.clone(),
99 cause: self
100 .cause
101 .as_ref()
102 .map(|e| Box::new(StringError(e.to_string())) as Box<DynError>),
103 }
104 }
105}
106
107impl Serialize for CuError {
109 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110 where
111 S: serde::Serializer,
112 {
113 use serde::ser::SerializeStruct;
114 let mut state = serializer.serialize_struct("CuError", 2)?;
115 state.serialize_field("message", &self.message)?;
116 state.serialize_field("cause", &self.cause.as_ref().map(|e| e.to_string()))?;
117 state.end()
118 }
119}
120
121impl<'de> Deserialize<'de> for CuError {
123 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124 where
125 D: serde::Deserializer<'de>,
126 {
127 #[derive(Deserialize)]
128 struct CuErrorHelper {
129 message: String,
130 cause: Option<String>,
131 }
132
133 let helper = CuErrorHelper::deserialize(deserializer)?;
134 Ok(CuError {
135 message: helper.message,
136 cause: helper
137 .cause
138 .map(|s| Box::new(StringError(s)) as Box<DynError>),
139 })
140 }
141}
142
143impl Display for CuError {
144 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
145 let context_str = match &self.cause {
146 Some(c) => c.to_string(),
147 None => "None".to_string(),
148 };
149 write!(f, "{}\n context:{}", self.message, context_str)?;
150 Ok(())
151 }
152}
153
154#[cfg(not(feature = "std"))]
155impl CoreError for CuError {
156 fn source(&self) -> Option<&(dyn CoreError + 'static)> {
157 self.cause
158 .as_deref()
159 .map(|e| e as &(dyn CoreError + 'static))
160 }
161}
162
163#[cfg(feature = "std")]
164impl Error for CuError {
165 fn source(&self) -> Option<&(dyn Error + 'static)> {
166 self.cause.as_deref().map(|e| e as &(dyn Error + 'static))
167 }
168}
169
170impl From<&str> for CuError {
171 fn from(s: &str) -> CuError {
172 CuError {
173 message: s.to_string(),
174 cause: None,
175 }
176 }
177}
178
179impl From<String> for CuError {
180 fn from(s: String) -> CuError {
181 CuError {
182 message: s,
183 cause: None,
184 }
185 }
186}
187
188impl CuError {
189 pub fn new(message_index: usize) -> CuError {
195 CuError {
196 message: format!("[interned:{}]", message_index),
197 cause: None,
198 }
199 }
200
201 #[cfg(feature = "std")]
211 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
212 where
213 E: std::error::Error + Send + Sync + 'static,
214 {
215 CuError {
216 message: message.to_string(),
217 cause: Some(Box::new(cause)),
218 }
219 }
220
221 #[cfg(not(feature = "std"))]
223 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
224 where
225 E: core::error::Error + Send + Sync + 'static,
226 {
227 CuError {
228 message: message.to_string(),
229 cause: Some(Box::new(cause)),
230 }
231 }
232
233 pub fn add_cause(mut self, context: &str) -> CuError {
244 self.cause = Some(Box::new(StringError(context.to_string())));
245 self
246 }
247
248 #[cfg(feature = "std")]
258 pub fn with_cause<E>(mut self, cause: E) -> CuError
259 where
260 E: std::error::Error + Send + Sync + 'static,
261 {
262 self.cause = Some(Box::new(cause));
263 self
264 }
265
266 #[cfg(not(feature = "std"))]
268 pub fn with_cause<E>(mut self, cause: E) -> CuError
269 where
270 E: core::error::Error + Send + Sync + 'static,
271 {
272 self.cause = Some(Box::new(cause));
273 self
274 }
275
276 pub fn cause(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> {
278 self.cause.as_deref()
279 }
280
281 pub fn message(&self) -> &str {
283 &self.message
284 }
285}
286
287#[cfg(feature = "std")]
299pub fn with_cause<E>(message: &str, cause: E) -> CuError
300where
301 E: std::error::Error + Send + Sync + 'static,
302{
303 CuError::new_with_cause(message, cause)
304}
305
306#[cfg(not(feature = "std"))]
308pub fn with_cause<E>(message: &str, cause: E) -> CuError
309where
310 E: core::error::Error + Send + Sync + 'static,
311{
312 CuError::new_with_cause(message, cause)
313}
314
315pub type CuResult<T> = Result<T, CuError>;
317
318#[cfg(feature = "std")]
319thread_local! {
320 static OBSERVED_ENCODE_BYTES: Cell<Option<usize>> = const { Cell::new(None) };
321}
322
323#[cfg(not(feature = "std"))]
324static OBSERVED_ENCODE_BYTES: SpinMutex<Option<usize>> = SpinMutex::new(None);
325
326pub fn begin_observed_encode() {
328 #[cfg(feature = "std")]
329 OBSERVED_ENCODE_BYTES.with(|bytes| {
330 debug_assert!(
331 bytes.get().is_none(),
332 "observed encode measurement must not be nested"
333 );
334 bytes.set(Some(0));
335 });
336
337 #[cfg(not(feature = "std"))]
338 {
339 let mut bytes = OBSERVED_ENCODE_BYTES.lock();
340 debug_assert!(
341 bytes.is_none(),
342 "observed encode measurement must not be nested"
343 );
344 *bytes = Some(0);
345 }
346}
347
348pub fn finish_observed_encode() -> usize {
350 #[cfg(feature = "std")]
351 {
352 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.replace(None).unwrap_or(0))
353 }
354
355 #[cfg(not(feature = "std"))]
356 {
357 OBSERVED_ENCODE_BYTES.lock().take().unwrap_or(0)
358 }
359}
360
361pub fn abort_observed_encode() {
363 #[cfg(feature = "std")]
364 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.set(None));
365
366 #[cfg(not(feature = "std"))]
367 {
368 *OBSERVED_ENCODE_BYTES.lock() = None;
369 }
370}
371
372pub fn observed_encode_bytes() -> usize {
374 #[cfg(feature = "std")]
375 {
376 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.get().unwrap_or(0))
377 }
378
379 #[cfg(not(feature = "std"))]
380 {
381 OBSERVED_ENCODE_BYTES.lock().as_ref().copied().unwrap_or(0)
382 }
383}
384
385pub fn record_observed_encode_bytes(bytes: usize) {
387 #[cfg(feature = "std")]
388 OBSERVED_ENCODE_BYTES.with(|total| {
389 if let Some(current) = total.get() {
390 total.set(Some(current.saturating_add(bytes)));
391 }
392 });
393
394 #[cfg(not(feature = "std"))]
395 {
396 let mut total = OBSERVED_ENCODE_BYTES.lock();
397 if let Some(current) = *total {
398 *total = Some(current.saturating_add(bytes));
399 }
400 }
401}
402
403pub struct ObservedWriter<W> {
406 inner: W,
407}
408
409impl<W> ObservedWriter<W> {
410 pub const fn new(inner: W) -> Self {
411 Self { inner }
412 }
413
414 pub fn into_inner(self) -> W {
415 self.inner
416 }
417
418 pub fn inner(&self) -> &W {
419 &self.inner
420 }
421
422 pub fn inner_mut(&mut self) -> &mut W {
423 &mut self.inner
424 }
425}
426
427impl<W: Writer> Writer for ObservedWriter<W> {
428 #[inline(always)]
429 fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
430 self.inner.write(bytes)?;
431 record_observed_encode_bytes(bytes.len());
432 Ok(())
433 }
434}
435
436pub trait WriteStream<E: Encode>: Debug + Send + Sync {
438 fn log(&mut self, obj: &E) -> CuResult<()>;
439 fn flush(&mut self) -> CuResult<()> {
440 Ok(())
441 }
442 fn last_log_bytes(&self) -> Option<usize> {
444 None
445 }
446}
447
448#[derive(dEncode, dDecode, Copy, Clone, Debug, PartialEq)]
450pub enum UnifiedLogType {
451 Empty, StructuredLogLine, CopperList, FrozenTasks, LastEntry, RuntimeLifecycle, }
458pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
460
461impl Metadata for () {}
462
463#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
465#[cfg_attr(feature = "reflect", derive(Reflect))]
466pub struct CuMsgOrigin {
467 pub subsystem_code: u16,
468 pub instance_id: u32,
469 pub cl_id: u64,
470}
471
472pub trait CuMsgMetadataTrait {
474 fn process_time(&self) -> PartialCuTimeRange;
476
477 fn status_txt(&self) -> &CuCompactString;
479
480 fn origin(&self) -> Option<&CuMsgOrigin> {
482 None
483 }
484}
485
486pub trait ErasedCuStampedData {
488 fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
489 #[cfg(feature = "reflect")]
490 fn payload_reflect(&self) -> Option<&dyn Reflect>;
491 fn tov(&self) -> Tov;
492 fn metadata(&self) -> &dyn CuMsgMetadataTrait;
493}
494
495pub trait ErasedCuStampedDataSet {
498 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
499}
500
501pub trait CuPayloadRawBytes {
503 fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
506}
507
508pub trait MatchingTasks {
513 fn get_all_task_ids() -> &'static [&'static str];
514}
515
516pub trait PayloadSchemas {
525 fn get_payload_schemas() -> Vec<(&'static str, String)> {
530 Vec::new()
531 }
532}
533
534pub trait CopperListTuple:
536 bincode::Encode
537 + bincode::Decode<()>
538 + Debug
539 + Serialize
540 + ErasedCuStampedDataSet
541 + MatchingTasks
542 + Default
543{
544} impl<T> CopperListTuple for T where
548 T: bincode::Encode
549 + bincode::Decode<()>
550 + Debug
551 + Serialize
552 + ErasedCuStampedDataSet
553 + MatchingTasks
554 + Default
555{
556}
557
558pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
562
563#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
564pub struct CuCompactString(pub CompactString);
565
566impl Encode for CuCompactString {
567 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
568 let CuCompactString(compact_string) = self;
569 let bytes = &compact_string.as_bytes();
570 bytes.encode(encoder)
571 }
572}
573
574impl Debug for CuCompactString {
575 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
576 if self.0.is_empty() {
577 return write!(f, "CuCompactString(Empty)");
578 }
579 write!(f, "CuCompactString({})", self.0)
580 }
581}
582
583impl<Context> Decode<Context> for CuCompactString {
584 fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
585 let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
587 CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
588 Ok(CuCompactString(compact_string))
589 }
590}
591
592impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
593 fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
594 CuCompactString::decode(decoder)
595 }
596}
597
598#[cfg(feature = "defmt")]
599impl defmt::Format for CuError {
600 fn format(&self, f: defmt::Formatter) {
601 match &self.cause {
602 Some(c) => {
603 let cause_str = c.to_string();
604 defmt::write!(
605 f,
606 "CuError {{ message: {}, cause: {} }}",
607 defmt::Display2Format(&self.message),
608 defmt::Display2Format(&cause_str),
609 )
610 }
611 None => defmt::write!(
612 f,
613 "CuError {{ message: {}, cause: None }}",
614 defmt::Display2Format(&self.message),
615 ),
616 }
617 }
618}
619
620#[cfg(feature = "defmt")]
621impl defmt::Format for CuCompactString {
622 fn format(&self, f: defmt::Formatter) {
623 if self.0.is_empty() {
624 defmt::write!(f, "CuCompactString(Empty)");
625 } else {
626 defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
627 }
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use crate::CuCompactString;
634 use bincode::{config, decode_from_slice, encode_to_vec};
635 use compact_str::CompactString;
636
637 #[test]
638 fn test_cucompactstr_encode_decode_empty() {
639 let cstr = CuCompactString(CompactString::from(""));
640 let config = config::standard();
641 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
642 assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
644 decode_from_slice(&encoded, config).expect("Decoding failed");
645 assert_eq!(cstr.0, decoded.0);
646 }
647
648 #[test]
649 fn test_cucompactstr_encode_decode_small() {
650 let cstr = CuCompactString(CompactString::from("test"));
651 let config = config::standard();
652 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
653 assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
655 decode_from_slice(&encoded, config).expect("Decoding failed");
656 assert_eq!(cstr.0, decoded.0);
657 }
658}
659
660#[cfg(all(test, feature = "std"))]
662mod std_tests {
663 use crate::{CuError, with_cause};
664
665 #[test]
666 fn test_cuerror_from_str() {
667 let err = CuError::from("test error");
668 assert_eq!(err.message(), "test error");
669 assert!(err.cause().is_none());
670 }
671
672 #[test]
673 fn test_cuerror_from_string() {
674 let err = CuError::from(String::from("test error"));
675 assert_eq!(err.message(), "test error");
676 assert!(err.cause().is_none());
677 }
678
679 #[test]
680 fn test_cuerror_new_index() {
681 let err = CuError::new(42);
682 assert_eq!(err.message(), "[interned:42]");
683 assert!(err.cause().is_none());
684 }
685
686 #[test]
687 fn test_cuerror_new_with_cause() {
688 let io_err = std::io::Error::other("io error");
689 let err = CuError::new_with_cause("wrapped error", io_err);
690 assert_eq!(err.message(), "wrapped error");
691 assert!(err.cause().is_some());
692 assert!(err.cause().unwrap().to_string().contains("io error"));
693 }
694
695 #[test]
696 fn test_cuerror_add_cause() {
697 let err = CuError::from("base error").add_cause("additional context");
698 assert_eq!(err.message(), "base error");
699 assert!(err.cause().is_some());
700 assert_eq!(err.cause().unwrap().to_string(), "additional context");
701 }
702
703 #[test]
704 fn test_cuerror_with_cause_method() {
705 let io_err = std::io::Error::other("io error");
706 let err = CuError::from("base error").with_cause(io_err);
707 assert_eq!(err.message(), "base error");
708 assert!(err.cause().is_some());
709 }
710
711 #[test]
712 fn test_cuerror_with_cause_free_function() {
713 let io_err = std::io::Error::other("io error");
714 let err = with_cause("wrapped", io_err);
715 assert_eq!(err.message(), "wrapped");
716 assert!(err.cause().is_some());
717 }
718
719 #[test]
720 fn test_cuerror_clone() {
721 let io_err = std::io::Error::other("io error");
722 let err = CuError::new_with_cause("test", io_err);
723 let cloned = err.clone();
724 assert_eq!(err.message(), cloned.message());
725 assert_eq!(
727 err.cause().map(|c| c.to_string()),
728 cloned.cause().map(|c| c.to_string())
729 );
730 }
731
732 #[test]
733 fn test_cuerror_serialize_deserialize_json() {
734 let io_err = std::io::Error::other("io error");
735 let err = CuError::new_with_cause("test", io_err);
736
737 let serialized = serde_json::to_string(&err).unwrap();
738 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
739
740 assert_eq!(err.message(), deserialized.message());
741 assert!(deserialized.cause().is_some());
743 }
744
745 #[test]
746 fn test_cuerror_serialize_deserialize_no_cause() {
747 let err = CuError::from("simple error");
748
749 let serialized = serde_json::to_string(&err).unwrap();
750 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
751
752 assert_eq!(err.message(), deserialized.message());
753 assert!(deserialized.cause().is_none());
754 }
755
756 #[test]
757 fn test_cuerror_display() {
758 let err = CuError::from("test error").add_cause("some context");
759 let display = format!("{}", err);
760 assert!(display.contains("test error"));
761 assert!(display.contains("some context"));
762 }
763
764 #[test]
765 fn test_cuerror_debug() {
766 let err = CuError::from("test error").add_cause("some context");
767 let debug = format!("{:?}", err);
768 assert!(debug.contains("test error"));
769 assert!(debug.contains("some context"));
770 }
771}