hugr_core/hugr/
serialize.rs

1//! Serialization definition for [`Hugr`]
2//! [`Hugr`]: `crate::hugr::Hugr`
3
4use 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/// A wrapper over the available HUGR serialization formats.
24///
25/// The implementation of `Serialize` for `Hugr` encodes the graph in the most
26/// recent version of the format. We keep the `Deserialize` implementations for
27/// older versions to allow for backwards compatibility.
28///
29/// The Generic `SerHugr` is always instantiated to the most recent version of
30/// the format outside this module.
31///
32/// Make sure to order the variants from newest to oldest, as the deserializer
33/// will try to deserialize them in order.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35#[serde(tag = "version", rename_all = "lowercase")]
36enum Versioned<SerHugr = SerHugrLatest> {
37    #[serde(skip_serializing)]
38    /// Version 0 of the HUGR serialization format.
39    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        // go is polymorphic in D. When we are upgrading to the latest version
59        // D is T. When we are upgrading to a version which is not the latest D
60        // is serde_json::Value.
61        #[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                // the upgrade lines remain unchanged when adding a new constructor
69                // Self::V1(json) => self = Self::V2(upgrade::v1_to_v2(json).and_then(go)?),
70                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    /// Node index of the parent.
82    parent: Node,
83    #[serde(flatten)]
84    op: OpType,
85}
86
87/// Version 1 of the HUGR serialization format.
88#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
89struct SerHugrLatest {
90    /// For each node: (parent, `node_operation`)
91    nodes: Vec<NodeSer>,
92    /// for each edge: (src, `src_offset`, tgt, `tgt_offset`)
93    edges: Vec<[(Node, Option<u32>); 2]>,
94    /// for each node: (metadata)
95    #[serde(default)]
96    metadata: Option<Vec<Option<NodeMetadataMap>>>,
97    /// A metadata field with the package identifier that encoded the HUGR.
98    #[serde(default)]
99    encoder: Option<String>,
100    /// The entrypoint of the HUGR.
101    ///
102    /// For backwards compatibility, if `None` the entrypoint is set to the root
103    /// of the node hierarchy.
104    #[serde(default)]
105    entrypoint: Option<Node>,
106}
107
108/// Errors that can occur while serializing a HUGR.
109#[derive(Debug, Clone, PartialEq, Error)]
110#[non_exhaustive]
111pub enum HUGRSerializationError {
112    /// Unexpected hierarchy error.
113    #[error("Failed to attach child to parent: {0}.")]
114    AttachError(#[from] AttachError),
115    /// Failed to add edge.
116    #[error("Failed to build edge when deserializing: {0}.")]
117    LinkError(#[from] LinkError<u32>),
118    /// Edges without port offsets cannot be present in operations without non-dataflow ports.
119    #[error(
120        "Cannot connect an {dir:?} edge without port offset to node {node} with operation type {op_type}."
121    )]
122    MissingPortOffset {
123        /// The node that has the port without offset.
124        node: Node,
125        /// The direction of the port without an offset
126        dir: Direction,
127        /// The operation type of the node.
128        op_type: OpType,
129    },
130    /// Edges with wrong node indices
131    #[error("The edge endpoint {node} is not a node in the graph.")]
132    UnknownEdgeNode {
133        /// The node that has the port without offset.
134        node: Node,
135    },
136    /// First node in node list must be the HUGR root.
137    #[error("The first node in the node list has parent {0}, should be itself (index 0)")]
138    FirstNodeNotRoot(Node),
139    /// Failed to deserialize the HUGR.
140    #[error(transparent)]
141    HugrError(#[from] HugrError),
142}
143
144impl Hugr {
145    /// Serializes the HUGR using a serde encoder.
146    ///
147    /// This is an internal API, used to generate the JSON variant of the HUGR envelope format.
148    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    /// Deserializes the HUGR using a serde decoder.
158    ///
159    /// This is an internal API, used to read the JSON variant of the HUGR envelope format.
160    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/// Deerialize the HUGR using a serde decoder.
171///
172/// This API is unstable API and will be removed in the future.
173#[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        // We compact the operation nodes during the serialization process,
191        // and ignore the copy nodes.
192        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        // Root must be first node
261        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        // if there are any unconnected ports or copy nodes the capacity will be
271        // an underestimate
272        let mut hugr = Hugr::with_capacity(root_type, nodes.len(), edges.len() * 2)?;
273
274        // Since the new Hugr may add some nodes to contain the root (if the
275        // encoded file did not have a module at the root), we need a function
276        // to map the node indices.
277        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)))]
332// Miri doesn't run the extension registration required by `typetag` for
333// registering `CustomConst`s.  https://github.com/rust-lang/miri/issues/450
334pub mod test;