1#![allow(deprecated)]
38mod header;
44
45pub use header::{EnvelopeConfig, EnvelopeFormat, ZstdConfig, MAGIC_NUMBERS};
46
47use crate::{
48 extension::ExtensionRegistry,
49 package::{Package, PackageEncodingError, PackageError},
50};
51use header::EnvelopeHeader;
52use std::io::BufRead;
53use std::io::Write;
54
55#[allow(unused_imports)]
56use itertools::Itertools as _;
57
58#[cfg(feature = "model_unstable")]
59use crate::import::ImportError;
60
61pub fn read_envelope(
70 mut reader: impl BufRead,
71 registry: &ExtensionRegistry,
72) -> Result<(EnvelopeConfig, Package), EnvelopeError> {
73 let header = EnvelopeHeader::read(&mut reader)?;
74
75 let package = match header.zstd {
76 #[cfg(feature = "zstd")]
77 true => read_impl(
78 std::io::BufReader::new(zstd::Decoder::new(reader)?),
79 header,
80 registry,
81 ),
82 #[cfg(not(feature = "zstd"))]
83 true => Err(EnvelopeError::ZstdUnsupported),
84 false => read_impl(reader, header, registry),
85 }?;
86 Ok((header.config(), package))
87}
88
89pub fn write_envelope(
94 mut writer: impl Write,
95 package: &Package,
96 config: EnvelopeConfig,
97) -> Result<(), EnvelopeError> {
98 let header = config.make_header();
99 header.write(&mut writer)?;
100
101 match config.zstd {
102 #[cfg(feature = "zstd")]
103 Some(zstd) => {
104 let writer = zstd::Encoder::new(writer, zstd.level())?.auto_finish();
105 write_impl(writer, package, config)?;
106 }
107 #[cfg(not(feature = "zstd"))]
108 Some(_) => return Err(EnvelopeError::ZstdUnsupported),
109 None => write_impl(writer, package, config)?,
110 }
111
112 Ok(())
113}
114
115#[derive(derive_more::Display, derive_more::Error, Debug, derive_more::From)]
117#[non_exhaustive]
118pub enum EnvelopeError {
119 #[display(
121 "Bad magic number. expected 0x{:X} found 0x{:X}",
122 u64::from_be_bytes(*expected),
123 u64::from_be_bytes(*found)
124 )]
125 #[from(ignore)]
126 MagicNumber {
127 expected: [u8; 8],
131 found: [u8; 8],
133 },
134 #[display("Format descriptor {descriptor} is invalid.")]
136 #[from(ignore)]
137 InvalidFormatDescriptor {
138 descriptor: usize,
140 },
141 #[display("Payload format {format} is not supported.{}",
143 match feature {
144 Some(f) => format!(" This requires the '{f}' feature for `hugr`."),
145 None => "".to_string()
146 },
147 )]
148 #[from(ignore)]
149 FormatUnsupported {
150 format: EnvelopeFormat,
152 feature: Option<&'static str>,
154 },
155 #[display("Envelope format {format} cannot be represented as ASCII.")]
159 #[from(ignore)]
160 NonASCIIFormat {
161 format: EnvelopeFormat,
163 },
164 #[display("Zstd compression is not supported. This requires the 'zstd' feature for `hugr`.")]
166 #[from(ignore)]
167 ZstdUnsupported,
168 #[display(
170 "Packages with multiple HUGRs are currently unsupported. Tried to encode {count} HUGRs, when 1 was expected."
171 )]
172 #[from(ignore)]
173 #[deprecated(since = "0.15.2", note = "Multiple HUGRs are supported via packages.")]
175 MultipleHugrs {
176 count: usize,
178 },
179 SerdeError {
181 source: serde_json::Error,
183 },
184 IO {
186 source: std::io::Error,
188 },
189 Package {
191 source: PackageError,
193 },
194 PackageEncoding {
196 source: PackageEncodingError,
198 },
199 #[cfg(feature = "model_unstable")]
201 ModelImport {
202 source: ImportError,
204 },
205 #[cfg(feature = "model_unstable")]
207 ModelRead {
208 source: hugr_model::v0::binary::ReadError,
210 },
211 #[cfg(feature = "model_unstable")]
213 ModelWrite {
214 source: hugr_model::v0::binary::WriteError,
216 },
217}
218
219fn read_impl(
221 payload: impl BufRead,
222 header: EnvelopeHeader,
223 registry: &ExtensionRegistry,
224) -> Result<Package, EnvelopeError> {
225 match header.format {
226 #[allow(deprecated)]
227 EnvelopeFormat::PackageJson => Ok(Package::from_json_reader(payload, registry)?),
228 #[cfg(feature = "model_unstable")]
229 EnvelopeFormat::Model | EnvelopeFormat::ModelWithExtensions => {
230 decode_model(payload, registry, header.format)
231 }
232 #[cfg(not(feature = "model_unstable"))]
233 EnvelopeFormat::Model | EnvelopeFormat::ModelWithExtensions => {
234 Err(EnvelopeError::FormatUnsupported {
235 format: header.format,
236 feature: Some("model_unstable"),
237 })
238 }
239 }
240}
241
242#[cfg(feature = "model_unstable")]
250fn decode_model(
251 mut stream: impl BufRead,
252 extension_registry: &ExtensionRegistry,
253 format: EnvelopeFormat,
254) -> Result<Package, EnvelopeError> {
255 use crate::{import::import_package, Extension};
256 use hugr_model::v0::bumpalo::Bump;
257
258 if format.model_version() != Some(0) {
259 return Err(EnvelopeError::FormatUnsupported {
260 format,
261 feature: None,
262 });
263 }
264
265 let bump = Bump::default();
266 let model_package = hugr_model::v0::binary::read_from_reader(&mut stream, &bump)?;
267
268 let mut extension_registry = extension_registry.clone();
269 if format.append_extensions() {
270 let extra_extensions: Vec<Extension> =
271 serde_json::from_reader::<_, Vec<Extension>>(stream)?;
272 for ext in extra_extensions {
273 extension_registry.register_updated(ext);
274 }
275 }
276
277 Ok(import_package(&model_package, &extension_registry)?)
278}
279
280fn write_impl(
282 writer: impl Write,
283 package: &Package,
284 config: EnvelopeConfig,
285) -> Result<(), EnvelopeError> {
286 match config.format {
287 #[allow(deprecated)]
288 EnvelopeFormat::PackageJson => package.to_json_writer(writer)?,
289 #[cfg(feature = "model_unstable")]
290 EnvelopeFormat::Model | EnvelopeFormat::ModelWithExtensions => {
291 encode_model(writer, package, config.format)?
292 }
293 #[cfg(not(feature = "model_unstable"))]
294 EnvelopeFormat::Model | EnvelopeFormat::ModelWithExtensions => {
295 return Err(EnvelopeError::FormatUnsupported {
296 format: config.format,
297 feature: Some("model_unstable"),
298 })
299 }
300 }
301 Ok(())
302}
303
304#[cfg(feature = "model_unstable")]
305fn encode_model(
306 mut writer: impl Write,
307 package: &Package,
308 format: EnvelopeFormat,
309) -> Result<(), EnvelopeError> {
310 use hugr_model::v0::{binary::write_to_writer, bumpalo::Bump};
311
312 use crate::export::export_package;
313
314 if format.model_version() != Some(0) {
315 return Err(EnvelopeError::FormatUnsupported {
316 format,
317 feature: None,
318 });
319 }
320
321 let bump = Bump::default();
322 let model_package = export_package(package, &bump);
323 write_to_writer(&model_package, &mut writer)?;
324
325 if format.append_extensions() {
326 serde_json::to_writer(writer, &package.extensions.iter().collect_vec())?;
327 }
328
329 Ok(())
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use cool_asserts::assert_matches;
336 use rstest::rstest;
337 use std::io::BufReader;
338
339 use crate::builder::test::{multi_module_package, simple_package};
340 use crate::extension::PRELUDE_REGISTRY;
341
342 #[rstest]
343 fn errors() {
344 let package = simple_package();
345 assert_matches!(
346 package.store_str(EnvelopeConfig::binary()),
347 Err(EnvelopeError::NonASCIIFormat { .. })
348 );
349 }
350
351 #[rstest]
352 #[case::empty(Package::default())]
353 #[case::simple(simple_package())]
354 #[case::multi(multi_module_package())]
355 fn text_roundtrip(#[case] package: Package) {
356 let envelope = package.store_str(EnvelopeConfig::text()).unwrap();
357 let new_package = Package::load_str(&envelope, None).unwrap();
358 assert_eq!(package, new_package);
359 }
360
361 #[rstest]
362 #[case::empty(Package::default())]
363 #[case::simple(simple_package())]
364 #[case::multi(multi_module_package())]
365 #[cfg_attr(all(miri, feature = "zstd"), ignore)] fn compressed_roundtrip(#[case] package: Package) {
367 let mut buffer = Vec::new();
368 let config = EnvelopeConfig {
369 format: EnvelopeFormat::PackageJson,
370 zstd: Some(ZstdConfig::default()),
371 };
372 let res = package.store(&mut buffer, config);
373
374 match cfg!(feature = "zstd") {
375 true => res.unwrap(),
376 false => {
377 assert_matches!(res, Err(EnvelopeError::ZstdUnsupported));
378 return;
379 }
380 }
381
382 let (decoded_config, new_package) =
383 read_envelope(BufReader::new(buffer.as_slice()), &PRELUDE_REGISTRY).unwrap();
384
385 assert_eq!(config.format, decoded_config.format);
386 assert_eq!(config.zstd.is_some(), decoded_config.zstd.is_some());
387 assert_eq!(package, new_package);
388 }
389
390 #[rstest]
391 #[case::simple(simple_package())]
393 #[cfg(feature = "model_unstable")]
395 fn module_exts_roundtrip(#[case] package: Package) {
396 let mut buffer = Vec::new();
397 let config = EnvelopeConfig {
398 format: EnvelopeFormat::ModelWithExtensions,
399 zstd: None,
400 };
401 package.store(&mut buffer, config).unwrap();
402 let (decoded_config, new_package) =
403 read_envelope(BufReader::new(buffer.as_slice()), &PRELUDE_REGISTRY).unwrap();
404
405 assert_eq!(config.format, decoded_config.format);
406 assert_eq!(config.zstd.is_some(), decoded_config.zstd.is_some());
407 assert_eq!(package, new_package);
408 }
409
410 #[rstest]
411 #[case::simple(simple_package())]
413 fn module_roundtrip(#[case] package: Package) {
415 let mut buffer = Vec::new();
416 let config = EnvelopeConfig {
417 format: EnvelopeFormat::Model,
418 zstd: None,
419 };
420 let res = package.store(&mut buffer, config);
421
422 match cfg!(feature = "model_unstable") {
423 true => res.unwrap(),
424 false => {
425 assert_matches!(res, Err(EnvelopeError::FormatUnsupported { .. }));
426 return;
427 }
428 }
429
430 let (decoded_config, new_package) =
431 read_envelope(BufReader::new(buffer.as_slice()), &PRELUDE_REGISTRY).unwrap();
432
433 assert_eq!(config.format, decoded_config.format);
434 assert_eq!(config.zstd.is_some(), decoded_config.zstd.is_some());
435
436 assert_eq!(package, new_package);
437 }
438}