Skip to main content

oxide_batch_core/domain/
error.rs

1use std::error::Error;
2use std::fmt;
3
4/// The kind of validated domain name.
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
6#[non_exhaustive]
7pub enum NameKind {
8    /// A job definition name.
9    Job,
10    /// A step definition name.
11    Step,
12    /// A job-parameter name.
13    Parameter,
14    /// An exit-status code.
15    ExitCode,
16}
17
18impl fmt::Display for NameKind {
19    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20        formatter.write_str(match self {
21            Self::Job => "job name",
22            Self::Step => "step name",
23            Self::Parameter => "parameter name",
24            Self::ExitCode => "exit code",
25        })
26    }
27}
28
29/// The kind of opaque numeric identifier.
30#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
31#[non_exhaustive]
32pub enum IdentifierKind {
33    /// A job-instance identifier.
34    JobInstance,
35    /// A job-execution identifier.
36    JobExecution,
37    /// A step-execution identifier.
38    StepExecution,
39    /// A durable flow-decision identifier.
40    FlowDecision,
41    /// An execution-local flow-decision ordering.
42    FlowDecisionSequence,
43    /// A durable recovery-decision identifier.
44    RecoveryDecision,
45    /// A durable operator-request identifier.
46    OperatorRequest,
47    /// A durable retention-action identifier.
48    RetentionAction,
49    /// A durable step-partition identifier.
50    StepPartition,
51    /// An opaque failure identifier.
52    Failure,
53}
54
55impl fmt::Display for IdentifierKind {
56    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57        formatter.write_str(match self {
58            Self::JobInstance => "job instance",
59            Self::JobExecution => "job execution",
60            Self::StepExecution => "step execution",
61            Self::FlowDecision => "flow decision",
62            Self::FlowDecisionSequence => "flow decision sequence",
63            Self::RecoveryDecision => "recovery decision",
64            Self::OperatorRequest => "operator request",
65            Self::RetentionAction => "retention action",
66            Self::StepPartition => "step partition",
67            Self::Failure => "failure",
68        })
69    }
70}
71
72/// A stable, value-redacted domain validation failure.
73#[derive(Clone, Debug, Eq, PartialEq)]
74#[non_exhaustive]
75pub enum DomainError {
76    /// A required name was empty.
77    EmptyName {
78        /// The name category.
79        kind: NameKind,
80    },
81    /// A name exceeded its UTF-8 byte limit.
82    NameTooLong {
83        /// The name category.
84        kind: NameKind,
85        /// The maximum accepted UTF-8 byte length.
86        max_bytes: usize,
87    },
88    /// A name had leading or trailing whitespace.
89    NameHasSurroundingWhitespace {
90        /// The name category.
91        kind: NameKind,
92    },
93    /// A name contained a control character.
94    NameContainsControl {
95        /// The name category.
96        kind: NameKind,
97        /// The zero-based character position, without disclosing the character.
98        character_index: usize,
99    },
100    /// A numeric identifier was zero.
101    ZeroIdentifier {
102        /// The identifier category.
103        kind: IdentifierKind,
104    },
105    /// A parameter with the same name was inserted more than once.
106    DuplicateParameter,
107    /// A string parameter exceeded its UTF-8 byte limit.
108    ParameterStringTooLong {
109        /// The maximum accepted UTF-8 byte length.
110        max_bytes: usize,
111    },
112    /// An execution timestamp preceded a timestamp that must come before it.
113    InvalidTimestampOrder,
114    /// A running execution included an end timestamp.
115    ActiveExecutionHasEndTime,
116    /// A finished execution omitted its end timestamp.
117    FinishedExecutionMissingEndTime,
118    /// A failed execution omitted its redacted failure summary.
119    FailedExecutionMissingFailure,
120}
121
122impl fmt::Display for DomainError {
123    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
124        match self {
125            Self::EmptyName { kind } => write!(formatter, "{kind} must not be empty"),
126            Self::NameTooLong { kind, max_bytes } => {
127                write!(formatter, "{kind} exceeds {max_bytes} UTF-8 bytes")
128            }
129            Self::NameHasSurroundingWhitespace { kind } => {
130                write!(formatter, "{kind} has surrounding whitespace")
131            }
132            Self::NameContainsControl {
133                kind,
134                character_index,
135            } => write!(
136                formatter,
137                "{kind} contains a control character at position {character_index}"
138            ),
139            Self::ZeroIdentifier { kind } => write!(formatter, "{kind} identifier must be nonzero"),
140            Self::DuplicateParameter => formatter.write_str("job parameter names must be unique"),
141            Self::ParameterStringTooLong { max_bytes } => {
142                write!(
143                    formatter,
144                    "string parameter exceeds {max_bytes} UTF-8 bytes"
145                )
146            }
147            Self::InvalidTimestampOrder => {
148                formatter.write_str("execution timestamps are out of order")
149            }
150            Self::ActiveExecutionHasEndTime => {
151                formatter.write_str("an active execution cannot have an end timestamp")
152            }
153            Self::FinishedExecutionMissingEndTime => {
154                formatter.write_str("a finished execution requires an end timestamp")
155            }
156            Self::FailedExecutionMissingFailure => {
157                formatter.write_str("a failed execution requires a failure summary")
158            }
159        }
160    }
161}
162
163impl Error for DomainError {}