Skip to content

Commit dcbb086

Browse files
authored
refactor: improve error handling, add custom Error structs (#28)
1 parent 83ea739 commit dcbb086

File tree

14 files changed

+326
-278
lines changed

14 files changed

+326
-278
lines changed

Cargo.lock

Lines changed: 112 additions & 148 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ serde = "1.0"
1818
serde_derive = "1.0"
1919
clap = { version = "3.0.0", features = ["derive", "cargo"] }
2020
dirs = "3.0"
21-
simple-error = "0.2"
2221
regex = "1"
2322
reqwest = { version = "0.10", features = ["blocking"] }
2423
openssl = { version = "0.10", features = ["vendored"] }

src/addons.rs

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
use crate::errors::{Error, Result};
12
use crate::htmlparser;
23

3-
use super::errors::ErrorChain;
44
use html5ever::tendril::TendrilSink;
55
use html5ever::{self, tree_builder::TreeBuilderOpts, ParseOpts};
66
use markup5ever_rcdom::{Node, NodeData, RcDom};
77
use regex::Regex;
88
use std::fs::{self, File};
99
use std::io::{self, BufRead};
1010
use std::path::{Path, PathBuf};
11-
use std::{error::Error, rc::Rc};
11+
use std::rc::Rc;
1212
use tempfile::tempfile;
1313
use walkdir::WalkDir;
1414

@@ -20,7 +20,7 @@ pub struct Addon {
2020

2121
pub struct AddonList {
2222
pub addons: Vec<Addon>,
23-
pub errors: Vec<Box<dyn Error>>,
23+
pub errors: Vec<Error>,
2424
}
2525

2626
pub struct Manager {
@@ -39,14 +39,23 @@ impl Manager {
3939
Manager { addon_dir: path }
4040
}
4141

42-
pub fn get_addons(&self) -> Result<AddonList, Box<dyn Error>> {
42+
pub fn get_addons(&self) -> Result<AddonList> {
4343
let mut addon_list = AddonList {
4444
addons: vec![],
4545
errors: vec![],
4646
};
4747

48+
if let Err(err) = fs::metadata(&self.addon_dir) {
49+
return Err(Error::CannotOpenAddonDirectory(
50+
self.addon_dir.clone(),
51+
Box::new(err),
52+
));
53+
}
54+
4855
for entry in WalkDir::new(&self.addon_dir) {
49-
let entry_dir = entry?;
56+
let entry_dir = entry.map_err(|err| {
57+
Error::CannotOpenAddonDirectory(self.addon_dir.clone(), Box::new(err))
58+
})?;
5059
let file_path = entry_dir.path();
5160

5261
let file_name = entry_dir.file_name();
@@ -67,31 +76,25 @@ impl Manager {
6776

6877
match self.read_addon(addon_dir) {
6978
Ok(addon) => addon_list.addons.push(addon),
70-
Err(err) => addon_list
71-
.errors
72-
.push(format!("while reading addon {:?}: {}", file_path, err).into()),
79+
Err(err) => addon_list.errors.push(err),
7380
}
7481
}
7582

7683
Ok(addon_list)
7784
}
7885

79-
pub fn get_addon(&self, name: &str) -> Result<Option<Addon>, Box<dyn Error>> {
80-
let addon_list = self.get_addons().chain_err("while getting addons")?;
86+
pub fn get_addon(&self, name: &str) -> Result<Option<Addon>> {
87+
let addon_list = self.get_addons()?;
8188
let found = addon_list.addons.into_iter().find(|x| x.name == name);
8289
Ok(found)
8390
}
8491

85-
fn read_addon(&self, path: &Path) -> Result<Addon, Box<dyn Error>> {
86-
let addon_name = path
87-
.file_name()
88-
.ok_or(simple_error!("cannot get filename"))?;
89-
90-
let addon_name = addon_name
91-
.to_str()
92-
.ok_or(simple_error!("failed to get addon name"))?;
92+
fn read_addon(&self, path: &Path) -> Result<Addon> {
93+
let addon_name = path.file_name().unwrap().to_str().unwrap();
9394

94-
let file = self.open_addon_metadata_file(path, addon_name)?;
95+
let file = self
96+
.open_addon_metadata_file(path, addon_name)
97+
.map_err(|err| Error::CannotReadAddon(addon_name.to_owned(), Box::new(err)))?;
9598
let re = Regex::new(r"## (.*): (.*)").unwrap();
9699

97100
let mut addon = Addon {
@@ -124,17 +127,18 @@ impl Manager {
124127
Ok(addon)
125128
}
126129

127-
pub fn delete_addon(&self, addon: &Addon) -> Result<(), Box<dyn Error>> {
130+
pub fn delete_addon(&self, addon: &Addon) -> Result<()> {
128131
let mut addon_path = self.addon_dir.to_owned();
129132
addon_path.push(&addon.name);
130133

131-
fs::remove_dir_all(addon_path).chain_err("while removing addon directory")?;
132-
134+
fs::remove_dir_all(addon_path)
135+
.map_err(|err| Error::CannotRemoveAddon(addon.name.to_owned(), Box::new(err)))?;
133136
Ok(())
134137
}
135138

136-
pub fn download_addon(&self, url: &str) -> Result<Addon, Box<dyn Error>> {
137-
let mut response = reqwest::blocking::get(url)?;
139+
pub fn download_addon(&self, url: &str) -> Result<Addon> {
140+
let mut response = reqwest::blocking::get(url)
141+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
138142

139143
let opts = ParseOpts {
140144
tree_builder: TreeBuilderOpts {
@@ -150,17 +154,25 @@ impl Manager {
150154

151155
let download_link = get_cdn_download_link(&dom);
152156

153-
let download_link =
154-
download_link.ok_or(simple_error!("failed to get CDN download link"))?;
157+
let download_link = download_link.ok_or(Error::CannotDownloadAddon(
158+
url.to_owned(),
159+
"failed to get CDN download link".into(),
160+
))?;
155161

156-
let mut response = reqwest::blocking::get(&download_link)?;
162+
let mut response = reqwest::blocking::get(&download_link)
163+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
157164

158165
let mut tmpfile = tempfile()?;
159-
response.copy_to(&mut tmpfile)?;
160-
let mut archive = zip::ZipArchive::new(tmpfile)?;
166+
response
167+
.copy_to(&mut tmpfile)
168+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
169+
let mut archive = zip::ZipArchive::new(tmpfile)
170+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
161171

162172
for i in 0..archive.len() {
163-
let mut file = archive.by_index(i)?;
173+
let mut file = archive
174+
.by_index(i)
175+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
164176
let outpath = match file.enclosed_name() {
165177
Some(path) => {
166178
let mut p = self.addon_dir.clone();
@@ -171,36 +183,37 @@ impl Manager {
171183
None => continue,
172184
};
173185

174-
if (&*file.name()).ends_with('/') {
175-
fs::create_dir_all(&outpath).unwrap();
186+
if (file.name()).ends_with('/') {
187+
fs::create_dir_all(&outpath)
188+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
176189
} else {
177190
if let Some(p) = outpath.parent() {
178191
if !p.exists() {
179-
fs::create_dir_all(&p).unwrap();
192+
fs::create_dir_all(&p).map_err(|err| {
193+
Error::CannotDownloadAddon(url.to_owned(), Box::new(err))
194+
})?;
180195
}
181196
}
182-
let mut outfile = fs::File::create(&outpath).unwrap();
183-
io::copy(&mut file, &mut outfile).unwrap();
197+
let mut outfile = fs::File::create(&outpath)
198+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
199+
io::copy(&mut file, &mut outfile)
200+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
184201
}
185202
}
186203

187204
let mut addon_path = self.addon_dir.to_owned();
188-
let addon_name = archive.by_index(0)?;
205+
let addon_name = archive
206+
.by_index(0)
207+
.map_err(|err| Error::CannotDownloadAddon(url.to_owned(), Box::new(err)))?;
189208
let addon_name = get_root_dir(&addon_name.mangled_name());
190209
addon_path.push(addon_name);
191210

192-
let addon = self
193-
.read_addon(&addon_path)
194-
.chain_err("while reading addon")?;
211+
let addon = self.read_addon(&addon_path)?;
195212

196213
Ok(addon)
197214
}
198215

199-
fn open_addon_metadata_file(
200-
&self,
201-
path: &Path,
202-
addon_name: &str,
203-
) -> Result<File, Box<dyn Error>> {
216+
fn open_addon_metadata_file(&self, path: &Path, addon_name: &str) -> Result<File> {
204217
let mut filepath = path.to_owned();
205218
let mut filepath_lowercase = path.to_owned();
206219

@@ -211,12 +224,11 @@ impl Manager {
211224
filepath_lowercase.push(filename_lowercase);
212225

213226
if filepath.exists() {
214-
File::open(&filepath).chain_err(&format!("failed to open {:?}", &filepath))
227+
File::open(&filepath).map_err(|err| Error::Other(Box::new(err)))
215228
} else if filepath_lowercase.exists() {
216-
File::open(&filepath_lowercase)
217-
.chain_err(&format!("failed to open {:?}", &filepath_lowercase))
229+
File::open(&filepath_lowercase).map_err(|err| Error::Other(Box::new(err)))
218230
} else {
219-
Err("metadata file is missing".into())
231+
Err(Error::Other("missing addon metadata file".into()))
220232
}
221233
}
222234
}
@@ -253,16 +265,13 @@ pub fn get_download_url(addon_url: &str) -> Option<String> {
253265
let fns: Vec<fn(&str) -> Option<String>> = vec![
254266
|url: &str| {
255267
let re = Regex::new(r"^https://.*esoui\.com/downloads/info(\d+)-(.+)$").unwrap();
256-
re.captures(url).map(|captures| {
257-
captures[1].to_owned()
258-
})
268+
re.captures(url).map(|captures| captures[1].to_owned())
259269
},
260270
|url: &str| {
261-
let re = Regex::new(r"^https://.+esoui\.com/downloads/fileinfo\.php\?id=(\d+)$").unwrap();
262-
re.captures(url).map(|captures| {
263-
captures[1].to_owned()
264-
})
265-
}
271+
let re =
272+
Regex::new(r"^https://.+esoui\.com/downloads/fileinfo\.php\?id=(\d+)$").unwrap();
273+
re.captures(url).map(|captures| captures[1].to_owned())
274+
},
266275
];
267276

268277
for f in fns {

src/cli/add.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ use eso_addons::{
33
addons,
44
addons::Manager,
55
config::{self, AddonEntry, Config},
6-
errors::ErrorChain,
76
htmlparser,
87
};
98
use html5ever::{tendril::TendrilSink, tree_builder::TreeBuilderOpts, ParseOpts};
109
use markup5ever_rcdom::{Node, NodeData, RcDom};
11-
use std::{error::Error, path::Path, rc::Rc};
10+
use std::{path::Path, rc::Rc};
11+
12+
use super::{Error, Result};
1213

1314
#[derive(Parser)]
1415
pub struct AddCommand {
@@ -28,17 +29,15 @@ impl AddCommand {
2829
cfg: &mut Config,
2930
config_filepath: &Path,
3031
addon_manager: &Manager,
31-
) -> Result<(), Box<dyn std::error::Error>> {
32+
) -> Result<()> {
3233
let mut entry = self.get_entry()?;
3334

3435
if cfg.addons.iter().find(|el| el.url == entry.url).is_some() {
3536
println!("Addon {} is already installed", &entry.name);
3637
return Ok(());
3738
}
3839

39-
let installed = addon_manager
40-
.download_addon(&entry.url.clone().unwrap())
41-
.chain_err(&format!("while downloading {}", &entry.name))?;
40+
let installed = addon_manager.download_addon(&entry.url.clone().unwrap())?;
4241

4342
if entry.name != installed.name {
4443
entry.name = installed.name;
@@ -53,16 +52,19 @@ impl AddCommand {
5352
Ok(())
5453
}
5554

56-
pub fn get_entry(&mut self) -> Result<AddonEntry, Box<dyn Error>> {
55+
pub fn get_entry(&mut self) -> Result<AddonEntry> {
5756
if self.addon_url.is_none() {
58-
self.ask_for_fields()
59-
.chain_err(&format!("failed to get parameters"))?;
57+
self.ask_for_fields()?;
6058
}
6159

62-
let addon_url = self.addon_url.clone().ok_or("missing addon URL")?;
60+
let addon_url = self
61+
.addon_url
62+
.clone()
63+
.ok_or(Error::Other("missing addon URL".into()))?;
6364
let dependency = self.dependency;
6465

65-
let mut response = reqwest::blocking::get(&addon_url)?;
66+
let mut response =
67+
reqwest::blocking::get(&addon_url).map_err(|err| Error::Other(Box::new(err)))?;
6668

6769
let opts = ParseOpts {
6870
tree_builder: TreeBuilderOpts {
@@ -74,9 +76,11 @@ impl AddCommand {
7476

7577
let dom = html5ever::parse_document(RcDom::default(), opts)
7678
.from_utf8()
77-
.read_from(&mut response)?;
79+
.read_from(&mut response)
80+
.map_err(|err| Error::Other(Box::new(err)))?;
7881

79-
let addon_name = get_addon_name(&dom).ok_or(simple_error!("failed to get addon name"))?;
82+
let addon_name =
83+
get_addon_name(&dom).ok_or(Error::Other("failed to get addon name".into()))?;
8084
let download_url = addons::get_download_url(&addon_url);
8185

8286
Ok(AddonEntry {
@@ -86,7 +90,7 @@ impl AddCommand {
8690
})
8791
}
8892

89-
fn ask_for_fields(&mut self) -> Result<(), Box<dyn std::error::Error>> {
93+
fn ask_for_fields(&mut self) -> Result<()> {
9094
let questions = vec![
9195
requestty::Question::input("addon_url")
9296
.message("URL of the addon on esoui.com")
@@ -97,7 +101,7 @@ impl AddCommand {
97101
.build(),
98102
];
99103

100-
let answers = requestty::prompt(questions)?;
104+
let answers = requestty::prompt(questions).map_err(|err| Error::Other(Box::new(err)))?;
101105

102106
if let Some(addon_url) = answers.get("addon_url") {
103107
self.addon_url = addon_url.as_string().map(|x| x.to_owned());

src/cli/errors.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#[derive(Debug)]
2+
pub enum Error {
3+
AddonNotFound(String),
4+
NoAddonsInstalled,
5+
AppError(eso_addons::errors::Error),
6+
Other(Box<dyn std::error::Error>),
7+
}
8+
9+
impl std::fmt::Display for Error {
10+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11+
match &self {
12+
Self::AddonNotFound(name) => f.write_str(&format!("addon {} not found", &name)),
13+
Self::NoAddonsInstalled => f.write_str("no addons installed"),
14+
Self::AppError(err) => f.write_str(&format!("app error: {}", err)),
15+
Self::Other(err) => f.write_str(&format!("other error: {}", err)),
16+
}
17+
}
18+
}
19+
20+
impl std::error::Error for Error {}
21+
22+
impl From<eso_addons::errors::Error> for Error {
23+
fn from(err: eso_addons::errors::Error) -> Self {
24+
Self::AppError(err)
25+
}
26+
}
27+
28+
pub type Result<T> = std::result::Result<T, Error>;

src/cli/list.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ use eso_addons::addons::Manager;
55
use eso_addons::config::Config;
66
use prettytable::{format, Table};
77

8+
use super::Result;
9+
810
#[derive(Parser)]
911
pub struct ListCommand {}
1012

1113
impl ListCommand {
12-
pub fn run(
13-
&self,
14-
addon_manager: &Manager,
15-
config: &Config,
16-
) -> Result<(), Box<dyn std::error::Error>> {
14+
pub fn run(&self, addon_manager: &Manager, config: &Config) -> Result<()> {
1715
let mut table = Table::new();
1816

1917
let mut addon_status: BTreeMap<String, Vec<String>> = BTreeMap::new();

0 commit comments

Comments
 (0)