Skip to content

Commit 9d85795

Browse files
Implement custom Plugin error, clean up lossy use, fix matching on bool
Signed-off-by: Kate Goldenring <kate.goldenring@fermyon.com>
1 parent d9d9417 commit 9d85795

File tree

12 files changed

+340
-265
lines changed

12 files changed

+340
-265
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/plugins/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ serde_json = "1.0"
1616
sha2 = "0.10.2"
1717
tar = "0.4.38"
1818
tempfile = "3.3.0"
19+
thiserror = "1"
1920
tokio = { version = "1.10", features = [ "fs", "process", "rt", "macros" ] }
2021
url = "2.2.2"

crates/plugins/src/error.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
pub type PluginLookupResult<T> = std::result::Result<T, Error>;
2+
3+
/// Error message during plugin lookup or deserializing
4+
#[derive(Debug, thiserror::Error)]
5+
pub enum Error {
6+
#[error("{0}")]
7+
NotFound(NotFoundError),
8+
9+
#[error("{0}")]
10+
ConnectionFailed(ConnectionFailedError),
11+
12+
#[error("{0}")]
13+
InvalidManifest(InvalidManifestError),
14+
15+
#[error("URL parse error {0}")]
16+
UrlParseError(#[from] url::ParseError),
17+
}
18+
19+
/// Contains error details for when a plugin resource cannot be found at expected location
20+
#[derive(Debug)]
21+
pub struct NotFoundError {
22+
name: Option<String>,
23+
addr: String,
24+
err: String,
25+
}
26+
27+
impl NotFoundError {
28+
pub fn new(name: Option<String>, addr: String, err: String) -> Self {
29+
Self { name, addr, err }
30+
}
31+
}
32+
33+
impl std::fmt::Display for NotFoundError {
34+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35+
f.write_str(&format!(
36+
"plugin '{}' not found at expected location {}: {}",
37+
self.name.as_deref().unwrap_or_default(),
38+
self.addr,
39+
self.err
40+
))
41+
}
42+
}
43+
44+
/// Contains error details for when a plugin manifest cannot be properly serialized
45+
#[derive(Debug)]
46+
pub struct InvalidManifestError {
47+
name: Option<String>,
48+
addr: String,
49+
err: String,
50+
}
51+
52+
impl InvalidManifestError {
53+
pub fn new(name: Option<String>, addr: String, err: String) -> Self {
54+
Self { name, addr, err }
55+
}
56+
}
57+
58+
impl std::fmt::Display for InvalidManifestError {
59+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60+
f.write_str(&format!(
61+
"invalid manifest for plugin '{}' at {}: {}",
62+
self.name.clone().unwrap_or_default(),
63+
self.addr,
64+
self.err
65+
))
66+
}
67+
}
68+
69+
/// Contains error details for when there is an error getting a plugin resource from an address.
70+
#[derive(Debug)]
71+
pub struct ConnectionFailedError {
72+
addr: String,
73+
err: String,
74+
}
75+
76+
impl ConnectionFailedError {
77+
pub fn new(addr: String, err: String) -> Self {
78+
Self { addr, err }
79+
}
80+
}
81+
82+
impl std::fmt::Display for ConnectionFailedError {
83+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84+
f.write_str(&format!(
85+
"failed to connect to endpoint {}: {}",
86+
self.addr, self.err
87+
))
88+
}
89+
}

