Skip to main content

zeph_gateway/
error.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use thiserror::Error;
5
6/// Errors that can be returned by the HTTP gateway.
7///
8/// All variants implement [`std::error::Error`] via [`thiserror`].
9#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum GatewayError {
12    /// The server could not bind to the requested address.
13    ///
14    /// The first field is the address string (e.g. `"127.0.0.1:8080"`) and the
15    /// second is the underlying I/O error.
16    #[error("failed to bind {0}: {1}")]
17    Bind(String, #[source] std::io::Error),
18
19    /// The `axum` server returned a fatal error after binding succeeded.
20    ///
21    /// This typically indicates a listener failure or an OS-level socket error.
22    #[error("server error: {0}")]
23    Server(#[source] std::io::Error),
24
25    /// The gateway is enabled but no bearer token was configured (#6487).
26    ///
27    /// `POST /webhook` forwards its body directly into the agent's turn loop as if it came
28    /// from a trusted channel — starting without a token would let any local or network
29    /// caller that can reach the listener inject arbitrary content. Set `[gateway] auth_token`
30    /// (resolved from the age vault key `ZEPH_GATEWAY_TOKEN`) before starting the gateway.
31    #[error(
32        "refusing to start gateway: no auth_token configured — every request would be \
33         unauthenticated. Set [gateway] auth_token or store one at vault key ZEPH_GATEWAY_TOKEN \
34         (`zeph vault set ZEPH_GATEWAY_TOKEN <token>`)"
35    )]
36    MissingAuthToken,
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn bind_error_exposes_io_error_as_source() {
45        let io_err = std::io::Error::new(std::io::ErrorKind::AddrInUse, "address in use");
46        let err = GatewayError::Bind("127.0.0.1:8080".to_string(), io_err);
47
48        let source = std::error::Error::source(&err).expect("Bind must expose a source");
49        let downcast = source
50            .downcast_ref::<std::io::Error>()
51            .expect("source must downcast to std::io::Error");
52        assert_eq!(downcast.kind(), std::io::ErrorKind::AddrInUse);
53
54        assert_eq!(
55            err.to_string(),
56            "failed to bind 127.0.0.1:8080: address in use"
57        );
58    }
59
60    #[test]
61    fn server_error_exposes_io_error_as_source() {
62        let io_err = std::io::Error::new(std::io::ErrorKind::BrokenPipe, "broken pipe");
63        let err = GatewayError::Server(io_err);
64
65        let source = std::error::Error::source(&err).expect("Server must expose a source");
66        let downcast = source
67            .downcast_ref::<std::io::Error>()
68            .expect("source must downcast to std::io::Error");
69        assert_eq!(downcast.kind(), std::io::ErrorKind::BrokenPipe);
70
71        assert_eq!(err.to_string(), "server error: broken pipe");
72    }
73}