use std::path::{Path, PathBuf};
use clap::Subcommand;
use vta_cli_common::consent_approve;
use vta_cli_common::render::bin_name;
use vta_sdk::task_consent::VerifiedConsentRequest;
use vtc_client::VtcError;
use crate::vtc::{self, VtcTarget};
type CliResult<T> = Result<T, Box<dyn std::error::Error>>;
#[derive(Subcommand)]
pub enum ConsentCommands {
Show {
request: PathBuf,
},
Approve {
request: PathBuf,
#[arg(long)]
match_code: Option<String>,
#[arg(long)]
reason: Option<String>,
},
Deny {
request: PathBuf,
#[arg(long)]
reason: Option<String>,
},
}
pub async fn run(command: ConsentCommands, keyring_key: &str, target: &VtcTarget) -> CliResult<()> {
match command {
ConsentCommands::Show { request } => {
let verified = load(&request, keyring_key, target).await?;
consent_approve::render(&verified);
Ok(())
}
ConsentCommands::Approve {
request,
match_code,
reason,
} => {
let verified = load(&request, keyring_key, target).await?;
consent_approve::render(&verified);
consent_approve::confirm_match_code(&verified, match_code.as_deref(), bin_name())?;
decide(&verified, true, reason.as_deref(), keyring_key, target).await
}
ConsentCommands::Deny { request, reason } => {
let verified = load(&request, keyring_key, target).await?;
consent_approve::render(&verified);
decide(&verified, false, reason.as_deref(), keyring_key, target).await
}
}
}
async fn load(
path: &Path,
keyring_key: &str,
target: &VtcTarget,
) -> CliResult<VerifiedConsentRequest> {
let approver = crate::auth::loaded_session(keyring_key)
.ok_or_else(|| {
format!(
"no stored identity for this community profile. Run `{} setup` first.",
bin_name()
)
})?
.client_did;
consent_approve::load(path, &approver, &target.did, bin_name()).await
}
async fn decide(
req: &VerifiedConsentRequest,
approve: bool,
reason: Option<&str>,
keyring_key: &str,
target: &VtcTarget,
) -> CliResult<()> {
let decision = req
.decision(approve, reason)
.map_err(|e| format!("could not build the decision: {e}"))?;
let vtc = vtc::connect(keyring_key, target).await?;
let response = vtc
.client
.decide_task_consent(&decision)
.await
.map_err(|e| decision_error(e, &vtc.client_did))?;
consent_approve::report(&response);
Ok(())
}
fn decision_error(err: VtcError, approver: &str) -> Box<dyn std::error::Error> {
let text = err.to_string();
let hint = if text.contains("permissionDenied") || text.contains("notAnApprover") {
Some(format!(
"{approver} is not an unrestricted administrator of this community, so its \
decision does not count. Only an administrator with community-wide scope can \
consent."
))
} else {
consent_approve::refusal_hint(&text, approver)
};
match hint {
Some(hint) => format!("{text}\n {hint}").into(),
None => text.into(),
}
}