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}