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
108
109
110
use crate::ExtId;
/// Errors from compiling or running the public API.
#[derive(Debug, Clone, PartialEq)]
pub enum ApiError {
/// Amount conservation failed: some input id's allocations do not sum to
/// its original amount (a row was partly/fully lost, or split incorrectly).
/// In the allocation-native (lot hypergraph) model this is the conserved
/// invariant — row *presence* is not, since a zero-amount row legitimately
/// produces no allocation. Should be impossible — a bug guard.
ConservationViolated {
id: ExtId,
original: i64,
accounted: i64,
},
/// A group id referenced by an interactive op does not exist.
UnknownGroup(u64),
/// A manual op referenced an id that is not in the workspace.
UnknownId(ExtId),
/// A manual op would disturb a frozen (signed-off) member; unpin first.
FrozenMember(ExtId),
/// A structural edit targeted a pinned (signed-off) group; unpin first.
FrozenGroup(u64),
/// A manual group needs at least two distinct members.
DegenerateGroup,
/// A requested allocation amount is not available in the live (unfrozen)
/// pool for that row id.
InsufficientLiveAmount {
id: ExtId,
requested: i64,
available: i64,
},
/// A group does not contain a live allocation for the requested row id.
UnknownAllocation { group_id: u64, id: ExtId },
}
impl ApiError {
/// A stable machine code for the wire error envelope.
pub fn code(&self) -> &'static str {
match self {
ApiError::ConservationViolated { .. } => "conservation_violated",
ApiError::UnknownGroup(_) => "unknown_group",
ApiError::UnknownId(_) => "unknown_id",
ApiError::FrozenMember(_) => "frozen_member",
ApiError::FrozenGroup(_) => "frozen_group",
ApiError::DegenerateGroup => "degenerate_group",
ApiError::InsufficientLiveAmount { .. } => "insufficient_live_amount",
ApiError::UnknownAllocation { .. } => "unknown_allocation",
}
}
/// The row id this error is about, if any (lets a client highlight it).
pub fn id(&self) -> Option<ExtId> {
match *self {
ApiError::ConservationViolated { id, .. }
| ApiError::UnknownId(id)
| ApiError::FrozenMember(id)
| ApiError::InsufficientLiveAmount { id, .. }
| ApiError::UnknownAllocation { id, .. } => Some(id),
_ => None,
}
}
/// The group id this error is about, if any.
pub fn group_id(&self) -> Option<u64> {
match *self {
ApiError::UnknownGroup(g)
| ApiError::FrozenGroup(g)
| ApiError::UnknownAllocation { group_id: g, .. } => Some(g),
_ => None,
}
}
}
impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ApiError::ConservationViolated {
id,
original,
accounted,
} => write!(
f,
"conservation violated: row {id} allocations sum to {accounted}, expected {original}"
),
ApiError::UnknownGroup(g) => write!(f, "unknown group id: {g}"),
ApiError::UnknownId(id) => write!(f, "unknown row id: {id}"),
ApiError::FrozenMember(id) => {
write!(f, "row {id} is in a pinned group; unpin it first")
}
ApiError::FrozenGroup(g) => {
write!(f, "group {g} is pinned; unpin it first")
}
ApiError::DegenerateGroup => write!(f, "a manual group needs at least two rows"),
ApiError::InsufficientLiveAmount {
id,
requested,
available,
} => write!(
f,
"row {id}: requested live amount {requested}, only {available} available"
),
ApiError::UnknownAllocation { group_id, id } => {
write!(f, "group {group_id} has no live allocation for row {id}")
}
}
}
}
impl std::error::Error for ApiError {}