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/// Emits events as afdata JSON to stdout — used by library callers.
95pub struct AfDataSink;
96
97fn emit(event: agent_first_data::Event) {
98    let stdout = std::io::stdout();
99    let mut emitter =
100        agent_first_data::CliEmitter::new(stdout.lock(), agent_first_data::OutputFormat::Json);
101    let _ = emitter.emit(event);
102}
103
104impl EventSink for AfDataSink {
105    fn emit(&self, event: HyphaEvent) {
106        match event {
107            HyphaEvent::Progress {
108                current,
109                total,
110                message,
111            } => {
112                emit(
113                    agent_first_data::json_progress(serde_json::json!({
114                        "current": current,
115                        "total": total,
116                        "message": message,
117                    }))
118                    .build(),
119                );
120            }
121            HyphaEvent::DownloadProgress {
122                downloaded_bytes,
123                total_bytes,
124            } => {
125                emit(
126                    agent_first_data::json_progress(serde_json::json!({
127                        "event": "download_progress",
128                        "downloaded_bytes": downloaded_bytes,
129                        "total_bytes": total_bytes,
130                    }))
131                    .build(),
132                );
133            }
134            HyphaEvent::Log { message } => {
135                emit(agent_first_data::json_log(serde_json::json!({ "message": message })).build());
136            }
137            HyphaEvent::Warn { message } => {
138                emit(
139                    agent_first_data::json_log(
140                        serde_json::json!({ "event": "warn", "message": message }),
141                    )
142                    .build(),
143                );
144            }
145        }
146    }
147}