Skip to main content

stackless_core/state/
error.rs

1//! State-store errors (codes in `fault::codes`).
2
3use crate::fault::{Fault, codes};
4
5#[derive(Debug, thiserror::Error)]
6pub enum StateError {
7    #[error("cannot open state store at {path}: {source}")]
8    Open {
9        path: String,
10        source: rusqlite::Error,
11    },
12
13    #[error("cannot create state directory {path}: {source}")]
14    StateDir {
15        path: String,
16        source: std::io::Error,
17    },
18
19    #[error("state store migration failed: {source}")]
20    Migrate { source: rusqlite::Error },
21
22    #[error("state store query failed: {source}")]
23    Query {
24        #[from]
25        source: rusqlite::Error,
26    },
27
28    #[error("instance {name:?} already exists on substrate {existing_substrate:?}")]
29    InstanceExists {
30        name: String,
31        existing_substrate: String,
32    },
33
34    #[error("no instance named {name:?}")]
35    InstanceNotFound { name: String },
36
37    #[error(
38        "instance {instance:?} is locked by operation {operation:?} (pid {holder_pid}, started {acquired_at})"
39    )]
40    LockHeld {
41        instance: String,
42        operation: String,
43        holder_pid: u32,
44        acquired_at: i64,
45    },
46
47    #[error("cannot open remote state store: {message}")]
48    RemoteOpen { message: String },
49
50    #[error("remote state store query failed: {message}")]
51    RemoteQuery { message: String },
52
53    #[error("cannot start the remote state-store runtime: {message}")]
54    RemoteRuntime { message: String },
55
56    #[error("the remote state-store worker is gone")]
57    RemoteWorker,
58
59    #[error("state row decode failed at column {column}: {detail}")]
60    RowDecode { column: usize, detail: String },
61}
62
63impl StateError {
64    pub(super) fn remote_open(e: libsql::Error) -> Self {
65        Self::RemoteOpen {
66            message: e.to_string(),
67        }
68    }
69    pub(super) fn remote_query(e: libsql::Error) -> Self {
70        Self::RemoteQuery {
71            message: e.to_string(),
72        }
73    }
74    pub(super) fn remote_runtime(e: std::io::Error) -> Self {
75        Self::RemoteRuntime {
76            message: e.to_string(),
77        }
78    }
79    pub(super) fn remote_worker_gone() -> Self {
80        Self::RemoteWorker
81    }
82    pub(super) fn row_range(column: usize) -> Self {
83        Self::RowDecode {
84            column,
85            detail: "column index out of range".into(),
86        }
87    }
88    pub(super) fn row_type(column: usize, want: &str) -> Self {
89        Self::RowDecode {
90            column,
91            detail: format!("value is not a {want}"),
92        }
93    }
94}
95
96impl Fault for StateError {
97    fn code(&self) -> &'static str {
98        match self {
99            Self::Open { .. } | Self::StateDir { .. } => codes::STATE_OPEN,
100            Self::Migrate { .. } => codes::STATE_MIGRATE,
101            Self::Query { .. } => codes::STATE_QUERY,
102            Self::InstanceExists { .. } => codes::STATE_INSTANCE_EXISTS,
103            Self::InstanceNotFound { .. } => codes::STATE_INSTANCE_NOT_FOUND,
104            Self::LockHeld { .. } => codes::STATE_LOCK_HELD,
105            Self::RemoteOpen { .. } => codes::STATE_REMOTE_OPEN,
106            Self::RemoteQuery { .. } => codes::STATE_REMOTE_QUERY,
107            Self::RemoteRuntime { .. } => codes::STATE_REMOTE_RUNTIME,
108            Self::RemoteWorker => codes::STATE_REMOTE_WORKER,
109            Self::RowDecode { .. } => codes::STATE_ROW_DECODE,
110        }
111    }
112
113    fn remediation(&self) -> String {
114        match self {
115            Self::Open { path, .. } | Self::StateDir { path, .. } => format!(
116                "check that {path} is writable; set XDG_STATE_HOME to relocate the state dir"
117            ),
118            Self::Migrate { .. } => {
119                "the state file may be from a newer stackless; upgrade stackless or move the \
120                 state file aside"
121                    .into()
122            }
123            Self::Query { .. } => {
124                "re-run the command; if it persists, the state file may be corrupt — move it \
125                 aside and re-adopt instances with `stackless up`"
126                    .into()
127            }
128            Self::InstanceExists {
129                name,
130                existing_substrate,
131            } => format!(
132                "names are unique across substrates: `stackless up --name {name}` resumes the \
133                 existing {existing_substrate} instance, or pick a different name"
134            ),
135            Self::InstanceNotFound { name } => format!(
136                "`stackless list` shows known instances; `stackless up --name {name}` creates it"
137            ),
138            Self::LockHeld { instance, .. } => format!(
139                "wait for the running operation on {instance:?} to finish and retry; if the \
140                 holder crashed it will be taken over automatically on the next attempt"
141            ),
142            Self::RemoteOpen { .. } => {
143                "check STACKLESS_STATE_URL (libsql://… or https://…) and STACKLESS_STATE_TOKEN; \
144                 unset both to use the local state file"
145                    .into()
146            }
147            Self::RemoteQuery { .. } => {
148                "the remote state store (Turso Cloud) rejected the request or was unreachable; \
149                 check connectivity and the token, then re-run"
150                    .into()
151            }
152            Self::RemoteRuntime { .. } | Self::RemoteWorker => {
153                "the remote state-store worker could not start or stopped; re-run the command, \
154                 or unset STACKLESS_STATE_URL to use the local state file"
155                    .into()
156            }
157            Self::RowDecode { .. } => {
158                "the state store returned an unexpected row shape; the state file may be from a \
159                 newer stackless — upgrade stackless"
160                    .into()
161            }
162        }
163    }
164
165    fn instance(&self) -> Option<&str> {
166        match self {
167            Self::InstanceExists { name, .. } | Self::InstanceNotFound { name } => Some(name),
168            Self::LockHeld { instance, .. } => Some(instance),
169            _ => None,
170        }
171    }
172}