Skip to main content

typst_pack/
package_failure.rs

1//! Stable Package Read Failure data carried by Pack Assembly.
2
3use std::collections::BTreeMap;
4
5use typst::syntax::package::{PackageSpec, PackageVersion};
6
7/// Package Read Failures keyed by exact package specification.
8///
9/// Pack Assembly updates this value between Pack Creation invocations. A
10/// separately supplied Package Catalog entry always takes precedence during
11/// Dependency Discovery.
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13pub struct PackageReadFailures {
14    failures: BTreeMap<String, PackageReadFailure>,
15}
16
17impl PackageReadFailures {
18    /// An empty failure map.
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Records the latest failed attempt for one exact specification.
24    pub fn insert(&mut self, failure: PackageReadFailure) -> Option<PackageReadFailure> {
25        self.failures.insert(failure.spec.to_string(), failure)
26    }
27
28    /// Failures in canonical exact-specification order.
29    pub fn entries(&self) -> impl Iterator<Item = &PackageReadFailure> {
30        self.failures.values()
31    }
32
33    /// Looks up the failed attempt for one exact specification.
34    pub fn get(&self, spec: &PackageSpec) -> Option<&PackageReadFailure> {
35        self.failures.get(&spec.to_string())
36    }
37
38    /// Removes an older failed attempt after the specification is read.
39    #[cfg(any(feature = "fs", all(feature = "opendal", feature = "package-reading")))]
40    pub(crate) fn remove(&mut self, spec: &PackageSpec) -> Option<PackageReadFailure> {
41        self.failures.remove(&spec.to_string())
42    }
43}
44
45impl FromIterator<PackageReadFailure> for PackageReadFailures {
46    fn from_iter<T: IntoIterator<Item = PackageReadFailure>>(failures: T) -> Self {
47        let mut result = Self::new();
48        for failure in failures {
49            result.insert(failure);
50        }
51        result
52    }
53}
54
55/// An external attempt to read one exact package specification failed.
56#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
57#[error("failed to read {spec}: {reason}")]
58pub struct PackageReadFailure {
59    spec: PackageSpec,
60    reason: PackageReadFailureReason,
61}
62
63impl PackageReadFailure {
64    pub fn new(spec: PackageSpec, reason: PackageReadFailureReason) -> Self {
65        Self { spec, reason }
66    }
67
68    /// The exact specification the failed attempt tried to read.
69    pub fn spec(&self) -> &PackageSpec {
70        &self.spec
71    }
72
73    /// The typed operational reason the attempt failed.
74    pub fn reason(&self) -> &PackageReadFailureReason {
75        &self.reason
76    }
77
78    pub fn into_parts(self) -> (PackageSpec, PackageReadFailureReason) {
79        (self.spec, self.reason)
80    }
81}
82
83/// The stable operational reason for a Package Read Failure.
84#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
85#[non_exhaustive]
86pub enum PackageReadFailureReason {
87    #[error("package not found")]
88    NotFound,
89    #[error("package version not found; latest available version is {latest}")]
90    VersionNotFound { latest: PackageVersion },
91    #[error("network request failed{detail}", detail = optional_detail(.detail))]
92    NetworkFailed { detail: Option<String> },
93    #[error("package archive is malformed{detail}", detail = optional_detail(.detail))]
94    MalformedArchive { detail: Option<String> },
95    #[error("package read failed{detail}", detail = optional_detail(.detail))]
96    Other { detail: Option<String> },
97}
98
99fn optional_detail(detail: &Option<String>) -> String {
100    detail
101        .as_ref()
102        .map(|detail| format!(": {detail:?}"))
103        .unwrap_or_default()
104}