Skip to main content

made_client/
made_client_error.rs

1use std::io;
2
3use thiserror::Error;
4use tonic::Code;
5
6/// Errors a reference client can act on without inspecting server internals.
7#[derive(Debug, Error)]
8pub enum MadeClientError {
9    #[error("invalid MADE endpoint: {0}")]
10    InvalidEndpoint(String),
11    #[error("invocation id must contain 1 to 256 non-whitespace bytes")]
12    InvalidInvocationId,
13    #[error("invalid TLS client configuration: {0}")]
14    TlsConfiguration(String),
15    #[error("connection attempts exhausted: {0}")]
16    ConnectionExhausted(String),
17    #[error("gRPC transport failed: {0}")]
18    Transport(String),
19    #[error("MADE returned {code:?}: {message}")]
20    RemoteStatus { code: Code, message: String },
21    #[error("public protocol violation: {0}")]
22    ProtocolViolation(String),
23    #[error("checkpoint belongs to {actual}, expected {expected}")]
24    CursorScopeMismatch { expected: String, actual: String },
25    #[error("cursor checkpoint is corrupt: {0}")]
26    CursorCheckpointCorrupt(String),
27    #[error("artifact digest mismatch: expected {expected}, observed {observed}")]
28    ArtifactIntegrityMismatch { expected: String, observed: String },
29    #[error("artifact size mismatch: expected {expected}, observed {observed}")]
30    ArtifactSizeMismatch { expected: u64, observed: u64 },
31    #[error("local I/O failed at {path}: {source}")]
32    Io { path: String, source: io::Error },
33}
34
35impl MadeClientError {
36    #[must_use]
37    pub fn is_retryable_read(&self) -> bool {
38        matches!(
39            self,
40            Self::RemoteStatus {
41                code: Code::Unavailable | Code::DeadlineExceeded,
42                ..
43            } | Self::Transport(_)
44        )
45    }
46
47    #[must_use]
48    pub fn exit_code(&self) -> u8 {
49        match self {
50            Self::InvalidEndpoint(_)
51            | Self::InvalidInvocationId
52            | Self::TlsConfiguration(_)
53            | Self::ConnectionExhausted(_)
54            | Self::Transport(_)
55            | Self::RemoteStatus { .. } => 3,
56            Self::ProtocolViolation(_)
57            | Self::CursorScopeMismatch { .. }
58            | Self::CursorCheckpointCorrupt(_)
59            | Self::ArtifactIntegrityMismatch { .. }
60            | Self::ArtifactSizeMismatch { .. } => 4,
61            Self::Io { .. } => 5,
62        }
63    }
64
65    pub(crate) fn from_status(status: tonic::Status) -> Self {
66        const MAX_MESSAGE_CHARS: usize = 512;
67        let code = status.code();
68        let message: String = status.message().chars().take(MAX_MESSAGE_CHARS).collect();
69        drop(status);
70        Self::RemoteStatus { code, message }
71    }
72
73    pub(crate) fn io(path: &std::path::Path, source: io::Error) -> Self {
74        Self::Io {
75            path: path.display().to_string(),
76            source,
77        }
78    }
79}