hugr_core/envelope/
serde_with.rs

1//! Derivation to serialize and deserialize Hugrs and Packages as envelopes in a
2//! serde compatible way.
3//!
4//! This module provides a default wrapper, [`AsStringEnvelope`], that decodes
5//! hugrs and packages using the [`STD_REG`] extension registry.
6//!
7//! When a different extension registry is needed, use the
8//! [`impl_serde_as_string_envelope!`] macro to create a custom wrapper.
9//!
10//! These are meant to be used with `serde_with`'s `#[serde_as]` decorator, see
11//! <https://docs.rs/serde_with/latest/serde_with>.
12
13use crate::std_extensions::STD_REG;
14
15/// De/Serialize a package or hugr by encoding it into a textual Envelope and
16/// storing it as a string.
17///
18/// Note that only PRELUDE extensions are used to decode the package's content.
19/// Additional extensions should be included in the serialized envelope.
20///
21/// # Examples
22///
23/// ```rust
24/// # use serde::{Deserialize, Serialize};
25/// # use serde_json::json;
26/// # use serde_with::{serde_as};
27/// # use hugr_core::Hugr;
28/// # use hugr_core::package::Package;
29/// # use hugr_core::envelope::serde_with::AsStringEnvelope;
30/// #
31/// #[serde_as]
32/// #[derive(Deserialize, Serialize)]
33/// struct A {
34///     #[serde_as(as = "AsStringEnvelope")]
35///     package: Package,
36///     #[serde_as(as = "Vec<AsStringEnvelope>")]
37///     hugrs: Vec<Hugr>,
38/// }
39/// ```
40///
41/// # Backwards compatibility
42///
43/// When reading an encoded HUGR, the `AsStringEnvelope` deserializer will first
44/// try to decode the value as an string-encoded envelope. If that fails, it
45/// will fallback to decoding the legacy HUGR serde definition. This temporary
46/// compatibility layer is meant to be removed in 0.21.0.
47pub struct AsStringEnvelope;
48
49/// Implements [`serde_with::DeserializeAs`] and [`serde_with::SerializeAs`] for
50/// the helper to deserialize `Hugr` and `Package` types, using the given
51/// extension registry.
52///
53/// This macro is used to implement the default [`AsStringEnvelope`] wrapper.
54///
55/// # Parameters
56///
57/// - `$adaptor`: The name of the adaptor type to implement.
58/// - `$extension_reg`: A reference to the extension registry to use for deserialization.
59///
60/// # Examples
61///
62/// ```rust
63/// # use serde::{Deserialize, Serialize};
64/// # use serde_json::json;
65/// # use serde_with::{serde_as};
66/// # use hugr_core::Hugr;
67/// # use hugr_core::package::Package;
68/// # use hugr_core::envelope::serde_with::AsStringEnvelope;
69/// # use hugr_core::envelope::serde_with::impl_serde_as_string_envelope;
70/// # use hugr_core::extension::ExtensionRegistry;
71/// #
72/// struct CustomAsEnvelope;
73///
74/// impl_serde_as_string_envelope!(CustomAsEnvelope, &hugr_core::extension::EMPTY_REG);
75///
76/// #[serde_as]
77/// #[derive(Deserialize, Serialize)]
78/// struct A {
79///     #[serde_as(as = "CustomAsEnvelope")]
80///     package: Package,
81/// }
82/// ```
83///
84#[macro_export]
85macro_rules! impl_serde_as_string_envelope {
86    ($adaptor:ident, $extension_reg:expr) => {
87        impl<'de> serde_with::DeserializeAs<'de, $crate::package::Package> for $adaptor {
88            fn deserialize_as<D>(deserializer: D) -> Result<$crate::package::Package, D::Error>
89            where
90                D: serde::Deserializer<'de>,
91            {
92                struct Helper;
93                impl serde::de::Visitor<'_> for Helper {
94                    type Value = $crate::package::Package;
95
96                    fn expecting(
97                        &self,
98                        formatter: &mut std::fmt::Formatter<'_>,
99                    ) -> std::fmt::Result {
100                        formatter.write_str("a string-encoded envelope")
101                    }
102
103                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
104                    where
105                        E: serde::de::Error,
106                    {
107                        let extensions: &$crate::extension::ExtensionRegistry = $extension_reg;
108                        $crate::package::Package::load_str(value, Some(extensions))
109                            .map_err(serde::de::Error::custom)
110                    }
111                }
112
113                deserializer.deserialize_str(Helper)
114            }
115        }
116
117        impl<'de> serde_with::DeserializeAs<'de, $crate::Hugr> for $adaptor {
118            fn deserialize_as<D>(deserializer: D) -> Result<$crate::Hugr, D::Error>
119            where
120                D: serde::Deserializer<'de>,
121            {
122                struct Helper;
123                impl<'vis> serde::de::Visitor<'vis> for Helper {
124                    type Value = $crate::Hugr;
125
126                    fn expecting(
127                        &self,
128                        formatter: &mut std::fmt::Formatter<'_>,
129                    ) -> std::fmt::Result {
130                        formatter.write_str("a string-encoded envelope")
131                    }
132
133                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
134                    where
135                        E: serde::de::Error,
136                    {
137                        let extensions: &$crate::extension::ExtensionRegistry = $extension_reg;
138                        $crate::Hugr::load_str(value, Some(extensions))
139                            .map_err(serde::de::Error::custom)
140                    }
141
142                    fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
143                    where
144                        A: serde::de::MapAccess<'vis>,
145                    {
146                        // Backwards compatibility: If the encoded value is not a
147                        // string, we may have a legacy HUGR serde structure instead. In that
148                        // case, we can add an envelope header and try again.
149                        //
150                        // TODO: Remove this fallback in 0.21.0
151                        let deserializer = serde::de::value::MapAccessDeserializer::new(map);
152                        #[allow(deprecated)]
153                        let mut hugr =
154                            $crate::hugr::serialize::serde_deserialize_hugr(deserializer)
155                                .map_err(serde::de::Error::custom)?;
156
157                        let extensions: &$crate::extension::ExtensionRegistry = $extension_reg;
158                        hugr.resolve_extension_defs(extensions)
159                            .map_err(serde::de::Error::custom)?;
160                        Ok(hugr)
161                    }
162                }
163
164                // TODO: Go back to `deserialize_str` once the fallback is removed.
165                deserializer.deserialize_any(Helper)
166            }
167        }
168
169        impl serde_with::SerializeAs<$crate::package::Package> for $adaptor {
170            fn serialize_as<S>(
171                source: &$crate::package::Package,
172                serializer: S,
173            ) -> Result<S::Ok, S::Error>
174            where
175                S: serde::Serializer,
176            {
177                let str = source
178                    .store_str($crate::envelope::EnvelopeConfig::text())
179                    .map_err(serde::ser::Error::custom)?;
180                serializer.collect_str(&str)
181            }
182        }
183
184        impl serde_with::SerializeAs<$crate::Hugr> for $adaptor {
185            fn serialize_as<S>(source: &$crate::Hugr, serializer: S) -> Result<S::Ok, S::Error>
186            where
187                S: serde::Serializer,
188            {
189                let str = source
190                    .store_str($crate::envelope::EnvelopeConfig::text())
191                    .map_err(serde::ser::Error::custom)?;
192                serializer.collect_str(&str)
193            }
194        }
195    };
196}
197pub use impl_serde_as_string_envelope;
198
199impl_serde_as_string_envelope!(AsStringEnvelope, &STD_REG);