1use std::process::Command;
7
8#[cfg(test)]
9use secrecy::ExposeSecret;
10use secrecy::SecretString;
11
12use crate::error::{Error, Result};
13
14#[derive(Debug, Clone)]
16pub enum Auth {
17 GhCli,
19
20 EnvVar(String),
22
23 Token(SecretString),
25}
26
27impl Auth {
28 #[must_use]
32 pub fn auto() -> Self {
33 if std::env::var("GITHUB_TOKEN").is_ok() {
34 Self::EnvVar("GITHUB_TOKEN".into())
35 } else {
36 Self::GhCli
37 }
38 }
39
40 pub fn resolve(&self) -> Result<SecretString> {
47 match self {
48 Self::GhCli => get_gh_token(),
49 Self::EnvVar(var) => std::env::var(var)
50 .map(SecretString::from)
51 .map_err(|_| Error::NoToken),
52 Self::Token(t) => Ok(t.clone()),
53 }
54 }
55}
56
57impl Default for Auth {
58 fn default() -> Self {
59 Self::auto()
60 }
61}
62
63fn get_gh_token() -> Result<SecretString> {
65 let output = Command::new("gh").args(["auth", "token"]).output()?;
66
67 if !output.status.success() {
68 return Err(Error::NoToken);
69 }
70
71 let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
72
73 if token.is_empty() {
74 return Err(Error::NoToken);
75 }
76
77 Ok(SecretString::from(token))
78}
79
80#[cfg(test)]
81#[allow(clippy::unwrap_used)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_auth_auto_prefers_env() {
87 let _auth = Auth::auto();
89 }
90
91 #[test]
92 fn test_token_auth() {
93 let auth = Auth::Token(SecretString::from("test_token"));
94 assert_eq!(auth.resolve().unwrap().expose_secret(), "test_token");
95 }
96}