Skip to main content

dapr_durabletask/api/
errors.rs

1use super::FailureDetails;
2
3/// Error types for the Durable Task SDK.
4#[derive(Debug, thiserror::Error)]
5pub enum DurableTaskError {
6    #[error("gRPC error: {0}")]
7    GrpcError(Box<tonic::Status>),
8
9    #[error("Orchestration '{instance_id}' failed: {message}")]
10    OrchestrationFailed {
11        instance_id: String,
12        message: String,
13        failure_details: Option<FailureDetails>,
14    },
15
16    #[error("Orchestration '{instance_id}' not found")]
17    InstanceNotFound { instance_id: String },
18
19    #[error("Task failed: {message}")]
20    TaskFailed {
21        message: String,
22        failure_details: Option<FailureDetails>,
23    },
24
25    #[error("Non-determinism error: {message}")]
26    NonDeterminism { message: String },
27
28    #[error("Orchestration state error: {message}")]
29    OrchestrationState { message: String },
30
31    #[error("Timeout waiting for orchestration")]
32    Timeout,
33
34    #[error("Serialisation error: {0}")]
35    Serialization(#[from] serde_json::Error),
36
37    /// Invalid endpoint, URI, or address-parsing failure.
38    #[error("Invalid address: {0}")]
39    InvalidAddress(String),
40
41    /// Failure establishing or maintaining a connection to the sidecar.
42    #[error("Connection failed: {0}")]
43    ConnectionFailed(String),
44
45    /// Internal SDK error (channel closed, semaphore poisoned, invariant violation, etc.).
46    #[error("Internal error: {0}")]
47    Internal(String),
48
49    #[error("{0}")]
50    Other(String),
51}
52
53impl From<tonic::Status> for DurableTaskError {
54    fn from(status: tonic::Status) -> Self {
55        DurableTaskError::GrpcError(Box::new(status))
56    }
57}
58
59/// Convenience alias for `Result<T, DurableTaskError>`.
60pub type Result<T> = std::result::Result<T, DurableTaskError>;
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn display_grpc_error() {
68        let err = DurableTaskError::GrpcError(Box::new(tonic::Status::internal("oops")));
69        let msg = err.to_string();
70        assert!(msg.starts_with("gRPC error: "));
71        assert!(msg.contains("oops"));
72    }
73
74    #[test]
75    fn display_orchestration_failed() {
76        let err = DurableTaskError::OrchestrationFailed {
77            instance_id: "abc".into(),
78            message: "boom".into(),
79            failure_details: None,
80        };
81        assert_eq!(err.to_string(), "Orchestration 'abc' failed: boom");
82    }
83
84    #[test]
85    fn display_instance_not_found() {
86        let err = DurableTaskError::InstanceNotFound {
87            instance_id: "xyz".into(),
88        };
89        assert_eq!(err.to_string(), "Orchestration 'xyz' not found");
90    }
91
92    #[test]
93    fn display_task_failed() {
94        let err = DurableTaskError::TaskFailed {
95            message: "task err".into(),
96            failure_details: None,
97        };
98        assert_eq!(err.to_string(), "Task failed: task err");
99    }
100
101    #[test]
102    fn display_timeout() {
103        assert_eq!(
104            DurableTaskError::Timeout.to_string(),
105            "Timeout waiting for orchestration"
106        );
107    }
108
109    #[test]
110    fn display_other() {
111        let err = DurableTaskError::Other("custom".into());
112        assert_eq!(err.to_string(), "custom");
113    }
114
115    #[test]
116    fn display_invalid_address() {
117        let err = DurableTaskError::InvalidAddress("not a url".into());
118        assert_eq!(err.to_string(), "Invalid address: not a url");
119    }
120
121    #[test]
122    fn display_connection_failed() {
123        let err = DurableTaskError::ConnectionFailed("refused".into());
124        assert_eq!(err.to_string(), "Connection failed: refused");
125    }
126
127    #[test]
128    fn display_internal() {
129        let err = DurableTaskError::Internal("semaphore closed".into());
130        assert_eq!(err.to_string(), "Internal error: semaphore closed");
131    }
132
133    #[test]
134    fn from_tonic_status() {
135        let status = tonic::Status::internal("test");
136        let err: DurableTaskError = status.into();
137        assert!(matches!(err, DurableTaskError::GrpcError(_)));
138    }
139
140    #[test]
141    fn from_serde_json_error() {
142        let json_err = serde_json::from_str::<String>("not valid json").unwrap_err();
143        let err: DurableTaskError = json_err.into();
144        assert!(matches!(err, DurableTaskError::Serialization(_)));
145    }
146}