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::{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,
82 #[serde(flatten)]
83 op: OpType,
84}
85
86#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
88struct SerHugrLatest {
89 nodes: Vec<NodeSer>,
91 edges: Vec<[(Node, Option<u16>); 2]>,
93 #[serde(default)]
95 metadata: Option<Vec<Option<NodeMetadataMap>>>,
96 #[serde(default)]
98 encoder: Option<String>,
99}
100
101#[derive(Debug, Clone, PartialEq, Error)]
103#[non_exhaustive]
104pub enum HUGRSerializationError {
105 #[error("Failed to attach child to parent: {0}.")]
107 AttachError(#[from] AttachError),
108 #[error("Failed to build edge when deserializing: {0}.")]
110 LinkError(#[from] LinkError),
111 #[error("Cannot connect an {dir:?} edge without port offset to node {node} with operation type {op_type}.")]
113 MissingPortOffset {
114 node: Node,
116 dir: Direction,
118 op_type: OpType,
120 },
121 #[error("The edge endpoint {node} is not a node in the graph.")]
123 UnknownEdgeNode {
124 node: Node,
126 },
127 #[error("The first node in the node list has parent {0}, should be itself (index 0)")]
129 FirstNodeNotRoot(Node),
130}
131
132impl Serialize for Hugr {
133 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134 where
135 S: serde::Serializer,
136 {
137 let shg: SerHugrLatest = self.try_into().map_err(serde::ser::Error::custom)?;
138 let versioned = Versioned::new_latest(shg);
139 versioned.serialize(serializer)
140 }
141}
142
143impl<'de> Deserialize<'de> for Hugr {
144 fn deserialize<D>(deserializer: D) -> Result<Hugr, D::Error>
145 where
146 D: Deserializer<'de>,
147 {
148 let versioned = Versioned::deserialize(deserializer)?;
149 let shl: SerHugrLatest = versioned.upgrade().map_err(serde::de::Error::custom)?;
150 shl.try_into().map_err(serde::de::Error::custom)
151 }
152}
153
154impl TryFrom<&Hugr> for SerHugrLatest {
155 type Error = HUGRSerializationError;
156
157 fn try_from(hugr: &Hugr) -> Result<Self, Self::Error> {
158 let mut node_rekey: HashMap<Node, Node> = HashMap::with_capacity(hugr.node_count());
161 for (order, node) in hugr.canonical_order(hugr.root()).enumerate() {
162 node_rekey.insert(node, portgraph::NodeIndex::new(order).into());
163 }
164
165 let mut nodes = vec![None; hugr.node_count()];
166 let mut metadata = vec![None; hugr.node_count()];
167 for n in hugr.nodes() {
168 let parent = node_rekey[&hugr.get_parent(n).unwrap_or(n)];
169 let opt = hugr.get_optype(n);
170 let new_node = node_rekey[&n].index();
171 nodes[new_node] = Some(NodeSer {
172 parent,
173 op: opt.clone(),
174 });
175 metadata[new_node].clone_from(hugr.metadata.get(n.pg_index()));
176 }
177 let nodes = nodes
178 .into_iter()
179 .collect::<Option<Vec<_>>>()
180 .expect("Could not reach one of the nodes");
181
182 let find_offset = |node: Node, offset: usize, dir: Direction, hugr: &Hugr| {
183 let op = hugr.get_optype(node);
184 let is_value_port = offset < op.value_port_count(dir);
185 let is_static_input = op.static_port(dir).is_some_and(|p| p.index() == offset);
186 let offset = (is_value_port || is_static_input).then_some(offset as u16);
187 (node_rekey[&node], offset)
188 };
189
190 let edges: Vec<_> = hugr
191 .nodes()
192 .flat_map(|node| {
193 hugr.node_ports(node, Direction::Outgoing)
194 .enumerate()
195 .flat_map(move |(src_offset, port)| {
196 let src = find_offset(node, src_offset, Direction::Outgoing, hugr);
197 hugr.linked_ports(node, port).map(move |(tgt_node, tgt)| {
198 let tgt = find_offset(tgt_node, tgt.index(), Direction::Incoming, hugr);
199 [src, tgt]
200 })
201 })
202 })
203 .collect();
204
205 let encoder = Some(format!("hugr-rs v{}", env!("CARGO_PKG_VERSION")));
206
207 Ok(Self {
208 nodes,
209 edges,
210 metadata: Some(metadata),
211 encoder,
212 })
213 }
214}
215
216impl TryFrom<SerHugrLatest> for Hugr {
217 type Error = HUGRSerializationError;
218 fn try_from(
219 SerHugrLatest {
220 nodes,
221 edges,
222 metadata,
223 encoder: _,
224 }: SerHugrLatest,
225 ) -> Result<Self, Self::Error> {
226 let mut nodes = nodes.into_iter();
228 let NodeSer {
229 parent: root_parent,
230 op: root_type,
231 ..
232 } = nodes.next().unwrap();
233 if root_parent.index() != 0 {
234 return Err(HUGRSerializationError::FirstNodeNotRoot(root_parent));
235 }
236 let mut hugr = Hugr::with_capacity(root_type, nodes.len(), edges.len() * 2);
239
240 for node_ser in nodes {
241 hugr.add_node_with_parent(node_ser.parent, node_ser.op);
242 }
243
244 if let Some(metadata) = metadata {
245 for (node, metadata) in metadata.into_iter().enumerate() {
246 if let Some(metadata) = metadata {
247 let node = portgraph::NodeIndex::new(node);
248 hugr.metadata[node] = Some(metadata);
249 }
250 }
251 }
252
253 let unwrap_offset = |node: Node, offset, dir, hugr: &Hugr| -> Result<usize, Self::Error> {
254 if !hugr.graph.contains_node(node.pg_index()) {
255 return Err(HUGRSerializationError::UnknownEdgeNode { node });
256 }
257 let offset = match offset {
258 Some(offset) => offset as usize,
259 None => {
260 let op_type = hugr.get_optype(node);
261 op_type
262 .other_port(dir)
263 .ok_or(HUGRSerializationError::MissingPortOffset {
264 node,
265 dir,
266 op_type: op_type.clone(),
267 })?
268 .index()
269 }
270 };
271 Ok(offset)
272 };
273 for [(src, from_offset), (dst, to_offset)] in edges {
274 let src_port = unwrap_offset(src, from_offset, Direction::Outgoing, &hugr)?;
275 let dst_port = unwrap_offset(dst, to_offset, Direction::Incoming, &hugr)?;
276
277 hugr.connect(src, src_port, dst, dst_port);
278 }
279
280 Ok(hugr)
281 }
282}
283
284#[cfg(all(test, not(miri)))]
285pub mod test;