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);