Skip to main content

armada_client/
error.rs

1use thiserror::Error;
2
3/// All errors that can be returned by the Armada client.
4///
5/// Most callers can pattern-match on the variant that matters to them and
6/// propagate the rest with `?`. Use [`Error::auth`] as a convenience
7/// constructor when implementing [`crate::auth::TokenProvider`].
8///
9/// # Example
10///
11/// ```no_run
12/// use armada_client::{ArmadaClient, Error, StaticTokenProvider};
13///
14/// async fn submit_or_retry(client: &ArmadaClient) {
15///     // ... build request ...
16/// # let request = armada_client::JobSubmitRequest {
17/// #     queue: "q".into(), job_set_id: "js".into(), job_request_items: vec![],
18/// # };
19///     match client.submit(request).await {
20///         Ok(resp) => { /* handle success */ }
21///         Err(Error::Grpc(status)) => {
22///             // The server returned a non-OK gRPC status (e.g. NOT_FOUND).
23///             eprintln!("gRPC error: {status}");
24///         }
25///         Err(Error::Auth(msg)) => {
26///             // Token provider failed — check credentials.
27///             eprintln!("auth failure: {msg}");
28///         }
29///         Err(e) => eprintln!("other error: {e}"),
30///     }
31/// }
32/// ```
33#[derive(Debug, Error)]
34pub enum Error {
35    /// A low-level transport failure occurred before a gRPC response could be
36    /// received — for example a connection refused, TLS handshake error, or
37    /// DNS resolution failure.
38    #[error("transport error: {0}")]
39    Transport(#[from] tonic::transport::Error),
40
41    /// The server returned a non-OK gRPC status code.
42    ///
43    /// Common codes:
44    /// - `NOT_FOUND` — the requested queue or job set does not exist.
45    /// - `PERMISSION_DENIED` — the bearer token lacks the required privileges.
46    /// - `INVALID_ARGUMENT` — the request payload was rejected by the server.
47    #[error("gRPC error: {0}")]
48    Grpc(#[source] Box<tonic::Status>),
49
50    /// Returned by custom [`crate::auth::TokenProvider`] implementations
51    /// to signal that token retrieval failed.
52    #[error("auth error: {0}")]
53    Auth(String),
54
55    /// Returned when the endpoint string passed to
56    /// [`crate::client::ArmadaClient::connect`] or
57    /// [`crate::client::ArmadaClient::connect_tls`] is not a valid URI.
58    #[error("invalid endpoint URI: {0}")]
59    InvalidUri(String),
60
61    /// The bearer token string contained characters that are not valid in an
62    /// HTTP header value. Token strings must be ASCII and must not contain
63    /// control characters (`\0`, `\r`, `\n`, etc.).
64    #[error("invalid metadata value: {0}")]
65    InvalidMetadata(#[from] tonic::metadata::errors::InvalidMetadataValue),
66}
67
68impl From<tonic::Status> for Error {
69    fn from(s: tonic::Status) -> Self {
70        Self::Grpc(Box::new(s))
71    }
72}
73
74impl Error {
75    /// Convenience constructor for [`Error::Auth`].
76    ///
77    /// Use this in custom [`crate::auth::TokenProvider`] implementations:
78    /// ```ignore
79    /// return Err(Error::auth("token expired"));
80    /// ```
81    pub fn auth(msg: impl Into<String>) -> Self {
82        Self::Auth(msg.into())
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn from_status() {
92        let status = tonic::Status::not_found("not found");
93        let err = Error::from(status);
94        assert!(matches!(err, Error::Grpc(_)));
95    }
96
97    #[test]
98    fn from_invalid_metadata() {
99        let bad: Result<tonic::metadata::MetadataValue<tonic::metadata::Ascii>, _> = "\x00".parse();
100        let err = Error::from(bad.unwrap_err());
101        assert!(matches!(err, Error::InvalidMetadata(_)));
102    }
103
104    #[test]
105    fn invalid_uri_holds_message() {
106        let err = Error::InvalidUri("not a uri".to_string());
107        assert!(err.to_string().contains("not a uri"));
108    }
109
110    #[test]
111    fn auth_constructor() {
112        let err = Error::auth("token expired");
113        assert!(matches!(err, Error::Auth(_)));
114        assert_eq!(err.to_string(), "auth error: token expired");
115    }
116}