1use std::collections::HashMap;
8use std::io::Write as _;
9use std::sync::{Arc, Mutex};
10
11use paralock::paralock_core::protocol::SwapEvent;
12use paralock::{Swap, SwapStatus};
13use serde_json::{Value, json};
14
15use crate::error::CliError;
16
17pub fn warn(message: &str) {
19 let _ = writeln!(std::io::stderr(), "warning: {message}");
20}
21
22pub const fn status_str(status: SwapStatus) -> &'static str {
24 match status {
25 SwapStatus::Quoted => "quoted",
26 SwapStatus::ZcashLocked => "zcash_locked",
27 SwapStatus::EvmLocked => "evm_locked",
28 SwapStatus::EvmClaimed => "evm_claimed",
29 SwapStatus::Completed => "completed",
30 SwapStatus::RefundedZcash => "refunded_zcash",
31 SwapStatus::RefundedEvm => "refunded_evm",
32 SwapStatus::Failed => "failed",
33 }
34}
35
36pub fn event_line(event: &SwapEvent) -> String {
38 let detail = event.detail.as_deref().unwrap_or("");
39 let txid = event.txid.as_deref().map(|t| format!(" {t}")).unwrap_or_default();
40 format!(" [{:<8}] {:<15} {detail}{txid}", event.chain.as_str(), status_str(event.status))
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Output {
46 pub json: bool,
48}
49
50pub type Progress = Arc<dyn Fn(&Swap) + Send + Sync>;
52
53impl Output {
54 pub fn say(&self, line: impl AsRef<str>) {
56 if !self.json {
57 let _ = writeln!(std::io::stdout(), "{}", line.as_ref());
58 }
59 }
60
61 pub fn progress(&self, with_id: bool) -> Progress {
64 let json = self.json;
65 let printed: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
66 Arc::new(move |swap: &Swap| {
67 if json {
68 return;
69 }
70 let mut printed = printed.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
71 let done = printed.entry(swap.id.clone()).or_insert(0);
72 let mut out = std::io::stdout().lock();
73 if with_id && *done == 0 && swap.events.is_empty() {
74 let _ = writeln!(
75 out,
76 "[{}] new {} swap: {} ZEC → {} {} on {}",
77 short(&swap.id),
78 swap.mode.as_str(),
79 crate::amount::zec(swap.zcash.amount_zat),
80 crate::amount::usd(swap.evm.amount, 6),
81 swap.quote.token_symbol,
82 swap.evm.chain
83 );
84 }
85 for event in swap.events.iter().skip(*done) {
86 let line = event_line(event);
87 if with_id {
88 let _ = writeln!(out, "[{}]{line}", short(&swap.id));
89 } else {
90 let _ = writeln!(out, "{line}");
91 }
92 }
93 *done = swap.events.len();
94 })
95 }
96
97 pub fn result(&self, fields: Value) {
99 if !self.json {
100 return;
101 }
102 let mut doc = json!({ "ok": true });
103 if let (Some(doc), Value::Object(fields)) = (doc.as_object_mut(), fields) {
104 doc.extend(fields);
105 }
106 print_json(&doc);
107 }
108
109 pub fn error(&self, error: &CliError) {
112 if self.json {
113 let mut doc = json!({
114 "ok": false,
115 "error": {
116 "code": error.code.map(|c| c.as_str()),
117 "message": error.message,
118 "hint": error.hint,
119 "exitCode": error.exit.code(),
120 },
121 });
122 if let (Some(swap), Some(map)) = (&error.swap, doc.as_object_mut()) {
123 map.insert("swap".into(), serde_json::to_value(swap).unwrap_or(Value::Null));
124 }
125 print_json(&doc);
126 return;
127 }
128 let mut err = std::io::stderr().lock();
129 let code = error.code.map(|c| format!(" ({})", c.as_str())).unwrap_or_default();
130 let _ = writeln!(err, "error{code}: {}", error.message);
131 if let Some(hint) = &error.hint {
132 let _ = writeln!(err, " hint: {hint}");
133 }
134 }
135}
136
137pub fn print_document(value: &Value) {
139 print_json(value);
140}
141
142pub fn print_record(value: &Value) {
144 let _ = writeln!(std::io::stdout(), "{value}");
145}
146
147fn print_json(value: &Value) {
148 let text = serde_json::to_string_pretty(value).unwrap_or_else(|_| "{\"ok\":false}".into());
149 let _ = writeln!(std::io::stdout(), "{text}");
150}
151
152pub fn short(id: &str) -> &str {
154 id.get(..10).unwrap_or(id)
155}