From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/icu_provider/src/response.rs | 748 ++++++++++++++++++++++++++ 1 file changed, 748 insertions(+) create mode 100644 third_party/rust/icu_provider/src/response.rs (limited to 'third_party/rust/icu_provider/src/response.rs') diff --git a/third_party/rust/icu_provider/src/response.rs b/third_party/rust/icu_provider/src/response.rs new file mode 100644 index 0000000000..09b11830fa --- /dev/null +++ b/third_party/rust/icu_provider/src/response.rs @@ -0,0 +1,748 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use crate::buf::BufferMarker; +use crate::error::{DataError, DataErrorKind}; +use crate::marker::DataMarker; +use crate::request::DataLocale; +use alloc::boxed::Box; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::marker::PhantomData; +use core::ops::Deref; +use yoke::trait_hack::YokeTraitHack; +use yoke::*; + +#[cfg(not(feature = "sync"))] +use alloc::rc::Rc as SelectedRc; +#[cfg(feature = "sync")] +use alloc::sync::Arc as SelectedRc; + +/// A response object containing metadata about the returned data. +#[derive(Debug, Clone, PartialEq, Default)] +#[non_exhaustive] +pub struct DataResponseMetadata { + /// The resolved locale of the returned data, if locale fallbacking was performed. + pub locale: Option, + /// The format of the buffer for buffer-backed data, if known (for example, JSON). + pub buffer_format: Option, +} + +/// A container for data payloads returned from a data provider. +/// +/// [`DataPayload`] is built on top of the [`yoke`] framework, which allows for cheap, zero-copy +/// operations on data via the use of self-references. +/// +/// The type of the data stored in [`DataPayload`] is determined by the [`DataMarker`] type parameter. +/// +/// ## Accessing the data +/// +/// To get a reference to the data inside [`DataPayload`], use [`DataPayload::get()`]. If you need +/// to store the data for later use, you need to store the [`DataPayload`] itself, since `get` only +/// returns a reference with an ephemeral lifetime. +/// +/// ## Mutating the data +/// +/// To modify the data stored in a [`DataPayload`], use [`DataPayload::with_mut()`]. +/// +/// ## Transforming the data to a different type +/// +/// To transform a [`DataPayload`] to a different type backed by the same data store (cart), use +/// [`DataPayload::map_project()`] or one of its sister methods. +/// +/// # Cargo feature: `sync` +/// +/// By default, the payload uses non-concurrent reference counting internally, and hence is neither +/// [`Sync`] nor [`Send`]; if these traits are required, the `sync` Cargo feature can be enabled. +/// +/// # Examples +/// +/// Basic usage, using the `HelloWorldV1Marker` marker: +/// +/// ``` +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// use std::borrow::Cow; +/// +/// let payload = DataPayload::::from_owned(HelloWorldV1 { +/// message: Cow::Borrowed("Demo"), +/// }); +/// +/// assert_eq!("Demo", payload.get().message); +/// ``` +pub struct DataPayload(pub(crate) DataPayloadInner); + +pub(crate) enum DataPayloadInner { + Yoke(Yoke>), + StaticRef(&'static M::Yokeable), +} + +/// The type of the "cart" that is used by `DataPayload`. +#[derive(Clone, Debug)] +#[allow(clippy::redundant_allocation)] // false positive, it's cheaper to wrap an existing Box in an Rc than to reallocate a huge Rc +pub struct Cart(SelectedRc>); + +impl Deref for Cart { + type Target = Box<[u8]>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +// Safe because both Rc and Arc are StableDeref, and our impl delegates. +unsafe impl stable_deref_trait::StableDeref for Cart {} +// Safe because both Rc and Arc are CloneableCart, and our impl delegates. +unsafe impl yoke::CloneableCart for Cart {} + +impl Cart { + /// Creates a `Yoke>` from owned bytes by applying `f`. + pub fn try_make_yoke(cart: Box<[u8]>, f: F) -> Result>, E> + where + for<'a> Y: Yokeable<'a>, + F: FnOnce(&[u8]) -> Result<::Output, E>, + { + Yoke::try_attach_to_cart(SelectedRc::new(cart), |b| f(b)) + // Safe because the cart is only wrapped + .map(|yoke| unsafe { yoke.replace_cart(Cart) }) + .map(Yoke::wrap_cart_in_option) + } +} + +impl Debug for DataPayload +where + M: DataMarker, + for<'a> &'a >::Output: Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.get().fmt(f) + } +} + +/// Cloning a DataPayload is generally a cheap operation. +/// See notes in the `Clone` impl for [`Yoke`]. +/// +/// # Examples +/// +/// ```no_run +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// +/// let resp1: DataPayload = todo!(); +/// let resp2 = resp1.clone(); +/// ``` +impl Clone for DataPayload +where + M: DataMarker, + for<'a> YokeTraitHack<>::Output>: Clone, +{ + fn clone(&self) -> Self { + Self(match &self.0 { + DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke.clone()), + DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(*r), + }) + } +} + +impl PartialEq for DataPayload +where + M: DataMarker, + for<'a> YokeTraitHack<>::Output>: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + YokeTraitHack(self.get()).into_ref() == YokeTraitHack(other.get()).into_ref() + } +} + +impl Eq for DataPayload +where + M: DataMarker, + for<'a> YokeTraitHack<>::Output>: Eq, +{ +} + +#[test] +fn test_clone_eq() { + use crate::hello_world::*; + let p1 = DataPayload::::from_static_str("Demo"); + #[allow(clippy::redundant_clone)] + let p2 = p1.clone(); + assert_eq!(p1, p2); +} + +impl DataPayload +where + M: DataMarker, +{ + /// Convert a fully owned (`'static`) data struct into a DataPayload. + /// + /// This constructor creates `'static` payloads. + /// + /// # Examples + /// + /// ``` + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// use std::borrow::Cow; + /// + /// let local_struct = HelloWorldV1 { + /// message: Cow::Owned("example".to_owned()), + /// }; + /// + /// let payload = + /// DataPayload::::from_owned(local_struct.clone()); + /// + /// assert_eq!(payload.get(), &local_struct); + /// ``` + #[inline] + pub const fn from_owned(data: M::Yokeable) -> Self { + Self(DataPayloadInner::Yoke(Yoke::new_owned(data))) + } + + #[doc(hidden)] + #[inline] + pub const fn from_static_ref(data: &'static M::Yokeable) -> Self { + Self(DataPayloadInner::StaticRef(data)) + } + + /// Convert a DataPayload that was created via [`DataPayload::from_owned()`] back into the + /// concrete type used to construct it. + pub fn try_unwrap_owned(self) -> Result { + match self.0 { + DataPayloadInner::Yoke(yoke) => yoke.try_into_yokeable().ok(), + DataPayloadInner::StaticRef(_) => None, + } + .ok_or(DataErrorKind::InvalidState.with_str_context("try_unwrap_owned")) + } + + /// Mutate the data contained in this DataPayload. + /// + /// For safety, all mutation operations must take place within a helper function that cannot + /// borrow data from the surrounding context. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use icu_provider::hello_world::HelloWorldV1Marker; + /// use icu_provider::prelude::*; + /// + /// let mut payload = + /// DataPayload::::from_static_str("Hello"); + /// + /// payload.with_mut(|s| s.message.to_mut().push_str(" World")); + /// + /// assert_eq!("Hello World", payload.get().message); + /// ``` + /// + /// To transfer data from the context into the data struct, use the `move` keyword: + /// + /// ``` + /// use icu_provider::hello_world::HelloWorldV1Marker; + /// use icu_provider::prelude::*; + /// + /// let mut payload = + /// DataPayload::::from_static_str("Hello"); + /// + /// let suffix = " World"; + /// payload.with_mut(move |s| s.message.to_mut().push_str(suffix)); + /// + /// assert_eq!("Hello World", payload.get().message); + /// ``` + pub fn with_mut<'a, F>(&'a mut self, f: F) + where + F: 'static + for<'b> FnOnce(&'b mut >::Output), + M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>, + { + if let DataPayloadInner::StaticRef(r) = self.0 { + self.0 = DataPayloadInner::Yoke(Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r))); + } + match &mut self.0 { + DataPayloadInner::Yoke(yoke) => yoke.with_mut(f), + _ => unreachable!(), + } + } + + /// Borrows the underlying data. + /// + /// This function should be used like `Deref` would normally be used. For more information on + /// why DataPayload cannot implement `Deref`, see the `yoke` crate. + /// + /// # Examples + /// + /// ``` + /// use icu_provider::hello_world::HelloWorldV1Marker; + /// use icu_provider::prelude::*; + /// + /// let payload = DataPayload::::from_static_str("Demo"); + /// + /// assert_eq!("Demo", payload.get().message); + /// ``` + #[inline] + #[allow(clippy::needless_lifetimes)] + pub fn get<'a>(&'a self) -> &'a >::Output { + match &self.0 { + DataPayloadInner::Yoke(yoke) => yoke.get(), + DataPayloadInner::StaticRef(r) => Yokeable::transform(*r), + } + } + + /// Maps `DataPayload` to `DataPayload` by projecting it with [`Yoke::map_project`]. + /// + /// This is accomplished by a function that takes `M`'s data type and returns `M2`'s data + /// type. The function takes a second argument which should be ignored. For more details, + /// see [`Yoke::map_project()`]. + /// + /// The standard [`DataPayload::map_project()`] function moves `self` and cannot capture any + /// data from its context. Use one of the sister methods if you need these capabilities: + /// + /// - [`DataPayload::map_project_cloned()`] if you don't have ownership of `self` + /// - [`DataPayload::try_map_project()`] to bubble up an error + /// - [`DataPayload::try_map_project_cloned()`] to do both of the above + /// + /// # Examples + /// + /// Map from `HelloWorldV1` to a `Cow` containing just the message: + /// + /// ``` + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// use std::borrow::Cow; + /// + /// // A custom marker type is required when using `map_project`. The Yokeable should be the + /// // target type, and the Cart should correspond to the type being transformed. + /// + /// struct HelloWorldV1MessageMarker; + /// impl DataMarker for HelloWorldV1MessageMarker { + /// type Yokeable = Cow<'static, str>; + /// } + /// + /// let p1: DataPayload = DataPayload::from_owned(HelloWorldV1 { + /// message: Cow::Borrowed("Hello World"), + /// }); + /// + /// assert_eq!("Hello World", p1.get().message); + /// + /// let p2: DataPayload = p1.map_project(|obj, _| obj.message); + /// + /// // Note: at this point, p1 has been moved. + /// assert_eq!("Hello World", p2.get()); + /// ``` + #[allow(clippy::type_complexity)] + pub fn map_project(self, f: F) -> DataPayload + where + M2: DataMarker, + F: for<'a> FnOnce( + >::Output, + PhantomData<&'a ()>, + ) -> >::Output, + M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>, + { + DataPayload(DataPayloadInner::Yoke( + match self.0 { + DataPayloadInner::Yoke(yoke) => yoke, + DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)), + } + .map_project(f), + )) + } + + /// Version of [`DataPayload::map_project()`] that borrows `self` instead of moving `self`. + /// + /// # Examples + /// + /// Same example as above, but this time, do not move out of `p1`: + /// + /// ``` + /// // Same imports and definitions as above + /// # use icu_provider::hello_world::*; + /// # use icu_provider::prelude::*; + /// # use std::borrow::Cow; + /// # struct HelloWorldV1MessageMarker; + /// # impl DataMarker for HelloWorldV1MessageMarker { + /// # type Yokeable = Cow<'static, str>; + /// # } + /// + /// let p1: DataPayload = + /// DataPayload::from_owned(HelloWorldV1 { + /// message: Cow::Borrowed("Hello World"), + /// }); + /// + /// assert_eq!("Hello World", p1.get().message); + /// + /// let p2: DataPayload = + /// p1.map_project_cloned(|obj, _| obj.message.clone()); + /// + /// // Note: p1 is still valid. + /// assert_eq!(p1.get().message, *p2.get()); + /// ``` + #[allow(clippy::type_complexity)] + pub fn map_project_cloned<'this, M2, F>(&'this self, f: F) -> DataPayload + where + M2: DataMarker, + F: for<'a> FnOnce( + &'this >::Output, + PhantomData<&'a ()>, + ) -> >::Output, + { + DataPayload(DataPayloadInner::Yoke(match &self.0 { + DataPayloadInner::Yoke(yoke) => yoke.map_project_cloned(f), + DataPayloadInner::StaticRef(r) => { + let output: >::Output = + f(Yokeable::transform(*r), PhantomData); + // Safety: >::Output is the same type as M2::Yokeable; + // we're going from 'static to 'static, however in a generic context it's not + // clear to the compiler that that is the case. We have to use the unsafe make API to do this. + let yokeable: M2::Yokeable = unsafe { M2::Yokeable::make(output) }; + Yoke::new_owned(yokeable) + } + })) + } + + /// Version of [`DataPayload::map_project()`] that bubbles up an error from `f`. + /// + /// # Examples + /// + /// Same example as above, but bubble up an error: + /// + /// ``` + /// // Same imports and definitions as above + /// # use icu_provider::hello_world::*; + /// # use icu_provider::prelude::*; + /// # use std::borrow::Cow; + /// # struct HelloWorldV1MessageMarker; + /// # impl DataMarker for HelloWorldV1MessageMarker { + /// # type Yokeable = Cow<'static, str>; + /// # } + /// + /// let p1: DataPayload = + /// DataPayload::from_owned(HelloWorldV1 { + /// message: Cow::Borrowed("Hello World"), + /// }); + /// + /// assert_eq!("Hello World", p1.get().message); + /// + /// let string_to_append = "Extra"; + /// let p2: DataPayload = + /// p1.try_map_project(|mut obj, _| { + /// if obj.message.is_empty() { + /// return Err("Example error"); + /// } + /// obj.message.to_mut().push_str(string_to_append); + /// Ok(obj.message) + /// })?; + /// + /// assert_eq!("Hello WorldExtra", p2.get()); + /// # Ok::<(), &'static str>(()) + /// ``` + #[allow(clippy::type_complexity)] + pub fn try_map_project(self, f: F) -> Result, E> + where + M2: DataMarker, + F: for<'a> FnOnce( + >::Output, + PhantomData<&'a ()>, + ) -> Result<>::Output, E>, + M::Yokeable: zerofrom::ZeroFrom<'static, M::Yokeable>, + { + Ok(DataPayload(DataPayloadInner::Yoke( + match self.0 { + DataPayloadInner::Yoke(yoke) => yoke, + DataPayloadInner::StaticRef(r) => Yoke::new_owned(zerofrom::ZeroFrom::zero_from(r)), + } + .try_map_project(f)?, + ))) + } + + /// Version of [`DataPayload::map_project_cloned()`] that bubbles up an error from `f`. + /// + /// # Examples + /// + /// Same example as above, but bubble up an error: + /// + /// ``` + /// // Same imports and definitions as above + /// # use icu_provider::hello_world::*; + /// # use icu_provider::prelude::*; + /// # use std::borrow::Cow; + /// # struct HelloWorldV1MessageMarker; + /// # impl DataMarker for HelloWorldV1MessageMarker { + /// # type Yokeable = Cow<'static, str>; + /// # } + /// + /// let p1: DataPayload = + /// DataPayload::from_owned(HelloWorldV1 { + /// message: Cow::Borrowed("Hello World"), + /// }); + /// + /// assert_eq!("Hello World", p1.get().message); + /// + /// let string_to_append = "Extra"; + /// let p2: DataPayload = p1 + /// .try_map_project_cloned(|obj, _| { + /// if obj.message.is_empty() { + /// return Err("Example error"); + /// } + /// let mut message = obj.message.clone(); + /// message.to_mut().push_str(string_to_append); + /// Ok(message) + /// })?; + /// + /// // Note: p1 is still valid, but the values no longer equal. + /// assert_ne!(p1.get().message, *p2.get()); + /// assert_eq!("Hello WorldExtra", p2.get()); + /// # Ok::<(), &'static str>(()) + /// ``` + #[allow(clippy::type_complexity)] + pub fn try_map_project_cloned<'this, M2, F, E>(&'this self, f: F) -> Result, E> + where + M2: DataMarker, + F: for<'a> FnOnce( + &'this >::Output, + PhantomData<&'a ()>, + ) -> Result<>::Output, E>, + { + Ok(DataPayload(DataPayloadInner::Yoke(match &self.0 { + DataPayloadInner::Yoke(yoke) => yoke.try_map_project_cloned(f)?, + DataPayloadInner::StaticRef(r) => { + let output: >::Output = + f(Yokeable::transform(*r), PhantomData)?; + // Safety: >::Output is the same type as M2::Yokeable + Yoke::new_owned(unsafe { M2::Yokeable::make(output) }) + } + }))) + } + + /// Convert between two [`DataMarker`] types that are compatible with each other + /// with compile-time type checking. + /// + /// This happens if they both have the same [`DataMarker::Yokeable`] type. + /// + /// Can be used to erase the key of a data payload in cases where multiple keys correspond + /// to the same data struct. + /// + /// For runtime dynamic casting, use [`DataPayload::dynamic_cast_mut()`]. + /// + /// # Examples + /// + /// ```no_run + /// use icu_locid::locale; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// + /// struct CustomHelloWorldV1Marker; + /// impl DataMarker for CustomHelloWorldV1Marker { + /// type Yokeable = HelloWorldV1<'static>; + /// } + /// + /// let hello_world: DataPayload = todo!(); + /// let custom: DataPayload = hello_world.cast(); + /// ``` + #[inline] + pub fn cast(self) -> DataPayload + where + M2: DataMarker, + { + DataPayload(match self.0 { + DataPayloadInner::Yoke(yoke) => DataPayloadInner::Yoke(yoke), + DataPayloadInner::StaticRef(r) => DataPayloadInner::StaticRef(r), + }) + } + + /// Convert a mutable reference of a [`DataPayload`] to another mutable reference + /// of the same type with runtime type checking. + /// + /// Primarily useful to convert from a generic to a concrete marker type. + /// + /// If the `M2` type argument does not match the true marker type, a `DataError` is returned. + /// + /// For compile-time static casting, use [`DataPayload::cast()`]. + /// + /// # Examples + /// + /// Change the results of a particular request based on key: + /// + /// ``` + /// use icu_locid::locale; + /// use icu_provider::hello_world::*; + /// use icu_provider::prelude::*; + /// + /// struct MyWrapper

