Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/tombi-config/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}")]
Expand Down
121 changes: 79 additions & 42 deletions rust/serde_tombi/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@ struct Tool {
tombi: Option<Config>,
}

#[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<P: AsRef<std::path::Path>>(
config_path: P,
) -> Result<Option<Config>, tombi_config::Error> {
Expand All @@ -69,27 +91,18 @@ pub fn try_from_path<P: AsRef<std::path::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
Expand Down Expand Up @@ -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(_) => {}
}
}
}

Expand All @@ -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
);
}
};
}

Expand All @@ -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]
Expand Down Expand Up @@ -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");
Expand Down
Loading