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, }
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}