1use secrecy::{ExposeSecret, SecretString};
4
5use crate::error::{MktError, Result};
6
7pub fn resolve_token(
19 provider: &str,
20 env_var_name: &str,
21 config_value: Option<&str>,
22) -> Result<SecretString> {
23 if let Ok(token) = std::env::var(env_var_name) {
25 if !token.is_empty() {
26 return Ok(SecretString::from(token));
27 }
28 }
29
30 if let Some(token) = config_value {
32 if !token.is_empty() {
33 return Ok(SecretString::from(token.to_owned()));
34 }
35 }
36
37 Err(MktError::auth_error(
39 provider,
40 &format!("No access token found. Set {env_var_name} or configure it in your profile."),
41 ))
42}
43
44pub fn validate_token(token: &SecretString) -> bool {
46 !token.expose_secret().is_empty()
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 #[allow(clippy::expect_used)]
55 fn resolve_from_config_value() {
56 let key = "MKT_TEST_TOKEN_DEFINITELY_NOT_SET_98765";
58 let result = resolve_token("test", key, Some("config-token"));
59 let token = result.expect("should resolve");
60 assert_eq!(token.expose_secret(), "config-token");
61 }
62
63 #[test]
64 #[allow(clippy::panic)]
65 fn missing_token_returns_auth_error() {
66 let key = "MKT_TEST_TOKEN_DEFINITELY_NOT_SET_11111";
67 let result = resolve_token("test", key, None);
68 let Err(e) = result else {
69 panic!("expected auth error");
70 };
71 assert!(e.to_string().contains("No access token"));
72 }
73
74 #[test]
75 fn empty_config_value_returns_error() {
76 let key = "MKT_TEST_TOKEN_DEFINITELY_NOT_SET_22222";
77 let result = resolve_token("test", key, Some(""));
78 assert!(result.is_err());
79 }
80
81 #[test]
82 fn env_var_resolution_with_existing_var() {
83 let result = resolve_token("test", "PATH", None);
85 assert!(result.is_ok());
86 }
87
88 #[test]
89 fn validate_non_empty_token() {
90 let token = SecretString::from("valid".to_owned());
91 assert!(validate_token(&token));
92 }
93
94 #[test]
95 fn validate_empty_token() {
96 let token = SecretString::from(String::new());
97 assert!(!validate_token(&token));
98 }
99}