Skip to main content

hypha/sink/
mod.rs

1/// A structured event emitted by hypha library functions.
2///
3/// Callers decide how to handle these — print them (CLI), forward to a channel
4/// (GUI/agent), or ignore them entirely (NoopSink).
5#[derive(Debug, Clone)]
6pub enum HyphaEvent {
7    /// A multi-step operation made progress.
8    Progress {
9        current: u32,
10        total: u32,
11        message: String,
12    },
13    /// Byte-level download progress for speed/ETA display.
14    DownloadProgress {
15        downloaded_bytes: u64,
16        total_bytes: Option<u64>,
17    },
18    /// A non-fatal informational message.
19    Log { message: String },
20    /// A non-fatal warning.
21    Warn { message: String },
22}
23
24/// Receiver of events emitted during a hypha operation.
25pub trait EventSink: Send + Sync {
26    fn emit(&self, event: HyphaEvent);
27}
28
29/// Discards all events — use when the caller only cares about the return value.
30pub struct NoopSink;
31
32impl EventSink for NoopSink {
33    fn emit(&self, _: HyphaEvent) {}
34}
35
36/// Structured error returned by hypha library functions.
37///
38/// The `code` field is a machine-readable identifier (e.g. `"invalid_uri"`,
39/// `"dns_failed"`).  The `message` is human-readable detail.
40/// The optional `hint` provides actionable remediation advice.
41#[derive(Debug, Clone)]
42pub struct HyphaError {
43    pub code: String,
44    pub message: String,
45    pub hint: Option<String>,
46}
47
48impl HyphaError {
49    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
50        Self {
51            code: code.into(),
52            message: message.into(),
53            hint: None,
54        }
55    }
56
57    pub fn with_hint(
58        code: impl Into<String>,
59        message: impl Into<String>,
60        hint: impl Into<String>,
61    ) -> Self {
62        Self {
63            code: code.into(),
64            message: message.into(),
65            hint: Some(hint.into()),
66        }
67    }
68}
69
70impl std::fmt::Display for HyphaError {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "{}: {}", self.code, self.message)
73    }
74}
75
76impl std::error::Error for HyphaError {}
77
78/// Prints events as afdata JSON to stdout — used by the hypha CLI.
79pub struct AfDataSink;
80
81impl EventSink for AfDataSink {
82    #[allow(clippy::print_stdout)]
83    fn emit(&self, event: HyphaEvent) {
84        match event {
85            HyphaEvent::Progress {
86                current,
87                total,
88                message,
89            } => {
90                let v = agent_first_data::build_json(
91                    "progress",
92                    serde_json::json!({
93                        "current": current,
94                        "total": total,
95                        "message": message,
96                    }),
97                    None,
98                );
99                println!("{}", agent_first_data::output_json(&v));
100            }
101            HyphaEvent::DownloadProgress {
102                downloaded_bytes,
103                total_bytes,
104            } => {
105                let v = agent_first_data::build_json(
106                    "download_progress",
107                    serde_json::json!({
108                        "downloaded_bytes": downloaded_bytes,
109                        "total_bytes": total_bytes,
110                    }),
111                    None,
112                );
113                println!("{}", agent_first_data::output_json(&v));
114            }
115            HyphaEvent::Log { message } => {
116                let v = agent_first_data::build_json(
117                    "log",
118                    serde_json::json!({ "message": message }),
119                    None,
120                );
121                println!("{}", agent_first_data::output_json(&v));
122            }
123            HyphaEvent::Warn { message } => {
124                let v = agent_first_data::build_json(
125                    "warn",
126                    serde_json::json!({ "message": message }),
127                    None,
128                );
129                println!("{}", agent_first_data::output_json(&v));
130            }
131        }
132    }
133}