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, ApplicationMetadata,
462 ValueDecodeCatalog,
463}
464pub trait Metadata: Default + Debug + Clone + Encode + Decode<()> + Serialize {}
466
467impl Metadata for () {}
468
469#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
471#[cfg_attr(feature = "reflect", derive(Reflect))]
472pub struct CuMsgOrigin {
473 pub subsystem_code: u16,
474 pub instance_id: u32,
475 pub cl_id: u64,
476}
477
478pub trait CuMsgMetadataTrait {
480 fn process_time(&self) -> PartialCuTimeRange;
482
483 fn status_txt(&self) -> &CuCompactString;
485
486 fn origin(&self) -> Option<&CuMsgOrigin> {
488 None
489 }
490}
491
492pub trait ErasedCuStampedData {
494 fn payload(&self) -> Option<&dyn erased_serde::Serialize>;
495 #[cfg(feature = "reflect")]
496 fn payload_reflect(&self) -> Option<&dyn Reflect>;
497 fn tov(&self) -> Tov;
498 fn metadata(&self) -> &dyn CuMsgMetadataTrait;
499}
500
501pub trait ErasedCuStampedDataSet {
504 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData>;
505}
506
507pub trait CuPayloadRawBytes {
509 fn payload_raw_bytes(&self) -> Vec<Option<u64>>;
512}
513
514#[derive(Debug, Clone, Copy)]
519pub struct TaskOutputSpec {
520 pub task_id: &'static str,
521 pub msg_type: &'static str,
522 pub payload_type_path_fn: fn() -> &'static str,
523 #[cfg(feature = "reflect")]
524 payload_type_registration_fn: fn(&mut TypeRegistry),
525}
526
527impl TaskOutputSpec {
528 #[cfg(feature = "reflect")]
529 pub const fn new<T>(task_id: &'static str, msg_type: &'static str) -> Self
530 where
531 T: GetTypeRegistration + TypePath,
532 {
533 Self {
534 task_id,
535 msg_type,
536 payload_type_path_fn: payload_type_path::<T>,
537 payload_type_registration_fn: register_payload_type::<T>,
538 }
539 }
540
541 #[cfg(not(feature = "reflect"))]
542 pub const fn new<T>(task_id: &'static str, msg_type: &'static str) -> Self {
543 Self {
544 task_id,
545 msg_type,
546 payload_type_path_fn: payload_type_path::<T>,
547 }
548 }
549
550 #[inline]
551 pub fn payload_type_path(&self) -> &'static str {
552 (self.payload_type_path_fn)()
553 }
554
555 #[cfg(feature = "reflect")]
556 #[inline]
557 pub fn register_payload_type(&self, registry: &mut TypeRegistry) {
558 (self.payload_type_registration_fn)(registry);
559 }
560}
561
562#[cfg(feature = "reflect")]
563fn payload_type_path<T: TypePath>() -> &'static str {
564 T::type_path()
565}
566
567#[cfg(not(feature = "reflect"))]
568fn payload_type_path<T>() -> &'static str {
569 core::any::type_name::<T>()
570}
571
572#[cfg(feature = "reflect")]
573fn register_payload_type<T: GetTypeRegistration>(registry: &mut TypeRegistry) {
574 registry.register::<T>();
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
578pub enum DebugFieldSemantics {
579 Time,
580 OptionalTime,
581 Duration,
582 GeodeticPosition,
583 Quantity {
584 quantity_name: String,
585 unit_symbol: String,
586 },
587}
588
589#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
590#[serde(rename_all = "snake_case")]
591pub enum DebugFieldKind {
592 Scalar,
593 Struct,
594 TupleStruct,
595 Tuple,
596 List,
597 Array,
598 Map,
599 Set,
600 Enum,
601}
602
603#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
604#[serde(rename_all = "snake_case")]
605pub enum DebugScalarKind {
606 Bool,
607 I8,
608 I16,
609 I32,
610 I64,
611 I128,
612 Isize,
613 U8,
614 U16,
615 U32,
616 U64,
617 U128,
618 Usize,
619 F32,
620 F64,
621 Char,
622 String,
623}
624
625impl DebugScalarKind {
626 pub const fn type_name(self) -> &'static str {
627 match self {
628 Self::Bool => "bool",
629 Self::I8 => "i8",
630 Self::I16 => "i16",
631 Self::I32 => "i32",
632 Self::I64 => "i64",
633 Self::I128 => "i128",
634 Self::Isize => "isize",
635 Self::U8 => "u8",
636 Self::U16 => "u16",
637 Self::U32 => "u32",
638 Self::U64 => "u64",
639 Self::U128 => "u128",
640 Self::Usize => "usize",
641 Self::F32 => "f32",
642 Self::F64 => "f64",
643 Self::Char => "char",
644 Self::String => "String",
645 }
646 }
647
648 pub const fn is_numeric(self) -> bool {
649 matches!(
650 self,
651 Self::I8
652 | Self::I16
653 | Self::I32
654 | Self::I64
655 | Self::I128
656 | Self::Isize
657 | Self::U8
658 | Self::U16
659 | Self::U32
660 | Self::U64
661 | Self::U128
662 | Self::Usize
663 | Self::F32
664 | Self::F64
665 )
666 }
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
670#[serde(rename_all = "snake_case")]
671pub enum DebugEnumVariantKind {
672 Unit,
673 Tuple,
674 Struct,
675}
676
677#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
678pub struct DebugEnumVariantDescriptor {
679 pub name: String,
680 pub kind: DebugEnumVariantKind,
681 #[serde(default, skip_serializing_if = "Vec::is_empty")]
682 pub fields: Vec<DebugFieldDescriptor>,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
686pub struct DebugFieldDescriptor {
687 pub display_path: String,
688 #[serde(
689 default,
690 skip_serializing_if = "Option::is_none",
691 deserialize_with = "deserialize_debug_binding_name"
692 )]
693 pub binding_name: Option<String>,
694 pub value_type_path: String,
695 #[serde(default, skip_serializing_if = "Option::is_none")]
696 pub scalar_kind: Option<DebugScalarKind>,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub semantics: Option<DebugFieldSemantics>,
699 pub nullable: bool,
700 pub kind: DebugFieldKind,
701 #[serde(default, skip_serializing_if = "Vec::is_empty")]
702 pub children: Vec<DebugFieldDescriptor>,
703 #[serde(default, skip_serializing_if = "Option::is_none")]
704 pub map_key: Option<Box<DebugFieldDescriptor>>,
705 #[serde(default, skip_serializing_if = "Option::is_none")]
706 pub map_value: Option<Box<DebugFieldDescriptor>>,
707 #[serde(default, skip_serializing_if = "Vec::is_empty")]
708 pub enum_variants: Vec<DebugEnumVariantDescriptor>,
709}
710
711fn deserialize_debug_binding_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
712where
713 D: Deserializer<'de>,
714{
715 struct BindingNameVisitor;
716
717 impl<'de> Visitor<'de> for BindingNameVisitor {
718 type Value = Option<String>;
719
720 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
721 formatter.write_str("a string, null, or an empty sequence")
722 }
723
724 fn visit_none<E>(self) -> Result<Self::Value, E>
725 where
726 E: de::Error,
727 {
728 Ok(None)
729 }
730
731 fn visit_unit<E>(self) -> Result<Self::Value, E>
732 where
733 E: de::Error,
734 {
735 Ok(None)
736 }
737
738 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
739 where
740 D: Deserializer<'de>,
741 {
742 deserialize_debug_binding_name(deserializer)
743 }
744
745 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
746 where
747 E: de::Error,
748 {
749 Ok(Some(value.to_owned()))
750 }
751
752 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
753 where
754 E: de::Error,
755 {
756 Ok(Some(value))
757 }
758
759 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
760 where
761 A: SeqAccess<'de>,
762 {
763 if seq.next_element::<de::IgnoredAny>()?.is_none() {
764 return Ok(None);
765 }
766 Err(de::Error::invalid_type(
767 de::Unexpected::Seq,
768 &"an empty sequence",
769 ))
770 }
771 }
772
773 deserializer.deserialize_any(BindingNameVisitor)
774}
775
776#[derive(Debug, Clone, PartialEq, Eq)]
777pub struct DebugScalarRegistration {
778 pub type_path: &'static str,
779 pub scalar_kind: DebugScalarKind,
780 pub semantics: DebugFieldSemantics,
781}
782
783pub trait DebugScalarType: 'static {
784 fn debug_scalar_registration() -> DebugScalarRegistration;
785}
786
787pub trait MatchingTasks {
788 fn get_all_task_ids() -> &'static [&'static str];
789
790 fn get_output_specs() -> &'static [TaskOutputSpec] {
791 &[]
792 }
793}
794
795pub trait SerializedPayloadSchema {
804 fn serialized_payload_schema() -> &'static str;
806}
807
808#[derive(Clone, Copy)]
810pub struct ReflectSerializedPayloadSchema {
811 schema_fn: fn() -> &'static str,
812}
813
814impl ReflectSerializedPayloadSchema {
815 pub fn schema(&self) -> &'static str {
816 (self.schema_fn)()
817 }
818}
819
820#[cfg(feature = "reflect")]
821impl<T> bevy_reflect::FromType<T> for ReflectSerializedPayloadSchema
822where
823 T: SerializedPayloadSchema,
824{
825 fn from_type() -> Self {
826 Self {
827 schema_fn: T::serialized_payload_schema,
828 }
829 }
830}
831
832#[deprecated(
841 since = "1.2.0",
842 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"
843)]
844pub trait PayloadSchemas {
845 fn get_payload_schemas() -> Vec<(&'static str, String)> {
850 Vec::new()
851 }
852}
853
854pub trait CopperListTuple:
856 bincode::Encode
857 + bincode::Decode<()>
858 + Debug
859 + Serialize
860 + ErasedCuStampedDataSet
861 + MatchingTasks
862 + Default
863{
864} impl<T> CopperListTuple for T where
868 T: bincode::Encode
869 + bincode::Decode<()>
870 + Debug
871 + Serialize
872 + ErasedCuStampedDataSet
873 + MatchingTasks
874 + Default
875{
876}
877
878pub const COMPACT_STRING_CAPACITY: usize = size_of::<String>();
882
883#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
884pub struct CuCompactString(pub CompactString);
885
886impl Encode for CuCompactString {
887 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
888 let CuCompactString(compact_string) = self;
889 let bytes = &compact_string.as_bytes();
890 bytes.encode(encoder)
891 }
892}
893
894impl Debug for CuCompactString {
895 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
896 if self.0.is_empty() {
897 return write!(f, "CuCompactString(Empty)");
898 }
899 write!(f, "CuCompactString({})", self.0)
900 }
901}
902
903impl<Context> Decode<Context> for CuCompactString {
904 fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
905 let bytes = <Vec<u8> as Decode<D::Context>>::decode(decoder)?; let compact_string =
907 CompactString::from_utf8(bytes).map_err(|e| DecodeError::Utf8 { inner: e })?;
908 Ok(CuCompactString(compact_string))
909 }
910}
911
912impl<'de, Context> BorrowDecode<'de, Context> for CuCompactString {
913 fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
914 CuCompactString::decode(decoder)
915 }
916}
917
918#[cfg(feature = "defmt")]
919impl defmt::Format for CuError {
920 fn format(&self, f: defmt::Formatter) {
921 match &self.cause {
922 Some(c) => {
923 let cause_str = c.to_string();
924 defmt::write!(
925 f,
926 "CuError {{ message: {}, cause: {} }}",
927 defmt::Display2Format(&self.message),
928 defmt::Display2Format(&cause_str),
929 )
930 }
931 None => defmt::write!(
932 f,
933 "CuError {{ message: {}, cause: None }}",
934 defmt::Display2Format(&self.message),
935 ),
936 }
937 }
938}
939
940#[cfg(feature = "defmt")]
941impl defmt::Format for CuCompactString {
942 fn format(&self, f: defmt::Formatter) {
943 if self.0.is_empty() {
944 defmt::write!(f, "CuCompactString(Empty)");
945 } else {
946 defmt::write!(f, "CuCompactString({})", defmt::Display2Format(&self.0));
947 }
948 }
949}
950
951impl bincode::ValueDecode for CuCompactString {
952 const DECODE: &'static bincode::ValueDecodeSpec =
953 <alloc::string::String as bincode::ValueDecode>::DECODE;
954}
955
956#[cfg(test)]
957mod tests {
958 use crate::CuCompactString;
959 use bincode::{config, decode_from_slice, encode_to_vec};
960 use compact_str::CompactString;
961
962 #[test]
963 fn test_cucompactstr_encode_decode_empty() {
964 let cstr = CuCompactString(CompactString::from(""));
965 let config = config::standard();
966 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
967 assert_eq!(encoded.len(), 1); let (decoded, _): (CuCompactString, usize) =
969 decode_from_slice(&encoded, config).expect("Decoding failed");
970 assert_eq!(cstr.0, decoded.0);
971 }
972
973 #[test]
974 fn test_cucompactstr_encode_decode_small() {
975 let cstr = CuCompactString(CompactString::from("test"));
976 let config = config::standard();
977 let encoded = encode_to_vec(&cstr, config).expect("Encoding failed");
978 assert_eq!(encoded.len(), 5); let (decoded, _): (CuCompactString, usize) =
980 decode_from_slice(&encoded, config).expect("Decoding failed");
981 assert_eq!(cstr.0, decoded.0);
982 }
983}
984
985#[cfg(all(test, feature = "std"))]
987mod std_tests {
988 use crate::{
989 CuError, DebugFieldDescriptor, DebugFieldKind, DebugFieldSemantics, DebugScalarKind,
990 with_cause,
991 };
992 use serde_json::json;
993
994 #[test]
995 fn test_cuerror_from_str() {
996 let err = CuError::from("test error");
997 assert_eq!(err.message(), "test error");
998 assert!(err.cause().is_none());
999 }
1000
1001 #[test]
1002 fn test_cuerror_from_string() {
1003 let err = CuError::from(String::from("test error"));
1004 assert_eq!(err.message(), "test error");
1005 assert!(err.cause().is_none());
1006 }
1007
1008 #[test]
1009 fn test_cuerror_new_index() {
1010 let err = CuError::new(42);
1011 assert_eq!(err.message(), "[interned:42]");
1012 assert!(err.cause().is_none());
1013 }
1014
1015 #[test]
1016 fn test_cuerror_new_with_cause() {
1017 let io_err = std::io::Error::other("io error");
1018 let err = CuError::new_with_cause("wrapped error", io_err);
1019 assert_eq!(err.message(), "wrapped error");
1020 assert!(err.cause().is_some());
1021 assert!(err.cause().unwrap().to_string().contains("io error"));
1022 }
1023
1024 #[test]
1025 fn test_cuerror_add_cause() {
1026 let err = CuError::from("base error").add_cause("additional context");
1027 assert_eq!(err.message(), "base error");
1028 assert!(err.cause().is_some());
1029 assert_eq!(err.cause().unwrap().to_string(), "additional context");
1030 }
1031
1032 #[test]
1033 fn test_cuerror_with_cause_method() {
1034 let io_err = std::io::Error::other("io error");
1035 let err = CuError::from("base error").with_cause(io_err);
1036 assert_eq!(err.message(), "base error");
1037 assert!(err.cause().is_some());
1038 }
1039
1040 #[test]
1041 fn test_cuerror_with_cause_free_function() {
1042 let io_err = std::io::Error::other("io error");
1043 let err = with_cause("wrapped", io_err);
1044 assert_eq!(err.message(), "wrapped");
1045 assert!(err.cause().is_some());
1046 }
1047
1048 #[test]
1049 fn test_cuerror_clone() {
1050 let io_err = std::io::Error::other("io error");
1051 let err = CuError::new_with_cause("test", io_err);
1052 let cloned = err.clone();
1053 assert_eq!(err.message(), cloned.message());
1054 assert_eq!(
1056 err.cause().map(|c| c.to_string()),
1057 cloned.cause().map(|c| c.to_string())
1058 );
1059 }
1060
1061 #[test]
1062 fn test_cuerror_serialize_deserialize_json() {
1063 let io_err = std::io::Error::other("io error");
1064 let err = CuError::new_with_cause("test", io_err);
1065
1066 let serialized = serde_json::to_string(&err).unwrap();
1067 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
1068
1069 assert_eq!(err.message(), deserialized.message());
1070 assert!(deserialized.cause().is_some());
1072 }
1073
1074 #[test]
1075 fn test_cuerror_serialize_deserialize_no_cause() {
1076 let err = CuError::from("simple error");
1077
1078 let serialized = serde_json::to_string(&err).unwrap();
1079 let deserialized: CuError = serde_json::from_str(&serialized).unwrap();
1080
1081 assert_eq!(err.message(), deserialized.message());
1082 assert!(deserialized.cause().is_none());
1083 }
1084
1085 #[test]
1086 fn test_cuerror_display() {
1087 let err = CuError::from("test error").add_cause("some context");
1088 let display = err.to_string();
1089 assert!(display.contains("test error"));
1090 assert!(display.contains("some context"));
1091 }
1092
1093 #[test]
1094 fn test_cuerror_debug() {
1095 let err = CuError::from("test error").add_cause("some context");
1096 let debug = format!("{:?}", err);
1097 assert!(debug.contains("test error"));
1098 assert!(debug.contains("some context"));
1099 }
1100
1101 #[test]
1102 fn debug_field_descriptor_skips_missing_binding_name_on_serialize() {
1103 let descriptor = DebugFieldDescriptor {
1104 display_path: "meta.process_time.start_ns".to_owned(),
1105 binding_name: None,
1106 value_type_path: "cu29_clock::CuTime".to_owned(),
1107 scalar_kind: Some(DebugScalarKind::U64),
1108 semantics: Some(DebugFieldSemantics::Time),
1109 nullable: true,
1110 kind: DebugFieldKind::Scalar,
1111 children: Vec::new(),
1112 map_key: None,
1113 map_value: None,
1114 enum_variants: Vec::new(),
1115 };
1116
1117 let encoded = serde_json::to_value(&descriptor).unwrap();
1118 assert!(encoded.get("binding_name").is_none());
1119 }
1120
1121 #[test]
1122 fn debug_field_descriptor_accepts_empty_array_binding_name() {
1123 let encoded = json!({
1124 "display_path": "meta.process_time.start_ns",
1125 "binding_name": [],
1126 "value_type_path": "cu29_clock::CuTime",
1127 "semantics": "Time",
1128 "nullable": true,
1129 "kind": "scalar",
1130 });
1131
1132 let descriptor: DebugFieldDescriptor = serde_json::from_value(encoded).unwrap();
1133 assert_eq!(descriptor.binding_name, None);
1134 assert_eq!(descriptor.semantics, Some(DebugFieldSemantics::Time));
1135 assert_eq!(descriptor.scalar_kind, None);
1136 assert_eq!(descriptor.kind, DebugFieldKind::Scalar);
1137 assert!(descriptor.children.is_empty());
1138 }
1139}