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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//! Typed algorithm failures shared with the facade.
/// Structured failures produced by Rust algorithm dispatch.
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum AlgorithmError {
/// No Rust handler has registered this typed algorithm yet.
#[error("Rust algorithm capability is unavailable: {algorithm}")]
Unavailable {
/// Canonical `verb.by` identity.
algorithm: String,
},
/// Two handlers attempted to own the same canonical algorithm.
#[error("duplicate Rust algorithm capability: {algorithm}")]
DuplicateCapability {
/// Canonical `verb.by` identity.
algorithm: String,
},
/// Cooperative cancellation was requested.
#[error("algorithm execution cancelled")]
Cancelled,
/// Selected graph exceeded the node budget.
#[error("algorithm node limit exceeded: observed {observed}, limit {limit}")]
NodeLimit {
/// Selected node count.
observed: u64,
/// Configured maximum.
limit: u64,
},
/// Selected graph exceeded the adjacency-entry budget.
#[error("algorithm edge limit exceeded: observed {observed}, limit {limit}")]
EdgeLimit {
/// Selected adjacency-entry count.
observed: u64,
/// Configured maximum.
limit: u64,
},
/// Handler produced more rows than permitted.
#[error("algorithm output row limit exceeded: observed {observed}, limit {limit}")]
OutputLimit {
/// Produced row count.
observed: u64,
/// Configured maximum.
limit: u64,
},
/// Cooperative iteration budget was exhausted.
#[error("algorithm iteration limit exceeded: observed {observed}, limit {limit}")]
IterationLimit {
/// Attempted iteration number.
observed: u64,
/// Configured maximum.
limit: u64,
},
/// An exact solver exceeded its aggregate search-state budget.
#[error("algorithm state-space limit exceeded: observed {observed}, limit {limit}")]
StateLimit {
/// Attempted cumulative state count.
observed: u64,
/// Configured maximum cumulative state count.
limit: u64,
},
/// An exact solver's aggregate state counter exceeded `UInt64`.
#[error("algorithm state-space counter exceeds UInt64 range")]
StateOverflow,
/// A Steiner invocation supplied an option outside its closed contract.
#[error("{algorithm} invalid option {option}: {reason}")]
SteinerOption {
/// Canonical path catalog value.
algorithm: &'static str,
/// Canonical option name.
option: &'static str,
/// Stable rejection reason.
reason: &'static str,
},
/// A Steiner invocation has too few distinct mandatory terminals.
#[error("{algorithm} requires at least {required} distinct terminals; observed {observed}")]
SteinerTerminalCardinality {
/// Canonical path catalog value.
algorithm: &'static str,
/// Distinct terminal count after normalization.
observed: usize,
/// Minimum distinct terminal count.
required: usize,
},
/// A mandatory Steiner terminal is outside the selected projection.
#[error("Steiner terminal {uuid:?} is outside the selected graph")]
SteinerTerminalOutsideProjection {
/// Canonical graph-native terminal UUID.
uuid: [u8; 16],
},
/// An iterative algorithm stopped without satisfying its convergence rule.
#[error("algorithm did not converge after {iterations} iterations")]
NonConvergence {
/// Completed iterations.
iterations: u64,
},
/// Handler-specific execution failure without graph data in the message.
#[error("Rust algorithm execution failed: {message}")]
Execution {
/// Sanitized diagnostic.
message: String,
},
/// Conductance is undefined for a zero-volume partition or complement.
#[error("conductance is undefined for partition {partition}: denominator volume is zero")]
UndefinedConductance {
/// Normalized partition identifier.
partition: String,
},
/// Modularity has no denominator because the selected graph has zero total edge weight.
#[error("modularity is undefined: total edge weight is zero")]
UndefinedModularity,
/// Exact automorphism counting exceeded the canonical unsigned result range.
#[error("automorphism count exceeds UInt64 range")]
AutomorphismCountOverflow,
/// Automorphism counting exceeded its deterministic search-state budget.
#[error(
"automorphism count search-state limit exceeded: observed {observed} entries, limit {limit}"
)]
AutomorphismCountStateLimit {
/// Attempted cumulative retained/generated search-state entries.
observed: u64,
/// Maximum cumulative retained/generated search-state entries.
limit: u64,
},
/// No Euler circuit exists for the selected canonical projection.
#[error("Euler circuit is undefined for the selected graph")]
UndefinedEulerCircuit,
/// No Euler path exists for the selected canonical projection.
#[error("Euler path is undefined for the selected graph")]
UndefinedEulerPath,
}
impl AlgorithmError {
/// Established public fault domain for this algorithm failure.
#[must_use]
pub const fn fault_domain(&self) -> &'static str {
match self {
Self::Unavailable { .. } | Self::DuplicateCapability { .. } => "validation",
_ => "execution",
}
}
}
#[cfg(test)]
mod tests {
use super::AlgorithmError as E;
use crate::GfError;
#[test]
fn every_algorithm_payload_survives_public_conversion() {
let cases = [
E::Unavailable {
algorithm: "rank.future".into(),
},
E::DuplicateCapability {
algorithm: "rank.degree".into(),
},
E::Cancelled,
E::NodeLimit {
observed: 3,
limit: 2,
},
E::EdgeLimit {
observed: 5,
limit: 4,
},
E::OutputLimit {
observed: 7,
limit: 6,
},
E::IterationLimit {
observed: 9,
limit: 8,
},
E::StateLimit {
observed: 11,
limit: 10,
},
E::StateOverflow,
E::SteinerOption {
algorithm: "steiner",
option: "terminals",
reason: "invalid",
},
E::SteinerTerminalCardinality {
algorithm: "steiner",
observed: 1,
required: 2,
},
E::SteinerTerminalOutsideProjection { uuid: [42; 16] },
E::NonConvergence { iterations: 17 },
E::Execution {
message: "safe detail".into(),
},
E::UndefinedConductance {
partition: "p".into(),
},
E::UndefinedModularity,
E::AutomorphismCountOverflow,
E::AutomorphismCountStateLimit {
observed: 19,
limit: 18,
},
E::UndefinedEulerCircuit,
E::UndefinedEulerPath,
];
for original in cases {
let expected = if matches!(
original,
E::Unavailable { .. } | E::DuplicateCapability { .. }
) {
"GF_VALIDATION"
} else {
"GF_EXECUTION"
};
let error = GfError::from(original.clone());
assert_eq!(error.code(), expected);
assert_eq!(
error.to_string(),
format!("{} error: {original}", original.fault_domain())
);
let GfError::Algorithm(recovered) = error else {
panic!("algorithm kind lost")
};
assert_eq!(recovered, original);
}
}
}