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::de::{self, SeqAccess, Visitor};
35use serde::{Deserialize, Deserializer, Serialize};
36
37use alloc::borrow::ToOwned;
38use alloc::boxed::Box;
39use alloc::format;
40use alloc::string::{String, ToString};
41use alloc::vec::Vec;
42#[cfg(feature = "std")]
43use core::cell::Cell;
44#[cfg(not(feature = "std"))]
45use core::error::Error as CoreError;
46use core::fmt::{Debug, Display, Formatter};
47#[cfg(feature = "std")]
48use std::error::Error;
49
50#[cfg(not(feature = "std"))]
51use spin::Mutex as SyncMutex;
52
53#[cfg(feature = "std")]
55type DynError = dyn std::error::Error + Send + Sync + 'static;
56#[cfg(not(feature = "std"))]
57type DynError = dyn core::error::Error + Send + Sync + 'static;
58
59#[derive(Debug)]
62struct StringError(String);
63
64impl Display for StringError {
65 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
66 write!(f, "{}", self.0)
67 }
68}
69
70#[cfg(feature = "std")]
71impl std::error::Error for StringError {}
72
73#[cfg(not(feature = "std"))]
74impl core::error::Error for StringError {}
75
76pub struct CuError {
82 message: String,
83 cause: Option<Box<DynError>>,
84}
85
86impl Debug for CuError {
88 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
89 f.debug_struct("CuError")
90 .field("message", &self.message)
91 .field("cause", &self.cause.as_ref().map(|e| e.to_string()))
92 .finish()
93 }
94}
95
96impl Clone for CuError {
98 fn clone(&self) -> Self {
99 CuError {
100 message: self.message.clone(),
101 cause: self
102 .cause
103 .as_ref()
104 .map(|e| Box::new(StringError(e.to_string())) as Box<DynError>),
105 }
106 }
107}
108
109impl Serialize for CuError {
111 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
112 where
113 S: serde::Serializer,
114 {
115 use serde::ser::SerializeStruct;
116 let mut state = serializer.serialize_struct("CuError", 2)?;
117 state.serialize_field("message", &self.message)?;
118 state.serialize_field("cause", &self.cause.as_ref().map(|e| e.to_string()))?;
119 state.end()
120 }
121}
122
123impl<'de> Deserialize<'de> for CuError {
125 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
126 where
127 D: serde::Deserializer<'de>,
128 {
129 #[derive(Deserialize)]
130 struct CuErrorHelper {
131 message: String,
132 cause: Option<String>,
133 }
134
135 let helper = CuErrorHelper::deserialize(deserializer)?;
136 Ok(CuError {
137 message: helper.message,
138 cause: helper
139 .cause
140 .map(|s| Box::new(StringError(s)) as Box<DynError>),
141 })
142 }
143}
144
145impl Display for CuError {
146 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
147 let context_str = match &self.cause {
148 Some(c) => c.to_string(),
149 None => "None".to_string(),
150 };
151 write!(f, "{}\n context:{}", self.message, context_str)?;
152 Ok(())
153 }
154}
155
156#[cfg(not(feature = "std"))]
157impl CoreError for CuError {
158 fn source(&self) -> Option<&(dyn CoreError + 'static)> {
159 self.cause
160 .as_deref()
161 .map(|e| e as &(dyn CoreError + 'static))
162 }
163}
164
165#[cfg(feature = "std")]
166impl Error for CuError {
167 fn source(&self) -> Option<&(dyn Error + 'static)> {
168 self.cause.as_deref().map(|e| e as &(dyn Error + 'static))
169 }
170}
171
172impl From<&str> for CuError {
173 fn from(s: &str) -> CuError {
174 CuError {
175 message: s.to_string(),
176 cause: None,
177 }
178 }
179}
180
181impl From<String> for CuError {
182 fn from(s: String) -> CuError {
183 CuError {
184 message: s,
185 cause: None,
186 }
187 }
188}
189
190impl CuError {
191 pub fn new(message_index: usize) -> CuError {
197 CuError {
198 message: format!("[interned:{}]", message_index),
199 cause: None,
200 }
201 }
202
203 #[cfg(feature = "std")]
213 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
214 where
215 E: std::error::Error + Send + Sync + 'static,
216 {
217 CuError {
218 message: message.to_string(),
219 cause: Some(Box::new(cause)),
220 }
221 }
222
223 #[cfg(not(feature = "std"))]
225 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
226 where
227 E: core::error::Error + Send + Sync + 'static,
228 {
229 CuError {
230 message: message.to_string(),
231 cause: Some(Box::new(cause)),
232 }
233 }
234
235 pub fn add_cause(mut self, context: &str) -> CuError {
246 self.cause = Some(Box::new(StringError(context.to_string())));
247 self
248 }
249
250 #[cfg(feature = "std")]
260 pub fn with_cause<E>(mut self, cause: E) -> CuError
261 where
262 E: std::error::Error + Send + Sync + 'static,
263 {
264 self.cause = Some(Box::new(cause));
265 self
266 }
267
268 #[cfg(not(feature = "std"))]
270 pub fn with_cause<E>(mut self, cause: E) -> CuError
271 where
272 E: core::error::Error + Send + Sync + 'static,
273 {
274 self.cause = Some(Box::new(cause));
275 self
276 }
277
278 pub fn cause(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> {
280 self.cause.as_deref()
281 }
282
283 pub fn message(&self) -> &str {
285 &self.message
286 }
287}
288
289#[cfg(feature = "std")]
301pub fn with_cause<E>(message: &str, cause: E) -> CuError
302where
303 E: std::error::Error + Send + Sync + 'static,
304{
305 CuError::new_with_cause(message, cause)
306}
307
308#[cfg(not(feature = "std"))]
310pub fn with_cause<E>(message: &str, cause: E) -> CuError
311where
312 E: core::error::Error + Send + Sync + 'static,
313{
314 CuError::new_with_cause(message, cause)
315}
316
317pub type CuResult<T> = Result<T, CuError>;
319
320#[cfg(feature = "std")]
321thread_local! {
322 static OBSERVED_ENCODE_BYTES: Cell<Option<usize>> = const { Cell::new(None) };
323}
324
325#[cfg(not(feature = "std"))]
326static OBSERVED_ENCODE_BYTES: SyncMutex<Option<usize>> = SyncMutex::new(None);
327
328pub fn begin_observed_encode() {
330 #[cfg(feature = "std")]
331 OBSERVED_ENCODE_BYTES.with(|bytes| {
332 debug_assert!(
333 bytes.get().is_none(),
334 "observed encode measurement must not be nested"
335 );
336 bytes.set(Some(0));
337 });
338
339 #[cfg(not(feature = "std"))]
340 {
341 let mut bytes = OBSERVED_ENCODE_BYTES.lock();
342 debug_assert!(
343 bytes.is_none(),
344 "observed encode measurement must not be nested"
345 );
346 *bytes = Some(0);
347 }
348}
349
350pub fn finish_observed_encode() -> usize {
352 #[cfg(feature = "std")]
353 {
354 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.replace(None).unwrap_or(0))
355 }
356
357 #[cfg(not(feature = "std"))]
358 {
359 OBSERVED_ENCODE_BYTES.lock().take().unwrap_or(0)
360 }
361}
362
363pub fn abort_observed_encode() {
365 #[cfg(feature = "std")]
366 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.set(None));
367
368 #[cfg(not(feature = "std"))]
369 {
370 *OBSERVED_ENCODE_BYTES.lock() = None;
371 }
372}
373
374pub fn observed_encode_bytes() -> usize {
376 #[cfg(feature = "std")]
377 {
378 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.get().unwrap_or(0))
379 }
380
381 #[cfg(not(feature = "std"))]
382 {
383 OBSERVED_ENCODE_BYTES.lock().as_ref().copied().unwrap_or(0)
384 }
385}
386
387pub fn record_observed_encode_bytes(bytes: usize) {
389 #[cfg(feature = "std")]
390 OBSERVED_ENCODE_BYTES.with(|total| {
391 if let Some(current) = total.get() {
392 total.set(Some(current.saturating_add(bytes)));
393 }
394 });
395
396 #[cfg(not(feature = "std"))]
397 {
398 let mut total = OBSERVED_ENCODE_BYTES.lock();
399 if let Some(current) = *total {
400 *total = Some(current.saturating_add(bytes));
401 }
402 }
403}
404
405pub struct ObservedWriter<W> {
408 inner: W,
409}
410
411impl<W> ObservedWriter<W> {
412 pub const fn new(inner: W) -> Self {
413 Self { inner }
414 }
415
416 pub fn into_inner(self) -> W {
417 self.inner
418 }
419
420 pub fn inner(&self) -> &W {
421 &self.inner
422 }
423
424 pub fn inner_mut(&mut self) -> &mut W {
425 &mut self.inner
426 }
427}
428
429impl<W: Writer> Writer for ObservedWriter<W> {
430 #[inline(always)]
431 fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
432 self.inner.write(bytes)?;
433 record_observed_encode_bytes(bytes.len());
434 Ok(())
435 }
436}
437
438pub trait WriteStream<E: Encode>: Debug + Send + Sync {
440 fn log(&mut self, obj: &E) -> CuResult<()>;
441 fn flush(&mut self) -> CuResult<()> {
442 Ok(())
443 }
444 fn last_log_bytes(&self) -> Option<usize> {
446 None
447 }
448}
449
450#[derive(dEncode, dDecode, Copy, Clone, Debug, PartialEq)]
452pub enum UnifiedLogType {
453 Empty, StructuredLogLine, CopperList, FrozenTasks, LastEntry, RuntimeLifecycle, }
460pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
462
463impl Metadata for () {}
464
465#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
467#[cfg_attr(feature = "reflect", derive(Reflect))]
468pub struct CuMsgOrigin {
469 pub subsystem_code: u16,
470 pub instance_id: u32,
471 pub cl_id: u64,
472}
473
474pub trait CuMsgMetadataTrait {
476 fn process_time(&self) -> PartialCuTimeRange;
478
479 fn status_txt(&self) -> &CuCompactString;
481
482 fn origin(&self) -> Option<&CuMsgOrigin> {
484 None
485 }
486}
487
488pub trait ErasedCuStampedData {
490 fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
491 #[cfg(feature = "reflect")]
492 fn payload_reflect(&self) -> Option<&dyn Reflect>;
493 fn tov(&self) -> Tov;
494 fn metadata(&self) -> &dyn CuMsgMetadataTrait;
495}
496
497pub trait ErasedCuStampedDataSet {
500 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
501}
502
503pub trait CuPayloadRawBytes {
505 fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
508}
509
510#[derive(Debug, Clone, Copy)]
515pub struct TaskOutputSpec {
516 pub task_id: &'static str,
517 pub msg_type: &'static str,
518 pub payload_type_path_fn: fn() -> &'static str,
519}
520
521impl TaskOutputSpec {
522 #[inline]
523 pub fn payload_type_path(&self) -> &'static str {
524 (self.payload_type_path_fn)()
525 }
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
529pub enum DebugFieldSemantics {
530 Time,
531 OptionalTime,
532 Duration,
533 GeodeticPosition,
534 Quantity {
535 quantity_name: String,
536 unit_symbol: String,
537 },
538}
539
540#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
541#[serde(rename_all = "snake_case")]
542pub enum DebugFieldKind {
543 Scalar,
544 Struct,
545 TupleStruct,
546 Tuple,
547 List,
548 Array,
549 Map,
550 Set,
551 Enum,
552}
553
554#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
555#[serde(rename_all = "snake_case")]
556pub enum DebugScalarKind {
557 Bool,
558 I8,
559 I16,
560 I32,
561 I64,
562 I128,
563 Isize,
564 U8,
565 U16,
566 U32,
567 U64,
568 U128,
569 Usize,
570 F32,
571 F64,
572 Char,
573 String,
574}
575
576impl DebugScalarKind {
577 pub const fn type_name(self) -> &'static str {
578 match self {
579 Self::Bool => "bool",
580 Self::I8 => "i8",
581 Self::I16 => "i16",
582 Self::I32 => "i32",
583 Self::I64 => "i64",
584 Self::I128 => "i128",
585 Self::Isize => "isize",
586 Self::U8 => "u8",
587 Self::U16 => "u16",
588 Self::U32 => "u32",
589 Self::U64 => "u64",
590 Self::U128 => "u128",
591 Self::Usize => "usize",
592 Self::F32 => "f32",
593 Self::F64 => "f64",
594 Self::Char => "char",
595 Self::String => "String",
596 }
597 }
598
599 pub const fn is_numeric(self) -> bool {
600 matches!(
601 self,
602 Self::I8
603 | Self::I16
604 | Self::I32
605 | Self::I64
606 | Self::I128
607 | Self::Isize
608 | Self::U8
609 | Self::U16
610 | Self::U32
611 | Self::U64
612 | Self::U128
613 | Self::Usize
614 | Self::F32
615 | Self::F64
616 )
617 }
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
621pub struct DebugFieldDescriptor {
622 pub display_path: String,
623 #[serde(
624 default,
625 skip_serializing_if = "Option::is_none",
626 deserialize_with = "deserialize_debug_binding_name"
627 )]
628 pub binding_name: Option<String>,
629 pub value_type_path: String,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
631 pub scalar_kind: Option<DebugScalarKind>,
632 #[serde(default, skip_serializing_if = "Option::is_none")]
633 pub semantics: Option<DebugFieldSemantics>,
634 pub nullable: bool,
635 pub kind: DebugFieldKind,
636 #[serde(default, skip_serializing_if = "Vec::is_empty")]
637 pub children: Vec<DebugFieldDescriptor>,
638}
639
640fn deserialize_debug_binding_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
641where
642 D: Deserializer<'de>,
643{
644 struct BindingNameVisitor;
645
646 impl<'de> Visitor<'de> for BindingNameVisitor {
647 type Value = Option<String>;
648
649 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
650 formatter.write_str("a string, null, or an empty sequence")
651 }
652
653 fn visit_none<E>(self) -> Result<Self::Value, E>
654 where
655 E: de::Error,
656 {
657 Ok(None)
658 }
659
660 fn visit_unit<E>(self) -> Result<Self::Value, E>
661 where
662 E: de::Error,
663 {
664 Ok(None)
665 }
666
667 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
668 where
669 D: Deserializer<'de>,
670 {
671 deserialize_debug_binding_name(deserializer)
672 }
673
674 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
675 where
676 E: de::Error,
677 {
678 Ok(Some(value.to_owned()))
679 }
680
681 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
682 where
683 E: de::Error,
684 {
685 Ok(Some(value))
686 }
687
688 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
689 where
690 A: SeqAccess<'de>,
691 {
692 if seq.next_element::<de::IgnoredAny>()?.is_none() {
693 return Ok(None);
694 }
695 Err(de::Error::invalid_type(
696 de::Unexpected::Seq,
697 &"an empty sequence",
698 ))
699 }
700 }
701
702 deserializer.deserialize_any(BindingNameVisitor)
703}
704
705#[derive(Debug, Clone, PartialEq, Eq)]
706pub struct DebugScalarRegistration {
707 pub type_path: &'static str,
708 pub scalar_kind: DebugScalarKind,
709 pub semantics: DebugFieldSemantics,
710}
711
712pub trait DebugScalarType: 'static {
713 fn debug_scalar_registration() -> DebugScalarRegistration;
714}
715
716pub trait MatchingTasks {
717 fn get_all_task_ids() -> &'static [&'static str];
718
719 fn get_output_specs() -> &'static [TaskOutputSpec] {
720 &[]
721 }
722}
723
724pub trait PayloadSchemas {
733 fn get_payload_schemas() -> Vec<(&'static str, String)> {
738 Vec::new()
739 }
740}
741
742pub trait CopperListTuple:
744 bincode::Encode
745 + bincode::Decode<()>
746 + Debug
747 + Serialize
748 + ErasedCuStampedDataSet
749 + MatchingTasks
750 + Default
751{
752} impl<T> CopperListTuple for T where
756 T: bincode::Encode
757 + bincode::Decode<()>
758 + Debug
759 + Serialize
760 + ErasedCuStampedDataSet
761 + MatchingTasks
762 + Default
763{
764}
765
766pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
770
771#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
772pub struct CuCompactString(pub CompactString);
773
774impl Encode for CuCompactString {
775 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
776 let CuCompactString(compact_string) = self;
777 let bytes = &compact_string.as_bytes();
778 bytes.encode(encoder)
779 }
780}
781
782impl Debug for CuCompactString {
783 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
784 if self.0.is_empty() {
785 return write!(f, "CuCompactString(Empty)");
786 }
787 write!(f, "CuCompactString({})", self.0)
788 }
789}
790
791impl<Context> Decode<Context> for CuCompactString {
792 fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
793 let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
795 CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
796 Ok(CuCompactString(compact_string))
797 }
798}
799
800impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
801 fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
802 CuCompactString::decode(decoder)
803 }
804}
805
806#[cfg(feature = "defmt")]
807impl defmt::Format for CuError {
808 fn format(&self, f: defmt::Formatter) {
809 match &self.cause {
810 Some(c) => {
811 let cause_str = c.to_string();
812 defmt::write!(
813 f,
814 "CuError {{ message: {}, cause: {} }}",
815 defmt::Display2Format(&self.message),
816 defmt::Display2Format(&cause_str),
817 )
818 }
819 None => defmt::write!(
820 f,
821 "CuError {{ message: {}, cause: None }}",
822 defmt::Display2Format(&self.message),
823 ),
824 }
825 }
826}
827
828#[cfg(feature = "defmt")]
829impl defmt::Format for CuCompactString {
830 fn format(&self, f: defmt::Formatter) {
831 if self.0.is_empty() {
832 defmt::write!(f, "CuCompactString(Empty)");
833 } else {
834 defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
835 }
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use crate::CuCompactString;
842 use bincode::{config, decode_from_slice, encode_to_vec};
843 use compact_str::CompactString;
844
845 #[test]
846 fn test_cucompactstr_encode_decode_empty() {
847 let cstr = CuCompactString(CompactString::from(""));
848 let config = config::standard();
849 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
850 assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
852 decode_from_slice(&encoded, config).expect("Decoding failed");
853 assert_eq!(cstr.0, decoded.0);
854 }
855
856 #[test]
857 fn test_cucompactstr_encode_decode_small() {
858 let cstr = CuCompactString(CompactString::from("test"));
859 let config = config::standard();
860 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
861 assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
863 decode_from_slice(&encoded, config).expect("Decoding failed");
864 assert_eq!(cstr.0, decoded.0);
865 }
866}
867
868#[cfg(all(test, feature = "std"))]
870mod std_tests {
871 use crate::{
872 CuError, DebugFieldDescriptor, DebugFieldKind, DebugFieldSemantics, DebugScalarKind,
873 with_cause,
874 };
875 use serde_json::json;
876
877 #[test]
878 fn test_cuerror_from_str() {
879 let err = CuError::from("test error");
880 assert_eq!(err.message(), "test error");
881 assert!(err.cause().is_none());
882 }
883
884 #[test]
885 fn test_cuerror_from_string() {
886 let err = CuError::from(String::from("test error"));
887 assert_eq!(err.message(), "test error");
888 assert!(err.cause().is_none());
889 }
890
891 #[test]
892 fn test_cuerror_new_index() {
893 let err = CuError::new(42);
894 assert_eq!(err.message(), "[interned:42]");
895 assert!(err.cause().is_none());
896 }
897
898 #[test]
899 fn test_cuerror_new_with_cause() {
900 let io_err = std::io::Error::other("io error");
901 let err = CuError::new_with_cause("wrapped error", io_err);
902 assert_eq!(err.message(), "wrapped error");
903 assert!(err.cause().is_some());
904 assert!(err.cause().unwrap().to_string().contains("io error"));
905 }
906
907 #[test]
908 fn test_cuerror_add_cause() {
909 let err = CuError::from("base error").add_cause("additional context");
910 assert_eq!(err.message(), "base error");
911 assert!(err.cause().is_some());
912 assert_eq!(err.cause().unwrap().to_string(), "additional context");
913 }
914
915 #[test]
916 fn test_cuerror_with_cause_method() {
917 let io_err = std::io::Error::other("io error");
918 let err = CuError::from("base error").with_cause(io_err);
919 assert_eq!(err.message(), "base error");
920 assert!(err.cause().is_some());
921 }
922
923 #[test]
924 fn test_cuerror_with_cause_free_function() {
925 let io_err = std::io::Error::other("io error");
926 let err = with_cause("wrapped", io_err);
927 assert_eq!(err.message(), "wrapped");
928 assert!(err.cause().is_some());
929 }
930
931 #[test]
932 fn test_cuerror_clone() {
933 let io_err = std::io::Error::other("io error");
934 let err = CuError::new_with_cause("test", io_err);
935 let cloned = err.clone();
936 assert_eq!(err.message(), cloned.message());
937 assert_eq!(
939 err.cause().map(|c| c.to_string()),
940 cloned.cause().map(|c| c.to_string())
941 );
942 }
943
944 #[test]
945 fn test_cuerror_serialize_deserialize_json() {
946 let io_err = std::io::Error::other("io error");
947 let err = CuError::new_with_cause("test", io_err);
948
949 let serialized = serde_json::to_string(&err).unwrap();
950 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
951
952 assert_eq!(err.message(), deserialized.message());
953 assert!(deserialized.cause().is_some());
955 }
956
957 #[test]
958 fn test_cuerror_serialize_deserialize_no_cause() {
959 let err = CuError::from("simple error");
960
961 let serialized = serde_json::to_string(&err).unwrap();
962 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
963
964 assert_eq!(err.message(), deserialized.message());
965 assert!(deserialized.cause().is_none());
966 }
967
968 #[test]
969 fn test_cuerror_display() {
970 let err = CuError::from("test error").add_cause("some context");
971 let display = format!("{}", err);
972 assert!(display.contains("test error"));
973 assert!(display.contains("some context"));
974 }
975
976 #[test]
977 fn test_cuerror_debug() {
978 let err = CuError::from("test error").add_cause("some context");
979 let debug = format!("{:?}", err);
980 assert!(debug.contains("test error"));
981 assert!(debug.contains("some context"));
982 }
983
984 #[test]
985 fn debug_field_descriptor_skips_missing_binding_name_on_serialize() {
986 let descriptor = DebugFieldDescriptor {
987 display_path: "meta.process_time.start_ns".to_owned(),
988 binding_name: None,
989 value_type_path: "cu29_clock::CuTime".to_owned(),
990 scalar_kind: Some(DebugScalarKind::U64),
991 semantics: Some(DebugFieldSemantics::Time),
992 nullable: true,
993 kind: DebugFieldKind::Scalar,
994 children: Vec::new(),
995 };
996
997 let encoded = serde_json::to_value(&descriptor).unwrap();
998 assert!(encoded.get("binding_name").is_none());
999 }
1000
1001 #[test]
1002 fn debug_field_descriptor_accepts_empty_array_binding_name() {
1003 let encoded = json!({
1004 "display_path": "meta.process_time.start_ns",
1005 "binding_name": [],
1006 "value_type_path": "cu29_clock::CuTime",
1007 "semantics": "Time",
1008 "nullable": true,
1009 "kind": "scalar",
1010 });
1011
1012 let descriptor: DebugFieldDescriptor = serde_json::from_value(encoded).unwrap();
1013 assert_eq!(descriptor.binding_name, None);
1014 assert_eq!(descriptor.semantics, Some(DebugFieldSemantics::Time));
1015 assert_eq!(descriptor.scalar_kind, None);
1016 assert_eq!(descriptor.kind, DebugFieldKind::Scalar);
1017 assert!(descriptor.children.is_empty());
1018 }
1019}