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
78impl From<String> for HyphaError {
79    fn from(msg: String) -> Self {
80        Self {
81            code: "error".to_string(),
82            message: msg,
83            hint: None,
84        }
85    }
86}
87
88impl From<&str> for HyphaError {
89    fn from(msg: &str) -> Self {
90        Self::from(msg.to_string())
91    }
92}
93
94/// Prints events as afdata JSON to stdout — used by the hypha CLI.
95pub struct AfDataSink;
96
97impl EventSink for AfDataSink {
98    #[allow(clippy::print_stdout)]
99    fn emit(&self, event: HyphaEvent) {
100        match event {
101            HyphaEvent::Progress {
102                current,
103                total,
104                message,
105            } => {
106                let v = agent_first_data::build_json(
107                    "progress",
108                    serde_json::json!({
109                        "current": current,
110                        "total": total,
111                        "message": message,
112                    }),
113                    None,
114                );
115                println!("{}", agent_first_data::output_json(&v));
116            }
117            HyphaEvent::DownloadProgress {
118                downloaded_bytes,
119                total_bytes,
120            } => {
121                let v = agent_first_data::build_json(
122                    "download_progress",
123                    serde_json::json!({
124                        "downloaded_bytes": downloaded_bytes,
125                        "total_bytes": total_bytes,
126                    }),
127                    None,
128                );
129                println!("{}", agent_first_data::output_json(&v));
130            }
131            HyphaEvent::Log { message } => {
132                let v = agent_first_data::build_json(
133                    "log",
134                    serde_json::json!({ "message": message }),
135                    None,
136                );
137                println!("{}", agent_first_data::output_json(&v));
138            }
139            HyphaEvent::Warn { message } => {
140                let v = agent_first_data::build_json(
141                    "warn",
142                    serde_json::json!({ "message": message }),
143                    None,
144                );
145                println!("{}", agent_first_data::output_json(&v));
146            }
147        }
148    }
149}