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