use std::path::{Path, PathBuf};
use anyhow::{Result, anyhow};
use auths_keri::{Acdc, IpexAdmit, IpexGrant, Prefix};
use auths_utils::path::expand_tilde;
use clap::{Parser, Subcommand};
use crate::config::CliConfig;
const DEFAULT_DT: &str = "1970-01-01T00:00:00.000000+00:00";
#[derive(Parser, Debug, Clone)]
#[command(
about = "Exchange a credential over IPEX (grant/admit) — interoperable with keripy/KERIA",
after_help = "Examples:
auths ipex grant --acdc cred.json --sender EOoC... --recipient EBHn...
auths ipex admit --grant grant.json --sender EBHn..."
)]
pub struct IpexCommand {
#[command(subcommand)]
pub action: IpexAction,
}
#[derive(Subcommand, Debug, Clone)]
pub enum IpexAction {
Grant(GrantArgs),
Admit(AdmitArgs),
}
#[derive(Parser, Debug, Clone)]
pub struct GrantArgs {
#[clap(long, value_name = "ACDC.json")]
pub acdc: PathBuf,
#[clap(long, value_name = "AID")]
pub sender: String,
#[clap(long, value_name = "AID")]
pub recipient: String,
#[clap(long, default_value = "")]
pub message: String,
#[clap(long, default_value = DEFAULT_DT)]
pub dt: String,
}
#[derive(Parser, Debug, Clone)]
pub struct AdmitArgs {
#[clap(long, value_name = "GRANT.json")]
pub grant: PathBuf,
#[clap(long, value_name = "AID")]
pub sender: String,
#[clap(long, default_value = "")]
pub message: String,
#[clap(long, default_value = DEFAULT_DT)]
pub dt: String,
}
impl IpexCommand {
pub fn execute(&self, _ctx: &CliConfig) -> Result<()> {
match &self.action {
IpexAction::Grant(args) => args.run(),
IpexAction::Admit(args) => args.run(),
}
}
}
impl GrantArgs {
fn run(&self) -> Result<()> {
let sender = Prefix::new(self.sender.clone())
.map_err(|e| anyhow!("parse sender AID {:?}: {e}", self.sender))?;
let recipient = Prefix::new(self.recipient.clone())
.map_err(|e| anyhow!("parse recipient AID {:?}: {e}", self.recipient))?;
let acdc = read_acdc(&self.acdc)?;
let grant = IpexGrant::new(
sender,
recipient,
acdc,
self.message.clone(),
self.dt.clone(),
)
.map_err(|e| anyhow!("build IPEX grant: {e}"))?;
println!("{}", serde_json::to_string(&grant)?);
Ok(())
}
}
impl AdmitArgs {
fn run(&self) -> Result<()> {
let sender = Prefix::new(self.sender.clone())
.map_err(|e| anyhow!("parse sender AID {:?}: {e}", self.sender))?;
let path = expand_tilde(&self.grant)?;
let json = std::fs::read_to_string(&path)
.map_err(|e| anyhow!("read grant {}: {e}", path.display()))?;
let grant = IpexGrant::parse(&json).map_err(|e| anyhow!("parse IPEX grant: {e}"))?;
let admit = IpexAdmit::new(
sender,
grant.d.clone(),
self.message.clone(),
self.dt.clone(),
)
.map_err(|e| anyhow!("build IPEX admit: {e}"))?;
println!("{}", serde_json::to_string(&admit)?);
Ok(())
}
}
fn read_acdc(acdc_path: &Path) -> Result<Acdc> {
let path = expand_tilde(acdc_path)?;
let json =
std::fs::read_to_string(&path).map_err(|e| anyhow!("read ACDC {}: {e}", path.display()))?;
let acdc: Acdc =
serde_json::from_str(&json).map_err(|e| anyhow!("parse ACDC {}: {e}", path.display()))?;
acdc.verify_said()
.map_err(|e| anyhow!("verify ACDC SAID {}: {e}", path.display()))?;
Ok(acdc)
}