pub mod create_report;
pub mod crypto;
pub mod http;
pub mod identity;
pub mod pipeline;
pub mod report;
pub mod subscription;
use std::io;
use std::process::ExitCode;
use std::time::Duration;
use clap::Args;
use miette::Report;
use crate::commands::test::labeler::create_report::self_mint::{SelfMintCurve, SelfMintSigner};
use crate::common::{
APP_USER_AGENT,
identity::{Did, RealDnsResolver, RealHttpClient, is_local_labeler_hostname},
};
use pipeline::{LabelerOptions, parse_target, run_pipeline};
use report::RenderConfig;
#[derive(Debug, Args)]
pub struct LabelerCmd {
pub target: String,
#[arg(long)]
pub did: Option<String>,
#[arg(
long,
default_value = "5s",
value_parser = parse_subscribe_timeout,
)]
pub subscribe_timeout: Duration,
#[arg(long)]
pub no_color: bool,
#[arg(long)]
pub verbose: bool,
#[arg(long)]
pub commit_report: bool,
#[arg(long)]
pub force_self_mint: bool,
#[arg(long, value_enum, default_value_t = SelfMintCurve::default())]
pub self_mint_curve: SelfMintCurve,
#[arg(long)]
pub report_subject_did: Option<String>,
#[arg(long, requires = "app_password")]
pub handle: Option<String>,
#[arg(long, requires = "handle")]
pub app_password: Option<String>,
}
impl LabelerCmd {
pub async fn run(self, no_color: bool) -> Result<ExitCode, Report> {
let target =
parse_target(&self.target, self.did.as_deref()).map_err(|e| miette::miette!("{e}"))?;
let tentative_endpoint: Option<url::Url> = match &target {
pipeline::LabelerTarget::Endpoint { url, .. } => Some(url.clone()),
pipeline::LabelerTarget::Identified { .. } => None,
};
let tentative_local = tentative_endpoint
.as_ref()
.map(is_local_labeler_hostname)
.unwrap_or(false);
let reqwest_client = reqwest::Client::builder()
.use_rustls_tls()
.user_agent(APP_USER_AGENT)
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| miette::miette!("Failed to initialize HTTP client: {}", e))?;
let http = RealHttpClient::from_client(reqwest_client.clone());
let dns = RealDnsResolver::new();
let report_subject_override = self.report_subject_did.clone().map(Did);
let run_id = create_report::sentinel::new_run_id();
let self_mint_signer_opt = if self.force_self_mint || tentative_local {
Some(
SelfMintSigner::spawn(self.self_mint_curve)
.await
.map_err(|e| miette::miette!("Failed to bind self-mint DID server: {e}"))?,
)
} else {
None
};
let self_mint_signer_ref = self_mint_signer_opt.as_ref();
let pds_credentials = match (self.handle.as_deref(), self.app_password.as_deref()) {
(Some(h), Some(p)) => Some(pipeline::PdsCredentials {
handle: h.to_string(),
app_password: p.to_string(),
}),
_ => None,
};
let pds_credentials_ref = pds_credentials.as_ref();
let opts = LabelerOptions {
http: &http,
dns: &dns,
http_tee: pipeline::HttpTee::Real(&reqwest_client),
ws_client: None,
subscribe_timeout: self.subscribe_timeout,
verbose: self.verbose,
create_report_tee: pipeline::CreateReportTeeKind::Real(&reqwest_client),
commit_report: self.commit_report,
force_self_mint: self.force_self_mint,
self_mint_curve: self.self_mint_curve,
report_subject_override: report_subject_override.as_ref(),
self_mint_signer: self_mint_signer_ref,
pds_credentials: pds_credentials_ref,
pds_xrpc_client: None,
pds_xrpc_client_override: None,
run_id: &run_id,
};
let report = run_pipeline(target, opts).await;
let mut stdout = io::stdout().lock();
report
.render(&mut stdout, &RenderConfig { no_color })
.map_err(|e| miette::miette!("Failed to render report: {}", e))?;
let exit_code = report.exit_code();
Ok(ExitCode::from(exit_code as u8))
}
}
pub fn parse_subscribe_timeout(raw: &str) -> Result<Duration, String> {
let parsed = humantime::parse_duration(raw)
.map_err(|e| format!("invalid --subscribe-timeout value `{raw}`: {e}"))?;
if parsed < Duration::from_secs(1) {
return Err(format!(
"--subscribe-timeout must be at least 1 second (got {parsed:?})"
));
}
Ok(parsed)
}