diff --git a/crates/tombi-config/src/error.rs b/crates/tombi-config/src/error.rs index 8ef57fe31..782cd6f76 100644 --- a/crates/tombi-config/src/error.rs +++ b/crates/tombi-config/src/error.rs @@ -9,10 +9,10 @@ pub enum Error { #[error("config file not found: {config_path:?}")] ConfigFileNotFound { config_path: std::path::PathBuf }, - #[error("failed to read {config_path:?}")] + #[error("failed to read config file {config_path:?}")] ConfigFileReadFailed { config_path: std::path::PathBuf }, - #[error("failed to parse {config_path:?}")] + #[error("failed to parse config file {config_path:?}")] ConfigFileParseFailed { config_path: std::path::PathBuf }, #[error("unsupported config file: {config_path:?}")] diff --git a/rust/serde_tombi/src/config.rs b/rust/serde_tombi/src/config.rs index f323e4f36..f73dd0c69 100644 --- a/rust/serde_tombi/src/config.rs +++ b/rust/serde_tombi/src/config.rs @@ -58,6 +58,28 @@ struct Tool { tombi: Option, } +#[inline] +fn config_file_parse_error(config_path: &std::path::Path) -> tombi_config::Error { + let error = tombi_config::Error::ConfigFileParseFailed { + config_path: config_path.to_owned(), + }; + + log::warn!("{}", error); + + error +} + +#[inline] +fn config_file_read_error(config_path: &std::path::Path) -> tombi_config::Error { + let error = tombi_config::Error::ConfigFileReadFailed { + config_path: config_path.to_owned(), + }; + + log::warn!("{}", error); + + error +} + pub fn try_from_path>( config_path: P, ) -> Result, tombi_config::Error> { @@ -69,27 +91,18 @@ pub fn try_from_path>( }); } - let Ok(config_text) = std::fs::read_to_string(config_path) else { - return Err(tombi_config::Error::ConfigFileReadFailed { - config_path: config_path.to_owned(), - }); - }; + let config_text = + std::fs::read_to_string(config_path).map_err(|_| config_file_read_error(config_path))?; match config_path.file_name().and_then(|name| name.to_str()) { Some(DOT_TOMBI_TOML_FILENAME | TOMBI_TOML_FILENAME | CONFIG_TOML_FILENAME) => { - match crate::config::from_str(&config_text, config_path) { - Ok(tombi_config) => Ok(Some(tombi_config)), - Err(_) => Err(tombi_config::Error::ConfigFileParseFailed { - config_path: config_path.to_owned(), - }), - } + crate::config::from_str(&config_text, config_path) + .map(Some) + .map_err(|_| config_file_parse_error(config_path)) } Some(PYPROJECT_TOML_FILENAME) => { - let Ok(pyproject_toml) = PyProjectToml::from_str(&config_text, config_path) else { - return Err(tombi_config::Error::ConfigFileParseFailed { - config_path: config_path.to_owned(), - }); - }; + let pyproject_toml = PyProjectToml::from_str(&config_text, config_path) + .map_err(|_| config_file_parse_error(config_path))?; if let Some(Tool { tombi: Some(tombi_config), }) = pyproject_toml.tool @@ -119,14 +132,18 @@ pub fn load_with_path_and_level( if config_path.is_file() { log::debug!("Project config found at {:?}", config_path); - let Some(config) = try_from_path(&config_path)? else { - unreachable!( - "project config should always be parsed successfully: {:?}", - config_path - ); - }; - - return Ok((config, Some(config_path), ConfigLevel::Project)); + match try_from_path(&config_path) { + Ok(Some(config)) => { + return Ok((config, Some(config_path), ConfigLevel::Project)); + } + Ok(None) => { + unreachable!( + "project config should always be parsed successfully: {:?}", + config_path + ); + } + Err(_) => {} + } } } @@ -139,20 +156,13 @@ pub fn load_with_path_and_level( pyproject_toml_path ); - match try_from_path(&pyproject_toml_path) { - Ok(Some(config)) => { + match try_from_path(&pyproject_toml_path).ok().flatten() { + Some(config) => { return Ok((config, Some(pyproject_toml_path), ConfigLevel::Project)); } - Ok(None) => { + None => { log::debug!("No [tool.tombi] found in {:?}", pyproject_toml_path); } - Err(error) => { - log::debug!( - "Failed to parse pyproject.toml file for config at {:?}: {}", - pyproject_toml_path, - error - ); - } }; } @@ -165,15 +175,19 @@ pub fn load_with_path_and_level( if let Some((user_config_path, config_level)) = get_user_or_system_tombi_config_path_and_level() { log::debug!("{CONFIG_TOML_FILENAME} found at {:?}", user_config_path); - let Some(config) = try_from_path(&user_config_path)? else { - unreachable!("{CONFIG_TOML_FILENAME} should always be parsed successfully."); - }; - Ok((config, Some(user_config_path), config_level)) - } else { - log::debug!("config file not found, use default config"); - - Ok((Config::default(), None, ConfigLevel::Default)) + match try_from_path(&user_config_path).ok().flatten() { + Some(config) => { + return Ok((config, Some(user_config_path), config_level)); + } + None => { + unreachable!("{CONFIG_TOML_FILENAME} should always be parsed successfully."); + } + } } + + log::debug!("config file not found, use default config"); + + Ok((Config::default(), None, ConfigLevel::Default)) } #[inline] @@ -265,6 +279,29 @@ mod tests { .unwrap() } + #[test] + fn loads_next_project_config_after_parse_error() { + let temp_dir = temp_test_dir("config-parse-error-fallback"); + let nested_dir = temp_dir.path().join("workspace/nested"); + + std::fs::create_dir_all(&nested_dir).unwrap(); + write_file( + &temp_dir.path().join(DOT_TOMBI_TOML_FILENAME), + "toml-version = ", + ); + write_file( + &temp_dir.path().join(TOMBI_TOML_FILENAME), + "toml-version = \"v1.0.0\"\n", + ); + + let (config, config_path, config_level) = + load_with_path_and_level(Some(nested_dir)).unwrap(); + + assert_eq!(config.toml_version, Some(TomlVersion::V1_0_0)); + assert_eq!(config_path, Some(temp_dir.path().join(TOMBI_TOML_FILENAME))); + assert_eq!(config_level, ConfigLevel::Project); + } + #[test] fn loads_project_config_from_dot_config_tombi_toml_before_pyproject() { let temp_dir = temp_test_dir("dot-config-tombi");