From 1dcd1e7591a6323ebdcfa409de6b5c44c0fc2a29 Mon Sep 17 00:00:00 2001 From: Zixuan Zhang <74850854+zixuanzhang-x@users.noreply.github.com> Date: Sun, 18 Sep 2022 18:37:38 -0400 Subject: [PATCH] [#83] Create 'AssetError' and refactor 'select_asset' function to return it (#97) Resolves #83 Refactored `ToolInfo.select_asset(&assets)` to return a custom `AssetError` error instead of a string and added tests for the function. The `AssetError` enum has two possible values: - `NameUnknown`: this is when `AssetName` does not know what the asset name is for a specific OS the user uses. - `NotFound`: this is when the `AssetName` is not in the `Assets` slice passed into the `select_asset(&assets)` method. Implemented `std::fmt::Display` trait for `AssetError` and added tests for it. ### Additional tasks - [x] Documentation for changes provided/changed - [x] Tests added Co-authored-by: Dmitrii Kovanikov --- README.md | 2 +- src/model/release.rs | 30 ++++++++++- src/model/tool.rs | 119 +++++++++++++++++++++++++++++++++++++++++-- src/sync/prefetch.rs | 4 +- 4 files changed, 146 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1517f9d..efa3d1f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ store_directory = "~/.local/bin" By default `tool-sync` reads configuration from `$HOME/.tool.toml` you can run `tool default-config` to print a default configuration example to std out. You can redirect this out put to a file like so `tool default-config > -$HOME/.tools.toml`. +$HOME/.tool.toml`. You can also quickly copy the above configuration to the default path by running the following command (Unix-only): diff --git a/src/model/release.rs b/src/model/release.rs index bee39f3..5354424 100644 --- a/src/model/release.rs +++ b/src/model/release.rs @@ -1,4 +1,6 @@ use serde::Deserialize; +use std::env; +use std::fmt::{Display, Formatter}; #[derive(Deserialize, Debug)] pub struct Release { @@ -6,9 +8,35 @@ pub struct Release { pub assets: Vec, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] pub struct Asset { pub id: u32, pub name: String, pub size: u64, } + +#[derive(Debug, PartialEq, Eq)] +pub enum AssetError { + /// Asset name of this OS is unknown + OsSelectorUnknown, + + /// Asset name is not in the fetched assets + NotFound(String), +} + +impl Display for AssetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::OsSelectorUnknown => { + write!( + f, + "Unknown asset selector for OS: {}. Specify 'asset_name.your_os' in the cofig.", + env::consts::OS + ) + } + Self::NotFound(asset_name) => { + write!(f, "No asset matching name: {}", asset_name) + } + } + } +} diff --git a/src/model/tool.rs b/src/model/tool.rs index a2c0d3b..1236c59 100644 --- a/src/model/tool.rs +++ b/src/model/tool.rs @@ -1,6 +1,7 @@ use super::release::Asset; use crate::infra::client::Client; use crate::model::asset_name::AssetName; +use crate::model::release::AssetError; #[derive(Debug, PartialEq, Eq)] pub enum Tool { @@ -69,16 +70,15 @@ pub struct ToolInfo { } impl ToolInfo { - pub fn select_asset(&self, assets: &[Asset]) -> Result { + /// Select an Asset from all Assets based on which Operating System is used + pub fn select_asset(&self, assets: &[Asset]) -> Result { match self.asset_name.get_name_by_os() { - None => Err(String::from( - "Don't know the asset name for this OS: specify it explicitly in the config", - )), + None => Err(AssetError::OsSelectorUnknown), Some(asset_name) => { let asset = assets.iter().find(|&asset| asset.name.contains(asset_name)); match asset { - None => Err(format!("No asset matching name: {}", asset_name)), + None => Err(AssetError::NotFound(asset_name.to_owned())), Some(asset) => Ok(asset.clone()), } } @@ -107,3 +107,112 @@ pub struct ToolAsset { /// GitHub API client that produces the stream for downloading the asset pub client: Client, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn asset_fount() { + let asset_name = "asset"; + + let tool_info = ToolInfo { + owner: "owner".to_string(), + repo: "repo".to_string(), + exe_name: "exe".to_string(), + tag: ToolInfoTag::Latest, + asset_name: AssetName { + linux: Some(asset_name.to_string()), + macos: Some(asset_name.to_string()), + windows: Some(asset_name.to_string()), + }, + }; + + let assets = vec![ + Asset { + id: 1, + name: "1".to_string(), + size: 10, + }, + Asset { + id: 2, + name: asset_name.to_string(), + size: 50, + }, + Asset { + id: 3, + name: "3".to_string(), + size: 77, + }, + ]; + + assert_eq!( + tool_info.select_asset(&assets), + Ok(Asset { + id: 2, + name: asset_name.to_string(), + size: 50 + }) + ); + } + + #[test] + fn asset_not_found() { + let asset_name = "asset"; + + let tool_info = ToolInfo { + owner: "owner".to_string(), + repo: "repo".to_string(), + exe_name: "exe".to_string(), + tag: ToolInfoTag::Latest, + asset_name: AssetName { + linux: Some(asset_name.to_string()), + macos: Some(asset_name.to_string()), + windows: Some(asset_name.to_string()), + }, + }; + + let assets = vec![ + Asset { + id: 1, + name: "1".to_string(), + size: 10, + }, + Asset { + id: 2, + name: "2".to_string(), + size: 50, + }, + Asset { + id: 3, + name: "3".to_string(), + size: 77, + }, + ]; + + assert_eq!( + tool_info.select_asset(&assets), + Err(AssetError::NotFound(asset_name.to_string())) + ); + } + + #[test] + fn asset_os_selector_unknown() { + let tool_info = ToolInfo { + owner: "owner".to_string(), + repo: "repo".to_string(), + exe_name: "exe".to_string(), + tag: ToolInfoTag::Latest, + asset_name: AssetName { + linux: None, + macos: None, + windows: None, + }, + }; + + assert_eq!( + tool_info.select_asset(&Vec::new()), + Err(AssetError::OsSelectorUnknown) + ); + } +} diff --git a/src/sync/prefetch.rs b/src/sync/prefetch.rs index 1040ef3..d0aff46 100644 --- a/src/sync/prefetch.rs +++ b/src/sync/prefetch.rs @@ -124,8 +124,8 @@ fn prefetch_tool( None } Ok(release) => match tool_info.select_asset(&release.assets) { - Err(msg) => { - prefetch_progress.unexpected_err_msg(tool_name, &msg); + Err(err) => { + prefetch_progress.unexpected_err_msg(tool_name, &err.to_string()); prefetch_progress.update_message(already_completed); None }