Better error message when package version not exists (#4429)

This commit is contained in:
Yip Coekjan 2024-06-22 18:33:33 +08:00 committed by GitHub
parent 09e3bbd3b4
commit e90c30903d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 35 deletions

View File

@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use crate::args::PackageStorageArgs; use crate::args::PackageStorageArgs;
use codespan_reporting::term::{self, termcolor}; use codespan_reporting::term::{self, termcolor};
use ecow::eco_format; use ecow::eco_format;
use once_cell::sync::OnceCell;
use termcolor::WriteColor; use termcolor::WriteColor;
use typst::diag::{bail, PackageError, PackageResult, StrResult}; use typst::diag::{bail, PackageError, PackageResult, StrResult};
use typst::syntax::package::{ use typst::syntax::package::{
@ -21,6 +22,7 @@ const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages";
pub struct PackageStorage { pub struct PackageStorage {
pub package_cache_path: Option<PathBuf>, pub package_cache_path: Option<PathBuf>,
pub package_path: Option<PathBuf>, pub package_path: Option<PathBuf>,
index: OnceCell<Vec<PackageInfo>>,
} }
impl PackageStorage { impl PackageStorage {
@ -31,7 +33,11 @@ impl PackageStorage {
let package_path = args.package_path.clone().or_else(|| { let package_path = args.package_path.clone().or_else(|| {
dirs::data_dir().map(|data_dir| data_dir.join(DEFAULT_PACKAGES_SUBDIR)) dirs::data_dir().map(|data_dir| data_dir.join(DEFAULT_PACKAGES_SUBDIR))
}); });
Self { package_cache_path, package_path } Self {
package_cache_path,
package_path,
index: OnceCell::new(),
}
} }
/// Make a package available in the on-disk cache. /// Make a package available in the on-disk cache.
@ -53,7 +59,7 @@ impl PackageStorage {
// Download from network if it doesn't exist yet. // Download from network if it doesn't exist yet.
if spec.namespace == "preview" { if spec.namespace == "preview" {
download_package(spec, &dir)?; self.download_package(spec, &dir)?;
if dir.exists() { if dir.exists() {
return Ok(dir); return Ok(dir);
} }
@ -71,7 +77,7 @@ impl PackageStorage {
if spec.namespace == "preview" { if spec.namespace == "preview" {
// For `@preview`, download the package index and find the latest // For `@preview`, download the package index and find the latest
// version. // version.
download_index()? self.download_index()?
.iter() .iter()
.filter(|package| package.name == spec.name) .filter(|package| package.name == spec.name)
.map(|package| package.version) .map(|package| package.version)
@ -95,8 +101,13 @@ impl PackageStorage {
} }
} }
impl PackageStorage {
/// Download a package over the network. /// Download a package over the network.
fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()> { fn download_package(
&self,
spec: &PackageSpec,
package_dir: &Path,
) -> PackageResult<()> {
// The `@preview` namespace is the only namespace that supports on-demand // The `@preview` namespace is the only namespace that supports on-demand
// fetching. // fetching.
assert_eq!(spec.namespace, "preview"); assert_eq!(spec.namespace, "preview");
@ -108,9 +119,15 @@ fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()>
let data = match download_with_progress(&url) { let data = match download_with_progress(&url) {
Ok(data) => data, Ok(data) => data,
Err(ureq::Error::Status(404, _)) => { Err(ureq::Error::Status(404, _)) => {
return Err(PackageError::NotFound(spec.clone())) if let Ok(version) = self.determine_latest_version(&spec.versionless()) {
return Err(PackageError::VersionNotFound(spec.clone(), version));
} else {
return Err(PackageError::NotFound(spec.clone()));
}
}
Err(err) => {
return Err(PackageError::NetworkFailed(Some(eco_format!("{err}"))))
} }
Err(err) => return Err(PackageError::NetworkFailed(Some(eco_format!("{err}")))),
}; };
let decompressed = flate2::read::GzDecoder::new(data.as_slice()); let decompressed = flate2::read::GzDecoder::new(data.as_slice());
@ -121,7 +138,10 @@ fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()>
} }
/// Download the `@preview` package index. /// Download the `@preview` package index.
fn download_index() -> StrResult<Vec<PackageInfo>> { ///
/// To avoid downloading the index multiple times, the result is cached.
fn download_index(&self) -> StrResult<&Vec<PackageInfo>> {
self.index.get_or_try_init(|| {
let url = format!("{HOST}/preview/index.json"); let url = format!("{HOST}/preview/index.json");
match download(&url) { match download(&url) {
Ok(response) => response Ok(response) => response
@ -132,6 +152,8 @@ fn download_index() -> StrResult<Vec<PackageInfo>> {
} }
Err(err) => bail!("failed to fetch package index ({err})"), Err(err) => bail!("failed to fetch package index ({err})"),
} }
})
}
} }
/// Print that a package downloading is happening. /// Print that a package downloading is happening.

View File

@ -85,6 +85,15 @@ pub struct PackageSpec {
pub version: PackageVersion, pub version: PackageVersion,
} }
impl PackageSpec {
pub fn versionless(&self) -> VersionlessPackageSpec {
VersionlessPackageSpec {
namespace: self.namespace.clone(),
name: self.name.clone(),
}
}
}
impl FromStr for PackageSpec { impl FromStr for PackageSpec {
type Err = EcoString; type Err = EcoString;

View File

@ -9,7 +9,7 @@ use std::string::FromUtf8Error;
use comemo::Tracked; use comemo::Tracked;
use ecow::{eco_vec, EcoVec}; use ecow::{eco_vec, EcoVec};
use crate::syntax::package::PackageSpec; use crate::syntax::package::{PackageSpec, PackageVersion};
use crate::syntax::{Span, Spanned, SyntaxError}; use crate::syntax::{Span, Spanned, SyntaxError};
use crate::{World, WorldExt}; use crate::{World, WorldExt};
@ -503,6 +503,8 @@ pub type PackageResult<T> = Result<T, PackageError>;
pub enum PackageError { pub enum PackageError {
/// The specified package does not exist. /// The specified package does not exist.
NotFound(PackageSpec), NotFound(PackageSpec),
/// The specified package found, but the version does not exist.
VersionNotFound(PackageSpec, PackageVersion),
/// Failed to retrieve the package through the network. /// Failed to retrieve the package through the network.
NetworkFailed(Option<EcoString>), NetworkFailed(Option<EcoString>),
/// The package archive was malformed. /// The package archive was malformed.
@ -519,6 +521,13 @@ impl Display for PackageError {
Self::NotFound(spec) => { Self::NotFound(spec) => {
write!(f, "package not found (searched for {spec})",) write!(f, "package not found (searched for {spec})",)
} }
Self::VersionNotFound(spec, latest) => {
write!(
f,
"package found, but version {} does not exist (latest is {})",
spec.version, latest,
)
}
Self::NetworkFailed(Some(err)) => { Self::NetworkFailed(Some(err)) => {
write!(f, "failed to download package ({err})") write!(f, "failed to download package ({err})")
} }