Skip to main content

stepflow_flow/
task_error_code.rs

1// Copyright 2025 DataStax Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the License
9// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10// or implied. See the License for the specific language governing permissions and limitations under
11// the License.
12
13//! Error code enum for task/step failures.
14//!
15//! This mirrors the `TaskErrorCode` proto enum but is defined as a native Rust type
16//! so that `stepflow-flow` does not depend on protobuf code generation.
17
18/// Categorizes the type of error that occurred during task execution.
19///
20/// Each variant maps to a specific retry policy:
21/// - **Always retried (transport)**: [`Unreachable`](Self::Unreachable), [`Timeout`](Self::Timeout)
22/// - **Retried with onError policy**: [`ComponentFailed`](Self::ComponentFailed),
23///   [`ResourceUnavailable`](Self::ResourceUnavailable)
24/// - **Never retried**: all others
25#[derive(
26    Debug,
27    Clone,
28    Copy,
29    PartialEq,
30    Eq,
31    Hash,
32    serde::Serialize,
33    serde::Deserialize,
34    schemars::JsonSchema,
35)]
36#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
37pub enum TaskErrorCode {
38    /// Default value; should not be used explicitly.
39    Unspecified,
40    /// The task exceeded its execution deadline or heartbeat timeout.
41    Timeout,
42    /// The component rejected its input.
43    InvalidInput,
44    /// The component executed but returned a business-logic failure.
45    ComponentFailed,
46    /// The task was explicitly cancelled by the orchestrator.
47    Cancelled,
48    /// The worker or component could not be reached.
49    Unreachable,
50    /// The requested component does not exist on the worker.
51    ComponentNotFound,
52    /// A resource required by the component was not available.
53    ResourceUnavailable,
54    /// The orchestrator failed to resolve a value expression.
55    ExpressionFailure,
56    /// Catch-all for unexpected orchestrator errors.
57    OrchestratorError,
58    /// Catch-all for unexpected worker/SDK errors.
59    WorkerError,
60}
61
62impl TaskErrorCode {
63    /// Returns the proto-style name (e.g. `"TASK_ERROR_CODE_TIMEOUT"`).
64    pub fn as_str_name(&self) -> &'static str {
65        match self {
66            Self::Unspecified => "TASK_ERROR_CODE_UNSPECIFIED",
67            Self::Timeout => "TASK_ERROR_CODE_TIMEOUT",
68            Self::InvalidInput => "TASK_ERROR_CODE_INVALID_INPUT",
69            Self::ComponentFailed => "TASK_ERROR_CODE_COMPONENT_FAILED",
70            Self::Cancelled => "TASK_ERROR_CODE_CANCELLED",
71            Self::Unreachable => "TASK_ERROR_CODE_UNREACHABLE",
72            Self::ComponentNotFound => "TASK_ERROR_CODE_COMPONENT_NOT_FOUND",
73            Self::ResourceUnavailable => "TASK_ERROR_CODE_RESOURCE_UNAVAILABLE",
74            Self::ExpressionFailure => "TASK_ERROR_CODE_EXPRESSION_FAILURE",
75            Self::OrchestratorError => "TASK_ERROR_CODE_ORCHESTRATOR_ERROR",
76            Self::WorkerError => "TASK_ERROR_CODE_WORKER_ERROR",
77        }
78    }
79
80    /// Returns the numeric proto value.
81    pub fn proto_number(&self) -> i32 {
82        match self {
83            Self::Unspecified => 0,
84            Self::Timeout => 1,
85            Self::InvalidInput => 2,
86            Self::ComponentFailed => 3,
87            Self::Cancelled => 4,
88            Self::Unreachable => 5,
89            Self::ComponentNotFound => 6,
90            Self::ResourceUnavailable => 7,
91            Self::ExpressionFailure => 8,
92            Self::OrchestratorError => 9,
93            Self::WorkerError => 10,
94        }
95    }
96}
97
98impl From<TaskErrorCode> for i32 {
99    fn from(code: TaskErrorCode) -> i32 {
100        code.proto_number()
101    }
102}
103
104impl TryFrom<i32> for TaskErrorCode {
105    type Error = i32;
106
107    fn try_from(value: i32) -> Result<Self, Self::Error> {
108        match value {
109            0 => Ok(Self::Unspecified),
110            1 => Ok(Self::Timeout),
111            2 => Ok(Self::InvalidInput),
112            3 => Ok(Self::ComponentFailed),
113            4 => Ok(Self::Cancelled),
114            5 => Ok(Self::Unreachable),
115            6 => Ok(Self::ComponentNotFound),
116            7 => Ok(Self::ResourceUnavailable),
117            8 => Ok(Self::ExpressionFailure),
118            9 => Ok(Self::OrchestratorError),
119            10 => Ok(Self::WorkerError),
120            other => Err(other),
121        }
122    }
123}
124
125impl std::fmt::Display for TaskErrorCode {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        // Strip the TASK_ERROR_CODE_ prefix for display
128        let proto_name = self.as_str_name();
129        let short = &proto_name["TASK_ERROR_CODE_".len()..];
130        f.write_str(short)
131    }
132}