crates/plugins/src/git.rs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{anyhow, Result};
1+
use anyhow::Result;
22
use std::path::{Path, PathBuf};
33
use tokio::process::Command;
44
use url::Url;
@@ -18,54 +18,48 @@ pub struct GitSource {
1818

1919
impl GitSource {
2020
/// Creates a new git source
21-
pub fn new(
22-
source_url: &Url,
23-
branch: Option<String>,
24-
git_root: impl AsRef<Path>,
25-
) -> Result<GitSource> {
26-
Ok(Self {
21+
pub fn new(source_url: &Url, branch: Option<String>, git_root: impl AsRef<Path>) -> GitSource {
22+
Self {
2723
source_url: source_url.clone(),
2824
branch: branch.unwrap_or_else(|| DEFAULT_BRANCH.to_owned()),
2925
git_root: git_root.as_ref().to_owned(),
30-
})
26+
}
3127
}
3228

3329
/// Clones a contents of a git repository to a local directory
34-
pub async fn clone(&self) -> Result<()> {
30+
pub async fn clone_repo(&self) -> Result<()> {
3531
let mut git = Command::new("git");
3632
git.args([
3733
"clone",
3834
self.source_url.as_ref(),
3935
"--branch",
4036
&self.branch,
4137
"--single-branch",
42-
&self.git_root.to_string_lossy(),
43-
]);
38+
])
39+
.arg(&self.git_root);
4440
let clone_result = git.output().await?;
45-
match clone_result.status.success() {
46-
true => Ok(()),
47-
false => Err(anyhow!(
41+
if !clone_result.status.success() {
42+
anyhow::bail!(
4843
"Error cloning Git repo {}: {}",
4944
self.source_url,
50-
String::from_utf8(clone_result.stderr)
51-
.unwrap_or_else(|_| "(cannot get error)".to_owned())
52-
)),
45+
String::from_utf8_lossy(&clone_result.stderr)
46+
)
5347
}
48+
Ok(())
5449
}
5550

5651
/// Fetches the latest changes from the source repository
5752
pub async fn pull(&self) -> Result<()> {
5853
let mut git = Command::new("git");
59-
git.args(["-C", &self.git_root.to_string_lossy(), "pull"]);
54+
git.arg("-C").arg(&self.git_root).arg("pull");
6055
let pull_result = git.output().await?;
61-
match pull_result.status.success() {
62-
true => Ok(()),
63-
false => Err(anyhow!(
56+
if !pull_result.status.success() {
57+
anyhow::bail!(
6458
"Error updating Git repo at {}: {}",
6559
self.git_root.display(),
66-
String::from_utf8(pull_result.stderr)
67-
.unwrap_or_else(|_| "(cannot update error)".to_owned())
68-
)),
60+
String::from_utf8_lossy(&pull_result.stderr)
61+
)
6962
}
63+
Ok(())
7064
}
7165
}

crates/plugins/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
pub mod error;
12
mod git;
2-
mod lookup;
3+
pub mod lookup;
34
pub mod manager;
45
pub mod manifest;
56
mod prompt;
67
mod store;
7-
pub use lookup::{fetch_plugins_repo, PluginLookup, PLUGIN_NOT_FOUND_ERROR_MSG};
88
pub use prompt::prompt_confirm_install;
99
pub use store::PluginStore;
1010

crates/plugins/src/lookup.rs

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use crate::{git::GitSource, manifest::PluginManifest, store::manifest_file_name};
2-
use anyhow::{Context, Result};
1+
use crate::{error::*, git::GitSource, manifest::PluginManifest, store::manifest_file_name};
32
use semver::Version;
43
use std::{
54
fs::File,
@@ -14,9 +13,6 @@ const PLUGINS_REPO_LOCAL_DIRECTORY: &str = ".spin-plugins";
1413
// Name of directory containing the installed manifests
1514
const PLUGINS_REPO_MANIFESTS_DIRECTORY: &str = "manifests";
1615

17-
// Error message indicating plugin could not be found in plugins repository.
18-
pub const PLUGIN_NOT_FOUND_ERROR_MSG: &str = "plugin not found";
19-
2016
const SPIN_PLUGINS_REPO: &str = "https://github.com/fermyon/spin-plugins/";
2117

2218
/// Looks up plugin manifests in centralized spin plugin repository.
@@ -33,49 +29,53 @@ impl PluginLookup {
3329
}
3430
}
3531

36-
fn version_string(&self) -> String {
37-
self.version
38-
.as_ref()
39-
.map(|s| s.to_string())
40-
.unwrap_or_else(|| String::from("latest"))
41-
}
42-
43-
pub async fn get_manifest_from_repository(&self, plugins_dir: &Path) -> Result<PluginManifest> {
44-
log::info!(
45-
"Pulling manifest for plugin {} from {}",
46-
self.name,
47-
SPIN_PLUGINS_REPO
48-
);
49-
fetch_plugins_repo(plugins_dir, false).await?;
50-
let file = File::open(spin_plugins_repo_manifest_path(
51-
&self.name,
52-
&self.version,
53-
plugins_dir,
54-
))
55-
.with_context(|| {
56-
format!(
57-
"{} {} {} in centralized repository",
58-
self.name,
59-
self.version_string(),
60-
PLUGIN_NOT_FOUND_ERROR_MSG,
61-
)
32+
pub async fn get_manifest_from_repository(
33+
&self,
34+
plugins_dir: &Path,
35+
) -> PluginLookupResult<PluginManifest> {
36+
let url = plugins_repo_url()?;
37+
log::info!("Pulling manifest for plugin {} from {url}", self.name);
38+
fetch_plugins_repo(&url, plugins_dir, false)
39+
.await
40+
.map_err(|e| {
41+
Error::ConnectionFailed(ConnectionFailedError::new(url.to_string(), e.to_string()))
42+
})?;
43+
let expected_path = spin_plugins_repo_manifest_path(&self.name, &self.version, plugins_dir);
44+
let file = File::open(&expected_path).map_err(|e| {
45+
Error::NotFound(NotFoundError::new(
46+
Some(self.name.clone()),
47+
expected_path.display().to_string(),
48+
e.to_string(),
49+
))
50+
})?;
51+
let manifest: PluginManifest = serde_json::from_reader(file).map_err(|e| {
52+
Error::InvalidManifest(InvalidManifestError::new(
53+
Some(self.name.clone()),
54+
expected_path.display().to_string(),
55+
e.to_string(),
56+
))
6257
})?;
63-
let manifest: PluginManifest = serde_json::from_reader(file)
64-
.with_context(|| format!("Failed to deserialize {} plugin manifest", self.name))?;
6558
Ok(manifest)
6659
}
6760
}
6861

69-
pub async fn fetch_plugins_repo(plugins_dir: &Path, update: bool) -> Result<()> {
70-
let repo_url = Url::parse(SPIN_PLUGINS_REPO)?;
62+
pub fn plugins_repo_url() -> Result<Url, url::ParseError> {
63+
Url::parse(SPIN_PLUGINS_REPO)
64+
}
65+
66+
pub async fn fetch_plugins_repo(
67+
repo_url: &Url,
68+
plugins_dir: &Path,
69+
update: bool,
70+
) -> anyhow::Result<()> {
7171
let git_root = plugin_manifests_repo_path(plugins_dir);
72-
let git_source = GitSource::new(&repo_url, None, &git_root)?;
72+
let git_source = GitSource::new(repo_url, None, &git_root);
7373
if git_root.join(".git").exists() {
7474
if update {
7575
git_source.pull().await?;
7676
}
7777
} else {
78-
git_source.clone().await?;
78+
git_source.clone_repo().await?;
7979
}
8080
Ok(())
8181
}

0 commit comments

Comments
 (0)