stackless_core/state/
error.rs1use 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}