hugr_core/
package.rs

1//! Bundles of hugr modules along with the extension required to load them.
2
3use std::io;
4
5use crate::envelope::{EnvelopeConfig, EnvelopeError, read_envelope, write_envelope};
6use crate::extension::ExtensionRegistry;
7use crate::hugr::{HugrView, ValidationError};
8use crate::std_extensions::STD_REG;
9use crate::{Hugr, Node};
10use thiserror::Error;
11
12#[derive(Debug, Default, Clone, PartialEq)]
13/// Package of module HUGRs.
14pub struct Package {
15    /// Module HUGRs included in the package.
16    pub modules: Vec<Hugr>,
17    /// Extensions used in the modules.
18    ///
19    /// This is a superset of the extensions used in the modules.
20    pub extensions: ExtensionRegistry,
21}
22
23impl Package {
24    /// Create a new package from a list of hugrs.
25    ///
26    /// The hugr extensions are not automatically added to the package. Make
27    /// sure to manually include any non-standard extensions to
28    /// [`Package::extensions`].
29    pub fn new(modules: impl IntoIterator<Item = Hugr>) -> Self {
30        let modules: Vec<Hugr> = modules.into_iter().collect();
31        Self {
32            modules,
33            extensions: ExtensionRegistry::default(),
34        }
35    }
36
37    /// Create a new package containing a single HUGR.
38    ///
39    /// The hugr extensions are not automatically added to the package. Make
40    /// sure to manually include any non-standard extensions to
41    /// [`Package::extensions`].
42    pub fn from_hugr(hugr: Hugr) -> Self {
43        Package {
44            extensions: ExtensionRegistry::default(),
45            modules: vec![hugr],
46        }
47    }
48
49    /// Validate the modules of the package.
50    ///
51    /// Ensures that the top-level extension list is a superset of the extensions used in the modules.
52    pub fn validate(&self) -> Result<(), PackageValidationError> {
53        for hugr in &self.modules {
54            hugr.validate()?;
55        }
56        Ok(())
57    }
58
59    /// Read a Package from a HUGR envelope.
60    ///
61    /// To load a Package, all the extensions used in its definition must be
62    /// available. The Envelope may include some of the extensions, but any
63    /// additional extensions must be provided in the `extensions` parameter. If
64    /// `extensions` is `None`, the default [`crate::std_extensions::STD_REG`]
65    /// is used.
66    pub fn load(
67        reader: impl io::BufRead,
68        extensions: Option<&ExtensionRegistry>,
69    ) -> Result<Self, EnvelopeError> {
70        let extensions = extensions.unwrap_or(&STD_REG);
71        let (_, pkg) = read_envelope(reader, extensions)?;
72        Ok(pkg)
73    }
74
75    /// Read a Package from a HUGR envelope encoded in a string.
76    ///
77    /// Note that not all envelopes are valid strings. In the general case,
78    /// it is recommended to use `Package::load` with a bytearray instead.
79    ///
80    /// To load a Package, all the extensions used in its definition must be
81    /// available. The Envelope may include some of the extensions, but any
82    /// additional extensions must be provided in the `extensions` parameter. If
83    /// `extensions` is `None`, the default [`crate::std_extensions::STD_REG`]
84    /// is used.
85    pub fn load_str(
86        envelope: impl AsRef<str>,
87        extensions: Option<&ExtensionRegistry>,
88    ) -> Result<Self, EnvelopeError> {
89        Self::load(envelope.as_ref().as_bytes(), extensions)
90    }
91
92    /// Store the Package in a HUGR envelope.
93    ///
94    /// The Envelope will embed the definitions of the extensions in
95    /// [`Package::extensions`]. Any other extension used in the definition must
96    /// be passed to [`Package::load`] to load back the package.
97    pub fn store(
98        &self,
99        writer: impl io::Write,
100        config: EnvelopeConfig,
101    ) -> Result<(), EnvelopeError> {
102        write_envelope(writer, self, config)
103    }
104
105    /// Store the Package in a HUGR envelope encoded in a string.
106    ///
107    /// Note that not all envelopes are valid strings. In the general case,
108    /// it is recommended to use `Package::store` with a bytearray instead.
109    /// See [`EnvelopeFormat::ascii_printable`][crate::envelope::EnvelopeFormat::ascii_printable].
110    ///
111    /// The Envelope will embed the definitions of the extensions in
112    /// [`Package::extensions`]. Any other extension used in the definition must
113    /// be passed to [`Package::load_str`] to load back the package.
114    pub fn store_str(&self, config: EnvelopeConfig) -> Result<String, EnvelopeError> {
115        if !config.format.ascii_printable() {
116            return Err(EnvelopeError::NonASCIIFormat {
117                format: config.format,
118            });
119        }
120
121        let mut buf = Vec::new();
122        self.store(&mut buf, config)?;
123        Ok(String::from_utf8(buf).expect("Envelope is valid utf8"))
124    }
125}
126
127impl AsRef<[Hugr]> for Package {
128    fn as_ref(&self) -> &[Hugr] {
129        &self.modules
130    }
131}
132
133/// Error raised while validating a package.
134#[derive(Debug, Error)]
135#[non_exhaustive]
136#[error("Package validation error.")]
137pub enum PackageValidationError {
138    /// Error raised while validating the package hugrs.
139    Validation(#[from] ValidationError<Node>),
140}
141
142#[cfg(test)]
143mod test {
144    use super::*;
145    use crate::builder::test::{
146        simple_cfg_hugr, simple_dfg_hugr, simple_funcdef_hugr, simple_module_hugr,
147    };
148    use rstest::rstest;
149
150    #[rstest]
151    #[case::module("module", simple_module_hugr())]
152    #[case::funcdef("funcdef", simple_funcdef_hugr())]
153    #[case::dfg("dfg", simple_dfg_hugr())]
154    #[case::cfg("cfg", simple_cfg_hugr())]
155    #[cfg_attr(miri, ignore)] // Opening files is not supported in (isolated) miri
156    fn hugr_to_package(#[case] test_name: &str, #[case] hugr: Hugr) {
157        let package = &Package::from_hugr(hugr.clone());
158        assert_eq!(package.modules.len(), 1);
159
160        assert_eq!(
161            package.modules[0].entrypoint_optype(),
162            hugr.entrypoint_optype()
163        );
164
165        insta::assert_snapshot!(test_name, hugr.mermaid_string());
166    }
167
168    #[rstest]
169    fn package_properties() {
170        let module = simple_module_hugr();
171        let dfg = simple_dfg_hugr();
172
173        let pkg = Package::new([module, dfg]);
174        pkg.validate().unwrap();
175
176        assert_eq!(pkg.modules.len(), 2);
177    }
178}