1use std::any::TypeId;
13use std::collections::HashMap;
14use std::fmt;
15use std::io::Cursor;
16use std::sync::LazyLock;
17
18use enum_as_inner::EnumAsInner;
19use serde::Deserialize;
20use serde::Serialize;
21use serde::de::DeserializeOwned;
22
23use crate as hyperactor;
24use crate::config;
25
26pub trait Named: Sized + 'static {
28 fn typename() -> &'static str;
31
32 fn typehash() -> u64 {
35 cityhasher::hash(Self::typename())
38 }
39
40 fn typeid() -> TypeId {
43 TypeId::of::<Self>()
44 }
45
46 fn port() -> u64 {
49 Self::typehash() | (1 << 63)
50 }
51
52 fn arm(&self) -> Option<&'static str> {
55 None
56 }
57
58 unsafe fn arm_unchecked(self_: *const ()) -> Option<&'static str> {
61 unsafe { &*(self_ as *const Self) }.arm()
63 }
64}
65
66#[doc(hidden)]
67pub trait NamedDumpable: Named + Serialize + for<'de> Deserialize<'de> {
71 fn dump(data: Serialized) -> Result<serde_json::Value, anyhow::Error>;
73}
74
75impl<T: Named + Serialize + for<'de> Deserialize<'de>> NamedDumpable for T {
76 fn dump(data: Serialized) -> Result<serde_json::Value, anyhow::Error> {
77 let value = data.deserialized::<Self>()?;
78 Ok(serde_json::to_value(value)?)
79 }
80}
81
82macro_rules! impl_basic {
83 ($t:ty) => {
84 impl Named for $t {
85 fn typename() -> &'static str {
86 stringify!($t)
87 }
88 }
89 };
90}
91
92impl_basic!(());
93impl_basic!(bool);
94impl_basic!(i8);
95impl_basic!(u8);
96impl_basic!(i16);
97impl_basic!(u16);
98impl_basic!(i32);
99impl_basic!(u32);
100impl_basic!(i64);
101impl_basic!(u64);
102impl_basic!(i128);
103impl_basic!(u128);
104impl_basic!(isize);
105impl_basic!(usize);
106impl_basic!(f32);
107impl_basic!(f64);
108impl_basic!(String);
109impl_basic!(std::net::IpAddr);
110impl_basic!(std::net::Ipv4Addr);
111impl_basic!(std::net::Ipv6Addr);
112impl_basic!(std::time::Duration);
113impl_basic!(std::time::SystemTime);
114impl_basic!(bytes::Bytes);
115impl_basic!(ndslice::Point);
119
120impl Named for &'static str {
121 fn typename() -> &'static str {
122 "&str"
123 }
124}
125
126#[doc(hidden)] #[macro_export]
130macro_rules! intern_typename {
131 ($key:ty, $format_string:expr, $($args:ty),+) => {
132 {
133 static CACHE: std::sync::LazyLock<$crate::dashmap::DashMap<std::any::TypeId, &'static str>> =
134 std::sync::LazyLock::new($crate::dashmap::DashMap::new);
135
136 match CACHE.entry(std::any::TypeId::of::<$key>()) {
137 $crate::dashmap::mapref::entry::Entry::Vacant(entry) => {
138 let typename = format!($format_string, $(<$args>::typename()),+).leak();
139 entry.insert(typename);
140 typename
141 }
142 $crate::dashmap::mapref::entry::Entry::Occupied(entry) => *entry.get(),
143 }
144 }
145 };
146}
147pub use intern_typename;
148
149macro_rules! tuple_format_string {
150 ($a:ident,) => { "{}" };
151 ($a:ident, $($rest_a:ident,)+) => { concat!("{}, ", tuple_format_string!($($rest_a,)+)) };
152}
153
154macro_rules! impl_tuple_peel {
155 ($name:ident, $($other:ident,)*) => (impl_tuple! { $($other,)* })
156}
157
158macro_rules! impl_tuple {
159 () => ();
160 ( $($name:ident,)+ ) => (
161 impl<$($name:Named + 'static),+> Named for ($($name,)+) {
162 fn typename() -> &'static str {
163 intern_typename!(Self, concat!("(", tuple_format_string!($($name,)+), ")"), $($name),+)
164 }
165 }
166 impl_tuple_peel! { $($name,)+ }
167 )
168}
169
170impl_tuple! { E, D, C, B, A, Z, Y, X, W, V, U, T, }
171
172impl<T: Named + 'static> Named for Option<T> {
173 fn typename() -> &'static str {
174 intern_typename!(Self, "Option<{}>", T)
175 }
176}
177
178impl<T: Named + 'static> Named for Vec<T> {
179 fn typename() -> &'static str {
180 intern_typename!(Self, "Vec<{}>", T)
181 }
182}
183
184impl<K: Named + 'static, V: Named + 'static> Named for HashMap<K, V> {
185 fn typename() -> &'static str {
186 intern_typename!(Self, "HashMap<{}, {}>", K, V)
187 }
188}
189
190impl<T: Named + 'static, E: Named + 'static> Named for Result<T, E> {
191 fn typename() -> &'static str {
192 intern_typename!(Self, "Result<{}, {}>", T, E)
193 }
194}
195
196static SHAPE_CACHED_TYPEHASH: LazyLock<u64> =
197 LazyLock::new(|| cityhasher::hash(<ndslice::shape::Shape as Named>::typename()));
198
199impl Named for ndslice::shape::Shape {
200 fn typename() -> &'static str {
201 "ndslice::shape::Shape"
202 }
203
204 fn typehash() -> u64 {
205 *SHAPE_CACHED_TYPEHASH
206 }
207}
208
209#[doc(hidden)]
211#[derive(Debug)]
212pub struct TypeInfo {
213 pub typename: fn() -> &'static str,
215 pub typehash: fn() -> u64,
217 pub typeid: fn() -> TypeId,
219 pub port: fn() -> u64,
221 pub dump: Option<fn(Serialized) -> Result<serde_json::Value, anyhow::Error>>,
223 pub arm_unchecked: unsafe fn(*const ()) -> Option<&'static str>,
225}
226
227#[allow(dead_code)]
228impl TypeInfo {
229 pub(crate) fn get(typehash: u64) -> Option<&'static TypeInfo> {
231 TYPE_INFO.get(&typehash).map(|v| &**v)
232 }
233
234 pub(crate) fn get_by_typeid(typeid: TypeId) -> Option<&'static TypeInfo> {
236 TYPE_INFO_BY_TYPE_ID.get(&typeid).map(|v| &**v)
237 }
238
239 pub(crate) fn of<T: ?Sized + 'static>() -> Option<&'static TypeInfo> {
241 Self::get_by_typeid(TypeId::of::<T>())
242 }
243
244 pub(crate) fn typename(&self) -> &'static str {
245 (self.typename)()
246 }
247 pub(crate) fn typehash(&self) -> u64 {
248 (self.typehash)()
249 }
250 pub(crate) fn typeid(&self) -> TypeId {
251 (self.typeid)()
252 }
253 pub(crate) fn port(&self) -> u64 {
254 (self.port)()
255 }
256 pub(crate) fn dump(&self, data: Serialized) -> Result<serde_json::Value, anyhow::Error> {
257 if let Some(dump) = self.dump {
258 (dump)(data)
259 } else {
260 anyhow::bail!("binary does not have dumper for {}", self.typehash())
261 }
262 }
263 pub(crate) unsafe fn arm_unchecked(&self, value: *const ()) -> Option<&'static str> {
264 unsafe { (self.arm_unchecked)(value) }
266 }
267}
268
269inventory::collect!(TypeInfo);
270
271static TYPE_INFO: LazyLock<HashMap<u64, &'static TypeInfo>> = LazyLock::new(|| {
273 inventory::iter::<TypeInfo>()
274 .map(|entry| (entry.typehash(), entry))
275 .collect()
276});
277
278static TYPE_INFO_BY_TYPE_ID: LazyLock<HashMap<std::any::TypeId, &'static TypeInfo>> =
280 LazyLock::new(|| {
281 TYPE_INFO
282 .values()
283 .map(|info| (info.typeid(), &**info))
284 .collect()
285 });
286
287#[macro_export]
293macro_rules! register_type {
294 ($type:ty) => {
295 hyperactor::submit! {
296 hyperactor::data::TypeInfo {
297 typename: <$type as hyperactor::data::Named>::typename,
298 typehash: <$type as hyperactor::data::Named>::typehash,
299 typeid: <$type as hyperactor::data::Named>::typeid,
300 port: <$type as hyperactor::data::Named>::port,
301 dump: Some(<$type as hyperactor::data::NamedDumpable>::dump),
302 arm_unchecked: <$type as hyperactor::data::Named>::arm_unchecked,
303 }
304 }
305 };
306}
307
308#[derive(
311 Debug,
312 Clone,
313 Copy,
314 Serialize,
315 Deserialize,
316 PartialEq,
317 Eq,
318 crate::AttrValue,
319 crate::Named,
320 strum::EnumIter,
321 strum::Display,
322 strum::EnumString
323)]
324pub enum Encoding {
325 #[strum(to_string = "bincode")]
327 Bincode,
328 #[strum(to_string = "serde_json")]
330 Json,
331 #[strum(to_string = "serde_multipart")]
333 Multipart,
334}
335
336#[derive(Clone, Serialize, Deserialize, PartialEq, EnumAsInner)]
338enum Encoded {
339 Bincode(bytes::Bytes),
340 Json(bytes::Bytes),
341 Multipart(serde_multipart::Message),
342}
343
344impl Encoded {
345 pub fn len(&self) -> usize {
347 match &self {
348 Encoded::Bincode(data) => data.len(),
349 Encoded::Json(data) => data.len(),
350 Encoded::Multipart(message) => message.len(),
351 }
352 }
353
354 pub fn is_empty(&self) -> bool {
356 match &self {
357 Encoded::Bincode(data) => data.is_empty(),
358 Encoded::Json(data) => data.is_empty(),
359 Encoded::Multipart(message) => message.is_empty(),
360 }
361 }
362
363 pub fn encoding(&self) -> Encoding {
365 match &self {
366 Encoded::Bincode(_) => Encoding::Bincode,
367 Encoded::Json(_) => Encoding::Json,
368 Encoded::Multipart(_) => Encoding::Multipart,
369 }
370 }
371
372 pub fn crc(&self) -> u32 {
374 match &self {
375 Encoded::Bincode(data) => crc32fast::hash(data),
376 Encoded::Json(data) => crc32fast::hash(data),
377 Encoded::Multipart(message) => {
378 let mut hasher = crc32fast::Hasher::new();
379 hasher.update(message.body().as_ref());
380 for part in message.parts() {
381 hasher.update(part.as_ref());
382 }
383 hasher.finalize()
384 }
385 }
386 }
387}
388
389impl std::fmt::Debug for Encoded {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 match self {
392 Encoded::Bincode(data) => write!(f, "Encoded::Bincode({})", HexFmt(data)),
393 Encoded::Json(data) => write!(f, "Encoded::Json({})", HexFmt(data)),
394 Encoded::Multipart(message) => {
395 write!(
396 f,
397 "Encoded::Multipart(illegal?={} body={}",
398 message.is_illegal(),
399 HexFmt(message.body())
400 )?;
401 for (index, part) in message.parts().iter().enumerate() {
402 write!(f, ", part[{}]={}", index, HexFmt(part))?;
403 }
404 write!(f, ")")
405 }
406 }
407 }
408}
409
410#[derive(Debug, thiserror::Error)]
412pub enum Error {
413 #[error(transparent)]
415 Bincode(#[from] bincode::Error),
416
417 #[error(transparent)]
419 Json(#[from] serde_json::Error),
420
421 #[error("unknown encoding: {0}")]
423 InvalidEncoding(String),
424}
425
426#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
433pub struct Serialized {
434 encoded: Encoded,
436 typehash: u64,
439}
440
441impl std::fmt::Display for Serialized {
442 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443 match self.dump() {
444 Ok(value) => {
445 let typename = self.typename().unwrap();
447 let basename = typename.split("::").last().unwrap_or(typename);
449 write!(f, "{}{}", basename, JsonFmt(&value))
450 }
451 Err(_) => write!(f, "{:?}", self.encoded),
452 }
453 }
454}
455
456impl Serialized {
457 pub fn serialize<T: Serialize + Named>(value: &T) -> Result<Self, Error> {
462 Self::serialize_with_encoding(config::global::get(config::DEFAULT_ENCODING), value)
463 }
464
465 pub fn serialize_as<T: Named, U: Serialize>(value: &U) -> Result<Self, Error> {
469 Self::serialize_with_encoding_as::<T, U>(
470 config::global::get(config::DEFAULT_ENCODING),
471 value,
472 )
473 }
474
475 pub fn serialize_with_encoding<T: Serialize + Named>(
477 encoding: Encoding,
478 value: &T,
479 ) -> Result<Self, Error> {
480 Self::serialize_with_encoding_as::<T, T>(encoding, value)
481 }
482
483 pub fn serialize_with_encoding_as<T: Named, U: Serialize>(
487 encoding: Encoding,
488 value: &U,
489 ) -> Result<Self, Error> {
490 Ok(Self {
491 encoded: match encoding {
492 Encoding::Bincode => Encoded::Bincode(bincode::serialize(value)?.into()),
493 Encoding::Json => Encoded::Json(serde_json::to_vec(value)?.into()),
494 Encoding::Multipart => {
495 Encoded::Multipart(serde_multipart::serialize_bincode(value)?)
496 }
497 },
498 typehash: T::typehash(),
499 })
500 }
501
502 pub fn deserialized<T: DeserializeOwned + Named>(&self) -> Result<T, anyhow::Error> {
504 anyhow::ensure!(
505 self.is::<T>(),
506 "attempted to serialize {}-typed serialized into type {}",
507 self.typename().unwrap_or("unknown"),
508 T::typename()
509 );
510 self.deserialized_unchecked()
511 }
512
513 pub fn deserialized_unchecked<T: DeserializeOwned>(&self) -> Result<T, anyhow::Error> {
517 match &self.encoded {
518 Encoded::Bincode(data) => bincode::deserialize(data).map_err(anyhow::Error::from),
519 Encoded::Json(data) => serde_json::from_slice(data).map_err(anyhow::Error::from),
520 Encoded::Multipart(message) => {
521 serde_multipart::deserialize_bincode(message.clone()).map_err(anyhow::Error::from)
522 }
523 }
524 }
525
526 pub fn transcode_to_json(self) -> Result<Self, Self> {
529 match self.encoded {
530 Encoded::Bincode(_) | Encoded::Multipart(_) => {
531 let json_value = match self.dump() {
532 Ok(json_value) => json_value,
533 Err(_) => return Err(self),
534 };
535 let json_data = match serde_json::to_vec(&json_value) {
536 Ok(json_data) => json_data,
537 Err(_) => return Err(self),
538 };
539 Ok(Self {
540 encoded: Encoded::Json(json_data.into()),
541 typehash: self.typehash,
542 })
543 }
544 Encoded::Json(_) => Ok(self),
545 }
546 }
547
548 pub fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
551 match &self.encoded {
552 Encoded::Bincode(_) | Encoded::Multipart(_) => {
553 let Some(typeinfo) = TYPE_INFO.get(&self.typehash) else {
554 anyhow::bail!("binary does not have typeinfo for {}", self.typehash);
555 };
556 typeinfo.dump(self.clone())
557 }
558 Encoded::Json(data) => serde_json::from_slice(data).map_err(anyhow::Error::from),
559 }
560 }
561
562 pub fn encoding(&self) -> Encoding {
564 self.encoded.encoding()
565 }
566
567 pub fn typehash(&self) -> u64 {
569 self.typehash
570 }
571
572 pub fn typename(&self) -> Option<&'static str> {
574 TYPE_INFO
575 .get(&self.typehash)
576 .map(|typeinfo| typeinfo.typename())
577 }
578
579 pub fn prefix<T: DeserializeOwned>(&self) -> Result<T, anyhow::Error> {
584 match &self.encoded {
585 Encoded::Bincode(data) => bincode::deserialize(data).map_err(anyhow::Error::from),
586 _ => anyhow::bail!("only bincode supports prefix emplacement"),
587 }
588 }
589
590 pub fn emplace_prefix<T: Serialize + DeserializeOwned>(
593 &mut self,
594 prefix: T,
595 ) -> Result<(), anyhow::Error> {
596 let data = match &self.encoded {
597 Encoded::Bincode(data) => data,
598 _ => anyhow::bail!("only bincode supports prefix emplacement"),
599 };
600
601 let mut cursor = Cursor::new(data.clone());
606 let _prefix: T = bincode::deserialize_from(&mut cursor).unwrap();
607 let position = cursor.position() as usize;
608 let suffix = &cursor.into_inner()[position..];
609 let mut data = bincode::serialize(&prefix)?;
610 data.extend_from_slice(suffix);
611 self.encoded = Encoded::Bincode(data.into());
612
613 Ok(())
614 }
615
616 pub fn len(&self) -> usize {
618 self.encoded.len()
619 }
620
621 pub fn is_empty(&self) -> bool {
623 self.encoded.is_empty()
624 }
625
626 pub fn crc(&self) -> u32 {
628 self.encoded.crc()
629 }
630
631 pub fn is<M: Named>(&self) -> bool {
634 self.typehash == M::typehash()
635 }
636}
637
638const MAX_BYTE_PREVIEW_LENGTH: usize = 8;
639
640fn display_bytes_as_hash(f: &mut impl std::fmt::Write, bytes: &[u8]) -> std::fmt::Result {
641 let hash = crc32fast::hash(bytes);
642 write!(f, "CRC:{:x}", hash)?;
643 for &byte in bytes.iter().take(MAX_BYTE_PREVIEW_LENGTH) {
645 write!(f, " {:x}", byte)?;
646 }
647 if bytes.len() > MAX_BYTE_PREVIEW_LENGTH {
648 write!(f, " [...{} bytes]", bytes.len() - MAX_BYTE_PREVIEW_LENGTH)?;
649 }
650 Ok(())
651}
652
653pub struct HexFmt<'a>(pub &'a [u8]);
655
656impl<'a> std::fmt::Display for HexFmt<'a> {
657 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658 display_bytes_as_hash(f, self.0)
660 }
661}
662
663pub struct JsonFmt<'a>(pub &'a serde_json::Value);
666
667const MAX_JSON_VALUE_DISPLAY_LENGTH: usize = 8;
668
669impl<'a> std::fmt::Display for JsonFmt<'a> {
670 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671 fn truncate_and_hash(value_str: &str) -> String {
674 let truncated_str = &value_str[..MAX_JSON_VALUE_DISPLAY_LENGTH];
675 let mut result = truncated_str.to_string();
676 result.push_str(&format!("[...{} chars] ", value_str.len()));
677 display_bytes_as_hash(&mut result, value_str.as_bytes()).unwrap();
678 result
679 }
680
681 fn truncate_json_values(value: &serde_json::Value) -> serde_json::Value {
683 match value {
684 serde_json::Value::String(s) => {
685 if s.len() > MAX_JSON_VALUE_DISPLAY_LENGTH {
686 serde_json::Value::String(truncate_and_hash(s))
687 } else {
688 value.clone()
689 }
690 }
691 serde_json::Value::Array(arr) => {
692 let array_str = serde_json::to_string(arr).unwrap();
693 if array_str.len() > MAX_JSON_VALUE_DISPLAY_LENGTH {
694 serde_json::Value::String(truncate_and_hash(&array_str))
695 } else {
696 value.clone()
697 }
698 }
699 serde_json::Value::Object(obj) => {
700 let truncated_obj: serde_json::Map<_, _> = obj
701 .iter()
702 .map(|(k, v)| (k.clone(), truncate_json_values(v)))
703 .collect();
704 serde_json::Value::Object(truncated_obj)
705 }
706 _ => value.clone(),
707 }
708 }
709
710 let truncated = truncate_json_values(self.0);
711 write!(f, "{}", truncated)
712 }
713}
714
715#[cfg(test)]
716mod tests {
717
718 use serde::Deserialize;
719 use serde::Serialize;
720 use serde_multipart::Part;
721 use strum::IntoEnumIterator;
722
723 use super::*;
724 use crate as hyperactor; use crate::Named;
726
727 #[derive(Named, Serialize, Deserialize)]
728 struct TestStruct;
729
730 #[test]
731 fn test_names() {
732 assert_eq!(String::typename(), "String");
733 assert_eq!(Option::<String>::typename(), "Option<String>");
734 assert_eq!(Vec::<String>::typename(), "Vec<String>");
735 assert_eq!(Vec::<Vec::<String>>::typename(), "Vec<Vec<String>>");
736 assert_eq!(
737 Vec::<Vec::<Vec::<String>>>::typename(),
738 "Vec<Vec<Vec<String>>>"
739 );
740 assert_eq!(
741 <(u64, String, Option::<isize>)>::typename(),
742 "(u64, String, Option<isize>)"
743 );
744 assert_eq!(
745 TestStruct::typename(),
746 "hyperactor::data::tests::TestStruct"
747 );
748 assert_eq!(
749 Vec::<TestStruct>::typename(),
750 "Vec<hyperactor::data::tests::TestStruct>"
751 );
752 }
753
754 #[test]
755 fn test_ports() {
756 assert_eq!(String::typehash(), 3947244799002047352u64);
757 assert_eq!(String::port(), 13170616835856823160u64);
758 assert_ne!(
759 Vec::<Vec::<Vec::<String>>>::typehash(),
760 Vec::<Vec::<Vec::<Vec::<String>>>>::typehash(),
761 );
762 }
763
764 #[derive(Named, Serialize, Deserialize, PartialEq, Eq, Debug)]
765 struct TestDumpStruct {
766 a: String,
767 b: u64,
768 c: Option<i32>,
769 d: Option<Part>,
770 }
771 crate::register_type!(TestDumpStruct);
772
773 #[test]
774 fn test_dump_struct() {
775 let data = TestDumpStruct {
776 a: "hello".to_string(),
777 b: 1234,
778 c: Some(5678),
779 d: None,
780 };
781 let serialized = Serialized::serialize(&data).unwrap();
782 let serialized_json = serialized.clone().transcode_to_json().unwrap();
783
784 assert!(serialized.encoded.is_multipart());
785 assert!(serialized_json.encoded.is_json());
786
787 let json_string =
788 String::from_utf8(serialized_json.encoded.as_json().unwrap().to_vec().clone()).unwrap();
789 assert_eq!(
791 json_string,
792 "{\"a\":\"hello\",\"b\":1234,\"c\":5678,\"d\":null}"
793 );
794
795 for serialized in [serialized, serialized_json] {
796 assert_eq!(
799 serialized.typename(),
800 Some("hyperactor::data::tests::TestDumpStruct")
801 );
802
803 let json = serialized.dump().unwrap();
804 assert_eq!(
805 json,
806 serde_json::json!({
807 "a": "hello",
808 "b": 1234,
809 "c": 5678,
810 "d": null,
811 })
812 );
813
814 assert_eq!(
815 format!("{}", serialized),
816 "TestDumpStruct{\"a\":\"hello\",\"b\":1234,\"c\":5678,\"d\":null}",
817 );
818 }
819 }
820
821 #[test]
822 fn test_emplace_prefix() {
823 let config = config::global::lock();
824 let _guard = config.override_key(config::DEFAULT_ENCODING, Encoding::Bincode);
825 let data = TestDumpStruct {
826 a: "hello".to_string(),
827 b: 1234,
828 c: Some(5678),
829 d: None,
830 };
831
832 let mut ser = Serialized::serialize(&data).unwrap();
833 assert_eq!(ser.prefix::<String>().unwrap(), "hello".to_string());
834
835 ser.emplace_prefix("hello, world, 123!".to_string())
836 .unwrap();
837
838 assert_eq!(
839 ser.deserialized::<TestDumpStruct>().unwrap(),
840 TestDumpStruct {
841 a: "hello, world, 123!".to_string(),
842 b: 1234,
843 c: Some(5678),
844 d: None,
845 }
846 );
847 }
848
849 #[test]
850 fn test_arms() {
851 #[derive(Named, Serialize, Deserialize)]
852 enum TestArm {
853 #[allow(dead_code)]
854 A(u32),
855 B,
856 C(),
857 D {
858 #[allow(dead_code)]
859 a: u32,
860 #[allow(dead_code)]
861 b: String,
862 },
863 }
864
865 assert_eq!(TestArm::A(1234).arm(), Some("A"));
866 assert_eq!(TestArm::B.arm(), Some("B"));
867 assert_eq!(TestArm::C().arm(), Some("C"));
868 assert_eq!(
869 TestArm::D {
870 a: 1234,
871 b: "hello".to_string()
872 }
873 .arm(),
874 Some("D")
875 );
876 }
877
878 #[test]
879 fn display_hex() {
880 assert_eq!(
881 format!("{}", HexFmt("hello world".as_bytes())),
882 "CRC:d4a1185 68 65 6c 6c 6f 20 77 6f [...3 bytes]"
883 );
884 assert_eq!(format!("{}", HexFmt("".as_bytes())), "CRC:0");
885 assert_eq!(
886 format!("{}", HexFmt("a very long string that is long".as_bytes())),
887 "CRC:c7e24f62 61 20 76 65 72 79 20 6c [...23 bytes]"
888 );
889 }
890
891 #[test]
892 fn test_json_fmt() {
893 let json_value = serde_json::json!({
894 "name": "test",
895 "number": 42,
896 "nested": {
897 "key": "value"
898 }
899 });
900 assert_eq!(
902 format!("{}", JsonFmt(&json_value)),
903 "{\"name\":\"test\",\"nested\":{\"key\":\"value\"},\"number\":42}",
904 );
905
906 let empty_json = serde_json::json!({});
907 assert_eq!(format!("{}", JsonFmt(&empty_json)), "{}");
908
909 let simple_array = serde_json::json!([1, 2, 3]);
910 assert_eq!(format!("{}", JsonFmt(&simple_array)), "[1,2,3]");
911
912 let long_string_json = serde_json::json!({
914 "long_string": "a".repeat(MAX_JSON_VALUE_DISPLAY_LENGTH * 5)
915 });
916 assert_eq!(
917 format!("{}", JsonFmt(&long_string_json)),
918 "{\"long_string\":\"aaaaaaaa[...40 chars] CRC:c95b8a25 61 61 61 61 61 61 61 61 [...32 bytes]\"}"
919 );
920
921 let long_array_json =
923 serde_json::json!((1..=(MAX_JSON_VALUE_DISPLAY_LENGTH + 4)).collect::<Vec<_>>());
924 assert_eq!(
925 format!("{}", JsonFmt(&long_array_json)),
926 "\"[1,2,3,4[...28 chars] CRC:e5c881af 5b 31 2c 32 2c 33 2c 34 [...20 bytes]\""
927 );
928
929 let nested_json = serde_json::json!({
931 "simple_number": 42,
932 "simple_bool": true,
933 "outer": {
934 "long_string": "a".repeat(MAX_JSON_VALUE_DISPLAY_LENGTH + 10),
935 "long_array": (1..=(MAX_JSON_VALUE_DISPLAY_LENGTH + 4)).collect::<Vec<_>>(),
936 "inner": {
937 "simple_value": "short",
938 }
939 }
940 });
941 println!("{}", JsonFmt(&nested_json));
942 assert_eq!(
943 format!("{}", JsonFmt(&nested_json)),
944 "{\"outer\":{\"inner\":{\"simple_value\":\"short\"},\"long_array\":\"[1,2,3,4[...28 chars] CRC:e5c881af 5b 31 2c 32 2c 33 2c 34 [...20 bytes]\",\"long_string\":\"aaaaaaaa[...18 chars] CRC:b8ac0e31 61 61 61 61 61 61 61 61 [...10 bytes]\"},\"simple_bool\":true,\"simple_number\":42}",
945 );
946 }
947
948 #[test]
949 fn test_encodings() {
950 let value = TestDumpStruct {
951 a: "hello, world".to_string(),
952 b: 123,
953 c: Some(321),
954 d: Some(Part::from("hello, world, again")),
955 };
956 for enc in Encoding::iter() {
957 let ser = Serialized::serialize_with_encoding(enc, &value).unwrap();
958 assert_eq!(ser.encoding(), enc);
959 assert_eq!(ser.deserialized::<TestDumpStruct>().unwrap(), value);
960 }
961 }
962}