From 7eb23d9f3cae0876dec541f06da633b066f583e7 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Sat, 4 Oct 2025 01:22:02 -0400 Subject: [PATCH] refactor(config)!: transition from anyhow to thiserror --- Cargo.lock | 2 +- crates/config/Cargo.toml | 2 +- crates/config/src/tree_sitter_config.rs | 79 ++++++++++++++++++++----- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1297ecd3..726289b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2035,11 +2035,11 @@ dependencies = [ name = "tree-sitter-config" version = "0.26.0" dependencies = [ - "anyhow", "etcetera", "log", "serde", "serde_json", + "thiserror 2.0.16", ] [[package]] diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index b9bc239e..641b434b 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -20,8 +20,8 @@ path = "src/tree_sitter_config.rs" workspace = true [dependencies] -anyhow.workspace = true etcetera.workspace = true log.workspace = true serde.workspace = true serde_json.workspace = true +thiserror.workspace = true diff --git a/crates/config/src/tree_sitter_config.rs b/crates/config/src/tree_sitter_config.rs index 85dc003d..17b1d384 100644 --- a/crates/config/src/tree_sitter_config.rs +++ b/crates/config/src/tree_sitter_config.rs @@ -1,12 +1,54 @@ #![cfg_attr(not(any(test, doctest)), doc = include_str!("../README.md"))] -use std::{env, fs, path::PathBuf}; +use std::{ + env, fs, + path::{Path, PathBuf}, +}; -use anyhow::{Context, Result}; use etcetera::BaseStrategy as _; use log::warn; use serde::{Deserialize, Serialize}; use serde_json::Value; +use thiserror::Error; + +pub type ConfigResult = Result; + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("Bad JSON config {0} -- {1}")] + ConfigRead(String, serde_json::Error), + #[error(transparent)] + HomeDir(#[from] etcetera::HomeDirError), + #[error(transparent)] + IO(IoError), + #[error(transparent)] + Serialization(#[from] serde_json::Error), +} + +#[derive(Debug, Error)] +pub struct IoError { + pub error: std::io::Error, + pub path: Option, +} + +impl IoError { + fn new(error: std::io::Error, path: Option<&Path>) -> Self { + Self { + error, + path: path.map(|p| p.to_string_lossy().to_string()), + } + } +} + +impl std::fmt::Display for IoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.error)?; + if let Some(ref path) = self.path { + write!(f, " ({path})")?; + } + Ok(()) + } +} /// Holds the contents of tree-sitter's configuration file. /// @@ -23,7 +65,7 @@ pub struct Config { } impl Config { - pub fn find_config_file() -> Result> { + pub fn find_config_file() -> ConfigResult> { if let Ok(path) = env::var("TREE_SITTER_DIR") { let mut path = PathBuf::from(path); path.push("config.json"); @@ -46,8 +88,12 @@ impl Config { .join("tree-sitter") .join("config.json"); if legacy_apple_path.is_file() { - fs::create_dir_all(xdg_path.parent().unwrap())?; - fs::rename(&legacy_apple_path, &xdg_path)?; + let xdg_dir = xdg_path.parent().unwrap(); + fs::create_dir_all(xdg_dir) + .map_err(|e| ConfigError::IO(IoError::new(e, Some(xdg_dir))))?; + fs::rename(&legacy_apple_path, &xdg_path).map_err(|e| { + ConfigError::IO(IoError::new(e, Some(legacy_apple_path.as_path()))) + })?; warn!( "Your config.json file has been automatically migrated from \"{}\" to \"{}\"", legacy_apple_path.display(), @@ -67,7 +113,7 @@ impl Config { Ok(None) } - fn xdg_config_file() -> Result { + fn xdg_config_file() -> ConfigResult { let xdg_path = etcetera::choose_base_strategy()? .config_dir() .join("tree-sitter") @@ -84,7 +130,7 @@ impl Config { /// [`etcetera::choose_base_strategy`](https://docs.rs/etcetera/*/etcetera/#basestrategy) /// - `$HOME/.tree-sitter/config.json` as a fallback from where tree-sitter _used_ to store /// its configuration - pub fn load(path: Option) -> Result { + pub fn load(path: Option) -> ConfigResult { let location = if let Some(path) = path { path } else if let Some(path) = Self::find_config_file()? { @@ -94,9 +140,9 @@ impl Config { }; let content = fs::read_to_string(&location) - .with_context(|| format!("Failed to read {}", location.to_string_lossy()))?; + .map_err(|e| ConfigError::IO(IoError::new(e, Some(location.as_path()))))?; let config = serde_json::from_str(&content) - .with_context(|| format!("Bad JSON config {}", location.to_string_lossy()))?; + .map_err(|e| ConfigError::ConfigRead(location.to_string_lossy().to_string(), e))?; Ok(Self { location, config }) } @@ -106,7 +152,7 @@ impl Config { /// disk. /// /// (Note that this is typically only done by the `tree-sitter init-config` command.) - pub fn initial() -> Result { + pub fn initial() -> ConfigResult { let location = if let Ok(path) = env::var("TREE_SITTER_DIR") { let mut path = PathBuf::from(path); path.push("config.json"); @@ -119,17 +165,20 @@ impl Config { } /// Saves this configuration to the file that it was originally loaded from. - pub fn save(&self) -> Result<()> { + pub fn save(&self) -> ConfigResult<()> { let json = serde_json::to_string_pretty(&self.config)?; - fs::create_dir_all(self.location.parent().unwrap())?; - fs::write(&self.location, json)?; + let config_dir = self.location.parent().unwrap(); + fs::create_dir_all(config_dir) + .map_err(|e| ConfigError::IO(IoError::new(e, Some(config_dir))))?; + fs::write(&self.location, json) + .map_err(|e| ConfigError::IO(IoError::new(e, Some(self.location.as_path()))))?; Ok(()) } /// Parses a component-specific configuration from the configuration file. The type `C` must /// be [deserializable](https://docs.rs/serde/*/serde/trait.Deserialize.html) from a JSON /// object, and must only include the fields relevant to that component. - pub fn get(&self) -> Result + pub fn get(&self) -> ConfigResult where C: for<'de> Deserialize<'de>, { @@ -140,7 +189,7 @@ impl Config { /// Adds a component-specific configuration to the configuration file. The type `C` must be /// [serializable](https://docs.rs/serde/*/serde/trait.Serialize.html) into a JSON object, and /// must only include the fields relevant to that component. - pub fn add(&mut self, config: C) -> Result<()> + pub fn add(&mut self, config: C) -> ConfigResult<()> where C: Serialize, {