1use std::fmt;
2use std::io;
3
4pub type Result<T> = std::result::Result<T, Error>;
6
7#[derive(Debug)]
9pub enum Error {
10 InvalidInput(String),
12 UnknownMember(String),
14 Io {
16 operation: &'static str,
18 source: io::Error,
20 },
21 UpdateInProgress {
23 lock: std::path::PathBuf,
25 },
26 DestinationConflict {
28 destination: std::path::PathBuf,
30 },
31 VerificationFailed(String),
33 CandidateExecution(String),
35 #[cfg(test)]
43 Injected(String),
44}
45
46#[non_exhaustive]
53#[derive(Debug)]
54pub enum RecoveryError {
55 Core(Error),
57 RecoveryRequired {
63 evidence: std::path::PathBuf,
65 detail: String,
67 },
68}
69
70pub type RecoveryResult<T> = std::result::Result<T, RecoveryError>;
72
73impl RecoveryError {
74 pub fn evidence(&self) -> Option<&std::path::Path> {
76 match self {
77 Self::Core(_) => None,
78 Self::RecoveryRequired { evidence, .. } => Some(evidence),
79 }
80 }
81
82 pub fn detail(&self) -> &str {
84 match self {
85 Self::Core(error) => match error {
86 Error::InvalidInput(m)
87 | Error::UnknownMember(m)
88 | Error::VerificationFailed(m)
89 | Error::CandidateExecution(m) => m,
90 _ => "",
91 },
92 Self::RecoveryRequired { detail, .. } => detail,
93 }
94 }
95
96 pub(crate) fn into_core(self) -> Error {
105 match self {
106 Self::Core(error) => error,
107 Self::RecoveryRequired { evidence, detail } => Error::io(
108 "internal recovery error outside a recovery entry point",
109 io::Error::other(format!("{detail} (retained at {})", evidence.display())),
110 ),
111 }
112 }
113}
114
115impl From<Error> for RecoveryError {
116 fn from(error: Error) -> Self {
117 Self::Core(error)
118 }
119}
120
121impl Error {
122 pub(crate) fn invalid(message: impl Into<String>) -> Self {
123 Self::InvalidInput(message.into())
124 }
125
126 #[cfg(test)]
129 pub(crate) fn injected(message: impl Into<String>) -> Self {
130 Self::Injected(message.into())
131 }
132
133 pub(crate) fn io(operation: &'static str, source: io::Error) -> Self {
134 Self::Io { operation, source }
135 }
136}
137
138impl fmt::Display for RecoveryError {
139 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::Core(error) => error.fmt(formatter),
142 Self::RecoveryRequired { evidence, detail } => write!(
143 formatter,
144 "recovery evidence retained at {}: {detail}",
145 evidence.display()
146 ),
147 }
148 }
149}
150
151impl std::error::Error for RecoveryError {
152 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
153 match self {
154 Self::Core(error) => Some(error),
155 Self::RecoveryRequired { .. } => None,
156 }
157 }
158}
159
160impl fmt::Display for Error {
161 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162 match self {
163 Self::InvalidInput(message) => write!(formatter, "invalid update input: {message}"),
164 Self::UnknownMember(member) => write!(formatter, "unknown artifact member: {member}"),
165 Self::Io { operation, source } => write!(formatter, "{operation}: {source}"),
166 Self::UpdateInProgress { lock } => {
167 write!(
168 formatter,
169 "installation update already in progress: {}",
170 lock.display()
171 )
172 }
173 Self::DestinationConflict { destination } => write!(
174 formatter,
175 "destination failed ownership revalidation: {}",
176 destination.display()
177 ),
178 Self::VerificationFailed(message) => {
179 write!(formatter, "verification failed: {message}")
180 }
181 Self::CandidateExecution(message) => {
182 write!(formatter, "candidate execution failed: {message}")
183 }
184 #[cfg(test)]
185 Self::Injected(message) => {
186 write!(formatter, "injected failure: {message}")
187 }
188 }
189 }
190}
191
192impl std::error::Error for Error {
193 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
194 match self {
195 Self::Io { source, .. } => Some(source),
196 #[cfg(test)]
197 Self::Injected(_) => None,
198 Self::InvalidInput(_)
199 | Self::UnknownMember(_)
200 | Self::UpdateInProgress { .. }
201 | Self::DestinationConflict { .. }
202 | Self::VerificationFailed(_)
203 | Self::CandidateExecution(_) => None,
204 }
205 }
206}