Skip to main content

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}