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