scientific_workflow/study/
error.rs1use std::io;
4use std::path::PathBuf;
5use std::time::Duration;
6
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum StudyError {
12 #[error("failed to serialize study record")]
13 SerializeStudyRecord {
14 #[source]
15 source: serde_json::Error,
16 },
17 #[error("failed to write study record `{path}`")]
18 WriteStudyRecord {
19 path: PathBuf,
20 #[source]
21 source: io::Error,
22 },
23 #[error("failed to format UTC timestamp while attempting to {operation}")]
24 StudyRecordTimestamp {
25 operation: &'static str,
26 #[source]
27 source: time::error::Format,
28 },
29 #[error("failed to serialize study plan")]
30 SerializeStudyPlan {
31 #[source]
32 source: serde_json::Error,
33 },
34 #[error("failed to write study plan `{path}`")]
35 WriteStudyPlan {
36 path: PathBuf,
37 #[source]
38 source: io::Error,
39 },
40 #[error("study plan destination `{path}` already contains different data")]
41 StudyPlanConflict { path: PathBuf },
42 #[error("study phase execution failed: {source}")]
43 PhaseExecutionFailed {
44 summary: super::StudySummary,
45 #[source]
46 source: Box<StudyError>,
47 },
48 #[error("another study renderer already owns the process terminal")]
49 TerminalAlreadyOwned,
50 #[error("phase {phase} must contain at least one task")]
51 EmptyPhase { phase: u64 },
52 #[error("a study must contain at least one phase")]
53 EmptyPhaseSet,
54 #[error("phase {phase} must have a nonempty label")]
55 InvalidPhaseLabel { phase: u64 },
56 #[error("phase {phase} max_active_tasks must be greater than zero")]
57 InvalidPhaseWorkloadLimit { phase: u64 },
58 #[error("phase {phase} prepared_task_queue_capacity must be greater than zero")]
59 InvalidPhaseQueueCapacity { phase: u64 },
60 #[error("phase {phase} timing setting `{setting}` must be nonzero and representable")]
61 InvalidPhaseTiming { phase: u64, setting: &'static str },
62 #[error("phase ID {phase} appears more than once")]
63 DuplicatePhaseId { phase: u64 },
64 #[error("phase {phase} depends on unknown phase {dependency}")]
65 UnknownPhaseDependency { phase: u64, dependency: u64 },
66 #[error("phase dependency graph contains a cycle involving phase {phase}")]
67 PhaseDependencyCycle { phase: u64 },
68 #[error("selected phase {phase} is not registered")]
69 UnknownSelectedPhase { phase: u64 },
70 #[error("selected phase {phase} requires unsatisfied phase {dependency}")]
71 UnsatisfiedPhaseDependency { phase: u64, dependency: u64 },
72 #[error("confirmation input ended after phase {phase} before the next phase could start")]
73 PhaseConfirmationEof { phase: u64 },
74 #[error("failed to read confirmation after phase {phase}")]
75 PhaseConfirmationInput {
76 phase: u64,
77 #[source]
78 source: io::Error,
79 },
80 #[error("task `{task}` has no workload")]
81 MissingTaskWorkload { task: String },
82 #[error("task `{task}` failed: {source}")]
83 TaskWorkload {
84 task: String,
85 #[source]
86 source: Box<dyn std::error::Error + Send + Sync + 'static>,
87 },
88 #[error("task `{task}` exceeded its timeout of {timeout:?}")]
89 TaskTimedOut { task: String, timeout: Duration },
90 #[error("phase {phase} exceeded its deadline of {deadline_after:?}")]
91 PhaseDeadlineExceeded {
92 phase: u64,
93 deadline_after: Duration,
94 },
95 #[error("a study scheduler worker panicked")]
96 SchedulerPanicked,
97 #[error("study execution was cancelled")]
98 Cancelled,
99 #[error("phase {phase} contains an empty task ID")]
100 InvalidTaskId { phase: u64 },
101 #[error("task `{task}` must have a nonempty category")]
102 InvalidTaskCategory { task: String },
103 #[error("phase {phase} repeats task ID `{task}`")]
104 DuplicateTaskId { phase: u64, task: String },
105 #[error("task selector `{selector}` matched no task")]
106 TaskNotFound { selector: String },
107 #[error("task selector `{selector}` is ambiguous between `{first}` and `{second}`")]
108 TaskSelectorAmbiguous {
109 selector: String,
110 first: String,
111 second: String,
112 },
113 #[error("task `{task}` does not contain metadata `{key}`")]
114 UnknownTaskMetadata { task: String, key: String },
115 #[error("task `{task}` metadata `{key}` could not be decoded")]
116 DecodeTaskMetadata {
117 task: String,
118 key: String,
119 #[source]
120 source: serde_json::Error,
121 },
122 #[error("task `{task}` is not registered with this renderer")]
123 UnknownTask { task: String },
124 #[error("task `{task}` is declared as {actual}, not {requested}")]
125 TaskModeMismatch {
126 task: String,
127 requested: &'static str,
128 actual: &'static str,
129 },
130 #[error("task `{identity}` has already started or reached a terminal status")]
131 TaskAlreadyStarted { identity: String },
132 #[error("task `{identity}` starts at iteration {initial}, beyond target {target}")]
133 InitialIterationBeyondTarget {
134 identity: String,
135 initial: u64,
136 target: u64,
137 },
138 #[error("task `{identity}` cannot move progress from iteration {current} back to {attempted}")]
139 IterationRegressed {
140 identity: String,
141 current: u64,
142 attempted: u64,
143 },
144 #[error("task `{identity}` reported iteration {iteration}, beyond target {target}")]
145 IterationBeyondTarget {
146 identity: String,
147 iteration: u64,
148 target: u64,
149 },
150 #[error("task `{identity}` completed at iteration {current}, before target {target}")]
151 TargetIterationNotReached {
152 identity: String,
153 current: u64,
154 target: u64,
155 },
156 #[error("failed to start the study renderer")]
157 StartRenderer {
158 #[source]
159 source: io::Error,
160 },
161 #[error("failed to {operation} for the study display")]
162 TerminalSetup {
163 operation: &'static str,
164 #[source]
165 source: io::Error,
166 },
167 #[error("the study renderer is no longer available")]
168 RendererUnavailable,
169 #[error("the study renderer panicked")]
170 RendererPanicked,
171 #[error(
172 "cannot report success with {pending} pending, {running} running, and {failed} failed tasks"
173 )]
174 IncompleteProgress {
175 pending: u64,
176 running: u64,
177 failed: u64,
178 },
179}
180
181impl StudyError {
182 pub fn study_summary(&self) -> Option<&super::StudySummary> {
183 match self {
184 Self::PhaseExecutionFailed { summary, .. } => Some(summary),
185 _ => None,
186 }
187 }
188
189 pub fn execution_cause(&self) -> Option<&StudyError> {
190 match self {
191 Self::PhaseExecutionFailed { source, .. } => Some(source),
192 _ => None,
193 }
194 }
195}