{ + /// inner: P, + /// } + /// + /// impl DataProvider for MyWrapper

+ /// where + /// M: KeyedDataMarker, + /// P: DataProvider, + /// { + /// #[inline] + /// fn load(&self, req: DataRequest) -> Result, DataError> { + /// let mut res = self.inner.load(req)?; + /// if let Some(ref mut generic_payload) = res.payload { + /// let mut cast_result = + /// generic_payload.dynamic_cast_mut::(); + /// if let Ok(ref mut concrete_payload) = cast_result { + /// // Add an emoji to the hello world message + /// concrete_payload.with_mut(|data| { + /// data.message.to_mut().insert_str(0, "✨ "); + /// }); + /// } + /// } + /// Ok(res) + /// } + /// } + /// + /// let provider = MyWrapper { + /// inner: HelloWorldProvider, + /// }; + /// let formatter = + /// HelloWorldFormatter::try_new_unstable(&provider, &locale!("de").into()) + /// .unwrap(); + /// + /// assert_eq!(formatter.format_to_string(), "✨ Hallo Welt"); + /// ``` + #[inline] + pub fn dynamic_cast_mut(&mut self) -> Result<&mut DataPayload, DataError> + where + M2: DataMarker, + { + let this: &mut dyn core::any::Any = self; + if let Some(this) = this.downcast_mut() { + Ok(this) + } else { + Err(DataError::for_type::().with_str_context(core::any::type_name::())) + } + } +} + +impl DataPayload { + /// Converts an owned byte buffer into a `DataPayload`. + pub fn from_owned_buffer(buffer: Box<[u8]>) -> Self { + let yoke = Yoke::attach_to_cart(SelectedRc::new(buffer), |b| &**b); + // Safe because cart is wrapped + let yoke = unsafe { yoke.replace_cart(|b| Some(Cart(b))) }; + Self(DataPayloadInner::Yoke(yoke)) + } + + /// Converts a yoked byte buffer into a `DataPayload`. + pub fn from_yoked_buffer(yoke: Yoke<&'static [u8], Option>) -> Self { + Self(DataPayloadInner::Yoke(yoke)) + } + + /// Converts a static byte buffer into a `DataPayload`. + pub fn from_static_buffer(buffer: &'static [u8]) -> Self { + Self(DataPayloadInner::Yoke(Yoke::new_owned(buffer))) + } +} + +impl Default for DataPayload +where + M: DataMarker, + M::Yokeable: Default, +{ + fn default() -> Self { + Self::from_owned(Default::default()) + } +} + +/// A response object containing an object as payload and metadata about it. +#[allow(clippy::exhaustive_structs)] // this type is stable +pub struct DataResponse +where + M: DataMarker, +{ + /// Metadata about the returned object. + pub metadata: DataResponseMetadata, + + /// The object itself; `None` if it was not loaded. + pub payload: Option>, +} + +impl DataResponse +where + M: DataMarker, +{ + /// Takes ownership of the underlying payload. Error if not present. + /// + /// To take the metadata, too, use [`Self::take_metadata_and_payload()`]. + #[inline] + pub fn take_payload(self) -> Result, DataError> { + Ok(self.take_metadata_and_payload()?.1) + } + + /// Takes ownership of the underlying metadata and payload. Error if payload is not present. + #[inline] + pub fn take_metadata_and_payload( + self, + ) -> Result<(DataResponseMetadata, DataPayload), DataError> { + Ok(( + self.metadata, + self.payload + .ok_or_else(|| DataErrorKind::MissingPayload.with_type_context::())?, + )) + } +} + +impl TryFrom> for DataPayload +where + M: DataMarker, +{ + type Error = DataError; + + fn try_from(response: DataResponse) -> Result { + response.take_payload() + } +} + +impl Debug for DataResponse +where + M: DataMarker, + for<'a> &'a >::Output: Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "DataResponse {{ metadata: {:?}, payload: {:?} }}", + self.metadata, self.payload + ) + } +} + +/// Cloning a DataResponse is generally a cheap operation. +/// See notes in the `Clone` impl for [`Yoke`]. +/// +/// # Examples +/// +/// ```no_run +/// use icu_provider::hello_world::*; +/// use icu_provider::prelude::*; +/// +/// let resp1: DataResponse = todo!(); +/// let resp2 = resp1.clone(); +/// ``` +impl Clone for DataResponse +where + M: DataMarker, + for<'a> YokeTraitHack<>::Output>: Clone, +{ + fn clone(&self) -> Self { + Self { + metadata: self.metadata.clone(), + payload: self.payload.clone(), + } + } +} + +#[test] +fn test_debug() { + use crate::hello_world::*; + use alloc::borrow::Cow; + let resp = DataResponse:: { + metadata: Default::default(), + payload: Some(DataPayload::from_owned(HelloWorldV1 { + message: Cow::Borrowed("foo"), + })), + }; + assert_eq!("DataResponse { metadata: DataResponseMetadata { locale: None, buffer_format: None }, payload: Some(HelloWorldV1 { message: \"foo\" }) }", format!("{resp:?}")); +} -- cgit v1.2.3