commit 1bbbcadab128f75ff7904c109ab5371b1aa71daa
parent 650cc623aeb211fa79d44aaf9a00cff2f33000d6
Author: Andy Khramtsov <>
Date: Tue, 10 Feb 2026 16:07:53 +0300
refactor: error handling
Diffstat:
4 files changed, 127 insertions(+), 95 deletions(-)
diff --git a/src/filetree/mod.rs b/src/filetree/mod.rs
@@ -55,6 +55,11 @@ impl<M> Filetree<M> {
.map(|node_id| self.nodes.get(node_id).expect("Filetree should have node"))
.map(Rc::clone)
}
+
+ /// Checks if a node with this path exists
+ pub fn check_path(&self, path: &Path) -> bool {
+ self.paths.get(path).is_some()
+ }
}
impl<M> Filetree<M> {
diff --git a/src/filetree/node.rs b/src/filetree/node.rs
@@ -38,6 +38,10 @@ impl<M> Node<M> {
&mut self.meta
}
+ pub fn set_meta(&mut self, meta: M) {
+ self.meta = meta;
+ }
+
/// A node is a leaf if it has no children
pub fn is_leaf(&self) -> bool {
self.children.is_empty()
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,4 +1,9 @@
-use std::{cell::BorrowError, collections::VecDeque, path::Path};
+use std::{
+ cell::{BorrowError, BorrowMutError},
+ collections::VecDeque,
+ path::{Path, StripPrefixError},
+ string::FromUtf8Error,
+};
use clap::Parser;
@@ -31,19 +36,20 @@ pub fn result_main() -> Result<(), Error> {
tracing::debug!("Read inventory: {:#?}", state.inventory);
- state.runtime.block_on(read(&state));
+ state.runtime.block_on(read(&state))?;
Ok(())
}
+/// This meta is used for display tree.
#[derive(Clone, Debug)]
struct Meta {
inventory: Option<Option<String>>,
blacklist: Option<bool>,
}
-/// Scan workspace
-async fn read(state: &State) {
+/// Scan workspace and display its tree.
+async fn read(state: &State) -> Result<(), Error> {
let root = Node::new(
state.config.root.clone(),
Meta {
@@ -53,9 +59,10 @@ async fn read(state: &State) {
);
let mut filetree = Filetree::new(root);
- let mut buffer = Vec::new();
+ let mut processing_buffer = Vec::new();
- buffer.extend(
+ // Add all whitelist to buffer
+ processing_buffer.extend(
state
.config
.filters
@@ -69,81 +76,78 @@ async fn read(state: &State) {
);
// This makes it so every prefix goes first.
- buffer.sort();
-
- // Always partially sorted
- let mut buffer = VecDeque::from(buffer);
-
- while let Some(dir) = buffer.pop_front() {
- tracing::debug!("Adding {:?} to tree", dir);
- let mut base = dir.clone();
- while filetree.node_by_path(&base).is_none() {
- base.pop();
- }
-
- let allowed = allowed(state, &dir);
- let in_inventory = state
- .inventory_tree
- .node_by_path(&dir)
- .filter(|node| node.borrow().is_leaf())
- .map(|node| node.borrow().meta().clone())
- .flatten()
- .map(|check| match check {
+ processing_buffer.sort();
+
+ // Has some specific ordering so the tree is built in the correct order.
+ let mut processing_buffer = VecDeque::from(processing_buffer);
+
+ while let Some(item) = processing_buffer.pop_front() {
+ tracing::debug!("Adding {:?} to tree", item);
+
+ let allowed_to_scan = allowed_to_scan(state, &item)?;
+ let inventory_message = (|| -> Result<Option<Option<String>>, Error> {
+ let Some(node) = state.inventory_tree.node_by_path(&item) else {
+ return Ok(None);
+ };
+ let node = node.try_borrow()?;
+ if !node.is_leaf() {
+ return Ok(None);
+ }
+ let Some(check) = node.meta().clone() else {
+ return Err(Error::NoInventoryCheckInfo);
+ };
+ Ok(Some(match check {
inventory::Check::None => None,
- inventory::Check::Shell(cmd) => Some(
- String::from_utf8(
- std::process::Command::new("sh")
- .arg("-c")
- .arg(cmd)
- .output()
- .expect("Should finish command")
- .stdout,
- )
- .expect("Should be utf8 string"),
- ),
- });
-
- if base != dir {
- let mut suffix = dir.strip_prefix(&base).unwrap().to_owned();
+ inventory::Check::Shell(cmd) => Some(String::from_utf8(
+ std::process::Command::new("sh")
+ .arg("-c")
+ .arg(cmd)
+ .output()
+ .map_err(Error::Command)?
+ .stdout,
+ )?),
+ }))
+ })()?;
+
+ if let Some(node) = filetree.node_by_path(&item) {
+ node.try_borrow_mut()?.meta_mut().blacklist = Some(!allowed_to_scan);
+ } else {
+ let mut base = item.clone();
+ while !filetree.check_path(&base) {
+ base.pop();
+ }
+ let mut suffix = item
+ .strip_prefix(&base)
+ .map_err(Error::BasePrefix)?
+ .to_owned();
suffix.pop();
for component in suffix.components() {
base.push(component);
- filetree
- .insert(Node::new(
- base.clone(),
- Meta {
- blacklist: None,
- inventory: None,
- },
- ))
- .unwrap();
- }
- filetree
- .insert(Node::new(
- dir.clone(),
+ filetree.insert(Node::new(
+ base.clone(),
Meta {
- blacklist: Some(!allowed),
- inventory: in_inventory,
+ blacklist: None,
+ inventory: None,
},
- ))
- .unwrap();
- } else {
- filetree
- .node_by_path(&dir)
- .unwrap()
- .borrow_mut()
- .meta_mut()
- .blacklist = Some(!allowed);
+ ))?;
+ }
+ filetree.insert(Node::new(
+ item.clone(),
+ Meta {
+ blacklist: Some(!allowed_to_scan),
+ inventory: inventory_message,
+ },
+ ))?;
}
- if allowed && should_scan(state, &dir).unwrap() {
- tracing::debug!("Scanning {:?}", dir.file_name());
- let mut iter = tokio::fs::read_dir(&dir).await.unwrap();
+ if allowed_to_scan && should_scan(state, &item)? {
+ tracing::debug!("Scanning {:?}", item.file_name());
+ let mut iter = tokio::fs::read_dir(&item).await.map_err(Error::ReadFs)?;
let mut dir_buffer = Vec::new();
- while let Some(result) = iter.next_entry().await.unwrap() {
+ while let Some(result) = iter.next_entry().await.map_err(Error::ReadFs)? {
let path = result.path();
dir_buffer.push(path)
}
@@ -151,12 +155,14 @@ async fn read(state: &State) {
dir_buffer.sort();
while let Some(path) = dir_buffer.pop() {
- buffer.push_front(path);
+ processing_buffer.push_front(path);
}
}
}
- print(&filetree).unwrap();
+ print(&filetree)?;
+
+ Ok(())
}
/// Does not check if allowed.
@@ -170,20 +176,22 @@ fn should_scan(state: &State, path: &Path) -> Result<bool, Error> {
false
};
- Ok(state.blacklist_tree.node_by_path(path).is_some() || inventory)
+ Ok(state.blacklist_tree.check_path(path) || inventory)
}
/// If a directory is allowed to be scanned.
/// Whitelist has more priority than blacklist on the same level of specificity.
-fn allowed(state: &State, path: &Path) -> bool {
+fn allowed_to_scan(state: &State, path: &Path) -> Result<bool, Error> {
// If there is no not-overridden blacklist
- let path = path.strip_prefix(&state.config.root).unwrap();
+ let path = path
+ .strip_prefix(&state.config.root)
+ .map_err(Error::RootPrefix)?;
// If no such blacklist filter found that blacklists the path such that
// for this blacklist filter no such whitelist found that whitelists the
// path back aka for which the blacklist is the prefix
- !state
+ Ok(!state
.config
.filters
.iter()
@@ -199,7 +207,7 @@ fn allowed(state: &State, path: &Path) -> bool {
path.starts_with(&wl_filter.path)
&& wl_filter.path.starts_with(&bl_filter.path)
})
- })
+ }))
}
fn print(tree: &Filetree<Meta>) -> Result<(), Error> {
@@ -207,7 +215,7 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> {
println!(
"{}",
tree.root()
- .borrow()
+ .try_borrow()?
.path()
.to_str()
.unwrap_or("UNKNOWN")
@@ -215,14 +223,14 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> {
);
print_buffer.extend(
tree.root()
- .borrow()
+ .try_borrow()?
.children()
.map(|child_id| (1, child_id)),
);
while let Some((offset, node_id)) = print_buffer.pop_front() {
let node = tree.node(node_id).expect("Shold have the node");
let name = node
- .borrow()
+ .try_borrow()?
.path()
.file_name()
.map(|name| name.to_str())
@@ -230,21 +238,21 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> {
.unwrap_or("UNKNOWN")
.to_owned();
let offset_text = " ".repeat(offset);
- if let Some(msg) = node.borrow().meta().inventory.as_ref() {
+ if let Some(msg) = node.try_borrow()?.meta().inventory.as_ref() {
let msg = msg.as_deref().unwrap_or("\x1b[31mno info\x1b[0m");
println!(
"{}\x1b[2m|\x1b[0m \x1b[32m/{}\x1b[0m \x1b[3m{}\x1b[0m",
offset_text, name, msg,
);
- } else if let Some(true) = node.borrow().meta().blacklist {
+ } else if let Some(true) = node.try_borrow()?.meta().blacklist {
println!("{}\x1b[2m|\x1b[0m \x1b[2;9m/{}\x1b[0m", offset_text, name);
- } else if node.borrow().is_leaf() {
+ } else if node.try_borrow()?.is_leaf() {
println!("{}\x1b[2m|\x1b[0m \x1b[31m/{}\x1b[0m", offset_text, name);
} else {
println!("{}\x1b[2m|\x1b[0m /{}", offset_text, name);
}
for child in node
- .borrow()
+ .try_borrow()?
.children()
.map(|child_id| (offset + 1, child_id))
.collect::<Vec<_>>()
@@ -265,6 +273,22 @@ pub enum Error {
State(#[from] state::Error),
#[error("Error setting up logging: {0}")]
Logging(#[from] logging::Error),
+ #[error("Error building filetree: {0}")]
+ Filetree(#[from] filetree::Error),
#[error("Borrow error: {0}")]
Borrow(#[from] BorrowError),
+ #[error("Borrow mut error: {0}")]
+ BorrowMut(#[from] BorrowMutError),
+ #[error("Error stripping root from path: {0}")]
+ RootPrefix(StripPrefixError),
+ #[error("Error stripping base from path: {0}")]
+ BasePrefix(StripPrefixError),
+ #[error("No inventory check info on tree node")]
+ NoInventoryCheckInfo,
+ #[error("Error executing command: {0}")]
+ Command(std::io::Error),
+ #[error("String returned by command is not utf8: {0}")]
+ StringUtf8(#[from] FromUtf8Error),
+ #[error("Error reading filesystem: {0}")]
+ ReadFs(tokio::io::Error),
}
diff --git a/src/state.rs b/src/state.rs
@@ -34,14 +34,13 @@ impl State {
.block_on(tokio::fs::read(inventory_path))
.map_err(Error::ReadFile)?;
- let inventory: Inventory = match config.inventory_format {
- InventoryFormat::Yaml => {
- serde_yaml::from_slice(inventory.as_slice()).map_err(Error::Yaml)?
- }
- InventoryFormat::Json => {
- serde_json::from_slice(inventory.as_slice()).map_err(Error::Json)?
- }
- };
+ let inventory: Inventory =
+ match config.inventory_format {
+ InventoryFormat::Yaml => serde_yaml::from_slice(inventory.as_slice())
+ .map_err(Error::ReadInventoryYaml)?,
+ InventoryFormat::Json => serde_json::from_slice(inventory.as_slice())
+ .map_err(Error::ReadInventoryJson)?,
+ };
let mut blacklist_tree = Filetree::new(Node::new("/".into(), ()));
let mut whitelist_tree = Filetree::new(Node::new("/".into(), ()));
@@ -136,10 +135,10 @@ pub enum Error {
Runtime(tokio::io::Error),
#[error("Error reading file: {0}")]
ReadFile(std::io::Error),
- #[error("Error parsing yaml: {0}")]
- Yaml(serde_yaml::Error),
- #[error("Error parsing json: {0}")]
- Json(serde_json::Error),
+ #[error("Error parsing yaml inventory: {0}")]
+ ReadInventoryYaml(serde_yaml::Error),
+ #[error("Error parsing json inventory: {0}")]
+ ReadInventoryJson(serde_json::Error),
#[error("Error building filetree: {0}")]
Filetree(#[from] filetree::Error),
}