1use serde::de::DeserializeOwned;
5use std::collections::HashMap;
6use thiserror::Error;
7
8use crate::core::NodeIndex;
9use crate::hugr::Hugr;
10use crate::ops::OpType;
11use crate::{Node, PortIndex};
12use portgraph::hierarchy::AttachError;
13use portgraph::{Direction, LinkError, PortView};
14
15use serde::{Deserialize, Deserializer, Serialize};
16
17use self::upgrade::UpgradeError;
18
19use super::{HugrError, HugrMut, HugrView, NodeMetadataMap};
20
21mod upgrade;
22
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35#[serde(tag = "version", rename_all = "lowercase")]
36enum Versioned<SerHugr = SerHugrLatest> {
37 #[serde(skip_serializing)]
38 V0,
40
41 V1(serde_json::Value),
42 V2(serde_json::Value),
43 Live(SerHugr),
44
45 #[serde(skip_serializing)]
46 #[serde(other)]
47 Unsupported,
48}
49
50impl<T> Versioned<T> {
51 pub fn new_latest(t: T) -> Self {
52 Self::Live(t)
53 }
54}
55
56impl<T: DeserializeOwned> Versioned<T> {
57 fn upgrade(self) -> Result<T, UpgradeError> {
58 #[allow(unused)]
62 fn go<D: serde::de::DeserializeOwned>(v: serde_json::Value) -> Result<D, UpgradeError> {
63 serde_json::from_value(v).map_err(Into::into)
64 }
65 loop {
66 match self {
67 Self::V0 => Err(UpgradeError::KnownVersionUnsupported("0".into()))?,
68 Self::V1(_) => Err(UpgradeError::KnownVersionUnsupported("1".into()))?,
71 Self::V2(_) => Err(UpgradeError::KnownVersionUnsupported("2".into()))?,
72 Self::Live(ser_hugr) => return Ok(ser_hugr),
73 Versioned::Unsupported => Err(UpgradeError::UnknownVersionUnsupported)?,
74 }
75 }
76 }
77}
78
79#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
80struct NodeSer {
81 parent: Node,
83 #[serde(flatten)]
84 op: OpType,
85}
86
87#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
89struct SerHugrLatest {
90 nodes: Vec<NodeSer>,
92 edges: Vec<[(Node, Option<u32>); 2]>,
94 #[serde(default)]
96 metadata: Option<Vec<Option<NodeMetadataMap>>>,
97 #[serde(default)]
99 encoder: Option<String>,
100 #[serde(default)]
105 entrypoint: Option<Node>,
106}
107
108#[derive(Debug, Clone, PartialEq, Error)]
110#[non_exhaustive]
111pub enum HUGRSerializationError {
112 #[error("Failed to attach child to parent: {0}.")]
114 AttachError(#[from] AttachError),
115 #[error("Failed to build edge when deserializing: {0}.")]
117 LinkError(#[from] LinkError<u32>),
118 #[error(
120 "Cannot connect an {dir:?} edge without port offset to node {node} with operation type {op_type}."
121 )]
122 MissingPortOffset {
123 node: Node,
125 dir: Direction,
127 op_type: OpType,
129 },
130 #[error("The edge endpoint {node} is not a node in the graph.")]
132 UnknownEdgeNode {
133 node: Node,
135 },
136 #[error("The first node in the node list has parent {0}, should be itself (index 0)")]
138 FirstNodeNotRoot(Node),
139 #[error(transparent)]
141 HugrError(#[from] HugrError),
142}
143
144impl Hugr {
145 pub(crate) fn serde_serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: serde::Serializer,
151 {
152 let shg: SerHugrLatest = self.try_into().map_err(serde::ser::Error::custom)?;
153 let versioned = Versioned::new_latest(shg);
154 versioned.serialize(serializer)
155 }
156
157 pub(crate) fn serde_deserialize<'de, D>(deserializer: D) -> Result<Hugr, D::Error>
161 where
162 D: Deserializer<'de>,
163 {
164 let versioned = Versioned::deserialize(deserializer)?;
165 let shl: SerHugrLatest = versioned.upgrade().map_err(serde::de::Error::custom)?;
166 shl.try_into().map_err(serde::de::Error::custom)
167 }
168}
169
170#[deprecated(
174 since = "0.20.0",
175 note = "This API is unstable and will be removed in the future.
176 Use `Hugr::load` or the `AsStringEnvelope` adaptor instead."
177)]
178#[doc(hidden)]
179pub fn serde_deserialize_hugr<'de, D>(deserializer: D) -> Result<Hugr, D::Error>
180where
181 D: Deserializer<'de>,
182{
183 Hugr::serde_deserialize(deserializer)
184}
185
186impl TryFrom<&Hugr> for SerHugrLatest {
187 type Error = HUGRSerializationError;
188
189 fn try_from(hugr: &Hugr) -> Result<Self, Self::Error> {
190 let mut node_rekey: HashMap<Node, Node> = HashMap::with_capacity(hugr.num_nodes());
193 for (order, node) in hugr.canonical_order(hugr.module_root()).enumerate() {
194 node_rekey.insert(node, portgraph::NodeIndex::new(order).into());
195 }
196
197 let mut nodes = vec![None; hugr.num_nodes()];
198 let mut metadata = vec![None; hugr.num_nodes()];
199 for n in hugr.nodes() {
200 let parent = node_rekey[&hugr.get_parent(n).unwrap_or(n)];
201 let opt = hugr.get_optype(n);
202 let new_node = node_rekey[&n].index();
203 nodes[new_node] = Some(NodeSer {
204 parent,
205 op: opt.clone(),
206 });
207 metadata[new_node].clone_from(hugr.metadata.get(n.into_portgraph()));
208 }
209 let nodes = nodes
210 .into_iter()
211 .collect::<Option<Vec<_>>>()
212 .expect("Could not reach one of the nodes");
213
214 let find_offset = |node: Node, offset: usize, dir: Direction, hugr: &Hugr| {
215 let op = hugr.get_optype(node);
216 let is_value_port = offset < op.value_port_count(dir);
217 let is_static_input = op.static_port(dir).is_some_and(|p| p.index() == offset);
218 let offset = (is_value_port || is_static_input).then_some(offset as u32);
219 (node_rekey[&node], offset)
220 };
221
222 let edges: Vec<_> = hugr
223 .nodes()
224 .flat_map(|node| {
225 hugr.node_ports(node, Direction::Outgoing)
226 .enumerate()
227 .flat_map(move |(src_offset, port)| {
228 let src = find_offset(node, src_offset, Direction::Outgoing, hugr);
229 hugr.linked_ports(node, port).map(move |(tgt_node, tgt)| {
230 let tgt = find_offset(tgt_node, tgt.index(), Direction::Incoming, hugr);
231 [src, tgt]
232 })
233 })
234 })
235 .collect();
236
237 let encoder = Some(format!("hugr-rs v{}", env!("CARGO_PKG_VERSION")));
238
239 Ok(Self {
240 nodes,
241 edges,
242 metadata: Some(metadata),
243 encoder,
244 entrypoint: Some(node_rekey[&hugr.entrypoint()]),
245 })
246 }
247}
248
249impl TryFrom<SerHugrLatest> for Hugr {
250 type Error = HUGRSerializationError;
251 fn try_from(
252 SerHugrLatest {
253 nodes,
254 edges,
255 metadata,
256 encoder: _,
257 entrypoint,
258 }: SerHugrLatest,
259 ) -> Result<Self, Self::Error> {
260 let mut nodes = nodes.into_iter();
262 let NodeSer {
263 parent: root_parent,
264 op: root_type,
265 ..
266 } = nodes.next().unwrap();
267 if root_parent.index() != 0 {
268 return Err(HUGRSerializationError::FirstNodeNotRoot(root_parent));
269 }
270 let mut hugr = Hugr::with_capacity(root_type, nodes.len(), edges.len() * 2)?;
273
274 let padding_nodes = hugr.entrypoint.index();
278 let hugr_node =
279 |node: Node| -> Node { portgraph::NodeIndex::new(node.index() + padding_nodes).into() };
280
281 for node_ser in nodes {
282 hugr.add_node_with_parent(hugr_node(node_ser.parent), node_ser.op);
283 }
284
285 if let Some(entrypoint) = entrypoint {
286 hugr.set_entrypoint(hugr_node(entrypoint));
287 }
288
289 if let Some(metadata) = metadata {
290 for (node_idx, metadata) in metadata.into_iter().enumerate() {
291 if let Some(metadata) = metadata {
292 let node = hugr_node(portgraph::NodeIndex::new(node_idx).into());
293 hugr.metadata[node.into_portgraph()] = Some(metadata);
294 }
295 }
296 }
297
298 let unwrap_offset = |node: Node, offset, dir, hugr: &Hugr| -> Result<usize, Self::Error> {
299 if !hugr.graph.contains_node(node.into_portgraph()) {
300 return Err(HUGRSerializationError::UnknownEdgeNode { node });
301 }
302 let offset = if let Some(offset) = offset {
303 offset as usize
304 } else {
305 let op_type = hugr.get_optype(node);
306 op_type
307 .other_port(dir)
308 .ok_or(HUGRSerializationError::MissingPortOffset {
309 node,
310 dir,
311 op_type: op_type.clone(),
312 })?
313 .index()
314 };
315 Ok(offset)
316 };
317 for [(src, from_offset), (dst, to_offset)] in edges {
318 let src = hugr_node(src);
319 let dst = hugr_node(dst);
320
321 let src_port = unwrap_offset(src, from_offset, Direction::Outgoing, &hugr)?;
322 let dst_port = unwrap_offset(dst, to_offset, Direction::Incoming, &hugr)?;
323
324 hugr.connect(src, src_port, dst, dst_port);
325 }
326
327 Ok(hugr)
328 }
329}
330
331#[cfg(all(test, not(miri)))]
332pub mod test;