dpm_store/run_error.rs
1//! Why a run store request was refused or could not be completed safely.
2
3use crate::StoreError;
4use dpm_model::{
5 ActivityEntry, LifecycleEntry, LineageId, OperationId, RunId, RunRecord, WorkspaceId,
6};
7use std::path::PathBuf;
8use thiserror::Error;
9
10/// A run store operation that was refused or could not be completed.
11///
12/// Every refusal leaves the store as it was: writes are one transaction, and a file that cannot be
13/// trusted is never opened for writing.
14#[derive(Debug, Error)]
15pub enum RunStoreError {
16 /// The database, its layout, its lineage binding or a stored record failed.
17 #[error(transparent)]
18 Store(#[from] StoreError),
19 /// The engine refuses the request: malformed, unauthorized or an illegal transition.
20 #[error(transparent)]
21 Run(#[from] dpm_engine::RunError),
22 /// No run is recorded under this identity.
23 #[error("run {0} is not recorded")]
24 UnknownRun(RunId),
25 /// The named parent run is not recorded.
26 #[error("parent run {0} is not recorded")]
27 UnknownParent(RunId),
28 /// The run identity is already recorded with a different request.
29 #[error("run {} is already recorded with different content; use a new run id for a new run", .recorded.id)]
30 DuplicateRun {
31 /// The run recorded under this identity.
32 recorded: Box<RunRecord>,
33 },
34 /// The transition identity is already recorded with different content.
35 #[error("transition {} is already recorded with different content; use a new event id", .recorded.event.id)]
36 DuplicateEvent {
37 /// The lifecycle fact recorded under this identity.
38 recorded: Box<LifecycleEntry>,
39 },
40 /// The activity key is already recorded for the run with different content.
41 #[error("activity {} of run {} is already recorded with different content", .recorded.record.source_sequence, .recorded.record.run)]
42 DuplicateActivity {
43 /// The activity record stored under this key.
44 recorded: Box<ActivityEntry>,
45 },
46 /// The activity record's source sequence is at or below the run's high-water mark and the store
47 /// no longer holds it: either a retry of a record retention already removed, or a record that
48 /// arrived out of order. Nothing was written, and the run's receipt time did not move.
49 #[error(
50 "activity {source_sequence} of run {run} is expired: the run's accepted sequences already reach {high_water}, and this one is not retained; sequences must increase"
51 )]
52 ActivityExpired {
53 /// The run.
54 run: RunId,
55 /// The refused record's source sequence.
56 source_sequence: u64,
57 /// The highest source sequence the run has accepted.
58 high_water: u64,
59 },
60 /// The run was recorded under another lineage than the store's current one, such as before a
61 /// restore. It stays a historical observation: nothing new is written to it.
62 #[error(
63 "run {run} was recorded under lineage {recorded}, but this store continues lineage {current}; it is a historical observation and takes no new facts, so start a new run"
64 )]
65 ForeignRun {
66 /// The run.
67 run: RunId,
68 /// The lineage the run was recorded under.
69 recorded: LineageId,
70 /// The lineage the store continues.
71 current: LineageId,
72 },
73 /// The operation is already linked to another run.
74 #[error("operation {operation} is already linked to run {linked}")]
75 LinkConflict {
76 /// The operation.
77 operation: OperationId,
78 /// The run it is linked to.
79 linked: RunId,
80 },
81 /// The run store belongs to another workspace than the project store it sits beside.
82 #[error("run store {path} belongs to workspace {actual}, not {expected}; nothing was changed")]
83 WorkspaceMismatch {
84 /// Run store file.
85 path: PathBuf,
86 /// Workspace of the project store.
87 expected: WorkspaceId,
88 /// Workspace the run store is bound to.
89 actual: WorkspaceId,
90 },
91 /// Stored run data is inconsistent, so the store cannot be trusted as it is.
92 #[error("run store {path} is inconsistent: {detail}")]
93 Corrupt {
94 /// Inspected run store.
95 path: PathBuf,
96 /// First inconsistency found.
97 detail: String,
98 },
99}
100
101impl RunStoreError {
102 /// Whether the stored data itself is damaged or inconsistent, as opposed to a refused request.
103 #[must_use]
104 pub fn is_corruption(&self) -> bool {
105 match self {
106 Self::Store(error) => error.is_corruption(),
107 Self::Corrupt { .. } => true,
108 _ => false,
109 }
110 }
111
112 /// Whether another connection held the database lock past the busy timeout; nothing was
113 /// written, and retrying later with the same identities is safe.
114 #[must_use]
115 pub fn is_busy(&self) -> bool {
116 matches!(self, Self::Store(error) if error.is_busy())
117 }
118}