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