use std::io::Write;
use serde::Serialize;
use crate::auth::{fetch_cred, AuthCtx, CredSource};
use crate::canonical::{CanonicalError, ErrorKind};
use crate::config::provider::{AuthId, Provider};
use crate::config::{config_path, defaults, partial_from_env, read_config_file, OutMode};
use crate::store::{CredStore, Secret};
pub struct ProvidersIo<'a> {
pub stdout: &'a mut dyn Write,
pub stderr: &'a mut dyn Write,
pub store: &'a dyn CredStore,
}
#[derive(Serialize)]
struct Row {
name: String,
protocol: String,
auth: String,
credential: &'static str,
}
pub fn list_providers(args: &crate::cli::Args, io: &mut ProvidersIo) -> u8 {
match run_list(args, io) {
Ok(code) => code,
Err(e) => {
let _ = writeln!(io.stderr, "{}", e.message);
e.exit_code()
}
}
}
fn run_list(args: &crate::cli::Args, io: &mut ProvidersIo) -> Result<u8, CanonicalError> {
let flags = crate::cli::parse_args(&args.argv)?;
if flags.help {
return Ok(super::emit(io.stdout, super::HELP));
}
if flags.skill {
return Ok(super::emit(io.stdout, super::SKILL));
}
if flags.version {
return Ok(super::emit(io.stdout, super::VERSION_LINE));
}
let file = read_config_file(&config_path(flags.config_path, &args.env))?;
let env = partial_from_env(&args.env).map_err(CanonicalError::from)?;
let merged = flags.config.or(env).or(file).or(defaults());
let json = merged.output == Some(OutMode::Ndjson);
let inline = merged.api_key.clone();
let rows: Vec<Row> = merged
.into_rows()?
.iter()
.map(|p| row(p, inline.as_ref(), io.store))
.collect();
print_rows(io.stdout, &rows, json).map_err(write_failed)?;
Ok(0)
}
fn row(provider: &Provider, inline: Option<&Secret>, store: &dyn CredStore) -> Row {
Row {
name: provider.name.clone(),
protocol: spelling(&provider.protocol),
auth: spelling(&provider.auth),
credential: credential(provider, inline, store),
}
}
fn spelling<T: Serialize>(id: &T) -> String {
serde_json::to_value(id)
.ok()
.and_then(|v| v.as_str().map(str::to_owned))
.unwrap_or_default()
}
fn credential(provider: &Provider, inline: Option<&Secret>, store: &dyn CredStore) -> &'static str {
if provider.auth == AuthId::None {
return "not required";
}
if provider.auth != AuthId::OAuth2 && inline.is_some() {
return "inline";
}
let ctx = AuthCtx {
store_key: &provider.name,
inline_key: None,
api_header: provider.api_header.as_ref(),
oauth: provider.oauth.as_ref(),
ambient: provider.ambient.as_ref(),
};
match fetch_cred(store, &ctx) {
Some(f) if f.source == CredSource::Owned => "stored",
Some(_) => "ambient",
None => "missing",
}
}
fn print_rows(out: &mut dyn Write, rows: &[Row], json: bool) -> std::io::Result<()> {
if json {
let obj = serde_json::json!({ "providers": rows });
return writeln!(out, "{obj}");
}
let (name, protocol, auth) = (
width(rows, |r| &r.name),
width(rows, |r| &r.protocol),
width(rows, |r| &r.auth),
);
for r in rows {
writeln!(
out,
"{:name$} {:protocol$} {:auth$} {}",
r.name, r.protocol, r.auth, r.credential
)?;
}
Ok(())
}
fn width(rows: &[Row], field: impl Fn(&Row) -> &String) -> usize {
rows.iter().map(|r| field(r).len()).max().unwrap_or(0)
}
fn write_failed(e: std::io::Error) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Transport,
message: format!("failed to write provider list: {e}"),
provider_detail: None,
retry_after_seconds: None,
}
}