Skip to main content

singularity_cli/
config.rs

1use std::fs;
2use std::path::PathBuf;
3
4use anyhow::{Context, Result, bail};
5
6const ENV_VAR: &str = "SINGULARITY_TOKEN";
7
8fn config_path() -> Result<PathBuf> {
9    let config_dir = dirs::config_dir().context("could not determine config directory")?;
10    Ok(config_dir.join("singularity").join("config.toml"))
11}
12
13pub fn resolve_token() -> Result<String> {
14    if let Ok(token) = std::env::var(ENV_VAR)
15        && !token.is_empty()
16    {
17        return Ok(token);
18    }
19
20    let path = config_path()?;
21    if path.exists() {
22        let content = fs::read_to_string(&path)
23            .with_context(|| format!("failed to read {}", path.display()))?;
24        let table: toml::Table = content.parse().context("invalid config file")?;
25        if let Some(token) = table.get("token").and_then(|v| v.as_str())
26            && !token.is_empty()
27        {
28            return Ok(token.to_string());
29        }
30    }
31
32    bail!(
33        "no API token found. Set {} env var or run: singularity config set-token <TOKEN>",
34        ENV_VAR
35    )
36}
37
38pub fn set_token(token: &str) -> Result<()> {
39    let path = config_path()?;
40    if let Some(parent) = path.parent() {
41        fs::create_dir_all(parent)
42            .with_context(|| format!("failed to create {}", parent.display()))?;
43    }
44    let content = format!("token = \"{}\"\n", token);
45    fs::write(&path, content).with_context(|| format!("failed to write {}", path.display()))?;
46    println!("Token saved to {}", path.display());
47    Ok(())
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn resolve_token_from_env_var() {
56        let key = "SINGULARITY_TOKEN_TEST_1";
57        unsafe { std::env::set_var(key, "my-secret") };
58        let val = std::env::var(key).unwrap();
59        assert_eq!(val, "my-secret");
60        unsafe { std::env::remove_var(key) };
61    }
62
63    #[test]
64    fn resolve_token_reads_toml() {
65        let content = "token = \"from-file\"\n";
66        let table: toml::Table = content.parse().unwrap();
67        let token = table.get("token").and_then(|v| v.as_str()).unwrap();
68        assert_eq!(token, "from-file");
69    }
70
71    #[test]
72    fn resolve_token_empty_toml_yields_none() {
73        let content = "token = \"\"\n";
74        let table: toml::Table = content.parse().unwrap();
75        let token = table.get("token").and_then(|v| v.as_str()).unwrap();
76        assert!(token.is_empty());
77    }
78
79    #[test]
80    fn config_path_is_under_config_dir() {
81        let path = config_path().unwrap();
82        assert!(path.ends_with("singularity/config.toml"));
83    }
84}