git-credential-idcat 0.1.0

Git credential helper that obtains GitHub installation tokens from idcat
use crate::config::TokenSource;
use anyhow::{Context, bail};
use std::path::Path;
use std::process::Command;

pub fn read_token(source: &TokenSource) -> anyhow::Result<String> {
    match source {
        TokenSource::Path(path) => read_token_path(path),
        TokenSource::Command(command) => run_token_command(command),
    }
}

fn read_token_path(path: &Path) -> anyhow::Result<String> {
    let token = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read token-path: {}", path.display()))?;
    trim_token(token, "token-path produced an empty token")
}

fn run_token_command(command: &str) -> anyhow::Result<String> {
    let output = Command::new("/bin/sh")
        .arg("-c")
        .arg(command)
        .output()
        .with_context(|| format!("failed to execute token-command: {command}"))?;

    if !output.status.success() {
        bail!(
            "token-command exited with status {}",
            output
                .status
                .code()
                .map_or_else(|| "unknown".to_owned(), |code| code.to_string())
        );
    }

    let token = String::from_utf8(output.stdout).context("token-command output was not UTF-8")?;
    trim_token(token, "token-command produced an empty token")
}

fn trim_token(token: String, empty_message: &str) -> anyhow::Result<String> {
    let token = token.trim();
    if token.is_empty() {
        bail!("{empty_message}");
    }

    Ok(token.to_owned())
}