From 8134a10b0d504a2b70272e8bc0d859f7a24cdc3d Mon Sep 17 00:00:00 2001 From: Daniel Macovei Date: Thu, 18 Apr 2024 17:40:12 -0500 Subject: [PATCH 1/5] support dependencies across multiple namespaces --- .github/workflows/main.yml | 58 ++++++------ Cargo.lock | 68 ++++++++------ Cargo.toml | 15 +-- crates/wac-resolver/Cargo.toml | 21 +++-- crates/wac-resolver/src/lib.rs | 75 +++++++++++++-- crates/wac-resolver/src/registry.rs | 112 ++++++++++++++-------- crates/wac-resolver/tests/registry.rs | 2 +- crates/wac-resolver/tests/support/mod.rs | 2 +- crates/wac-types/src/package.rs | 2 +- src/bin/wac.rs | 115 ++++++++++++++++++++--- src/commands/encode.rs | 34 ++++--- src/commands/parse.rs | 6 +- src/commands/resolve.rs | 15 ++- src/lib.rs | 22 ++--- 14 files changed, 374 insertions(+), 173 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4917c018..8f467d7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ on: push: branches: - main - tags: ['[0-9]*'] + tags: ["[0-9]*"] pull_request: branches: - main @@ -16,14 +16,14 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: | - rustup update stable --no-self-update - rustup default stable - shell: bash - - name: Run all tests - run: cargo test --all + - uses: actions/checkout@v3 + - name: Install Rust + run: | + rustup update stable --no-self-update + rustup default stable + shell: bash + - name: Run all tests + run: cargo test --all check: name: Check feature combinations @@ -32,29 +32,27 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: | - rustup update stable --no-self-update - rustup default stable - shell: bash - - name: Build without default features - run: cargo check --no-default-features - - name: Build the `wat` feature - run: cargo check --no-default-features --features wat - - name: Build the `wit` feature - run: cargo check --no-default-features --features wit - - name: Build the `registry` feature - run: cargo check --no-default-features --features registry - - name: Build all features - run: cargo check --all-features + - uses: actions/checkout@v3 + - name: Install Rust + run: | + rustup update stable --no-self-update + rustup default stable + shell: bash + - name: Build without default features + run: cargo check --no-default-features + - name: Build the `wat` feature + run: cargo check --no-default-features --features wat + - name: Build the `wit` feature + run: cargo check --no-default-features --features wit + - name: Build all features + run: cargo check --all-features rustfmt: name: Format source code runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: rustup update stable && rustup default stable && rustup component add rustfmt - - name: Run `cargo fmt` - run: cargo fmt -- --check + - uses: actions/checkout@v3 + - name: Install Rust + run: rustup update stable && rustup default stable && rustup component add rustfmt + - name: Run `cargo fmt` + run: cargo fmt -- --check diff --git a/Cargo.lock b/Cargo.lock index 3e12d94f..31809758 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -775,6 +775,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" @@ -1428,7 +1441,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -3036,9 +3049,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -3174,6 +3187,12 @@ dependencies = [ "lazy_static 1.4.0", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3847,6 +3866,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "dialoguer", "indexmap 2.2.6", "indicatif", "log", @@ -3862,6 +3882,7 @@ dependencies = [ "wac-parser", "wac-resolver", "wac-types", + "warg-client", "wasmprinter 0.202.0", "wat", ] @@ -3929,6 +3950,7 @@ dependencies = [ "pretty_assertions", "secrecy", "semver", + "serde_json", "tempdir", "thiserror", "tokio", @@ -3987,9 +4009,8 @@ dependencies = [ [[package]] name = "warg-api" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bf1e22e1e396b98a2181219b06d1a49a3478c1b9d87a29cd9cd819d714e6c3" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "indexmap 2.2.6", "itertools 0.12.1", @@ -4002,9 +4023,8 @@ dependencies = [ [[package]] name = "warg-client" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56cfaf9781ca2d084468bbdd8bbc1e35947bb2a19f8d3940d899852f6dd78aa" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "async-recursion", @@ -4046,9 +4066,8 @@ dependencies = [ [[package]] name = "warg-credentials" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626224ba1a00965282b669d2611654fd6292a15396ed8c850ce91684678fe19f" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "indexmap 2.2.6", @@ -4060,9 +4079,8 @@ dependencies = [ [[package]] name = "warg-crypto" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a8c47e96a7f1903931b34db9a1f0d22bcb3761a203ee6861db686daaedcb4b" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "base64", @@ -4081,9 +4099,8 @@ dependencies = [ [[package]] name = "warg-protobuf" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceed0e698efd0fab8bb747efd452156a65149eb389f7fe2a6b6b3ced4e25ab24" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "pbjson", @@ -4100,9 +4117,8 @@ dependencies = [ [[package]] name = "warg-protocol" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69be98a2e9e0aeace7cbd62184b11462d259c5e391e6208d59506c9a2d33571c" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "base64", @@ -4123,9 +4139,8 @@ dependencies = [ [[package]] name = "warg-server" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f15457ced83df5c2298f225fc83b6700e93c7bf320a2e4ef01114c0b34d7ce" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "axum", @@ -4154,9 +4169,8 @@ dependencies = [ [[package]] name = "warg-transparency" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d272b3002b9e5f6f636817089ba091e1ba7b85858e72529f96e24bc9827f530" +version = "0.5.0-dev" +source = "git+https://github.com/bytecodealliance/registry?branch=namespace-enhancements#fd8c963660644f1b13cc8547731fbbcf749e1178" dependencies = [ "anyhow", "indexmap 2.2.6", diff --git a/Cargo.toml b/Cargo.toml index cafdad15..47fca7ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,13 +37,14 @@ thiserror = { workspace = true } indexmap = { workspace = true } miette = { workspace = true, features = ["fancy"] } semver = { workspace = true } -indicatif = { workspace = true, optional = true } +indicatif = { workspace = true } +warg-client = { workspace = true } +dialoguer = "0.11.0" [features] default = ["wit"] wat = ["wac-resolver/wat"] wit = ["wac-resolver/wit"] -registry = ["wac-resolver/registry", "indicatif"] [workspace.dependencies] wac-parser = { path = "crates/wac-parser", version = "0.1.0", default-features = false } @@ -71,11 +72,11 @@ wat = "1.202.0" logos = "0.14.0" miette = "7.2.0" thiserror = "1.0.58" -warg-client = "0.4.1" -warg-protocol = "0.4.1" -warg-crypto = "0.4.1" -warg-server = "0.4.1" -warg-credentials = "0.4.1" +warg-protocol = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-crypto = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-client = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-credentials = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } +warg-server = { git = "https://github.com/bytecodealliance/registry", branch = "namespace-enhancements" } secrecy = "0.8.0" futures = "0.3.30" indicatif = "0.17.8" diff --git a/crates/wac-resolver/Cargo.toml b/crates/wac-resolver/Cargo.toml index 7ff8349e..1d379b56 100644 --- a/crates/wac-resolver/Cargo.toml +++ b/crates/wac-resolver/Cargo.toml @@ -13,7 +13,7 @@ repository = { workspace = true } wac-types = { workspace = true } wac-parser = { workspace = true } semver = { workspace = true } -thiserror = { workspace = true } +thiserror.workspace = true anyhow = { workspace = true } log = { workspace = true } wit-component = { workspace = true } @@ -21,13 +21,14 @@ indexmap = { workspace = true } wat = { workspace = true, optional = true } wit-parser = { workspace = true, optional = true } miette = { workspace = true } -warg-client = { workspace = true, optional = true } -warg-protocol = { workspace = true, optional = true } -warg-crypto = { workspace = true, optional = true } -warg-credentials = { workspace = true, optional = true } -secrecy = { workspace = true, optional = true } -tokio = { workspace = true, optional = true } -futures = { workspace = true, optional = true } +warg-client = { workspace = true } +warg-protocol = { workspace = true } +warg-crypto = { workspace = true } +warg-credentials = { workspace = true } +secrecy = { workspace = true} +tokio = { workspace = true} +futures = { workspace = true} +serde_json = "1.0.116" [dev-dependencies] wac-graph = { workspace = true } @@ -38,7 +39,7 @@ tokio-util = "0.7.10" tempdir = "0.3.7" [features] -default = ["registry"] +default = [] wat = ["dep:wat"] wit = ["wit-parser"] -registry = ["warg-client", "warg-protocol", "warg-crypto", "warg-credentials", "secrecy", "tokio", "futures"] + diff --git a/crates/wac-resolver/src/lib.rs b/crates/wac-resolver/src/lib.rs index 94763518..a768a7dd 100644 --- a/crates/wac-resolver/src/lib.rs +++ b/crates/wac-resolver/src/lib.rs @@ -1,19 +1,21 @@ //! Modules for package resolvers. +use crate::Error as WacResolutionError; +use anyhow::anyhow; use indexmap::IndexMap; use miette::{Diagnostic, SourceSpan}; -use wac_parser::Document; +use thiserror::Error; +use wac_parser::{resolution::Error as WacError, Document}; mod fs; -#[cfg(feature = "registry")] mod registry; mod visitor; pub use fs::*; -#[cfg(feature = "registry")] pub use registry::*; pub use visitor::*; use wac_types::BorrowedPackageKey; +use warg_client::ClientError; /// Represents a package resolution error. #[derive(thiserror::Error, Diagnostic, Debug)] @@ -29,7 +31,6 @@ pub enum Error { span: SourceSpan, }, /// An unknown package version was encountered. - #[cfg(feature = "registry")] #[error("version {version} of package `{name}` does not exist")] UnknownPackageVersion { /// The name of the package. @@ -57,7 +58,6 @@ pub enum Error { span: SourceSpan, }, /// The requested package version has been yanked. - #[cfg(feature = "registry")] #[error("version {version} of package `{name}` has been yanked")] PackageVersionYanked { /// The name of the package. @@ -69,7 +69,6 @@ pub enum Error { span: SourceSpan, }, /// A package log was empty. - #[cfg(feature = "registry")] #[error("a release for package `{name}` has not yet been published")] PackageLogEmpty { /// The name of the package. @@ -79,7 +78,6 @@ pub enum Error { span: SourceSpan, }, /// A failure occurred while updating logs from the registry. - #[cfg(feature = "registry")] #[error("failed to update registry logs")] RegistryUpdateFailure { /// The underlying error. @@ -87,7 +85,6 @@ pub enum Error { source: anyhow::Error, }, /// A failure occurred while downloading content from the registry. - #[cfg(feature = "registry")] #[error("failed to download content from the registry")] RegistryDownloadFailure { /// The underlying error. @@ -95,7 +92,6 @@ pub enum Error { source: anyhow::Error, }, /// A failure occurred while reading content from registry storage. - #[cfg(feature = "registry")] #[error("failed to read content path `{path}`", path = .path.display())] RegistryContentFailure { /// The path to the content. @@ -149,3 +145,64 @@ pub fn packages<'a>( visitor.visit(document)?; Ok(keys) } + +#[derive(Debug, Error)] +/// Error for WAC commands +pub enum CommandError { + /// General errors + #[error("Error: `{0}`")] + General(anyhow::Error), + /// Serde errors + #[error("Error: `{0}`")] + Serde(serde_json::Error), + /// Wac resolution errors + #[error("Error: `{0}`")] + WacResolution(WacResolutionError), + /// Wac errors + #[error("Error: `{0}`")] + Wac(WacError), + /// Client Error + #[error("Warg Client Error: {0}")] + WargClient(ClientError), + /// Client Error With Hint + #[error("Warg Client Error: {0}")] + WargHint(ClientError), +} + +/// Error from warg client +pub struct WargClientError(pub ClientError); + +impl std::fmt::Debug for WargClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("WargClientError").field(&self.0).finish() + } +} + +impl From for CommandError { + fn from(value: anyhow::Error) -> Self { + CommandError::General(value) + } +} + +impl From for CommandError { + fn from(value: WacResolutionError) -> Self { + CommandError::General(anyhow!(value)) + } +} + +impl From for CommandError { + fn from(value: serde_json::Error) -> Self { + CommandError::Serde(value) + } +} + +impl From for CommandError { + fn from(value: WargClientError) -> Self { + match &value.0 { + ClientError::PackageDoesNotExistWithHint { .. } => { + CommandError::WargHint(value.0.into()) + } + _ => CommandError::WargClient(value.0.into()), + } + } +} diff --git a/crates/wac-resolver/src/registry.rs b/crates/wac-resolver/src/registry.rs index 6bb20d8a..c21254f2 100644 --- a/crates/wac-resolver/src/registry.rs +++ b/crates/wac-resolver/src/registry.rs @@ -1,3 +1,5 @@ +use crate::CommandError; + use super::Error; use anyhow::Result; use futures::{stream::FuturesUnordered, StreamExt}; @@ -9,10 +11,11 @@ use std::{fs, path::Path, sync::Arc}; use wac_types::BorrowedPackageKey; use warg_client::{ storage::{ContentStorage, RegistryStorage}, - Client, ClientError, Config, FileSystemClient, RegistryUrl, + Client, ClientError, Config, FileSystemClient, RegistryUrl, Retry, }; use warg_credentials::keyring::get_auth_token; use warg_crypto::hash::AnyHash; +use warg_protocol::registry::PackageName; /// Implemented by progress bars. /// @@ -45,14 +48,18 @@ impl RegistryPackageResolver { /// client configuration file. /// /// If `url` is `None`, the default URL will be used. - pub fn new(url: Option<&str>, bar: Option>) -> Result { + pub async fn new( + url: Option<&str>, + bar: Option>, + retry: Option, + ) -> Result { let config = Config::from_default_file()?.unwrap_or_default(); + let client = Client::new_with_config(url, &config, Self::auth_token(&config, url)?).await?; + if let Some(retry) = retry { + retry.store_namespace(&client).await?; + } Ok(Self { - client: Arc::new(Client::new_with_config( - url, - &config, - Self::auth_token(&config, url)?, - )?), + client: Arc::new(client), bar, }) } @@ -60,17 +67,15 @@ impl RegistryPackageResolver { /// Creates a new registry package resolver with the given configuration. /// /// If `url` is `None`, the default URL will be used. - pub fn new_with_config( + pub async fn new_with_config( url: Option<&str>, config: &Config, bar: Option>, ) -> Result { Ok(Self { - client: Arc::new(Client::new_with_config( - url, - config, - Self::auth_token(config, url)?, - )?), + client: Arc::new( + Client::new_with_config(url, config, Self::auth_token(config, url)?).await?, + ), bar, }) } @@ -81,7 +86,7 @@ impl RegistryPackageResolver { pub async fn resolve<'a>( &self, keys: &IndexMap, SourceSpan>, - ) -> Result, Vec>, Error> { + ) -> Result, Vec>, CommandError> { // Start by fetching any required package logs self.fetch(keys).await?; @@ -89,7 +94,6 @@ impl RegistryPackageResolver { // is missing from local storage. let mut packages = IndexMap::new(); let missing = self.find_missing_content(keys, &mut packages).await?; - if !missing.is_empty() { if let Some(bar) = self.bar.as_ref() { bar.init(missing.len()); @@ -101,7 +105,7 @@ impl RegistryPackageResolver { let client = self.client.clone(); let hash = hash.clone(); tasks.push(tokio::spawn(async move { - Ok((index, client.download_content(&hash).await?)) + Ok((index, client.download_content(None, &hash).await?)) })); } @@ -145,12 +149,12 @@ impl RegistryPackageResolver { async fn fetch<'a>( &self, keys: &IndexMap, SourceSpan>, - ) -> Result<(), Error> { + ) -> Result<(), CommandError> { // First check if we already have the packages in client storage. // If not, we'll fetch the logs from the registry. let mut fetch = IndexMap::new(); for (key, span) in keys { - let id = + let id: PackageName = key.name .parse() .map_err(|e: anyhow::Error| Error::PackageResolutionFailure { @@ -164,7 +168,14 @@ impl RegistryPackageResolver { if let Some(info) = self .client .registry() - .load_package(self.client.get_warg_registry(), &id) + .load_package( + self.client + .get_warg_registry(id.namespace()) + .await + .map_err(|e| CommandError::WargClient(e))? + .as_ref(), + &id, + ) .await .map_err(|e| Error::PackageResolutionFailure { name: key.name.to_string(), @@ -217,15 +228,35 @@ impl RegistryPackageResolver { bar.finish(); } } - Err(ClientError::PackageDoesNotExist { name }) => { - return Err(Error::PackageDoesNotExist { - name: name.to_string(), - span: *fetch[&name], - }) - } - Err(e) => { - return Err(Error::RegistryUpdateFailure { source: e.into() }); - } + Err(e) => match &e { + ClientError::PackageDoesNotExistWithHint { .. } => { + return Err(CommandError::WargHint(e)); + } + ClientError::NoHomeRegistryUrl { .. } + | ClientError::ResettingRegistryLocalStateFailed { .. } + | ClientError::ClearContentCacheFailed { .. } + | ClientError::InvalidCheckpointSignature { .. } + | ClientError::InvalidCheckpointKeyId { .. } + | ClientError::NoOperatorRecords { .. } + | ClientError::OperatorValidationFailed { .. } + | ClientError::CannotInitializePackage { .. } + | ClientError::MustInitializePackage { .. } + | ClientError::NotPublishing { .. } + | ClientError::NothingToPublish { .. } + | ClientError::PackageDoesNotExist { .. } + | ClientError::PackageVersionDoesNotExist { .. } + | ClientError::PackageValidationFailed { .. } + | ClientError::ContentNotFound { .. } + | ClientError::IncorrectContent { .. } + | ClientError::PackageLogEmpty { .. } + | ClientError::PublishRejected { .. } + | ClientError::PackageMissingContent { .. } + | ClientError::CheckpointLogLengthRewind { .. } + | ClientError::CheckpointChangedLogRootOrMapRoot { .. } + | ClientError::SimilarNamespace { .. } + | ClientError::Api(_) + | ClientError::Other(_) => return Err(CommandError::WargClient(e)), + }, } } @@ -236,11 +267,11 @@ impl RegistryPackageResolver { &self, keys: &IndexMap, SourceSpan>, packages: &mut IndexMap, Vec>, - ) -> Result>)>, Error> { + ) -> Result>)>, CommandError> { let mut downloads: IndexMap)> = IndexMap::new(); for (key, span) in keys { - let id = + let id: PackageName = key.name .parse() .map_err(|e: anyhow::Error| Error::PackageResolutionFailure { @@ -252,7 +283,14 @@ impl RegistryPackageResolver { let info = self .client .registry() - .load_package(self.client.get_warg_registry(), &id) + .load_package( + self.client + .get_warg_registry(id.namespace()) + .await + .map_err(|e| CommandError::WargClient(e.into()))? + .as_ref(), + &id, + ) .await .map_err(|e| Error::PackageResolutionFailure { name: key.name.to_string(), @@ -277,24 +315,24 @@ impl RegistryPackageResolver { let release = match info.state.find_latest_release(&req) { Some(release) if !release.yanked() => release, Some(release) => { - return Err(Error::PackageVersionYanked { + return Err(CommandError::WacResolution(Error::PackageVersionYanked { name: key.name.to_string(), version: release.version.clone(), span: *span, - }); + })) } None => { if let Some(version) = key.version { - return Err(Error::UnknownPackageVersion { + return Err(CommandError::WacResolution(Error::UnknownPackageVersion { name: key.name.to_string(), version: version.clone(), span: *span, - }); + })); } else { - return Err(Error::PackageLogEmpty { + return Err(CommandError::WacResolution(Error::PackageLogEmpty { name: key.name.to_string(), span: *span, - }); + })); } } }; diff --git a/crates/wac-resolver/tests/registry.rs b/crates/wac-resolver/tests/registry.rs index 2b124a39..04a1c6d3 100644 --- a/crates/wac-resolver/tests/registry.rs +++ b/crates/wac-resolver/tests/registry.rs @@ -61,7 +61,7 @@ export i2.foo as "bar"; "#, )?; - let resolver = RegistryPackageResolver::new_with_config(None, &config, None)?; + let resolver = RegistryPackageResolver::new_with_config(None, &config, None).await?; let packages = resolver.resolve(&packages(&document)?).await?; let resolution = document.resolve(packages)?; diff --git a/crates/wac-resolver/tests/support/mod.rs b/crates/wac-resolver/tests/support/mod.rs index afe77c59..a6f79618 100644 --- a/crates/wac-resolver/tests/support/mod.rs +++ b/crates/wac-resolver/tests/support/mod.rs @@ -64,7 +64,7 @@ pub async fn publish( content: Vec, init: bool, ) -> Result<()> { - let client = FileSystemClient::new_with_config(None, config, None)?; + let client = FileSystemClient::new_with_config(None, config, None).await?; let digest = client .content() diff --git a/crates/wac-types/src/package.rs b/crates/wac-types/src/package.rs index cf249dd5..81a56c48 100644 --- a/crates/wac-types/src/package.rs +++ b/crates/wac-types/src/package.rs @@ -56,7 +56,7 @@ impl fmt::Display for PackageKey { } /// A borrowed package key. -#[derive(Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct BorrowedPackageKey<'a> { /// The package name. pub name: &'a str, diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 73abeaa2..3b8c7c92 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -1,7 +1,10 @@ use anyhow::Result; use clap::Parser; +use dialoguer::{theme::ColorfulTheme, Confirm}; use owo_colors::{OwoColorize, Stream, Style}; use wac_cli::commands::{EncodeCommand, ParseCommand, ResolveCommand}; +use wac_resolver::CommandError; +use warg_client::{with_interactive_retry, ClientError, Retry}; fn version() -> &'static str { option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) @@ -26,19 +29,103 @@ enum Wac { async fn main() -> Result<()> { pretty_env_logger::init(); - if let Err(e) = match Wac::parse() { - Wac::Parse(cmd) => cmd.exec().await, - Wac::Resolve(cmd) => cmd.exec().await, - Wac::Encode(cmd) => cmd.exec().await, - } { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); - } - + with_interactive_retry(|retry: Option| async { + if let Err(e) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => cmd.exec(retry).await, + Wac::Encode(cmd) => cmd.exec(retry).await, + } { + match e { + CommandError::General(e) => { + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + CommandError::Serde(e) => { + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + CommandError::Wac(e) => { + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + CommandError::WacResolution(e) => { + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + }, + CommandError::WargClient(e) => { + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + CommandError::WargHint(e) => { + if let ClientError::PackageDoesNotExistWithHint { name, hint } = e { + let hint_reg = hint.to_str().unwrap(); + let mut terms = hint_reg.split('='); + let namespace = terms.next(); + let registry = terms.next(); + if let (Some(namespace), Some(registry)) = (namespace, registry) { + let prompt = format!( + "The package `{}`, does not exist in the registry you're using.\nHowever, the package namespace `{namespace}` does exist in the registry at {registry}.\nWould you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", + name.name() + ); + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + if let Err(e) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await, + Wac::Encode(cmd) => cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } { + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } + } + } + } + } + } + } + Ok(()) + }) + .await?; Ok(()) } diff --git a/src/commands/encode.rs b/src/commands/encode.rs index 8a157a5f..786a2737 100644 --- a/src/commands/encode.rs +++ b/src/commands/encode.rs @@ -1,5 +1,5 @@ use crate::{fmt_err, PackageResolver}; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Args; use std::{ fs, @@ -8,6 +8,8 @@ use std::{ }; use wac_graph::EncodeOptions; use wac_parser::Document; +use wac_resolver::CommandError; +use warg_client::Retry; use wasmprinter::print_bytes; fn parse(s: &str) -> Result<(T, U)> @@ -60,7 +62,6 @@ pub struct EncodeCommand { pub output: Option, /// The URL of the registry to use. - #[cfg(feature = "registry")] #[clap(long, value_name = "URL")] pub registry: Option, @@ -71,7 +72,7 @@ pub struct EncodeCommand { impl EncodeCommand { /// Executes the command. - pub async fn exec(self) -> Result<()> { + pub async fn exec(self, retry: Option) -> Result<(), CommandError> { log::debug!("executing encode command"); let contents = fs::read_to_string(&self.path) @@ -82,28 +83,31 @@ impl EncodeCommand { let resolver = PackageResolver::new( self.deps_dir, self.deps.into_iter().collect(), - #[cfg(feature = "registry")] self.registry.as_deref(), - )?; + retry, + ) + .await?; - let packages = resolver - .resolve(&document) - .await - .map_err(|e| fmt_err(e, &self.path, &contents))?; + let packages = resolver.resolve(&document).await?; let resolution = document .resolve(packages) .map_err(|e| fmt_err(e, &self.path, &contents))?; if !self.wat && self.output.is_none() && std::io::stdout().is_terminal() { - bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead"); + return Err(anyhow!( + "cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead" + ) + .into()); } - let mut bytes = resolution.encode(EncodeOptions { - define_components: !self.import_dependencies, - validate: !self.no_validate, - ..Default::default() - })?; + let mut bytes = resolution + .encode(EncodeOptions { + define_components: !self.import_dependencies, + validate: !self.no_validate, + ..Default::default() + }) + .map_err(|e| CommandError::Wac(e))?; if self.wat { bytes = print_bytes(&bytes) .context("failed to convert binary wasm output to text")? diff --git a/src/commands/parse.rs b/src/commands/parse.rs index f68ad102..76e959ea 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -3,6 +3,7 @@ use anyhow::{Context, Result}; use clap::Args; use std::{fs, path::PathBuf}; use wac_parser::Document; +use wac_resolver::CommandError; /// Parses a WAC source file into a JSON AST representation. #[derive(Args)] @@ -15,7 +16,7 @@ pub struct ParseCommand { impl ParseCommand { /// Executes the command. - pub async fn exec(self) -> Result<()> { + pub async fn exec(self) -> Result<(), CommandError> { log::debug!("executing parse command"); let contents = fs::read_to_string(&self.path) @@ -23,7 +24,8 @@ impl ParseCommand { let document = Document::parse(&contents).map_err(|e| fmt_err(e, &self.path, &contents))?; - serde_json::to_writer_pretty(std::io::stdout(), &document)?; + serde_json::to_writer_pretty(std::io::stdout(), &document) + .map_err(|e| CommandError::Serde(e))?; println!(); Ok(()) diff --git a/src/commands/resolve.rs b/src/commands/resolve.rs index 92b9e13d..a18585ea 100644 --- a/src/commands/resolve.rs +++ b/src/commands/resolve.rs @@ -3,6 +3,8 @@ use anyhow::{Context, Result}; use clap::Args; use std::{fs, path::PathBuf}; use wac_parser::Document; +use wac_resolver::CommandError; +use warg_client::Retry; fn parse(s: &str) -> Result<(T, U)> where @@ -32,7 +34,6 @@ pub struct ResolveCommand { pub deps: Vec<(String, PathBuf)>, /// The URL of the registry to use. - #[cfg(feature = "registry")] #[clap(long, value_name = "URL")] pub registry: Option, @@ -43,7 +44,7 @@ pub struct ResolveCommand { impl ResolveCommand { /// Executes the command. - pub async fn exec(self) -> Result<()> { + pub async fn exec(self, retry: Option) -> Result<(), CommandError> { log::debug!("executing resolve command"); let contents = fs::read_to_string(&self.path) @@ -54,14 +55,12 @@ impl ResolveCommand { let resolver = PackageResolver::new( self.deps_dir, self.deps.into_iter().collect(), - #[cfg(feature = "registry")] self.registry.as_deref(), - )?; + retry, + ) + .await?; - let packages = resolver - .resolve(&document) - .await - .map_err(|e| fmt_err(e, &self.path, &contents))?; + let packages = resolver.resolve(&document).await?; let resolution = document .resolve(packages) diff --git a/src/lib.rs b/src/lib.rs index e94271c7..43d1fe24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,12 +11,12 @@ use std::{ path::{Path, PathBuf}, }; use wac_parser::Document; -use wac_resolver::{packages, Error, FileSystemPackageResolver}; +use wac_resolver::{packages, CommandError, Error, FileSystemPackageResolver}; use wac_types::BorrowedPackageKey; +use warg_client::Retry; pub mod commands; -#[cfg(feature = "registry")] mod progress; fn fmt_err(e: impl Into, path: &Path, source: &str) -> anyhow::Error { @@ -46,24 +46,25 @@ fn fmt_err(e: impl Into, path: &Path, source: &str) -> anyhow::Error { /// If it cannot find a matching package, it will check the registry. pub struct PackageResolver { fs: FileSystemPackageResolver, - #[cfg(feature = "registry")] registry: wac_resolver::RegistryPackageResolver, } impl PackageResolver { /// Creates a new package resolver. - pub fn new( + pub async fn new( dir: impl Into, overrides: HashMap, - #[cfg(feature = "registry")] registry: Option<&str>, + registry: Option<&str>, + retry: Option, ) -> Result { Ok(Self { fs: FileSystemPackageResolver::new(dir, overrides, false), - #[cfg(feature = "registry")] registry: wac_resolver::RegistryPackageResolver::new( registry, Some(Box::new(progress::ProgressBar::new())), - )?, + retry, + ) + .await?, }) } @@ -71,7 +72,7 @@ impl PackageResolver { pub async fn resolve<'a>( &self, document: &'a Document<'a>, - ) -> Result, Vec>, Error> { + ) -> Result, Vec>, CommandError> { let mut keys = packages(document)?; // Next, we resolve as many of the packages from the file system as possible @@ -82,7 +83,6 @@ impl PackageResolver { // Next resolve the remaining packages from the registry // The registry resolver will error on missing package - #[cfg(feature = "registry")] if !keys.is_empty() { let reg_packages = self.registry.resolve(&keys).await?; keys.retain(|key, _| !reg_packages.contains_key(key)); @@ -91,10 +91,10 @@ impl PackageResolver { // At this point keys should be empty, otherwise we have an unknown package if let Some((key, span)) = keys.first() { - return Err(Error::UnknownPackage { + return Err(CommandError::WacResolution(Error::UnknownPackage { name: key.name.to_string(), span: *span, - }); + })); } Ok(packages) From aa9d36491831d6f1a9154d2ea0226ead47699457 Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Fri, 19 Apr 2024 11:53:55 -0500 Subject: [PATCH 2/5] fixes --- crates/wac-resolver/src/lib.rs | 6 +- crates/wac-resolver/src/registry.rs | 4 +- src/bin/wac.rs | 118 ++++++++++------------------ src/commands/encode.rs | 2 +- src/commands/parse.rs | 3 +- 5 files changed, 46 insertions(+), 87 deletions(-) diff --git a/crates/wac-resolver/src/lib.rs b/crates/wac-resolver/src/lib.rs index a768a7dd..db5bae7b 100644 --- a/crates/wac-resolver/src/lib.rs +++ b/crates/wac-resolver/src/lib.rs @@ -199,10 +199,8 @@ impl From for CommandError { impl From for CommandError { fn from(value: WargClientError) -> Self { match &value.0 { - ClientError::PackageDoesNotExistWithHint { .. } => { - CommandError::WargHint(value.0.into()) - } - _ => CommandError::WargClient(value.0.into()), + ClientError::PackageDoesNotExistWithHint { .. } => CommandError::WargHint(value.0), + _ => CommandError::WargClient(value.0), } } } diff --git a/crates/wac-resolver/src/registry.rs b/crates/wac-resolver/src/registry.rs index c21254f2..346a60d4 100644 --- a/crates/wac-resolver/src/registry.rs +++ b/crates/wac-resolver/src/registry.rs @@ -172,7 +172,7 @@ impl RegistryPackageResolver { self.client .get_warg_registry(id.namespace()) .await - .map_err(|e| CommandError::WargClient(e))? + .map_err(CommandError::WargClient)? .as_ref(), &id, ) @@ -287,7 +287,7 @@ impl RegistryPackageResolver { self.client .get_warg_registry(id.namespace()) .await - .map_err(|e| CommandError::WargClient(e.into()))? + .map_err(CommandError::WargClient)? .as_ref(), &id, ) diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 3b8c7c92..290bf363 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -30,99 +30,61 @@ async fn main() -> Result<()> { pretty_env_logger::init(); with_interactive_retry(|retry: Option| async { - if let Err(e) = match Wac::parse() { + if let Err(err) = match Wac::parse() { Wac::Parse(cmd) => cmd.exec().await, Wac::Resolve(cmd) => cmd.exec(retry).await, Wac::Encode(cmd) => cmd.exec(retry).await, } { - match e { - CommandError::General(e) => { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) + if let CommandError::WargHint(ClientError::PackageDoesNotExistWithHint { name, hint }) = &err { + if let Some((namespace, registry)) = hint.to_str().unwrap().split_once('=') { + let prompt = format!( + "The package `{}`, does not exist in the registry you're using. + However, the package namespace `{namespace}` does exist in the registry at {registry}. + Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", + name.name(), ); - std::process::exit(1); - } - CommandError::Serde(e) => { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); - } - CommandError::Wac(e) => { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); - } - CommandError::WacResolution(e) => { - eprintln!( + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + if let Err(e) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + }, + Wac::Encode(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + }, + } + { + eprintln!( "{error}: {e:?}", error = "error".if_supports_color(Stream::Stderr, |text| { text.style(Style::new().red().bold()) }) ); std::process::exit(1); - }, - CommandError::WargClient(e) => { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); - } - CommandError::WargHint(e) => { - if let ClientError::PackageDoesNotExistWithHint { name, hint } = e { - let hint_reg = hint.to_str().unwrap(); - let mut terms = hint_reg.split('='); - let namespace = terms.next(); - let registry = terms.next(); - if let (Some(namespace), Some(registry)) = (namespace, registry) { - let prompt = format!( - "The package `{}`, does not exist in the registry you're using.\nHowever, the package namespace `{namespace}` does exist in the registry at {registry}.\nWould you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", - name.name() - ); - if Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .interact() - .unwrap() - { - if let Err(e) = match Wac::parse() { - Wac::Parse(cmd) => cmd.exec().await, - Wac::Resolve(cmd) => cmd.exec(Some(Retry::new( - namespace.to_string(), - registry.to_string(), - ))) - .await, - Wac::Encode(cmd) => cmd.exec(Some(Retry::new( - namespace.to_string(), - registry.to_string(), - ))) - .await - } { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); } } - } - } } } + + eprintln!( + "{error}: {err:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); } Ok(()) }) diff --git a/src/commands/encode.rs b/src/commands/encode.rs index 786a2737..e8e0e612 100644 --- a/src/commands/encode.rs +++ b/src/commands/encode.rs @@ -107,7 +107,7 @@ impl EncodeCommand { validate: !self.no_validate, ..Default::default() }) - .map_err(|e| CommandError::Wac(e))?; + .map_err(CommandError::Wac)?; if self.wat { bytes = print_bytes(&bytes) .context("failed to convert binary wasm output to text")? diff --git a/src/commands/parse.rs b/src/commands/parse.rs index 76e959ea..b2ffb6f2 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -24,8 +24,7 @@ impl ParseCommand { let document = Document::parse(&contents).map_err(|e| fmt_err(e, &self.path, &contents))?; - serde_json::to_writer_pretty(std::io::stdout(), &document) - .map_err(|e| CommandError::Serde(e))?; + serde_json::to_writer_pretty(std::io::stdout(), &document).map_err(CommandError::Serde)?; println!(); Ok(()) From bc3927042953a199f5027c1d55d69a0bd06827ce Mon Sep 17 00:00:00 2001 From: Daniel Macovei Date: Fri, 19 Apr 2024 15:45:28 -0500 Subject: [PATCH 3/5] address comments --- crates/wac-resolver/Cargo.toml | 8 +-- crates/wac-resolver/src/lib.rs | 52 ++++------------- crates/wac-resolver/src/registry.rs | 91 +++++++++++------------------ src/bin/wac.rs | 18 +++--- src/commands/encode.rs | 3 +- 5 files changed, 60 insertions(+), 112 deletions(-) diff --git a/crates/wac-resolver/Cargo.toml b/crates/wac-resolver/Cargo.toml index 1d379b56..0c816a56 100644 --- a/crates/wac-resolver/Cargo.toml +++ b/crates/wac-resolver/Cargo.toml @@ -13,7 +13,7 @@ repository = { workspace = true } wac-types = { workspace = true } wac-parser = { workspace = true } semver = { workspace = true } -thiserror.workspace = true +thiserror = { workspace = true } anyhow = { workspace = true } log = { workspace = true } wit-component = { workspace = true } @@ -25,9 +25,9 @@ warg-client = { workspace = true } warg-protocol = { workspace = true } warg-crypto = { workspace = true } warg-credentials = { workspace = true } -secrecy = { workspace = true} -tokio = { workspace = true} -futures = { workspace = true} +secrecy = { workspace = true } +tokio = { workspace = true } +futures = { workspace = true } serde_json = "1.0.116" [dev-dependencies] diff --git a/crates/wac-resolver/src/lib.rs b/crates/wac-resolver/src/lib.rs index db5bae7b..6124d5ae 100644 --- a/crates/wac-resolver/src/lib.rs +++ b/crates/wac-resolver/src/lib.rs @@ -1,7 +1,6 @@ //! Modules for package resolvers. use crate::Error as WacResolutionError; -use anyhow::anyhow; use indexmap::IndexMap; use miette::{Diagnostic, SourceSpan}; use thiserror::Error; @@ -150,23 +149,23 @@ pub fn packages<'a>( /// Error for WAC commands pub enum CommandError { /// General errors - #[error("Error: `{0}`")] - General(anyhow::Error), + #[error(transparent)] + General(#[from] anyhow::Error), /// Serde errors - #[error("Error: `{0}`")] - Serde(serde_json::Error), + #[error(transparent)] + Serde(#[from] serde_json::Error), /// Wac resolution errors - #[error("Error: `{0}`")] - WacResolution(WacResolutionError), + #[error(transparent)] + WacResolution(#[from] WacResolutionError), /// Wac errors - #[error("Error: `{0}`")] - Wac(WacError), + #[error(transparent)] + Wac(#[from] WacError), /// Client Error - #[error("Warg Client Error: {0}")] - WargClient(ClientError), + #[error("warg client error: {0}")] + WargClient(String, ClientError), /// Client Error With Hint - #[error("Warg Client Error: {0}")] - WargHint(ClientError), + #[error("warg client error with hint: {0}")] + WargHint(String, ClientError), } /// Error from warg client @@ -177,30 +176,3 @@ impl std::fmt::Debug for WargClientError { f.debug_tuple("WargClientError").field(&self.0).finish() } } - -impl From for CommandError { - fn from(value: anyhow::Error) -> Self { - CommandError::General(value) - } -} - -impl From for CommandError { - fn from(value: WacResolutionError) -> Self { - CommandError::General(anyhow!(value)) - } -} - -impl From for CommandError { - fn from(value: serde_json::Error) -> Self { - CommandError::Serde(value) - } -} - -impl From for CommandError { - fn from(value: WargClientError) -> Self { - match &value.0 { - ClientError::PackageDoesNotExistWithHint { .. } => CommandError::WargHint(value.0), - _ => CommandError::WargClient(value.0), - } - } -} diff --git a/crates/wac-resolver/src/registry.rs b/crates/wac-resolver/src/registry.rs index 346a60d4..c81791ce 100644 --- a/crates/wac-resolver/src/registry.rs +++ b/crates/wac-resolver/src/registry.rs @@ -1,6 +1,4 @@ -use crate::CommandError; - -use super::Error; +use super::{CommandError, Error}; use anyhow::Result; use futures::{stream::FuturesUnordered, StreamExt}; use indexmap::{IndexMap, IndexSet}; @@ -51,6 +49,10 @@ impl RegistryPackageResolver { pub async fn new( url: Option<&str>, bar: Option>, + // Indicates that this was created after an interactive retry. + // This occurs when the CLI attempted to leverage a package from the wrong registry. + // The server responds with a hint as to which registry is used and the client maps that package namespace to the domain provided. + // After this, the CLI command is retried retry: Option, ) -> Result { let config = Config::from_default_file()?.unwrap_or_default(); @@ -154,14 +156,7 @@ impl RegistryPackageResolver { // If not, we'll fetch the logs from the registry. let mut fetch = IndexMap::new(); for (key, span) in keys { - let id: PackageName = - key.name - .parse() - .map_err(|e: anyhow::Error| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })?; + let id: PackageName = key.name.parse()?; // Load the package from client storage to see if we already // have a matching version present. @@ -172,16 +167,17 @@ impl RegistryPackageResolver { self.client .get_warg_registry(id.namespace()) .await - .map_err(CommandError::WargClient)? + .map_err(|e| { + CommandError::WargClient( + "failed getting warg registry domain from package namespace" + .to_string(), + e, + ) + })? .as_ref(), &id, ) - .await - .map_err(|e| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })? + .await? { if let Some(version) = key.version { let req = VersionReq { @@ -230,32 +226,17 @@ impl RegistryPackageResolver { } Err(e) => match &e { ClientError::PackageDoesNotExistWithHint { .. } => { - return Err(CommandError::WargHint(e)); + return Err(CommandError::WargHint( + "failed updating warg logs".to_string(), + e, + )); + } + _ => { + return Err(CommandError::WargClient( + "failed updating warg logs".to_string(), + e, + )) } - ClientError::NoHomeRegistryUrl { .. } - | ClientError::ResettingRegistryLocalStateFailed { .. } - | ClientError::ClearContentCacheFailed { .. } - | ClientError::InvalidCheckpointSignature { .. } - | ClientError::InvalidCheckpointKeyId { .. } - | ClientError::NoOperatorRecords { .. } - | ClientError::OperatorValidationFailed { .. } - | ClientError::CannotInitializePackage { .. } - | ClientError::MustInitializePackage { .. } - | ClientError::NotPublishing { .. } - | ClientError::NothingToPublish { .. } - | ClientError::PackageDoesNotExist { .. } - | ClientError::PackageVersionDoesNotExist { .. } - | ClientError::PackageValidationFailed { .. } - | ClientError::ContentNotFound { .. } - | ClientError::IncorrectContent { .. } - | ClientError::PackageLogEmpty { .. } - | ClientError::PublishRejected { .. } - | ClientError::PackageMissingContent { .. } - | ClientError::CheckpointLogLengthRewind { .. } - | ClientError::CheckpointChangedLogRootOrMapRoot { .. } - | ClientError::SimilarNamespace { .. } - | ClientError::Api(_) - | ClientError::Other(_) => return Err(CommandError::WargClient(e)), }, } } @@ -271,14 +252,7 @@ impl RegistryPackageResolver { let mut downloads: IndexMap)> = IndexMap::new(); for (key, span) in keys { - let id: PackageName = - key.name - .parse() - .map_err(|e: anyhow::Error| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })?; + let id: PackageName = key.name.parse()?; let info = self .client @@ -287,16 +261,17 @@ impl RegistryPackageResolver { self.client .get_warg_registry(id.namespace()) .await - .map_err(CommandError::WargClient)? + .map_err(|e| { + CommandError::WargClient( + "failed getting warg resistry domain from package namespace" + .to_string(), + e, + ) + })? .as_ref(), &id, ) - .await - .map_err(|e| Error::PackageResolutionFailure { - name: key.name.to_string(), - span: *span, - source: e, - })? + .await? .expect("package log should be present after fetching"); let req = match key.version { diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 290bf363..de9e5210 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -35,7 +35,7 @@ async fn main() -> Result<()> { Wac::Resolve(cmd) => cmd.exec(retry).await, Wac::Encode(cmd) => cmd.exec(retry).await, } { - if let CommandError::WargHint(ClientError::PackageDoesNotExistWithHint { name, hint }) = &err { + if let CommandError::WargHint(_, ClientError::PackageDoesNotExistWithHint { name, hint }) = &err { if let Some((namespace, registry)) = hint.to_str().unwrap().split_once('=') { let prompt = format!( "The package `{}`, does not exist in the registry you're using. @@ -66,13 +66,15 @@ async fn main() -> Result<()> { }, } { - eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } else { + return Ok(()); } } } diff --git a/src/commands/encode.rs b/src/commands/encode.rs index e8e0e612..6a8b0c1c 100644 --- a/src/commands/encode.rs +++ b/src/commands/encode.rs @@ -97,8 +97,7 @@ impl EncodeCommand { if !self.wat && self.output.is_none() && std::io::stdout().is_terminal() { return Err(anyhow!( "cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead" - ) - .into()); + ))?; } let mut bytes = resolution From ae2e50d33cfae985bcf5b9e90c8426d4f9e8ce5d Mon Sep 17 00:00:00 2001 From: Daniel Macovei Date: Mon, 22 Apr 2024 12:28:19 -0500 Subject: [PATCH 4/5] better error handling --- crates/wac-resolver/src/lib.rs | 4 +- crates/wac-resolver/src/registry.rs | 9 +- src/bin/wac.rs | 140 +++++++++++++++++++--------- 3 files changed, 101 insertions(+), 52 deletions(-) diff --git a/crates/wac-resolver/src/lib.rs b/crates/wac-resolver/src/lib.rs index 6124d5ae..443b61eb 100644 --- a/crates/wac-resolver/src/lib.rs +++ b/crates/wac-resolver/src/lib.rs @@ -161,10 +161,10 @@ pub enum CommandError { #[error(transparent)] Wac(#[from] WacError), /// Client Error - #[error("warg client error: {0}")] + #[error("warg client error ({0}): {1}")] WargClient(String, ClientError), /// Client Error With Hint - #[error("warg client error with hint: {0}")] + #[error("warg client error with hint ({0}): {1}")] WargHint(String, ClientError), } diff --git a/crates/wac-resolver/src/registry.rs b/crates/wac-resolver/src/registry.rs index c81791ce..4fdb49ff 100644 --- a/crates/wac-resolver/src/registry.rs +++ b/crates/wac-resolver/src/registry.rs @@ -46,13 +46,14 @@ impl RegistryPackageResolver { /// client configuration file. /// /// If `url` is `None`, the default URL will be used. + /// + /// The `Retry` argument indicates that this was created after an interactive retry. + /// This occurs when the CLI attempted to leverage a package from the wrong registry. + /// The server responds with a hint as to which registry is used and the client maps that package namespace to the domain provided. + /// After this, the CLI command is retried pub async fn new( url: Option<&str>, bar: Option>, - // Indicates that this was created after an interactive retry. - // This occurs when the CLI attempted to leverage a package from the wrong registry. - // The server responds with a hint as to which registry is used and the client maps that package namespace to the domain provided. - // After this, the CLI command is retried retry: Option, ) -> Result { let config = Config::from_default_file()?.unwrap_or_default(); diff --git a/src/bin/wac.rs b/src/bin/wac.rs index de9e5210..1955de67 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -28,58 +28,108 @@ enum Wac { #[tokio::main] async fn main() -> Result<()> { pretty_env_logger::init(); - - with_interactive_retry(|retry: Option| async { - if let Err(err) = match Wac::parse() { - Wac::Parse(cmd) => cmd.exec().await, - Wac::Resolve(cmd) => cmd.exec(retry).await, - Wac::Encode(cmd) => cmd.exec(retry).await, - } { - if let CommandError::WargHint(_, ClientError::PackageDoesNotExistWithHint { name, hint }) = &err { - if let Some((namespace, registry)) = hint.to_str().unwrap().split_once('=') { - let prompt = format!( - "The package `{}`, does not exist in the registry you're using. - However, the package namespace `{namespace}` does exist in the registry at {registry}. - Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", - name.name(), - ); - if Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .interact() - .unwrap() - { - if let Err(e) = match Wac::parse() { - Wac::Parse(cmd) => cmd.exec().await, - Wac::Resolve(cmd) => { - cmd.exec(Some(Retry::new( - namespace.to_string(), - registry.to_string(), - ))) - .await - }, - Wac::Encode(cmd) => { - cmd.exec(Some(Retry::new( + if let Err(err) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => cmd.exec(None).await, + Wac::Encode(cmd) => cmd.exec(None).await, + } { + if let CommandError::WargHint(_, ClientError::PackageDoesNotExistWithHint { name, hint }) = + &err + { + if let Some((namespace, registry)) = hint.to_str().unwrap().split_once('=') { + let prompt = format!( + "The package `{}`, does not exist in the registry you're using. + However, the package namespace `{namespace}` does exist in the registry at {registry}. + Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", + name.name(), + ); + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + with_interactive_retry(|_: Option| async { + if let Err(err) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + Wac::Encode(cmd) => { + cmd.exec(Some(Retry::new( namespace.to_string(), registry.to_string(), ))) - .await - }, + .await } - { + } { + if let CommandError::WargHint( + _, + ClientError::PackageDoesNotExistWithHint { name, hint }, + ) = &err + { + if let Some((namespace, registry)) = + hint.to_str().unwrap().split_once('=') + { + let prompt = format!( + "The package `{}`, does not exist in the registry you're using. + However, the package namespace `{namespace}` does exist in the registry at {registry}. + Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", + name.name(), + ); + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + if let Err(e) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + Wac::Encode(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + } { + eprintln!( + "{error}: {e:?}", + error = "error" + .if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } else { + return Ok(()); + } + } + } + } + eprintln!( - "{error}: {e:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) + "{error}: {err:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) ); - std::process::exit(1); - } else { - return Ok(()); + std::process::exit(1); } - } + Ok(()) + }).await?; } } - + } else { eprintln!( "{error}: {err:?}", error = "error".if_supports_color(Stream::Stderr, |text| { @@ -88,8 +138,6 @@ async fn main() -> Result<()> { ); std::process::exit(1); } - Ok(()) - }) - .await?; + } Ok(()) } From 4f62af8105e68736135dc7df58f8a8d0a75a2aeb Mon Sep 17 00:00:00 2001 From: Daniel Macovei Date: Wed, 24 Apr 2024 14:33:10 -0500 Subject: [PATCH 5/5] remove interactive retry abstraction --- src/bin/wac.rs | 103 ++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 53 deletions(-) diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 1955de67..0ace54b5 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -4,7 +4,7 @@ use dialoguer::{theme::ColorfulTheme, Confirm}; use owo_colors::{OwoColorize, Stream, Style}; use wac_cli::commands::{EncodeCommand, ParseCommand, ResolveCommand}; use wac_resolver::CommandError; -use warg_client::{with_interactive_retry, ClientError, Retry}; +use warg_client::{ClientError, Retry}; fn version() -> &'static str { option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) @@ -48,8 +48,7 @@ async fn main() -> Result<()> { .interact() .unwrap() { - with_interactive_retry(|_: Option| async { - if let Err(err) = match Wac::parse() { + if let Err(err) = match Wac::parse() { Wac::Parse(cmd) => cmd.exec().await, Wac::Resolve(cmd) => { cmd.exec(Some(Retry::new( @@ -66,67 +65,65 @@ async fn main() -> Result<()> { .await } } { - if let CommandError::WargHint( - _, - ClientError::PackageDoesNotExistWithHint { name, hint }, - ) = &err + if let CommandError::WargHint( + _, + ClientError::PackageDoesNotExistWithHint { name, hint }, + ) = &err + { + if let Some((namespace, registry)) = + hint.to_str().unwrap().split_once('=') { - if let Some((namespace, registry)) = - hint.to_str().unwrap().split_once('=') - { - let prompt = format!( + let prompt = format!( "The package `{}`, does not exist in the registry you're using. However, the package namespace `{namespace}` does exist in the registry at {registry}. Would you like to configure your warg cli to use this registry for packages with this namespace in the future? y/N\n", name.name(), ); - if Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .interact() - .unwrap() - { - if let Err(e) = match Wac::parse() { - Wac::Parse(cmd) => cmd.exec().await, - Wac::Resolve(cmd) => { - cmd.exec(Some(Retry::new( - namespace.to_string(), - registry.to_string(), - ))) - .await - } - Wac::Encode(cmd) => { - cmd.exec(Some(Retry::new( - namespace.to_string(), - registry.to_string(), - ))) - .await - } - } { - eprintln!( - "{error}: {e:?}", - error = "error" - .if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); - } else { - return Ok(()); + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + if let Err(e) = match Wac::parse() { + Wac::Parse(cmd) => cmd.exec().await, + Wac::Resolve(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await + } + Wac::Encode(cmd) => { + cmd.exec(Some(Retry::new( + namespace.to_string(), + registry.to_string(), + ))) + .await } + } { + eprintln!( + "{error}: {e:?}", + error = "error" + .if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } else { + return Ok(()); } } } - - eprintln!( - "{error}: {err:?}", - error = "error".if_supports_color(Stream::Stderr, |text| { - text.style(Style::new().red().bold()) - }) - ); - std::process::exit(1); } - Ok(()) - }).await?; + + eprintln!( + "{error}: {err:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); + std::process::exit(1); + } } } } else {