1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Matchable errors so callers do not parse [`std::fmt::Display`] text.
use std::fmt;
/// Library result using [`Error`].
pub type Result<T> = std::result::Result<T, Error>;
/// Recoverable catalog and mutation failures with a stable shape.
#[derive(Debug)]
pub enum Error {
/// No heading in the corpus carries this id.
IssueNotFound {
/// The id that was looked up.
id: String,
},
/// Another identity already holds the issue.
ClaimConflict {
/// The issue that is already claimed.
id: String,
/// Who holds it.
holder: String,
/// When the claim was stamped, if the heading recorded it.
claimed_at: Option<String>,
},
/// The edge would close a loop in the blocker graph.
BlockerCycle {
/// The prospective prerequisite.
blocker: String,
/// The issue that would wait on it.
issue: String,
},
/// The issue is in a state that cannot be claimed.
InvalidState {
/// The issue that was refused.
id: String,
/// The heading state at the time of the refusal.
state: String,
},
/// Any other failure, usually I/O or a parse problem.
Other(anyhow::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::IssueNotFound { id } => write!(f, "issue {id} not found"),
Error::ClaimConflict {
id,
holder,
claimed_at,
} => write!(
f,
"{id} is claimed by {holder} since {}; pass --force to take it over",
claimed_at.as_deref().unwrap_or("an unknown time")
),
Error::BlockerCycle { blocker, issue } if blocker == issue => {
write!(f, "issue {issue} cannot block itself")
}
Error::BlockerCycle { blocker, issue } => {
write!(
f,
"adding {blocker} -> {issue} would create a blocker cycle"
)
}
Error::InvalidState { id, state } => {
write!(f, "{id} is already {state}; cannot claim")
}
Error::Other(err) => write!(f, "{err}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Other(err) => Some(err.as_ref()),
_ => None,
}
}
}
impl From<anyhow::Error> for Error {
fn from(err: anyhow::Error) -> Self {
match err.downcast::<Error>() {
Ok(typed) => typed,
Err(other) => Error::Other(other),
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Other(err.into())
}
}
impl From<fmt::Error> for Error {
fn from(err: fmt::Error) -> Self {
Error::Other(err.into())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Other(err.into())
}
}