use std::io::Write;
use std::num::NonZeroU64;
use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Duration;
use super::lys_pass::{LysAgent, Proof};
const USAGE: &str = "usage: haematite mcp-pass --lys-agent <issuer origin> <agent id> ((--credential-file | --key-file | --launch-proof-file) <absolute path> | --run-pass-env <variable>) [--timeout-ms N]";
const DEFAULT_TIMEOUT_MS: u64 = 10_000;
pub fn run(args: impl Iterator<Item = String>) -> ExitCode {
match run_inner(args) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("haematite mcp-pass: {error}");
ExitCode::FAILURE
}
}
}
fn run_inner(args: impl Iterator<Item = String>) -> Result<(), String> {
let mut agent = parse(args)?;
let pass = agent
.ask_now()
.map_err(|refusal| format!("{}: {}", refusal.code, refusal.message))?;
let line = header(pass).map_err(|error| format!("mcp_pass_unwritten: {error}"))?;
let mut stdout = std::io::stdout().lock();
stdout
.write_all(line.as_bytes())
.and_then(|()| stdout.flush())
.map_err(|error| format!("mcp_pass_unwritten: {}", error.kind()))
}
pub(super) fn header(pass: &str) -> Result<String, serde_json::Error> {
let mut line = serde_json::to_string(&serde_json::json!({
"Authorization": format!("Bearer {pass}"),
}))?;
line.push('\n');
Ok(line)
}
pub(super) fn parse(mut args: impl Iterator<Item = String>) -> Result<LysAgent, String> {
let mut lys_agent = None;
let mut proof = None;
let mut timeout_ms = None;
let path = |value: Option<String>| match value {
Some(value) if !value.is_empty() && !value.starts_with("--") => Ok(value),
_ => Err(USAGE.to_owned()),
};
while let Some(arg) = args.next() {
match arg.as_str() {
"--lys-agent" if lys_agent.is_none() => {
lys_agent = Some((path(args.next())?, path(args.next())?));
}
"--credential-file" if proof.is_none() => {
proof = Some(Proof::Grant(PathBuf::from(path(args.next())?)));
}
"--key-file" if proof.is_none() => {
proof = Some(Proof::Key(PathBuf::from(path(args.next())?)));
}
"--launch-proof-file" if proof.is_none() => {
proof = Some(Proof::Launch(PathBuf::from(path(args.next())?)));
}
"--run-pass-env" if proof.is_none() => {
proof = Some(Proof::RunPass(path(args.next())?));
}
"--timeout-ms" if timeout_ms.is_none() => {
let value = path(args.next())?;
timeout_ms =
Some(value.parse::<NonZeroU64>().map_err(|_| {
"timeout requires positive integer milliseconds".to_owned()
})?);
}
_ => return Err(USAGE.to_owned()),
}
}
let (Some((issuer, agent)), Some(proof)) = (lys_agent, proof) else {
return Err(USAGE.to_owned());
};
let timeout = Duration::from_millis(timeout_ms.map_or(DEFAULT_TIMEOUT_MS, NonZeroU64::get));
LysAgent::new(&issuer, &agent, proof, timeout).map_err(str::to_owned)
}
#[cfg(test)]
#[path = "pass_header_tests.rs"]
mod tests;