6 Commits

Author SHA1 Message Date
0e3aa16e00 Another autoformat 2025-07-20 17:36:49 -05:00
04dd333d72 Fix: use default "[all]" if one isn't present
Same thing as the previous commit, but for the "[all]" table.
2025-07-20 17:33:55 -05:00
13ef1d25eb Fix: use empty PartialConfig if proj conf missing
If there is no project-specific configuration, use a default one
instead. It still needs to be merged with the "[all]" one, assuming that
exists.

Now to do the same thing for the all-table.
2025-07-20 17:32:11 -05:00
56b0580a9a Add docstring for PartialConfig::try_from()
I started to replace this with an infallible `try()` implementation
before realizing that this exists specifically to filter out the
no-such-table result. That isn't an error *in this context*, which is
what the try_from() is doing for me.
2025-07-20 17:23:44 -05:00
46d8618e74 Fix config unit tests: project path is set!
The project path value gets set as a side-effect of loading the named
configuration table. Which... actually means this information isn't
important. I know it going in, and I know it coming out. I think the
real fix is to delete the field.
2025-07-20 17:23:15 -05:00
73363718c3 Add test for skipping unavailable conf files
Missing config files aren't an error. Make sure there isn't some kind of
early return logic that emits broken data.
2025-07-20 16:06:25 -05:00
3 changed files with 59 additions and 21 deletions

View File

@@ -105,15 +105,19 @@ pub fn get_config(
// 2. Get table
get_table(cfg_table, project)
// 3. convert to PartialConfig
// 3a. convert to PartialConfig
.and_then(PartialConfig::try_from)
// 3b. or default, if the table couldn't be found.
.or(Ok(PartialConfig::default()))
// 4. assemble a 2-tuple of PartialConfigs by...
.and_then(|proj| {
Ok((
// 4-1. Passing in the project-specific PartialConfig
proj.project_path(project),
// 4-2. Getting and converting to PartialConfig, or returning any Err() if one appears.
get_table(cfg_table, "all").and_then(PartialConfig::try_from)?,
get_table(cfg_table, "all")
.and_then(PartialConfig::try_from)
.unwrap_or(PartialConfig::default()),
))
})
.map(|pair| pair.0.merge(pair.1))
@@ -161,6 +165,12 @@ impl PartialConfig {
impl TryFrom<&Table> for PartialConfig {
type Error = crate::config::Error;
/// Scans properties out of a `toml::Table` to get a PartialConfig.
///
/// `Error::NoSuchProperty` is quietly ignored (mapped to `None`) since it
/// isn't an error in this context.
///
/// All other errors are propagated and should be treated as real failures.
fn try_from(value: &Table) -> Result<Self> {
Ok(Self {
// can't get table name because that key is gone by this point.
@@ -285,7 +295,7 @@ mod tests {
.map(PathBuf::from);
let load_result = get_config("/no/such/project", search_paths)?;
let expected = PartialConfig {
project_path: None,
project_path: Some(String::from("/no/such/project")),
owner: None,
repo: None,
gitea_url: Some(String::from("http://localhost:3000")),
@@ -304,10 +314,39 @@ mod tests {
.map(PathBuf::from);
let load_result = get_config("/some/other/path", search_paths)?;
let expected = PartialConfig {
gitea_url: Some(String::from("fake-url")),
..PartialConfig::default()
project_path: Some(String::from("/some/other/path")),
gitea_url: Some(String::from("fake-url")),
..PartialConfig::default()
};
assert_eq!(load_result, expected);
Ok(())
}
// Ensure that trying to load files that don't exist simply get skipped over
// instead of causing a short-circuit exit or other bogus output.
#[test]
fn test_get_config_many_missing_files() -> Result<()> {
let search_paths = [
"./test_data/not_real_1.toml",
"./test_data/not_real_2.toml",
"./test_data/not_real_3.toml",
"./test_data/not_real_4.toml",
"./test_data/not_real_5.toml",
"./test_data/sample_config.toml",
"./test_data/not_real_6.toml",
]
.into_iter()
.map(PathBuf::from);
let load_result = get_config("/home/robert/projects/gt-tool", search_paths)?;
let expected = PartialConfig {
project_path: Some(String::from("/home/robert/projects/gt-tool")),
owner: Some(String::from("robert")),
repo: Some(String::from("gt-tool")),
gitea_url: Some(String::from("http://localhost:3000")),
token: Some(String::from("fake-token")),
};
assert_eq!(load_result, expected);
Ok(())
}
}

View File

@@ -20,7 +20,7 @@ pub(crate) async fn decode_client_error(response: reqwest::Response) -> Result<A
#[derive(Debug)]
pub enum Error {
Placeholder, // TODO: Enumerate error modes
Placeholder, // TODO: Enumerate error modes
MissingGiteaUrl, // the gitea URL wasn't specified on the CLI, env, or config file.
MissingRepoFRQN, // either the owner, repo, or both weren't specified in the loaded PartialConfig
WrappedConfigErr(config::Error),

View File

@@ -15,31 +15,34 @@ async fn main() -> Result<(), gt_tool::Error> {
// TODO: Heuristics to guess project path
// See issue #8: https://git.gelvin.dev/robert/gt-tool/issues/8
let pwd = std::env::current_dir()
.map_err(|_e| gt_tool::Error::WrappedConfigErr(
gt_tool::config::Error::CouldntReadFile
))?;
.map_err(|_e| gt_tool::Error::WrappedConfigErr(gt_tool::config::Error::CouldntReadFile))?;
let config = gt_tool::config::get_config(
pwd.to_str().expect("I assumed the path can be UTF-8, but that didn't work out..."),
gt_tool::config::default_paths()
pwd.to_str()
.expect("I assumed the path can be UTF-8, but that didn't work out..."),
gt_tool::config::default_paths(),
)?;
println!("->> Loaded Config: {config:?}");
// arg parser also checks the environment. Prefer CLI/env, then config file.
let gitea_url = args.gitea_url.or(config.gitea_url).ok_or(gt_tool::Error::MissingGiteaUrl)?;
let gitea_url = args
.gitea_url
.or(config.gitea_url)
.ok_or(gt_tool::Error::MissingGiteaUrl)?;
// Config files split the repo FQRN into "owner" and "repo" (confusing naming, sorry)
// These must be merged back together and passed along.
let conf_fqrn = config.owner
let conf_fqrn = config
.owner
.ok_or(gt_tool::Error::MissingRepoFRQN)
.and_then(| mut own| {
.and_then(|mut own| {
let repo = config.repo.ok_or(gt_tool::Error::MissingRepoFRQN)?;
own.push_str("/");
own.push_str(&repo);
Ok(own)
});
let repo_fqrn = args.repo
let repo_fqrn = args
.repo
.ok_or(gt_tool::Error::MissingRepoFRQN)
.or(conf_fqrn)?;
let mut headers = reqwest::header::HeaderMap::new();
headers.append(ACCEPT, header::HeaderValue::from_static("application/json"));
@@ -121,11 +124,7 @@ async fn main() -> Result<(), gt_tool::Error> {
}
for file in files {
let _attach_desc = gt_tool::api::release_attachment::create_release_attachment(
&client,
&gitea_url,
&repo_fqrn,
release.id,
file,
&client, &gitea_url, &repo_fqrn, release.id, file,
)
.await?;
}