Skip to main content

iscp/
error.rs

1use std::borrow::Cow;
2
3use thiserror::Error;
4
5/// iSCPエラー型
6#[non_exhaustive]
7#[derive(Error, Debug)]
8pub enum Error {
9    /// トランスポートレベルのエラー
10    ///
11    /// 下位層のネットワーク通信で発生したエラーです。
12    /// WebSocketの切断、ネットワーク接続の問題、TLSエラーなどが含まれます。
13    #[error("{0}")]
14    Transport(#[from] crate::transport::TransportError),
15
16    /// トークンソースのエラー
17    ///
18    /// アクセストークンの取得中に発生したエラーです。
19    /// 認証の失敗、トークンの期限切れなどが含まれます。
20    #[error("{0}")]
21    TokenSource(#[from] crate::token_source::TokenSourceError),
22
23    /// 成功以外の結果コードを受信
24    ///
25    /// サーバーからエラー結果コードを含むレスポンスを受信しました。
26    /// これは通常、サーバー側で処理が拒否されたことを意味します。
27    #[error("result code is: {result_code:?}, detail: {detail:?})")]
28    FailedMessage {
29        /// 受信した結果コード
30        result_code: crate::message::ResultCode,
31        /// エラーの詳細メッセージ
32        detail: String,
33    },
34
35    /// リクエストがタイムアウトしました
36    ///
37    /// 指定された時間内にサーバーから応答がなかったか、
38    /// または操作が完了しませんでした。
39    #[error("timeout {0}")]
40    Timeout(Cow<'static, str>),
41
42    /// 接続が既に閉じられています
43    ///
44    /// 接続が既に終了している状態で操作を試みた場合に発生します。
45    #[error("connection closed")]
46    ConnectionClosed,
47
48    /// 閉じられたことによりキャンセルされた操作
49    ///
50    /// 進行中の操作が、接続が閉じられたためにキャンセルされました。
51    #[error("cancelled by close")]
52    CancelledByClose,
53
54    /// ストリームが既に閉じられています
55    ///
56    /// アップストリームやダウンストリームが既に終了している状態で
57    /// 操作を試みた場合に発生します。
58    #[error("stream closed")]
59    StreamClosed,
60
61    /// 予期せぬエラー
62    ///
63    /// ライブラリ内部で発生した予期しないエラーです。
64    /// バグの可能性があります。
65    #[error("unexpected: {0}")]
66    Unexpected(Cow<'static, str>),
67
68    /// 無効な値
69    ///
70    /// 引数やパラメータに無効な値が指定された場合に発生します。
71    #[error("invalid value `{0}`")]
72    InvalidValue(Cow<'static, str>),
73
74    /// 並べ替えエラー
75    ///
76    /// ダウンストリームでのチャンク並べ替え中に発生したエラーです。
77    #[error("cannot wait chunk in reordering, the upstream id is `{0}`")]
78    Reordering(uuid::Uuid),
79}
80
81impl Error {
82    pub(crate) fn can_retry(&self) -> bool {
83        matches!(
84            self,
85            Error::Transport(..) | Error::ConnectionClosed | Error::CancelledByClose
86        )
87    }
88
89    /// Whether the resume path should retry on this error. The deadline is owned
90    /// by the surrounding `timeout_with_ct(ct, expiry_interval, ..)`, so a transient
91    /// `Timeout` and an internal channel closure (`Unexpected`) are also retryable.
92    pub(crate) fn can_retry_resume(&self) -> bool {
93        self.can_retry() || matches!(self, Error::Timeout(..) | Error::Unexpected(..))
94    }
95
96    pub(crate) fn result_code(&self) -> Option<crate::message::ResultCode> {
97        match self {
98            Error::FailedMessage { result_code, .. } => Some(*result_code),
99            _ => None,
100        }
101    }
102
103    pub(crate) fn timeout<T: Into<Cow<'static, str>>>(msg: T) -> Self {
104        Self::Timeout(msg.into())
105    }
106
107    pub(crate) fn unexpected<T: Into<Cow<'static, str>>>(msg: T) -> Self {
108        Self::Unexpected(msg.into())
109    }
110
111    pub(crate) fn invalid_value<T: Into<Cow<'static, str>>>(msg: T) -> Self {
112        Self::InvalidValue(msg.into())
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn can_retry_resume_includes_timeout_and_unexpected() {
122        assert!(Error::timeout("resume").can_retry_resume());
123        assert!(Error::unexpected("internal channel closed").can_retry_resume());
124        assert!(Error::ConnectionClosed.can_retry_resume());
125        assert!(Error::CancelledByClose.can_retry_resume());
126    }
127
128    #[test]
129    fn can_retry_resume_excludes_non_retryable() {
130        assert!(!Error::StreamClosed.can_retry_resume());
131        assert!(!Error::Reordering(uuid::Uuid::nil()).can_retry_resume());
132    }
133
134    #[test]
135    fn can_retry_unchanged_for_timeout() {
136        // can_retry() keeps Timeout/Unexpected non-retryable for the unbounded
137        // e2e retry loops; only the resume path widens the set.
138        assert!(!Error::timeout("x").can_retry());
139        assert!(!Error::unexpected("x").can_retry());
140    }
141}