Skip to main content

wirevalue/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Wirevalue provides an erased serialization format. [`Any`] is a type-erased
10//! envelope containing a serialized value identified by a [`typeuri::Named`].
11//!
12//! Wirevalues also provide encoding polymorphism, allowing the same representation
13//! to carry multiple serialization formats, and to transcode between them for
14//! types that are registered through [`register_type!`].
15
16use std::any::TypeId;
17use std::collections::HashMap;
18use std::fmt;
19use std::io::Cursor;
20use std::marker::PhantomData;
21use std::sync::LazyLock;
22
23use enum_as_inner::EnumAsInner;
24use hyperactor_config::AttrValue;
25use serde::Deserialize;
26use serde::Serialize;
27use serde::de::DeserializeOwned;
28use serde::ser::SerializeStruct;
29pub use typeuri::Named;
30pub use typeuri::intern_typename;
31
32pub mod config;
33
34/// Typehash value indicating a broken (unknown type, no value) Any.
35pub const BROKEN_TYPEHASH: u64 = 0;
36
37#[doc(hidden)]
38/// Dump trait for Named types that are also serializable/deserializable.
39/// This is a utility used by [`Any::dump`], and is not intended
40/// for direct use.
41pub trait NamedDumpable: Named + Serialize + for<'de> Deserialize<'de> {
42    /// Dump the data in Any to a JSON value.
43    fn dump(data: Any) -> Result<serde_json::Value>;
44}
45
46impl<T: Named + Serialize + for<'de> Deserialize<'de>> NamedDumpable for T {
47    fn dump(data: Any) -> Result<serde_json::Value> {
48        let value = data.deserialized::<Self>()?;
49        Ok(serde_json::to_value(value)?)
50    }
51}
52
53#[doc(hidden)]
54#[derive(Debug)]
55pub struct TypeInfo {
56    /// Named::typename()
57    pub typename: fn() -> &'static str,
58    /// Named::typehash()
59    pub typehash: fn() -> u64,
60    /// Named::typeid()
61    pub typeid: fn() -> TypeId,
62    /// Named::typehash()
63    pub port: fn() -> u64,
64    /// A function that can transcode a serialized value to JSON.
65    pub dump: Option<fn(Any) -> Result<serde_json::Value>>,
66    /// Return the arm for this type, if available.
67    pub arm_unchecked: unsafe fn(*const ()) -> Option<&'static str>,
68    /// Return the endpoint name for this message, if available.
69    /// Separate from `arm_unchecked` because struct-typed messages (e.g.,
70    /// PythonMessage) have no enum arm but do carry an endpoint name inside
71    /// their payload. Types that use `register_type!` get a default that
72    /// delegates to `arm_unchecked`, which works for Rust enum handlers.
73    pub endpoint_name: unsafe fn(*const ()) -> Option<String>,
74}
75
76#[allow(dead_code)]
77impl TypeInfo {
78    /// Get the typeinfo for the provided type hash.
79    pub fn get(typehash: u64) -> Option<&'static TypeInfo> {
80        TYPE_INFO.get(&typehash).map(|v| &**v)
81    }
82
83    /// Get the typeinfo for the provided type id.
84    pub fn get_by_typeid(typeid: TypeId) -> Option<&'static TypeInfo> {
85        TYPE_INFO_BY_TYPE_ID.get(&typeid).map(|v| &**v)
86    }
87
88    /// Get the typeinfo for the provided type.
89    pub fn of<T: ?Sized + 'static>() -> Option<&'static TypeInfo> {
90        Self::get_by_typeid(TypeId::of::<T>())
91    }
92
93    /// Get the typename for this type.
94    pub fn typename(&self) -> &'static str {
95        (self.typename)()
96    }
97
98    /// Get the typehash for this type.
99    pub fn typehash(&self) -> u64 {
100        (self.typehash)()
101    }
102
103    /// Get the typeid for this type.
104    pub fn typeid(&self) -> TypeId {
105        (self.typeid)()
106    }
107
108    /// Get the port for this type.
109    pub fn port(&self) -> u64 {
110        (self.port)()
111    }
112
113    /// Dump the serialized data to a JSON value.
114    pub fn dump(&self, data: Any) -> Result<serde_json::Value> {
115        if let Some(dump) = self.dump {
116            (dump)(data)
117        } else {
118            Err(Error::MissingDumper(self.typehash()))
119        }
120    }
121
122    /// Get the arm name for an enum value.
123    ///
124    /// # Safety
125    /// The caller must ensure the value pointer is valid for this type.
126    pub unsafe fn arm_unchecked(&self, value: *const ()) -> Option<&'static str> {
127        // SAFETY: This isn't safe, we're passing it on.
128        unsafe { (self.arm_unchecked)(value) }
129    }
130
131    /// Get the endpoint name for a message value.
132    ///
133    /// # Safety
134    /// The caller must ensure the value pointer is valid for this type.
135    pub unsafe fn endpoint_name(&self, value: *const ()) -> Option<String> {
136        // SAFETY: This isn't safe, we're passing it on.
137        unsafe { (self.endpoint_name)(value) }
138    }
139}
140
141inventory::collect!(TypeInfo);
142
143/// Type infos for all types that have been linked into the binary, keyed by typehash.
144static TYPE_INFO: LazyLock<HashMap<u64, &'static TypeInfo>> = LazyLock::new(|| {
145    inventory::iter::<TypeInfo>()
146        .map(|entry| (entry.typehash(), entry))
147        .collect()
148});
149
150/// Type infos for all types that have been linked into the binary, keyed by typeid.
151static TYPE_INFO_BY_TYPE_ID: LazyLock<HashMap<std::any::TypeId, &'static TypeInfo>> =
152    LazyLock::new(|| {
153        TYPE_INFO
154            .values()
155            .map(|info| (info.typeid(), &**info))
156            .collect()
157    });
158
159/// Register a (concrete) type so that it may be looked up by name or hash. Type registration
160/// is required only to improve diagnostics, as it allows a binary to introspect serialized
161/// payloads under type erasure.
162///
163/// The provided type must implement [`typeuri::Named`], and must be concrete.
164#[macro_export]
165macro_rules! register_type {
166    ($type:ty) => {
167        $crate::submit! {
168            $crate::TypeInfo {
169                typename: <$type as $crate::Named>::typename,
170                typehash: <$type as $crate::Named>::typehash,
171                typeid: <$type as $crate::Named>::typeid,
172                port: <$type as $crate::Named>::port,
173                dump: Some(<$type as $crate::NamedDumpable>::dump),
174                arm_unchecked: <$type as $crate::Named>::arm_unchecked,
175                endpoint_name: |ptr| {
176                    // SAFETY: ptr points to a value of type $type, as guaranteed by the caller.
177                    unsafe { <$type as $crate::Named>::arm_unchecked(ptr).map(|s| s.to_string()) }
178                },
179            }
180        }
181    };
182}
183
184// Re-export inventory::submit for the register_type! macro
185#[doc(hidden)]
186pub use inventory::submit;
187
188/// An enumeration containing the supported encodings of serialized values.
189#[derive(
190    Debug,
191    Clone,
192    Copy,
193    Serialize,
194    Deserialize,
195    PartialEq,
196    Eq,
197    AttrValue,
198    typeuri::Named,
199    strum::EnumIter,
200    strum::Display,
201    strum::EnumString
202)]
203pub enum Encoding {
204    /// Serde bincode encoding.
205    #[strum(to_string = "bincode")]
206    Bincode,
207    /// Serde JSON encoding.
208    #[strum(to_string = "serde_json")]
209    Json,
210    /// Serde multipart encoding.
211    #[strum(to_string = "serde_multipart")]
212    Multipart,
213}
214
215/// Type-state markers for [`Any`] encodings.
216pub mod encoding {
217    /// Statically unconstrained encoding.
218    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
219    pub struct AnyEncoding;
220
221    /// Bincode encoding.
222    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
223    pub struct Bincode;
224
225    /// JSON encoding.
226    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
227    pub struct Json;
228
229    /// Multipart encoding.
230    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
231    pub struct Multipart;
232}
233
234mod private {
235    pub trait Sealed {}
236}
237
238/// Marker trait for supported [`Any`] encoding type states.
239pub trait EncodingMarker: private::Sealed + Clone + Copy + fmt::Debug + PartialEq + Eq {
240    #[doc(hidden)]
241    const EXPECTED_ENCODING: Option<Encoding>;
242}
243
244/// Marker trait for statically known [`Any`] encodings.
245pub trait StaticEncoding: EncodingMarker {
246    #[doc(hidden)]
247    const ENCODING: Encoding;
248}
249
250impl private::Sealed for encoding::AnyEncoding {}
251
252impl EncodingMarker for encoding::AnyEncoding {
253    const EXPECTED_ENCODING: Option<Encoding> = None;
254}
255
256macro_rules! impl_static_encoding {
257    ($marker:ty, $encoding:expr) => {
258        impl private::Sealed for $marker {}
259
260        impl EncodingMarker for $marker {
261            const EXPECTED_ENCODING: Option<Encoding> = Some($encoding);
262        }
263
264        impl StaticEncoding for $marker {
265            const ENCODING: Encoding = $encoding;
266        }
267    };
268}
269
270impl_static_encoding!(encoding::Bincode, Encoding::Bincode);
271impl_static_encoding!(encoding::Json, Encoding::Json);
272impl_static_encoding!(encoding::Multipart, Encoding::Multipart);
273
274/// The encoding used for a serialized value.
275#[derive(Clone, Serialize, Deserialize, PartialEq, EnumAsInner)]
276enum Encoded {
277    Bincode(bytes::Bytes),
278    Json(bytes::Bytes),
279    Multipart(serde_multipart::Message),
280}
281
282impl Encoded {
283    /// The length of the underlying serialized message
284    pub fn len(&self) -> usize {
285        match &self {
286            Encoded::Bincode(data) => data.len(),
287            Encoded::Json(data) => data.len(),
288            Encoded::Multipart(message) => message.len(),
289        }
290    }
291
292    /// Is the message empty. This should always return false.
293    pub fn is_empty(&self) -> bool {
294        match &self {
295            Encoded::Bincode(data) => data.is_empty(),
296            Encoded::Json(data) => data.is_empty(),
297            Encoded::Multipart(message) => message.is_empty(),
298        }
299    }
300
301    /// Returns the encoding of this serialized value.
302    pub fn encoding(&self) -> Encoding {
303        match &self {
304            Encoded::Bincode(_) => Encoding::Bincode,
305            Encoded::Json(_) => Encoding::Json,
306            Encoded::Multipart(_) => Encoding::Multipart,
307        }
308    }
309
310    /// Computes the 32bit crc of the encoded data
311    pub fn crc(&self) -> u32 {
312        match &self {
313            Encoded::Bincode(data) => crc32fast::hash(data),
314            Encoded::Json(data) => crc32fast::hash(data),
315            Encoded::Multipart(message) => {
316                let mut hasher = crc32fast::Hasher::new();
317                for fragment in message.body().iter() {
318                    hasher.update(fragment);
319                }
320                for part in message.parts() {
321                    if let Some(typehash) = part.typehash() {
322                        hasher.update(&typehash.to_be_bytes());
323                    }
324                    for fragment in part.iter() {
325                        hasher.update(fragment);
326                    }
327                }
328                hasher.finalize()
329            }
330        }
331    }
332}
333
334impl std::fmt::Debug for Encoded {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        match self {
337            Encoded::Bincode(data) => write!(f, "Encoded::Bincode({})", HexFmt(data)),
338            Encoded::Json(data) => write!(f, "Encoded::Json({})", HexFmt(data)),
339            Encoded::Multipart(message) => {
340                write!(
341                    f,
342                    "Encoded::Multipart(body={}",
343                    HexFmt(&message.body().to_bytes())
344                )?;
345                for (index, part) in message.parts().iter().enumerate() {
346                    if let Some(typehash) = part.typehash() {
347                        write!(
348                            f,
349                            ", part[{}](typehash={})={}",
350                            index,
351                            typehash,
352                            HexFmt(&part.to_bytes())
353                        )?;
354                    } else {
355                        write!(f, ", part[{}]={}", index, HexFmt(&part.to_bytes()))?;
356                    }
357                }
358                write!(f, ")")
359            }
360        }
361    }
362}
363
364/// The type of error returned by operations on [`Any`].
365#[derive(Debug, thiserror::Error)]
366pub enum Error {
367    /// Errors returned from serde bincode encoding.
368    #[error(transparent)]
369    BincodeEncode(#[from] bincode::error::EncodeError),
370
371    /// Errors returned from serde bincode decoding.
372    #[error(transparent)]
373    BincodeDecode(#[from] bincode::error::DecodeError),
374
375    /// Errors returned from serde JSON.
376    #[error(transparent)]
377    Json(#[from] serde_json::Error),
378
379    /// The encoding was not recognized.
380    #[error("unknown encoding: {0}")]
381    InvalidEncoding(String),
382
383    /// Attempted to deserialize a broken Any value.
384    #[error("attempted to deserialize a broken Any value")]
385    BrokenAny,
386
387    /// Type mismatch during deserialization.
388    #[error("type mismatch: expected {expected}, found {actual}")]
389    TypeMismatch {
390        expected: &'static str,
391        actual: String,
392    },
393
394    /// Type info not available for the given typehash.
395    #[error("binary does not have typeinfo for typehash {0}")]
396    MissingTypeInfo(u64),
397
398    /// Dumper not available for the given typehash.
399    #[error("binary does not have dumper for typehash {0}")]
400    MissingDumper(u64),
401
402    /// Operation requires bincode encoding.
403    #[error("only bincode encoding supports prefix operations")]
404    PrefixNotSupported,
405}
406
407/// A specialized Result type for wirevalue operations.
408pub type Result<T> = std::result::Result<T, Error>;
409
410/// Represents a serialized value, wrapping the underlying serialization
411/// and deserialization details, while ensuring that we pass correctly-serialized
412/// message throughout the system.
413///
414/// The default [`Any`] spelling is encoding-agnostic. Use [`Any`] with an
415/// [`encoding`] marker, such as `Any<encoding::Multipart>`, when an API
416/// requires a specific encoding.
417#[derive(Clone, Debug, PartialEq)]
418pub struct Any<E: EncodingMarker = encoding::AnyEncoding> {
419    /// The encoded data
420    encoded: Encoded,
421    /// The typehash of the serialized value. This is used to provide
422    /// typed introspection of the value.
423    typehash: u64,
424    _encoding: PhantomData<E>,
425}
426
427impl<E: EncodingMarker> std::fmt::Display for Any<E> {
428    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429        match self.dump() {
430            Ok(value) => {
431                // unwrap okay, self.dump() would return Err otherwise.
432                let typename = self.typename().unwrap();
433                // take the basename of the type (e.g. "foo::bar::baz" -> "baz")
434                let basename = typename.split("::").last().unwrap_or(typename);
435                write!(f, "{}{}", basename, JsonFmt(&value))
436            }
437            Err(_) => write!(f, "{:?}", self.encoded),
438        }
439    }
440}
441
442impl<E: EncodingMarker> Serialize for Any<E> {
443    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
444    where
445        S: serde::Serializer,
446    {
447        let mut state = serializer.serialize_struct("Any", 2)?;
448        state.serialize_field("encoded", &self.encoded)?;
449        state.serialize_field("typehash", &self.typehash)?;
450        state.end()
451    }
452}
453
454#[derive(Deserialize)]
455struct AnyFields {
456    encoded: Encoded,
457    typehash: u64,
458}
459
460impl<'de, E: EncodingMarker> Deserialize<'de> for Any<E> {
461    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
462    where
463        D: serde::Deserializer<'de>,
464    {
465        let fields = AnyFields::deserialize(deserializer)?;
466        if let Some(expected) = E::EXPECTED_ENCODING {
467            let actual = fields.encoded.encoding();
468            if actual != expected {
469                return Err(serde::de::Error::custom(format!(
470                    "expected {} encoding, found {}",
471                    expected, actual
472                )));
473            }
474        }
475
476        Ok(Self {
477            encoded: fields.encoded,
478            typehash: fields.typehash,
479            _encoding: PhantomData,
480        })
481    }
482}
483
484fn encode_with_encoding<U: Serialize>(encoding: Encoding, value: &U) -> Result<Encoded> {
485    match encoding {
486        Encoding::Bincode => Ok(Encoded::Bincode(
487            bincode::serde::encode_to_vec(value, bincode::config::legacy())?.into(),
488        )),
489        Encoding::Json => Ok(Encoded::Json(serde_json::to_vec(value)?.into())),
490        Encoding::Multipart => Ok(Encoded::Multipart(
491            serde_multipart::serialize_bincode(value)
492                .map_err(|e| Error::InvalidEncoding(e.to_string()))?,
493        )),
494    }
495}
496
497impl Any {
498    /// Serialize the value with the using the provided encoding.
499    pub fn serialize_with_encoding<T: Serialize + Named>(
500        encoding: Encoding,
501        value: &T,
502    ) -> Result<Self> {
503        Self::serialize_with_encoding_as::<T, T>(encoding, value)
504    }
505
506    /// Serialize U-typed value as a T-typed value. This should be used with care
507    /// (typically only in testing), as the value's representation may be illegally
508    /// coerced.
509    pub fn serialize_with_encoding_as<T: Named, U: Serialize>(
510        encoding: Encoding,
511        value: &U,
512    ) -> Result<Self> {
513        Ok(Self {
514            encoded: encode_with_encoding(encoding, value)?,
515            typehash: T::typehash(),
516            _encoding: PhantomData,
517        })
518    }
519
520    /// Create a new broken Any value. A broken value has unknown type and
521    /// no valid data. Attempting to deserialize a broken value will fail.
522    pub fn new_broken() -> Self {
523        Self {
524            encoded: Encoded::Bincode(bytes::Bytes::new()),
525            typehash: BROKEN_TYPEHASH,
526            _encoding: PhantomData,
527        }
528    }
529
530    /// Statically constrain this value to the requested encoding.
531    pub fn try_into_static_encoding<E: StaticEncoding>(self) -> std::result::Result<Any<E>, Self> {
532        if self.encoding() == E::ENCODING {
533            Ok(Any {
534                encoded: self.encoded,
535                typehash: self.typehash,
536                _encoding: PhantomData,
537            })
538        } else {
539            Err(self)
540        }
541    }
542
543    /// Statically constrain this value to bincode encoding.
544    pub fn try_into_bincode(self) -> std::result::Result<Any<encoding::Bincode>, Self> {
545        self.try_into_static_encoding()
546    }
547
548    /// Statically constrain this value to JSON encoding.
549    pub fn try_into_json(self) -> std::result::Result<Any<encoding::Json>, Self> {
550        self.try_into_static_encoding()
551    }
552
553    /// Statically constrain this value to multipart encoding.
554    pub fn try_into_multipart(self) -> std::result::Result<Any<encoding::Multipart>, Self> {
555        self.try_into_static_encoding()
556    }
557}
558
559impl<E: EncodingMarker> Any<E> {
560    /// Construct a new serialized value.
561    ///
562    /// Encoding-agnostic [`Any`] uses [`config::DEFAULT_ENCODING`]. Statically
563    /// constrained values, such as `Any<encoding::Multipart>`, use their static
564    /// encoding.
565    pub fn serialize<T: Serialize + Named>(value: &T) -> Result<Self> {
566        Self::serialize_as::<T, T>(value)
567    }
568
569    /// Serialize U-typed value as a T-typed value.
570    pub fn serialize_as<T: Named, U: Serialize>(value: &U) -> Result<Self> {
571        let encoding = E::EXPECTED_ENCODING
572            .unwrap_or_else(|| hyperactor_config::global::get(config::DEFAULT_ENCODING));
573        Ok(Self {
574            encoded: encode_with_encoding(encoding, value)?,
575            typehash: T::typehash(),
576            _encoding: PhantomData,
577        })
578    }
579
580    /// Erase this value's static encoding marker.
581    pub fn erase_encoding(self) -> Any {
582        Any {
583            encoded: self.encoded,
584            typehash: self.typehash,
585            _encoding: PhantomData,
586        }
587    }
588
589    /// Returns true if this Any is broken (unknown type, no value).
590    pub fn is_broken(&self) -> bool {
591        self.typehash == BROKEN_TYPEHASH
592    }
593
594    /// Deserialize a value to the provided type T.
595    pub fn deserialized<T: DeserializeOwned + Named>(&self) -> Result<T> {
596        if self.is_broken() {
597            return Err(Error::BrokenAny);
598        }
599        if !self.is::<T>() {
600            return Err(Error::TypeMismatch {
601                expected: T::typename(),
602                actual: self.typename().unwrap_or("unknown").to_string(),
603            });
604        }
605        self.deserialized_unchecked()
606    }
607
608    /// Deserialize a value to the provided type T, without checking for type conformance.
609    /// This should be used carefully, only when you know that the dynamic type check is
610    /// not needed.
611    pub fn deserialized_unchecked<T: DeserializeOwned>(&self) -> Result<T> {
612        match &self.encoded {
613            Encoded::Bincode(data) => Ok(bincode::serde::decode_from_slice(
614                data,
615                bincode::config::legacy(),
616            )
617            .map(|(v, _)| v)?),
618            Encoded::Json(data) => Ok(serde_json::from_slice(data)?),
619            Encoded::Multipart(message) => {
620                Ok(serde_multipart::deserialize_bincode(message.clone())
621                    .map_err(|e| Error::InvalidEncoding(e.to_string()))?)
622            }
623        }
624    }
625
626    /// Transcode the serialized value to JSON. This operation will succeed if the type hash
627    /// is embedded in the value, and the corresponding type is available in this binary.
628    pub fn transcode_to_json(self) -> std::result::Result<Any<encoding::Json>, Self> {
629        match self.encoded {
630            Encoded::Bincode(_) | Encoded::Multipart(_) => {
631                let json_value = match self.dump() {
632                    Ok(json_value) => json_value,
633                    Err(_) => return Err(self),
634                };
635                let json_data = match serde_json::to_vec(&json_value) {
636                    Ok(json_data) => json_data,
637                    Err(_) => return Err(self),
638                };
639                Ok(Any {
640                    encoded: Encoded::Json(json_data.into()),
641                    typehash: self.typehash,
642                    _encoding: PhantomData,
643                })
644            }
645            Encoded::Json(_) => Ok(Any {
646                encoded: self.encoded,
647                typehash: self.typehash,
648                _encoding: PhantomData,
649            }),
650        }
651    }
652
653    /// Dump the Any message into a JSON value. This will succeed if: 1) the typehash is embedded
654    /// in the serialized value; 2) the named type is linked into the binary.
655    pub fn dump(&self) -> Result<serde_json::Value> {
656        match &self.encoded {
657            Encoded::Bincode(_) | Encoded::Multipart(_) => {
658                let Some(typeinfo) = TYPE_INFO.get(&self.typehash) else {
659                    return Err(Error::MissingTypeInfo(self.typehash));
660                };
661                typeinfo.dump(self.clone().erase_encoding())
662            }
663            Encoded::Json(data) => Ok(serde_json::from_slice(data)?),
664        }
665    }
666
667    /// The encoding used by this serialized value.
668    pub fn encoding(&self) -> Encoding {
669        self.encoded.encoding()
670    }
671
672    /// The typehash of the serialized value.
673    pub fn typehash(&self) -> u64 {
674        self.typehash
675    }
676
677    /// The typename of the serialized value, if available.
678    pub fn typename(&self) -> Option<&'static str> {
679        TYPE_INFO
680            .get(&self.typehash)
681            .map(|typeinfo| typeinfo.typename())
682    }
683
684    /// Deserialize a prefix of the value. This is currently only supported
685    /// for bincode-serialized values.
686    // TODO: we should support this by formalizing the notion of a 'prefix'
687    // serialization, and generalize it to other codecs as well.
688    pub fn prefix<T: DeserializeOwned>(&self) -> Result<T> {
689        match &self.encoded {
690            Encoded::Bincode(data) => Ok(bincode::serde::decode_from_slice(
691                data,
692                bincode::config::legacy(),
693            )
694            .map(|(v, _)| v)?),
695            _ => Err(Error::PrefixNotSupported),
696        }
697    }
698
699    /// Emplace a new prefix to this value. This is currently only supported
700    /// for bincode-serialized values.
701    pub fn emplace_prefix<T: Serialize + DeserializeOwned>(&mut self, prefix: T) -> Result<()> {
702        let data = match &self.encoded {
703            Encoded::Bincode(data) => data,
704            _ => return Err(Error::PrefixNotSupported),
705        };
706
707        // This is a bit ugly, but: we first deserialize out the old prefix,
708        // then serialize the new prefix, then splice the two together.
709        // This is safe because we know that the prefix is the first thing
710        // in the serialized value, and that the serialization format is stable.
711        let mut cursor = Cursor::new(data.clone());
712        let _prefix: T =
713            bincode::serde::decode_from_std_read(&mut cursor, bincode::config::legacy()).unwrap();
714        let position = cursor.position() as usize;
715        let suffix = &cursor.into_inner()[position..];
716        let mut data = bincode::serde::encode_to_vec(&prefix, bincode::config::legacy())?;
717        data.extend_from_slice(suffix);
718        self.encoded = Encoded::Bincode(data.into());
719
720        Ok(())
721    }
722
723    /// The length of the underlying serialized message
724    pub fn len(&self) -> usize {
725        self.encoded.len()
726    }
727
728    /// Is the message empty. This should always return false.
729    pub fn is_empty(&self) -> bool {
730        self.encoded.is_empty()
731    }
732
733    /// Returns the 32bit crc of the serialized data
734    pub fn crc(&self) -> u32 {
735        self.encoded.crc()
736    }
737
738    /// Returns whether this value contains a serialized M-typed value. Returns None
739    /// when type information is unavailable.
740    pub fn is<M: Named>(&self) -> bool {
741        self.typehash == M::typehash()
742    }
743}
744
745impl Any<encoding::Multipart> {
746    /// Visit typed multipart parts in this value.
747    pub fn visit_multipart_parts_mut<T, E>(
748        &mut self,
749        f: impl FnMut(&mut T) -> std::result::Result<(), E>,
750    ) -> std::result::Result<(), E>
751    where
752        T: Serialize + DeserializeOwned + Named,
753        E: From<serde_multipart::Error>,
754    {
755        let Encoded::Multipart(message) = &mut self.encoded else {
756            panic!(
757                "multipart Any contained {} encoding",
758                self.encoded.encoding()
759            );
760        };
761        message.visit_parts_mut(f)
762    }
763}
764
765const MAX_BYTE_PREVIEW_LENGTH: usize = 8;
766
767fn display_bytes_as_hash(f: &mut impl std::fmt::Write, bytes: &[u8]) -> std::fmt::Result {
768    let hash = crc32fast::hash(bytes);
769    write!(f, "CRC:{:x}", hash)?;
770    // Implementing in this way lets us print without allocating a new intermediate string.
771    for &byte in bytes.iter().take(MAX_BYTE_PREVIEW_LENGTH) {
772        write!(f, " {:x}", byte)?;
773    }
774    if bytes.len() > MAX_BYTE_PREVIEW_LENGTH {
775        write!(f, " [...{} bytes]", bytes.len() - MAX_BYTE_PREVIEW_LENGTH)?;
776    }
777    Ok(())
778}
779
780/// Formats a binary slice as hex when its display function is called.
781pub struct HexFmt<'a>(pub &'a [u8]);
782
783impl std::fmt::Display for HexFmt<'_> {
784    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
785        // calculate a 2 byte checksum to prepend to the message
786        display_bytes_as_hash(f, self.0)
787    }
788}
789
790/// Formats a JSON value for display, printing all keys but
791/// truncating and displaying a hash if the content is too long.
792pub struct JsonFmt<'a>(pub &'a serde_json::Value);
793
794const MAX_JSON_VALUE_DISPLAY_LENGTH: usize = 8;
795
796impl std::fmt::Display for JsonFmt<'_> {
797    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
798        /// Truncate the input string to MAX_JSON_VALUE_DISPLAY_LENGTH and append
799        /// the truncated hash of the full value for easy comparison.
800        fn truncate_and_hash(value_str: &str) -> String {
801            let truncate_at = MAX_JSON_VALUE_DISPLAY_LENGTH.min(value_str.len());
802
803            // Respect UTF-8 boundaries (multi-byte chars like emojis can be up to 4 bytes)
804            let mut safe_truncate_at = truncate_at;
805            while safe_truncate_at > 0 && !value_str.is_char_boundary(safe_truncate_at) {
806                safe_truncate_at -= 1;
807            }
808
809            let truncated_str = &value_str[..safe_truncate_at];
810            let mut result = truncated_str.to_string();
811            result.push_str(&format!("[...{} chars] ", value_str.len()));
812            display_bytes_as_hash(&mut result, value_str.as_bytes()).unwrap();
813            result
814        }
815
816        /// Recursively truncate a serde_json::Value object.
817        fn truncate_json_values(value: &serde_json::Value) -> serde_json::Value {
818            match value {
819                serde_json::Value::String(s) => {
820                    if s.len() > MAX_JSON_VALUE_DISPLAY_LENGTH {
821                        serde_json::Value::String(truncate_and_hash(s))
822                    } else {
823                        value.clone()
824                    }
825                }
826                serde_json::Value::Array(arr) => {
827                    let array_str = serde_json::to_string(arr).unwrap();
828                    if array_str.len() > MAX_JSON_VALUE_DISPLAY_LENGTH {
829                        serde_json::Value::String(truncate_and_hash(&array_str))
830                    } else {
831                        value.clone()
832                    }
833                }
834                serde_json::Value::Object(obj) => {
835                    let truncated_obj: serde_json::Map<_, _> = obj
836                        .iter()
837                        .map(|(k, v)| (k.clone(), truncate_json_values(v)))
838                        .collect();
839                    serde_json::Value::Object(truncated_obj)
840                }
841                _ => value.clone(),
842            }
843        }
844
845        let truncated = truncate_json_values(self.0);
846        write!(f, "{}", truncated)
847    }
848}
849
850#[cfg(test)]
851mod tests {
852    use serde::Deserialize;
853    use serde::Serialize;
854    use serde_multipart::Part;
855    use strum::IntoEnumIterator;
856    use typeuri::Named;
857
858    use super::*;
859
860    #[derive(typeuri::Named, Serialize, Deserialize)]
861    struct TestStruct;
862
863    #[test]
864    fn test_names() {
865        assert_eq!(String::typename(), "String");
866        assert_eq!(Option::<String>::typename(), "Option<String>");
867        assert_eq!(Vec::<String>::typename(), "Vec<String>");
868        assert_eq!(Vec::<Vec::<String>>::typename(), "Vec<Vec<String>>");
869        assert_eq!(
870            Vec::<Vec::<Vec::<String>>>::typename(),
871            "Vec<Vec<Vec<String>>>"
872        );
873        assert_eq!(
874            <(u64, String, Option::<isize>)>::typename(),
875            "(u64, String, Option<isize>)"
876        );
877        assert_eq!(TestStruct::typename(), "wirevalue::tests::TestStruct");
878        assert_eq!(
879            Vec::<TestStruct>::typename(),
880            "Vec<wirevalue::tests::TestStruct>"
881        );
882    }
883
884    #[test]
885    fn test_ports() {
886        assert_eq!(String::typehash(), 3947244799002047352u64);
887        assert_eq!(String::port(), String::typehash());
888        assert_ne!(
889            Vec::<Vec::<Vec::<String>>>::typehash(),
890            Vec::<Vec::<Vec::<Vec::<String>>>>::typehash(),
891        );
892    }
893
894    #[derive(typeuri::Named, Serialize, Deserialize, PartialEq, Eq, Debug)]
895    struct TestDumpStruct {
896        a: String,
897        b: u64,
898        c: Option<i32>,
899        d: Option<Part>,
900    }
901    crate::register_type!(TestDumpStruct);
902
903    #[test]
904    fn test_dump_struct() {
905        let data = TestDumpStruct {
906            a: "hello".to_string(),
907            b: 1234,
908            c: Some(5678),
909            d: None,
910        };
911        let serialized: Any = Any::serialize(&data).unwrap();
912        let serialized_json = serialized.clone().transcode_to_json().unwrap();
913
914        assert!(serialized.encoded.is_multipart());
915        assert!(serialized_json.encoded.is_json());
916
917        let json_string =
918            String::from_utf8(serialized_json.encoded.as_json().unwrap().to_vec().clone()).unwrap();
919        // The serialized data for JSON is just the (compact) JSON string.
920        assert_eq!(
921            json_string,
922            "{\"a\":\"hello\",\"b\":1234,\"c\":5678,\"d\":null}"
923        );
924
925        for serialized in [serialized, serialized_json.erase_encoding()] {
926            // Note, at this point, serialized has no knowledge other than its embedded typehash.
927
928            assert_eq!(
929                serialized.typename(),
930                Some("wirevalue::tests::TestDumpStruct")
931            );
932
933            let json = serialized.dump().unwrap();
934            assert_eq!(
935                json,
936                serde_json::json!({
937                    "a": "hello",
938                    "b": 1234,
939                    "c": 5678,
940                    "d": null,
941                })
942            );
943
944            assert_eq!(
945                format!("{}", serialized),
946                "TestDumpStruct{\"a\":\"hello\",\"b\":1234,\"c\":5678,\"d\":null}",
947            );
948        }
949    }
950
951    #[test]
952    fn test_emplace_prefix() {
953        let config = hyperactor_config::global::lock();
954        let _guard = config.override_key(config::DEFAULT_ENCODING, Encoding::Bincode);
955        let data = TestDumpStruct {
956            a: "hello".to_string(),
957            b: 1234,
958            c: Some(5678),
959            d: None,
960        };
961
962        let mut ser: Any = Any::serialize(&data).unwrap();
963        assert_eq!(ser.prefix::<String>().unwrap(), "hello".to_string());
964
965        ser.emplace_prefix("hello, world, 123!".to_string())
966            .unwrap();
967
968        assert_eq!(
969            ser.deserialized::<TestDumpStruct>().unwrap(),
970            TestDumpStruct {
971                a: "hello, world, 123!".to_string(),
972                b: 1234,
973                c: Some(5678),
974                d: None,
975            }
976        );
977    }
978
979    #[test]
980    fn test_arms() {
981        #[derive(typeuri::Named, Serialize, Deserialize)]
982        enum TestArm {
983            #[allow(dead_code)]
984            A(u32),
985            B,
986            C(),
987            D {
988                #[allow(dead_code)]
989                a: u32,
990                #[allow(dead_code)]
991                b: String,
992            },
993        }
994
995        assert_eq!(TestArm::A(1234).arm(), Some("A"));
996        assert_eq!(TestArm::B.arm(), Some("B"));
997        assert_eq!(TestArm::C().arm(), Some("C"));
998        assert_eq!(
999            TestArm::D {
1000                a: 1234,
1001                b: "hello".to_string()
1002            }
1003            .arm(),
1004            Some("D")
1005        );
1006    }
1007
1008    #[test]
1009    fn display_hex() {
1010        assert_eq!(
1011            format!("{}", HexFmt("hello world".as_bytes())),
1012            "CRC:d4a1185 68 65 6c 6c 6f 20 77 6f [...3 bytes]"
1013        );
1014        assert_eq!(format!("{}", HexFmt("".as_bytes())), "CRC:0");
1015        assert_eq!(
1016            format!("{}", HexFmt("a very long string that is long".as_bytes())),
1017            "CRC:c7e24f62 61 20 76 65 72 79 20 6c [...23 bytes]"
1018        );
1019    }
1020
1021    #[test]
1022    fn test_json_fmt() {
1023        let json_value = serde_json::json!({
1024            "name": "test",
1025            "number": 42,
1026            "nested": {
1027                "key": "value"
1028            }
1029        });
1030        // JSON values with short values should print normally
1031        assert_eq!(
1032            format!("{}", JsonFmt(&json_value)),
1033            "{\"name\":\"test\",\"nested\":{\"key\":\"value\"},\"number\":42}",
1034        );
1035
1036        let empty_json = serde_json::json!({});
1037        assert_eq!(format!("{}", JsonFmt(&empty_json)), "{}");
1038
1039        let simple_array = serde_json::json!([1, 2, 3]);
1040        assert_eq!(format!("{}", JsonFmt(&simple_array)), "[1,2,3]");
1041
1042        // JSON values with very long strings should be truncated
1043        let long_string_json = serde_json::json!({
1044            "long_string": "a".repeat(MAX_JSON_VALUE_DISPLAY_LENGTH * 5)
1045        });
1046        assert_eq!(
1047            format!("{}", JsonFmt(&long_string_json)),
1048            "{\"long_string\":\"aaaaaaaa[...40 chars] CRC:c95b8a25 61 61 61 61 61 61 61 61 [...32 bytes]\"}"
1049        );
1050
1051        // JSON values with very long arrays should be truncated
1052        let long_array_json =
1053            serde_json::json!((1..=(MAX_JSON_VALUE_DISPLAY_LENGTH + 4)).collect::<Vec<_>>());
1054        assert_eq!(
1055            format!("{}", JsonFmt(&long_array_json)),
1056            "\"[1,2,3,4[...28 chars] CRC:e5c881af 5b 31 2c 32 2c 33 2c 34 [...20 bytes]\""
1057        );
1058
1059        // Test for truncation within nested blocks
1060        let nested_json = serde_json::json!({
1061            "simple_number": 42,
1062            "simple_bool": true,
1063            "outer": {
1064                "long_string": "a".repeat(MAX_JSON_VALUE_DISPLAY_LENGTH + 10),
1065                "long_array": (1..=(MAX_JSON_VALUE_DISPLAY_LENGTH + 4)).collect::<Vec<_>>(),
1066                "inner": {
1067                    "simple_value": "short",
1068                }
1069            }
1070        });
1071        println!("{}", JsonFmt(&nested_json));
1072        assert_eq!(
1073            format!("{}", JsonFmt(&nested_json)),
1074            "{\"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}",
1075        );
1076    }
1077
1078    #[test]
1079    fn test_json_fmt_utf8_truncation() {
1080        // Test that UTF-8 character boundaries are respected during truncation
1081        // Create a string with multi-byte characters that would be truncated
1082
1083        // String with 7 ASCII chars + 4-byte emoji (total 11 bytes, truncates at 8)
1084        let utf8_json = serde_json::json!({
1085            "emoji": "1234567🦀"  // 7 + 4 = 11 bytes, MAX is 8
1086        });
1087
1088        // Should truncate at byte 7 (before the emoji) to respect UTF-8 boundary
1089        let result = format!("{}", JsonFmt(&utf8_json));
1090
1091        // Verify it doesn't panic and produces valid output
1092        assert!(result.contains("1234567"));
1093        assert!(!result.contains("🦀")); // Emoji should be truncated away
1094
1095        // Test with all multi-byte characters
1096        let all_multibyte = serde_json::json!({
1097            "chinese": "你好世界"  // Each char is 3 bytes = 12 bytes total
1098        });
1099        let result3 = format!("{}", JsonFmt(&all_multibyte));
1100        assert!(!result3.is_empty());
1101    }
1102
1103    #[test]
1104    fn test_encodings() {
1105        let value = TestDumpStruct {
1106            a: "hello, world".to_string(),
1107            b: 123,
1108            c: Some(321),
1109            d: Some(Part::from("hello, world, again")),
1110        };
1111        for enc in Encoding::iter() {
1112            let ser = Any::serialize_with_encoding(enc, &value).unwrap();
1113            assert_eq!(ser.encoding(), enc);
1114            assert_eq!(ser.deserialized::<TestDumpStruct>().unwrap(), value);
1115        }
1116    }
1117
1118    #[test]
1119    fn test_static_multipart_any() {
1120        let value = TestDumpStruct {
1121            a: "hello, multipart".to_string(),
1122            b: 456,
1123            c: Some(654),
1124            d: Some(Part::from("part")),
1125        };
1126
1127        let multipart = Any::<encoding::Multipart>::serialize(&value).unwrap();
1128        assert_eq!(multipart.encoding(), Encoding::Multipart);
1129        assert_eq!(multipart.deserialized::<TestDumpStruct>().unwrap(), value);
1130
1131        let erased = multipart.erase_encoding();
1132        let multipart = erased.try_into_multipart().unwrap();
1133        assert_eq!(multipart.encoding(), Encoding::Multipart);
1134    }
1135
1136    #[test]
1137    fn test_static_encoding_deserialize_rejects_mismatch() {
1138        let value = TestDumpStruct {
1139            a: "hello, bincode".to_string(),
1140            b: 789,
1141            c: None,
1142            d: None,
1143        };
1144        let bincode = Any::<encoding::Bincode>::serialize(&value).unwrap();
1145        let serialized = serde_json::to_vec(&bincode.erase_encoding()).unwrap();
1146
1147        let err = serde_json::from_slice::<Any<encoding::Multipart>>(&serialized).unwrap_err();
1148        assert!(err.to_string().contains("expected serde_multipart"));
1149    }
1150
1151    #[test]
1152    fn test_broken_any() {
1153        let broken = Any::new_broken();
1154        assert!(broken.is_broken());
1155        assert_eq!(broken.typehash(), BROKEN_TYPEHASH);
1156
1157        // Normal values are not broken
1158        let normal: Any = Any::serialize(&"hello".to_string()).unwrap();
1159        assert!(!normal.is_broken());
1160
1161        // deserialized() should fail for broken values
1162        let err = broken.deserialized::<String>().unwrap_err();
1163        assert!(err.to_string().contains("broken"));
1164    }
1165}