use std::collections::BTreeMap;
use typst::syntax::package::{PackageSpec, PackageVersion};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct PackageReadFailures {
failures: BTreeMap<String, PackageReadFailure>,
}
impl PackageReadFailures {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, failure: PackageReadFailure) -> Option<PackageReadFailure> {
self.failures.insert(failure.spec.to_string(), failure)
}
pub fn entries(&self) -> impl Iterator<Item = &PackageReadFailure> {
self.failures.values()
}
pub fn get(&self, spec: &PackageSpec) -> Option<&PackageReadFailure> {
self.failures.get(&spec.to_string())
}
#[cfg(any(feature = "fs", all(feature = "opendal", feature = "package-reading")))]
pub(crate) fn remove(&mut self, spec: &PackageSpec) -> Option<PackageReadFailure> {
self.failures.remove(&spec.to_string())
}
}
impl FromIterator<PackageReadFailure> for PackageReadFailures {
fn from_iter<T: IntoIterator<Item = PackageReadFailure>>(failures: T) -> Self {
let mut result = Self::new();
for failure in failures {
result.insert(failure);
}
result
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[error("failed to read {spec}: {reason}")]
pub struct PackageReadFailure {
spec: PackageSpec,
reason: PackageReadFailureReason,
}
impl PackageReadFailure {
pub fn new(spec: PackageSpec, reason: PackageReadFailureReason) -> Self {
Self { spec, reason }
}
pub fn spec(&self) -> &PackageSpec {
&self.spec
}
pub fn reason(&self) -> &PackageReadFailureReason {
&self.reason
}
pub fn into_parts(self) -> (PackageSpec, PackageReadFailureReason) {
(self.spec, self.reason)
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum PackageReadFailureReason {
#[error("package not found")]
NotFound,
#[error("package version not found; latest available version is {latest}")]
VersionNotFound { latest: PackageVersion },
#[error("network request failed{detail}", detail = optional_detail(.detail))]
NetworkFailed { detail: Option<String> },
#[error("package archive is malformed{detail}", detail = optional_detail(.detail))]
MalformedArchive { detail: Option<String> },
#[error("package read failed{detail}", detail = optional_detail(.detail))]
Other { detail: Option<String> },
}
fn optional_detail(detail: &Option<String>) -> String {
detail
.as_ref()
.map(|detail| format!(": {detail:?}"))
.unwrap_or_default()
}