1#![cfg_attr(not(feature = "std"), no_std)]
23extern crate alloc;
24
25#[cfg(feature = "reflect")]
26pub use bevy_reflect::Reflect;
27#[cfg(feature = "reflect")]
28use bevy_reflect::{GetTypeRegistration, TypePath, TypeRegistry};
29use bincode::de::{BorrowDecoder, Decoder};
30use bincode::enc::Encoder;
31use bincode::enc::write::Writer;
32use bincode::error::{DecodeError, EncodeError};
33use bincode::{BorrowDecode, Decode as dDecode, Decode, Encode, Encode as dEncode};
34use compact_str::CompactString;
35use cu29_clock::{PartialCuTimeRange, Tov};
36use serde::de::{self, SeqAccess, Visitor};
37use serde::{Deserialize, Deserializer, Serialize};
38
39use alloc::borrow::ToOwned;
40use alloc::boxed::Box;
41use alloc::format;
42use alloc::string::{String, ToString};
43use alloc::vec::Vec;
44#[cfg(feature = "std")]
45use core::cell::Cell;
46#[cfg(not(feature = "std"))]
47use core::error::Error as CoreError;
48use core::fmt::{Debug, Display, Formatter};
49#[cfg(feature = "std")]
50use std::error::Error;
51
52#[cfg(not(feature = "std"))]
53use spin::Mutex as SyncMutex;
54
55#[cfg(feature = "std")]
57type DynError = dyn std::error::Error + Send + Sync + 'static;
58#[cfg(not(feature = "std"))]
59type DynError = dyn core::error::Error + Send + Sync + 'static;
60
61#[derive(Debug)]
64struct StringError(String);
65
66impl Display for StringError {
67 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
68 write!(f, "{}", self.0)
69 }
70}
71
72#[cfg(feature = "std")]
73impl std::error::Error for StringError {}
74
75#[cfg(not(feature = "std"))]
76impl core::error::Error for StringError {}
77
78pub struct CuError {
84 message: String,
85 cause: Option<Box<DynError>>,
86}
87
88impl Debug for CuError {
90 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
91 f.debug_struct("CuError")
92 .field("message", &self.message)
93 .field("cause", &self.cause.as_ref().map(|e| e.to_string()))
94 .finish()
95 }
96}
97
98impl Clone for CuError {
100 fn clone(&self) -> Self {
101 CuError {
102 message: self.message.clone(),
103 cause: self
104 .cause
105 .as_ref()
106 .map(|e| Box::new(StringError(e.to_string())) as Box<DynError>),
107 }
108 }
109}
110
111impl Serialize for CuError {
113 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
114 where
115 S: serde::Serializer,
116 {
117 use serde::ser::SerializeStruct;
118 let mut state = serializer.serialize_struct("CuError", 2)?;
119 state.serialize_field("message", &self.message)?;
120 state.serialize_field("cause", &self.cause.as_ref().map(|e| e.to_string()))?;
121 state.end()
122 }
123}
124
125impl<'de> Deserialize<'de> for CuError {
127 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
128 where
129 D: serde::Deserializer<'de>,
130 {
131 #[derive(Deserialize)]
132 struct CuErrorHelper {
133 message: String,
134 cause: Option<String>,
135 }
136
137 let helper = CuErrorHelper::deserialize(deserializer)?;
138 Ok(CuError {
139 message: helper.message,
140 cause: helper
141 .cause
142 .map(|s| Box::new(StringError(s)) as Box<DynError>),
143 })
144 }
145}
146
147impl Display for CuError {
148 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
149 write!(f, "{}\n context:", self.message)?;
150 match &self.cause {
151 Some(cause) => write!(f, "{cause}"),
152 None => f.write_str("None"),
153 }
154 }
155}
156
157#[cfg(not(feature = "std"))]
158impl CoreError for CuError {
159 fn source(&self) -> Option<&(dyn CoreError + 'static)> {
160 self.cause
161 .as_deref()
162 .map(|e| e as &(dyn CoreError + 'static))
163 }
164}
165
166#[cfg(feature = "std")]
167impl Error for CuError {
168 fn source(&self) -> Option<&(dyn Error + 'static)> {
169 self.cause.as_deref().map(|e| e as &(dyn Error + 'static))
170 }
171}
172
173impl From<&str> for CuError {
174 fn from(s: &str) -> CuError {
175 CuError {
176 message: s.to_string(),
177 cause: None,
178 }
179 }
180}
181
182impl From<String> for CuError {
183 fn from(s: String) -> CuError {
184 CuError {
185 message: s,
186 cause: None,
187 }
188 }
189}
190
191impl CuError {
192 pub fn new(message_index: usize) -> CuError {
198 CuError {
199 message: format!("[interned:{}]", message_index),
200 cause: None,
201 }
202 }
203
204 #[cfg(feature = "std")]
214 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
215 where
216 E: std::error::Error + Send + Sync + 'static,
217 {
218 CuError {
219 message: message.to_string(),
220 cause: Some(Box::new(cause)),
221 }
222 }
223
224 #[cfg(not(feature = "std"))]
226 pub fn new_with_cause<E>(message: &str, cause: E) -> CuError
227 where
228 E: core::error::Error + Send + Sync + 'static,
229 {
230 CuError {
231 message: message.to_string(),
232 cause: Some(Box::new(cause)),
233 }
234 }
235
236 pub fn add_cause(mut self, context: &str) -> CuError {
247 self.cause = Some(Box::new(StringError(context.to_string())));
248 self
249 }
250
251 #[cfg(feature = "std")]
261 pub fn with_cause<E>(mut self, cause: E) -> CuError
262 where
263 E: std::error::Error + Send + Sync + 'static,
264 {
265 self.cause = Some(Box::new(cause));
266 self
267 }
268
269 #[cfg(not(feature = "std"))]
271 pub fn with_cause<E>(mut self, cause: E) -> CuError
272 where
273 E: core::error::Error + Send + Sync + 'static,
274 {
275 self.cause = Some(Box::new(cause));
276 self
277 }
278
279 pub fn cause(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> {
281 self.cause.as_deref()
282 }
283
284 pub fn message(&self) -> &str {
286 &self.message
287 }
288}
289
290#[cfg(feature = "std")]
302pub fn with_cause<E>(message: &str, cause: E) -> CuError
303where
304 E: std::error::Error + Send + Sync + 'static,
305{
306 CuError::new_with_cause(message, cause)
307}
308
309#[cfg(not(feature = "std"))]
311pub fn with_cause<E>(message: &str, cause: E) -> CuError
312where
313 E: core::error::Error + Send + Sync + 'static,
314{
315 CuError::new_with_cause(message, cause)
316}
317
318pub type CuResult<T> = Result<T, CuError>;
320
321#[cfg(feature = "std")]
322thread_local! {
323 static OBSERVED_ENCODE_BYTES: Cell<Option<usize>> = const { Cell::new(None) };
324}
325
326#[cfg(not(feature = "std"))]
327static OBSERVED_ENCODE_BYTES: SyncMutex<Option<usize>> = SyncMutex::new(None);
328
329pub fn begin_observed_encode() {
331 #[cfg(feature = "std")]
332 OBSERVED_ENCODE_BYTES.with(|bytes| {
333 debug_assert!(
334 bytes.get().is_none(),
335 "observed encode measurement must not be nested"
336 );
337 bytes.set(Some(0));
338 });
339
340 #[cfg(not(feature = "std"))]
341 {
342 let mut bytes = OBSERVED_ENCODE_BYTES.lock();
343 debug_assert!(
344 bytes.is_none(),
345 "observed encode measurement must not be nested"
346 );
347 *bytes = Some(0);
348 }
349}
350
351pub fn finish_observed_encode() -> usize {
353 #[cfg(feature = "std")]
354 {
355 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.replace(None).unwrap_or(0))
356 }
357
358 #[cfg(not(feature = "std"))]
359 {
360 OBSERVED_ENCODE_BYTES.lock().take().unwrap_or(0)
361 }
362}
363
364pub fn abort_observed_encode() {
366 #[cfg(feature = "std")]
367 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.set(None));
368
369 #[cfg(not(feature = "std"))]
370 {
371 *OBSERVED_ENCODE_BYTES.lock() = None;
372 }
373}
374
375pub fn observed_encode_bytes() -> usize {
377 #[cfg(feature = "std")]
378 {
379 OBSERVED_ENCODE_BYTES.with(|bytes| bytes.get().unwrap_or(0))
380 }
381
382 #[cfg(not(feature = "std"))]
383 {
384 OBSERVED_ENCODE_BYTES.lock().as_ref().copied().unwrap_or(0)
385 }
386}
387
388pub fn record_observed_encode_bytes(bytes: usize) {
390 #[cfg(feature = "std")]
391 OBSERVED_ENCODE_BYTES.with(|total| {
392 if let Some(current) = total.get() {
393 total.set(Some(current.saturating_add(bytes)));
394 }
395 });
396
397 #[cfg(not(feature = "std"))]
398 {
399 let mut total = OBSERVED_ENCODE_BYTES.lock();
400 if let Some(current) = *total {
401 *total = Some(current.saturating_add(bytes));
402 }
403 }
404}
405
406pub struct ObservedWriter<W> {
409 inner: W,
410}
411
412impl<W> ObservedWriter<W> {
413 pub const fn new(inner: W) -> Self {
414 Self { inner }
415 }
416
417 pub fn into_inner(self) -> W {
418 self.inner
419 }
420
421 pub fn inner(&self) -> &W {
422 &self.inner
423 }
424
425 pub fn inner_mut(&mut self) -> &mut W {
426 &mut self.inner
427 }
428}
429
430impl<W: Writer> Writer for ObservedWriter<W> {
431 #[inline(always)]
432 fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
433 self.inner.write(bytes)?;
434 record_observed_encode_bytes(bytes.len());
435 Ok(())
436 }
437}
438
439pub trait WriteStream<E: Encode>: Debug + Send + Sync {
441 fn log(&mut self, obj: &E) -> CuResult<()>;
442 fn flush(&mut self) -> CuResult<()> {
443 Ok(())
444 }
445 fn last_log_bytes(&self) -> Option<usize> {
447 None
448 }
449}
450
451#[derive(dEncode, dDecode, Copy, Clone, Debug, PartialEq)]
453pub enum UnifiedLogType {
454 Empty, StructuredLogLine, CopperList, FrozenTasks, LastEntry, RuntimeLifecycle, StreamContinuity, }
462pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
464
465impl Metadata for () {}
466
467#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
469#[cfg_attr(feature = "reflect", derive(Reflect))]
470pub struct CuMsgOrigin {
471 pub subsystem_code: u16,
472 pub instance_id: u32,
473 pub cl_id: u64,
474}
475
476pub trait CuMsgMetadataTrait {
478 fn process_time(&self) -> PartialCuTimeRange;
480
481 fn status_txt(&self) -> &CuCompactString;
483
484 fn origin(&self) -> Option<&CuMsgOrigin> {
486 None
487 }
488}
489
490pub trait ErasedCuStampedData {
492 fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
493 #[cfg(feature = "reflect")]
494 fn payload_reflect(&self) -> Option<&dyn Reflect>;
495 fn tov(&self) -> Tov;
496 fn metadata(&self) -> &dyn CuMsgMetadataTrait;
497}
498
499pub trait ErasedCuStampedDataSet {
502 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
503}
504
505pub trait CuPayloadRawBytes {
507 fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
510}
511
512#[derive(Debug, Clone, Copy)]
517pub struct TaskOutputSpec {
518 pub task_id: &'static str,
519 pub msg_type: &'static str,
520 pub payload_type_path_fn: fn() -> &'static str,
521 #[cfg(feature = "reflect")]
522 payload_type_registration_fn: fn(&mut TypeRegistry),
523}
524
525impl TaskOutputSpec {
526 #[cfg(feature = "reflect")]
527 pub const fn new<T>(task_id: &'static str, msg_type: &'static str) -> Self
528 where
529 T: GetTypeRegistration + TypePath,
530 {
531 Self {
532 task_id,
533 msg_type,
534 payload_type_path_fn: payload_type_path::<T>,
535 payload_type_registration_fn: register_payload_type::<T>,
536 }
537 }
538
539 #[cfg(not(feature = "reflect"))]
540 pub const fn new<T>(task_id: &'static str, msg_type: &'static str) -> Self {
541 Self {
542 task_id,
543 msg_type,
544 payload_type_path_fn: payload_type_path::<T>,
545 }
546 }
547
548 #[inline]
549 pub fn payload_type_path(&self) -> &'static str {
550 (self.payload_type_path_fn)()
551 }
552
553 #[cfg(feature = "reflect")]
554 #[inline]
555 pub fn register_payload_type(&self, registry: &mut TypeRegistry) {
556 (self.payload_type_registration_fn)(registry);
557 }
558}
559
560#[cfg(feature = "reflect")]
561fn payload_type_path<T: TypePath>() -> &'static str {
562 T::type_path()
563}
564
565#[cfg(not(feature = "reflect"))]
566fn payload_type_path<T>() -> &'static str {
567 core::any::type_name::<T>()
568}
569
570#[cfg(feature = "reflect")]
571fn register_payload_type<T: GetTypeRegistration>(registry: &mut TypeRegistry) {
572 registry.register::<T>();
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
576pub enum DebugFieldSemantics {
577 Time,
578 OptionalTime,
579 Duration,
580 GeodeticPosition,
581 Quantity {
582 quantity_name: String,
583 unit_symbol: String,
584 },
585}
586
587#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
588#[serde(rename_all = "snake_case")]
589pub enum DebugFieldKind {
590 Scalar,
591 Struct,
592 TupleStruct,
593 Tuple,
594 List,
595 Array,
596 Map,
597 Set,
598 Enum,
599}
600
601#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
602#[serde(rename_all = "snake_case")]
603pub enum DebugScalarKind {
604 Bool,
605 I8,
606 I16,
607 I32,
608 I64,
609 I128,
610 Isize,
611 U8,
612 U16,
613 U32,
614 U64,
615 U128,
616 Usize,
617 F32,
618 F64,
619 Char,
620 String,
621}
622
623impl DebugScalarKind {
624 pub const fn type_name(self) -> &'static str {
625 match self {
626 Self::Bool => "bool",
627 Self::I8 => "i8",
628 Self::I16 => "i16",
629 Self::I32 => "i32",
630 Self::I64 => "i64",
631 Self::I128 => "i128",
632 Self::Isize => "isize",
633 Self::U8 => "u8",
634 Self::U16 => "u16",
635 Self::U32 => "u32",
636 Self::U64 => "u64",
637 Self::U128 => "u128",
638 Self::Usize => "usize",
639 Self::F32 => "f32",
640 Self::F64 => "f64",
641 Self::Char => "char",
642 Self::String => "String",
643 }
644 }
645
646 pub const fn is_numeric(self) -> bool {
647 matches!(
648 self,
649 Self::I8
650 | Self::I16
651 | Self::I32
652 | Self::I64
653 | Self::I128
654 | Self::Isize
655 | Self::U8
656 | Self::U16
657 | Self::U32
658 | Self::U64
659 | Self::U128
660 | Self::Usize
661 | Self::F32
662 | Self::F64
663 )
664 }
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
668#[serde(rename_all = "snake_case")]
669pub enum DebugEnumVariantKind {
670 Unit,
671 Tuple,
672 Struct,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
676pub struct DebugEnumVariantDescriptor {
677 pub name: String,
678 pub kind: DebugEnumVariantKind,
679 #[serde(default, skip_serializing_if = "Vec::is_empty")]
680 pub fields: Vec<DebugFieldDescriptor>,
681}
682
683#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
684pub struct DebugFieldDescriptor {
685 pub display_path: String,
686 #[serde(
687 default,
688 skip_serializing_if = "Option::is_none",
689 deserialize_with = "deserialize_debug_binding_name"
690 )]
691 pub binding_name: Option<String>,
692 pub value_type_path: String,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub scalar_kind: Option<DebugScalarKind>,
695 #[serde(default, skip_serializing_if = "Option::is_none")]
696 pub semantics: Option<DebugFieldSemantics>,
697 pub nullable: bool,
698 pub kind: DebugFieldKind,
699 #[serde(default, skip_serializing_if = "Vec::is_empty")]
700 pub children: Vec<DebugFieldDescriptor>,
701 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub map_key: Option<Box<DebugFieldDescriptor>>,
703 #[serde(default, skip_serializing_if = "Option::is_none")]
704 pub map_value: Option<Box<DebugFieldDescriptor>>,
705 #[serde(default, skip_serializing_if = "Vec::is_empty")]
706 pub enum_variants: Vec<DebugEnumVariantDescriptor>,
707}
708
709fn deserialize_debug_binding_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
710where
711 D: Deserializer<'de>,
712{
713 struct BindingNameVisitor;
714
715 impl<'de> Visitor<'de> for BindingNameVisitor {
716 type Value = Option<String>;
717
718 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
719 formatter.write_str("a string, null, or an empty sequence")
720 }
721
722 fn visit_none<E>(self) -> Result<Self::Value, E>
723 where
724 E: de::Error,
725 {
726 Ok(None)
727 }
728
729 fn visit_unit<E>(self) -> Result<Self::Value, E>
730 where
731 E: de::Error,
732 {
733 Ok(None)
734 }
735
736 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
737 where
738 D: Deserializer<'de>,
739 {
740 deserialize_debug_binding_name(deserializer)
741 }
742
743 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
744 where
745 E: de::Error,
746 {
747 Ok(Some(value.to_owned()))
748 }
749
750 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
751 where
752 E: de::Error,
753 {
754 Ok(Some(value))
755 }
756
757 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
758 where
759 A: SeqAccess<'de>,
760 {
761 if seq.next_element::<de::IgnoredAny>()?.is_none() {
762 return Ok(None);
763 }
764 Err(de::Error::invalid_type(
765 de::Unexpected::Seq,
766 &"an empty sequence",
767 ))
768 }
769 }
770
771 deserializer.deserialize_any(BindingNameVisitor)
772}
773
774#[derive(Debug, Clone, PartialEq, Eq)]
775pub struct DebugScalarRegistration {
776 pub type_path: &'static str,
777 pub scalar_kind: DebugScalarKind,
778 pub semantics: DebugFieldSemantics,
779}
780
781pub trait DebugScalarType: 'static {
782 fn debug_scalar_registration() -> DebugScalarRegistration;
783}
784
785pub trait MatchingTasks {
786 fn get_all_task_ids() -> &'static [&'static str];
787
788 fn get_output_specs() -> &'static [TaskOutputSpec] {
789 &[]
790 }
791}
792
793pub trait SerializedPayloadSchema {
802 fn serialized_payload_schema() -> &'static str;
804}
805
806#[derive(Clone, Copy)]
808pub struct ReflectSerializedPayloadSchema {
809 schema_fn: fn() -> &'static str,
810}
811
812impl ReflectSerializedPayloadSchema {
813 pub fn schema(&self) -> &'static str {
814 (self.schema_fn)()
815 }
816}
817
818#[cfg(feature = "reflect")]
819impl<T> bevy_reflect::FromType<T> for ReflectSerializedPayloadSchema
820where
821 T: SerializedPayloadSchema,
822{
823 fn from_type() -> Self {
824 Self {
825 schema_fn: T::serialized_payload_schema,
826 }
827 }
828}
829
830#[deprecated(
839 since = "1.2.0",
840 note = "generated logreaders now derive schemas from output metadata; use SerializedPayloadSchema for custom serialized payload shapes or export_to_mcap_with_schemas for explicit per-slot schemas"
841)]
842pub trait PayloadSchemas {
843 fn get_payload_schemas() -> Vec<(&'static str, String)> {
848 Vec::new()
849 }
850}
851
852pub trait CopperListTuple:
854 bincode::Encode
855 + bincode::Decode<()>
856 + Debug
857 + Serialize
858 + ErasedCuStampedDataSet
859 + MatchingTasks
860 + Default
861{
862} impl<T> CopperListTuple for T where
866 T: bincode::Encode
867 + bincode::Decode<()>
868 + Debug
869 + Serialize
870 + ErasedCuStampedDataSet
871 + MatchingTasks
872 + Default
873{
874}
875
876pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
880
881#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
882pub struct CuCompactString(pub CompactString);
883
884impl Encode for CuCompactString {
885 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
886 let CuCompactString(compact_string) = self;
887 let bytes = &compact_string.as_bytes();
888 bytes.encode(encoder)
889 }
890}
891
892impl Debug for CuCompactString {
893 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
894 if self.0.is_empty() {
895 return write!(f, "CuCompactString(Empty)");
896 }
897 write!(f, "CuCompactString({})", self.0)
898 }
899}
900
901impl<Context> Decode<Context> for CuCompactString {
902 fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
903 let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
905 CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
906 Ok(CuCompactString(compact_string))
907 }
908}
909
910impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
911 fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
912 CuCompactString::decode(decoder)
913 }
914}
915
916#[cfg(feature = "defmt")]
917impl defmt::Format for CuError {
918 fn format(&self, f: defmt::Formatter) {
919 match &self.cause {
920 Some(c) => {
921 let cause_str = c.to_string();
922 defmt::write!(
923 f,
924 "CuError {{ message: {}, cause: {} }}",
925 defmt::Display2Format(&self.message),
926 defmt::Display2Format(&cause_str),
927 )
928 }
929 None => defmt::write!(
930 f,
931 "CuError {{ message: {}, cause: None }}",
932 defmt::Display2Format(&self.message),
933 ),
934 }
935 }
936}
937
938#[cfg(feature = "defmt")]
939impl defmt::Format for CuCompactString {
940 fn format(&self, f: defmt::Formatter) {
941 if self.0.is_empty() {
942 defmt::write!(f, "CuCompactString(Empty)");
943 } else {
944 defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
945 }
946 }
947}
948
949#[cfg(test)]
950mod tests {
951 use crate::CuCompactString;
952 use bincode::{config, decode_from_slice, encode_to_vec};
953 use compact_str::CompactString;
954
955 #[test]
956 fn test_cucompactstr_encode_decode_empty() {
957 let cstr = CuCompactString(CompactString::from(""));
958 let config = config::standard();
959 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
960 assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
962 decode_from_slice(&encoded, config).expect("Decoding failed");
963 assert_eq!(cstr.0, decoded.0);
964 }
965
966 #[test]
967 fn test_cucompactstr_encode_decode_small() {
968 let cstr = CuCompactString(CompactString::from("test"));
969 let config = config::standard();
970 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
971 assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
973 decode_from_slice(&encoded, config).expect("Decoding failed");
974 assert_eq!(cstr.0, decoded.0);
975 }
976}
977
978#[cfg(all(test, feature = "std"))]
980mod std_tests {
981 use crate::{
982 CuError, DebugFieldDescriptor, DebugFieldKind, DebugFieldSemantics, DebugScalarKind,
983 with_cause,
984 };
985 use serde_json::json;
986
987 #[test]
988 fn test_cuerror_from_str() {
989 let err = CuError::from("test error");
990 assert_eq!(err.message(), "test error");
991 assert!(err.cause().is_none());
992 }
993
994 #[test]
995 fn test_cuerror_from_string() {
996 let err = CuError::from(String::from("test error"));
997 assert_eq!(err.message(), "test error");
998 assert!(err.cause().is_none());
999 }
1000
1001 #[test]
1002 fn test_cuerror_new_index() {
1003 let err = CuError::new(42);
1004 assert_eq!(err.message(), "[interned:42]");
1005 assert!(err.cause().is_none());
1006 }
1007
1008 #[test]
1009 fn test_cuerror_new_with_cause() {
1010 let io_err = std::io::Error::other("io error");
1011 let err = CuError::new_with_cause("wrapped error", io_err);
1012 assert_eq!(err.message(), "wrapped error");
1013 assert!(err.cause().is_some());
1014 assert!(err.cause().unwrap().to_string().contains("io error"));
1015 }
1016
1017 #[test]
1018 fn test_cuerror_add_cause() {
1019 let err = CuError::from("base error").add_cause("additional context");
1020 assert_eq!(err.message(), "base error");
1021 assert!(err.cause().is_some());
1022 assert_eq!(err.cause().unwrap().to_string(), "additional context");
1023 }
1024
1025 #[test]
1026 fn test_cuerror_with_cause_method() {
1027 let io_err = std::io::Error::other("io error");
1028 let err = CuError::from("base error").with_cause(io_err);
1029 assert_eq!(err.message(), "base error");
1030 assert!(err.cause().is_some());
1031 }
1032
1033 #[test]
1034 fn test_cuerror_with_cause_free_function() {
1035 let io_err = std::io::Error::other("io error");
1036 let err = with_cause("wrapped", io_err);
1037 assert_eq!(err.message(), "wrapped");
1038 assert!(err.cause().is_some());
1039 }
1040
1041 #[test]
1042 fn test_cuerror_clone() {
1043 let io_err = std::io::Error::other("io error");
1044 let err = CuError::new_with_cause("test", io_err);
1045 let cloned = err.clone();
1046 assert_eq!(err.message(), cloned.message());
1047 assert_eq!(
1049 err.cause().map(|c| c.to_string()),
1050 cloned.cause().map(|c| c.to_string())
1051 );
1052 }
1053
1054 #[test]
1055 fn test_cuerror_serialize_deserialize_json() {
1056 let io_err = std::io::Error::other("io error");
1057 let err = CuError::new_with_cause("test", io_err);
1058
1059 let serialized = serde_json::to_string(&err).unwrap();
1060 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
1061
1062 assert_eq!(err.message(), deserialized.message());
1063 assert!(deserialized.cause().is_some());
1065 }
1066
1067 #[test]
1068 fn test_cuerror_serialize_deserialize_no_cause() {
1069 let err = CuError::from("simple error");
1070
1071 let serialized = serde_json::to_string(&err).unwrap();
1072 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
1073
1074 assert_eq!(err.message(), deserialized.message());
1075 assert!(deserialized.cause().is_none());
1076 }
1077
1078 #[test]
1079 fn test_cuerror_display() {
1080 let err = CuError::from("test error").add_cause("some context");
1081 let display = err.to_string();
1082 assert!(display.contains("test error"));
1083 assert!(display.contains("some context"));
1084 }
1085
1086 #[test]
1087 fn test_cuerror_debug() {
1088 let err = CuError::from("test error").add_cause("some context");
1089 let debug = format!("{:?}", err);
1090 assert!(debug.contains("test error"));
1091 assert!(debug.contains("some context"));
1092 }
1093
1094 #[test]
1095 fn debug_field_descriptor_skips_missing_binding_name_on_serialize() {
1096 let descriptor = DebugFieldDescriptor {
1097 display_path: "meta.process_time.start_ns".to_owned(),
1098 binding_name: None,
1099 value_type_path: "cu29_clock::CuTime".to_owned(),
1100 scalar_kind: Some(DebugScalarKind::U64),
1101 semantics: Some(DebugFieldSemantics::Time),
1102 nullable: true,
1103 kind: DebugFieldKind::Scalar,
1104 children: Vec::new(),
1105 map_key: None,
1106 map_value: None,
1107 enum_variants: Vec::new(),
1108 };
1109
1110 let encoded = serde_json::to_value(&descriptor).unwrap();
1111 assert!(encoded.get("binding_name").is_none());
1112 }
1113
1114 #[test]
1115 fn debug_field_descriptor_accepts_empty_array_binding_name() {
1116 let encoded = json!({
1117 "display_path": "meta.process_time.start_ns",
1118 "binding_name": [],
1119 "value_type_path": "cu29_clock::CuTime",
1120 "semantics": "Time",
1121 "nullable": true,
1122 "kind": "scalar",
1123 });
1124
1125 let descriptor: DebugFieldDescriptor = serde_json::from_value(encoded).unwrap();
1126 assert_eq!(descriptor.binding_name, None);
1127 assert_eq!(descriptor.semantics, Some(DebugFieldSemantics::Time));
1128 assert_eq!(descriptor.scalar_kind, None);
1129 assert_eq!(descriptor.kind, DebugFieldKind::Scalar);
1130 assert!(descriptor.children.is_empty());
1131 }
1132}