use std::collections::HashMap;
use std::io::Write as _;
use std::sync::{Arc, Mutex};
use paralock::paralock_core::protocol::SwapEvent;
use paralock::{Swap, SwapStatus};
use serde_json::{Value, json};
use crate::error::CliError;
pub fn warn(message: &str) {
let _ = writeln!(std::io::stderr(), "warning: {message}");
}
pub const fn status_str(status: SwapStatus) -> &'static str {
match status {
SwapStatus::Quoted => "quoted",
SwapStatus::ZcashLocked => "zcash_locked",
SwapStatus::EvmLocked => "evm_locked",
SwapStatus::EvmClaimed => "evm_claimed",
SwapStatus::Completed => "completed",
SwapStatus::RefundedZcash => "refunded_zcash",
SwapStatus::RefundedEvm => "refunded_evm",
SwapStatus::Failed => "failed",
}
}
pub fn event_line(event: &SwapEvent) -> String {
let detail = event.detail.as_deref().unwrap_or("");
let txid = event.txid.as_deref().map(|t| format!(" {t}")).unwrap_or_default();
format!(" [{:<8}] {:<15} {detail}{txid}", event.chain.as_str(), status_str(event.status))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Output {
pub json: bool,
}
pub type Progress = Arc<dyn Fn(&Swap) + Send + Sync>;
impl Output {
pub fn say(&self, line: impl AsRef<str>) {
if !self.json {
let _ = writeln!(std::io::stdout(), "{}", line.as_ref());
}
}
pub fn progress(&self, with_id: bool) -> Progress {
let json = self.json;
let printed: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
Arc::new(move |swap: &Swap| {
if json {
return;
}
let mut printed = printed.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let done = printed.entry(swap.id.clone()).or_insert(0);
let mut out = std::io::stdout().lock();
if with_id && *done == 0 && swap.events.is_empty() {
let _ = writeln!(
out,
"[{}] new {} swap: {} ZEC → {} {} on {}",
short(&swap.id),
swap.mode.as_str(),
crate::amount::zec(swap.zcash.amount_zat),
crate::amount::usd(swap.evm.amount, 6),
swap.quote.token_symbol,
swap.evm.chain
);
}
for event in swap.events.iter().skip(*done) {
let line = event_line(event);
if with_id {
let _ = writeln!(out, "[{}]{line}", short(&swap.id));
} else {
let _ = writeln!(out, "{line}");
}
}
*done = swap.events.len();
})
}
pub fn result(&self, fields: Value) {
if !self.json {
return;
}
let mut doc = json!({ "ok": true });
if let (Some(doc), Value::Object(fields)) = (doc.as_object_mut(), fields) {
doc.extend(fields);
}
print_json(&doc);
}
pub fn error(&self, error: &CliError) {
if self.json {
let mut doc = json!({
"ok": false,
"error": {
"code": error.code.map(|c| c.as_str()),
"message": error.message,
"hint": error.hint,
"exitCode": error.exit.code(),
},
});
if let (Some(swap), Some(map)) = (&error.swap, doc.as_object_mut()) {
map.insert("swap".into(), serde_json::to_value(swap).unwrap_or(Value::Null));
}
print_json(&doc);
return;
}
let mut err = std::io::stderr().lock();
let code = error.code.map(|c| format!(" ({})", c.as_str())).unwrap_or_default();
let _ = writeln!(err, "error{code}: {}", error.message);
if let Some(hint) = &error.hint {
let _ = writeln!(err, " hint: {hint}");
}
}
}
pub fn print_document(value: &Value) {
print_json(value);
}
pub fn print_record(value: &Value) {
let _ = writeln!(std::io::stdout(), "{value}");
}
fn print_json(value: &Value) {
let text = serde_json::to_string_pretty(value).unwrap_or_else(|_| "{\"ok\":false}".into());
let _ = writeln!(std::io::stdout(), "{text}");
}
pub fn short(id: &str) -> &str {
id.get(..10).unwrap_or(id)
}