singularity_cli/
config.rs1use 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}