Skip to content

Commit a8ca450

Browse files
committed
monadic wrapper type for descriptions
1 parent 5b2ae6c commit a8ca450

File tree

13 files changed

+194
-68
lines changed

13 files changed

+194
-68
lines changed

hugr-cli/src/convert.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub struct ConvertArgs {
4949
impl ConvertArgs {
5050
/// Convert a HUGR between different envelope formats
5151
pub fn run_convert(&mut self) -> Result<()> {
52-
let (env_config, package) = self.input_args.get_described_package()?;
52+
let desc_pkg = self.input_args.get_described_package()?;
5353

5454
// Handle text and binary format flags, which override the format option
5555
let mut config = if self.text {
@@ -67,7 +67,7 @@ impl ConvertArgs {
6767
"model-text-exts" => EnvelopeFormat::ModelTextWithExtensions,
6868
_ => Err(CliError::InvalidFormat(fmt.clone()))?,
6969
},
70-
None => env_config.header.config().format, // Use input format if not specified
70+
None => desc_pkg.description().header.config().format, // Use input format if not specified
7171
};
7272
EnvelopeConfig::new(format)
7373
};
@@ -78,7 +78,7 @@ impl ConvertArgs {
7878
}
7979

8080
// Write the package with the requested format
81-
hugr::envelope::write_envelope(&mut self.output, &package, config)?;
81+
hugr::envelope::write_envelope(&mut self.output, desc_pkg.as_ref(), config)?;
8282

8383
Ok(())
8484
}

hugr-cli/src/hugr_io.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Input/output arguments for the HUGR CLI.
22
33
use clio::Input;
4-
use hugr::envelope::description::PackageDesc;
4+
use hugr::envelope::description::DescribedPackage;
55
use hugr::envelope::{EnvelopeConfig, read_described_envelope};
66
use hugr::extension::ExtensionRegistry;
77
use hugr::package::Package;
@@ -49,7 +49,8 @@ impl HugrInputArgs {
4949
/// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
5050
/// reading the input as a package will fail.
5151
pub fn get_package(&mut self) -> Result<Package, CliError> {
52-
self.get_described_package().map(|(_, package)| package)
52+
self.get_described_package()
53+
.map(|dsk_pkg| dsk_pkg.into_inner())
5354
}
5455

5556
/// Read a hugr envelope from the input and return the envelope
@@ -61,8 +62,10 @@ impl HugrInputArgs {
6162
/// reading the input as a package will fail.
6263
#[deprecated(since = "0.24.1", note = "Use get_described_envelope instead")]
6364
pub fn get_envelope(&mut self) -> Result<(EnvelopeConfig, Package), CliError> {
64-
let (desc, package) = self.get_described_package()?;
65-
Ok((desc.header.config(), package))
65+
let desc_pkg = self.get_described_package()?;
66+
67+
let config = desc_pkg.description().header.config();
68+
Ok((config, desc_pkg.into_inner()))
6669
}
6770

6871
/// Read a hugr envelope from the input and return the envelope
@@ -72,7 +75,7 @@ impl HugrInputArgs {
7275
///
7376
/// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
7477
/// reading the input as a package will fail.
75-
pub fn get_described_package(&mut self) -> Result<(PackageDesc, Package), CliError> {
78+
pub fn get_described_package(&mut self) -> Result<DescribedPackage, CliError> {
7679
let extensions = self.load_extensions()?;
7780
let buffer = BufReader::new(&mut self.input);
7881

hugr-cli/src/mermaid.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ impl MermaidArgs {
4343

4444
/// Write the mermaid diagram for a HUGR envelope.
4545
pub fn run_print_envelope(&mut self) -> Result<()> {
46-
let (desc, package) = self.input_args.get_described_package()?;
47-
let generator = desc.generator();
46+
let desc_pkg = self.input_args.get_described_package()?;
47+
let generator = desc_pkg.description().generator();
48+
let package = desc_pkg.into_inner();
4849
if self.validate {
4950
package
5051
.validate()

hugr-cli/src/validate.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ impl ValArgs {
3737
.map_err(PackageValidationError::Validation)
3838
.map_err(|val_err| CliError::validation(generator, val_err))?;
3939
} else {
40-
let (desc, package) = self.input_args.get_described_package()?;
41-
let generator = desc.generator();
42-
package
40+
let desc_pkg = self.input_args.get_described_package()?;
41+
let generator = desc_pkg.description().generator();
42+
desc_pkg
43+
.into_inner()
4344
.validate()
4445
.map_err(|val_err| CliError::validation(generator, val_err))?;
4546
};

hugr-cli/tests/convert.rs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ fn test_convert_to_json(test_envelope_file: NamedTempFile, mut convert_cmd: Comm
7777
let output_content = std::fs::read(output_file.path()).expect("Failed to read output file");
7878
let reader = BufReader::new(output_content.as_slice());
7979
let registry = ExtensionRegistry::default();
80-
let (desc, _) =
81-
read_described_envelope(reader, &registry).expect("Failed to read output envelope");
80+
let desc = read_described_envelope(reader, &registry)
81+
.expect("Failed to read output envelope")
82+
.description()
83+
.clone();
8284
let config = desc.header.config();
8385

8486
// Verify the format is correct
@@ -104,8 +106,10 @@ fn test_convert_to_model(test_envelope_file: NamedTempFile, mut convert_cmd: Com
104106
let output_content = std::fs::read(output_file.path()).expect("Failed to read output file");
105107
let reader = BufReader::new(output_content.as_slice());
106108
let registry = ExtensionRegistry::default();
107-
let (desc, _) =
108-
read_described_envelope(reader, &registry).expect("Failed to read output envelope");
109+
let desc = read_described_envelope(reader, &registry)
110+
.expect("Failed to read output envelope")
111+
.description()
112+
.clone();
109113
let config = desc.header.config();
110114
// Verify the format is correct
111115
assert_eq!(config.format, EnvelopeFormat::Model);
@@ -177,8 +181,10 @@ fn test_convert_model_text_format(test_envelope_file: NamedTempFile, mut convert
177181
let output_content = std::fs::read(output_file.path()).expect("Failed to read output file");
178182
let reader = BufReader::new(output_content.as_slice());
179183
let registry = ExtensionRegistry::default();
180-
let (desc, _) =
181-
read_described_envelope(reader, &registry).expect("Failed to read output envelope");
184+
let desc = read_described_envelope(reader, &registry)
185+
.expect("Failed to read output envelope")
186+
.description()
187+
.clone();
182188
let config = desc.header.config();
183189

184190
// Verify the format is correct
@@ -197,14 +203,18 @@ fn test_format_roundtrip(test_package: Package) {
197203
let config_model = EnvelopeConfig::new(EnvelopeFormat::Model);
198204
let reader = BufReader::new(json_data.as_slice());
199205
let registry = ExtensionRegistry::default();
200-
let (_, package) = read_described_envelope(reader, &registry).unwrap();
206+
let package = read_described_envelope(reader, &registry)
207+
.unwrap()
208+
.into_inner();
201209

202210
let mut model_data = Vec::new();
203211
hugr::envelope::write_envelope(&mut model_data, &package, config_model).unwrap();
204212

205213
// Convert back to JSON
206214
let reader = BufReader::new(model_data.as_slice());
207-
let (_, package_back) = read_described_envelope(reader, &registry).unwrap();
215+
let package_back = read_described_envelope(reader, &registry)
216+
.unwrap()
217+
.into_inner();
208218

209219
// Package should be the same after roundtrip conversion
210220
assert_eq!(test_package, package_back);
@@ -220,8 +230,10 @@ fn test_convert_text_flag(test_envelope_text: (String, Package), mut convert_cmd
220230

221231
let reader = BufReader::new(stdout.as_slice());
222232
let registry = ExtensionRegistry::default();
223-
let (desc, _) =
224-
read_described_envelope(reader, &registry).expect("Failed to read output envelope");
233+
let desc = read_described_envelope(reader, &registry)
234+
.expect("Failed to read output envelope")
235+
.description()
236+
.clone();
225237
let config = desc.header.config();
226238

227239
// Verify it's a text-based format
@@ -238,8 +250,10 @@ fn test_convert_binary_flag(test_envelope_text: (String, Package), mut convert_c
238250

239251
let reader = BufReader::new(stdout.as_slice());
240252
let registry = ExtensionRegistry::default();
241-
let (desc, _) =
242-
read_described_envelope(reader, &registry).expect("Failed to read output envelope");
253+
let desc = read_described_envelope(reader, &registry)
254+
.expect("Failed to read output envelope")
255+
.description()
256+
.clone();
243257
let config = desc.header.config();
244258

245259
// Verify it's a binary format (not ASCII printable)

hugr-core/src/envelope.rs

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub mod serde_with;
4646
pub use header::{EnvelopeConfig, EnvelopeFormat, EnvelopeHeader, MAGIC_NUMBERS, ZstdConfig};
4747
pub use package_json::PackageEncodingError;
4848

49-
use crate::envelope::description::PackageDesc;
49+
use crate::envelope::description::{Described, DescribedPackage, PackageDesc};
5050
use crate::envelope::header::HeaderError;
5151
use crate::extension::resolution::ExtensionResolutionError;
5252
use crate::{Hugr, HugrView};
@@ -157,7 +157,7 @@ pub fn read_envelope(
157157
) -> Result<(EnvelopeConfig, Package), EnvelopeError> {
158158
let reader = EnvelopeReader::new(reader, registry)?;
159159
let config = reader.description().header.config();
160-
let package = reader.read().1?;
160+
let package = reader.read().into_parts().0?;
161161

162162
Ok((config, package))
163163
}
@@ -174,39 +174,50 @@ pub fn read_envelope(
174174
pub fn read_described_envelope(
175175
reader: impl BufRead,
176176
registry: &ExtensionRegistry,
177-
) -> Result<(PackageDesc, Package), ReadError> {
178-
let reader = EnvelopeReader::new(reader, registry).map_err(Box::new)?;
179-
let (desc, res) = reader.read();
180-
match res {
181-
Ok(pkg) => Ok((desc, pkg)),
182-
Err(e) => Err(ReadError::Payload {
183-
source: Box::new(e),
184-
partial_description: desc,
177+
) -> Result<DescribedPackage, ReadError> {
178+
let reader = EnvelopeReader::new(reader, registry)?;
179+
let res = reader.read();
180+
match res.as_ref() {
181+
Ok(_) => Ok(res.map(Result::unwrap)),
182+
Err(_) => Err(ReadError::Payload {
183+
inner: res.map(|r| Box::new(r.expect_err("matched on error"))),
185184
}),
186185
}
187186
}
188187

189188
/// Errors during reading a HUGR envelope.
190-
#[derive(Debug, Error)]
189+
#[derive(Debug, derive_more::Display)]
190+
#[non_exhaustive]
191191
pub enum ReadError {
192192
/// Error reading the envelope header.
193-
#[error(transparent)]
194-
EnvelopeHeader(#[from] Box<HeaderError>),
193+
EnvelopeHeader(Box<HeaderError>),
195194
/// Error reading the package payload.
196-
#[error("Error reading package payload in envelope.")]
195+
#[display("Error reading package payload in envelope.")]
197196
Payload {
198197
/// The source error.
199-
source: Box<PayloadError>,
200-
/// Partial description of the envelope read before the error occurred.
201-
partial_description: PackageDesc,
198+
inner: Described<Box<PayloadError>, PackageDesc>,
202199
},
203200
}
201+
impl From<HeaderError> for ReadError {
202+
fn from(err: HeaderError) -> Self {
203+
ReadError::EnvelopeHeader(Box::new(err))
204+
}
205+
}
206+
207+
impl std::error::Error for ReadError {
208+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
209+
match self {
210+
ReadError::EnvelopeHeader(e) => Some(&**e),
211+
ReadError::Payload { inner } => Some(&**inner.as_ref()),
212+
}
213+
}
214+
}
204215

205216
impl From<ReadError> for EnvelopeError {
206217
fn from(err: ReadError) -> Self {
207218
match err {
208219
ReadError::EnvelopeHeader(e) => (*e).into(),
209-
ReadError::Payload { source, .. } => (*source).into(),
220+
ReadError::Payload { inner } => (*(inner.into_inner())).into(),
210221
}
211222
}
212223
}
@@ -650,12 +661,12 @@ pub(crate) mod test {
650661
}
651662
}
652663

653-
let (desc, new_package) =
664+
let desc_pkg =
654665
read_described_envelope(BufReader::new(buffer.as_slice()), &PRELUDE_REGISTRY).unwrap();
655-
let decoded_config = desc.header.config();
666+
let decoded_config = desc_pkg.description().header.config();
656667
assert_eq!(config.format, decoded_config.format);
657668
assert_eq!(config.zstd.is_some(), decoded_config.zstd.is_some());
658-
assert_eq!(package, new_package);
669+
assert_eq!(&package, desc_pkg.as_ref());
659670
}
660671

661672
#[rstest]
@@ -679,14 +690,14 @@ pub(crate) mod test {
679690
let config = EnvelopeConfig { format, zstd: None };
680691
package.store(&mut buffer, config).unwrap();
681692

682-
let (desc, new_package) =
693+
let desc_pkg =
683694
read_described_envelope(BufReader::new(buffer.as_slice()), &PRELUDE_REGISTRY).unwrap();
684-
let decoded_config = desc.header.config();
695+
let decoded_config = desc_pkg.description().header.config();
685696

686697
assert_eq!(config.format, decoded_config.format);
687698
assert_eq!(config.zstd.is_some(), decoded_config.zstd.is_some());
688699

689-
assert_eq!(package, new_package);
700+
assert_eq!(&package, desc_pkg.as_ref());
690701
}
691702

692703
/// Test helper to call `check_breaking_extensions_against_registry`

0 commit comments

Comments
 (0)