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