panproto_inst/error.rs
1//! Error types for instance operations.
2
3use std::fmt;
4
5use panproto_gat::Name;
6
7/// Errors from instance construction or manipulation.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum InstError {
11 /// A node ID was referenced but not found.
12 #[error("node not found: {0}")]
13 NodeNotFound(u32),
14
15 /// A vertex ID was referenced but not found in the schema.
16 #[error("vertex not found in schema: {0}")]
17 VertexNotFound(String),
18
19 /// The root node is missing.
20 #[error("root node missing from instance")]
21 MissingRoot,
22
23 /// An arc references a nonexistent node.
24 #[error("dangling arc: ({src}, {tgt})")]
25 DanglingArc {
26 /// Source node ID.
27 src: u32,
28 /// Target node ID.
29 tgt: u32,
30 },
31
32 /// Internal hom evaluation failed.
33 #[error("eval_hom failed: {0}")]
34 EvalHom(String),
35}
36
37/// Errors from the restrict operation.
38#[derive(Debug, thiserror::Error)]
39#[non_exhaustive]
40pub enum RestrictError {
41 /// No edge found between two vertices after ancestor contraction.
42 #[error("no edge found between {src} and {tgt} in target schema")]
43 NoEdgeFound {
44 /// Source vertex anchor.
45 src: String,
46 /// Target vertex anchor.
47 tgt: String,
48 },
49
50 /// Multiple edges found without a resolver entry.
51 #[error("ambiguous edge between {src} and {tgt}: {count} candidates")]
52 AmbiguousEdge {
53 /// Source vertex anchor.
54 src: String,
55 /// Target vertex anchor.
56 tgt: String,
57 /// Number of candidate edges.
58 count: usize,
59 },
60
61 /// The root was pruned during restriction.
62 #[error("root node was pruned during restriction")]
63 RootPruned,
64
65 /// Fan reconstruction failed.
66 #[error("fan reconstruction failed for hyper-edge {hyper_edge_id}: {detail}")]
67 FanReconstructionFailed {
68 /// The hyper-edge ID.
69 hyper_edge_id: String,
70 /// Details about the failure.
71 detail: String,
72 },
73
74 /// Cartesian product exceeded the configured size limit.
75 #[error("product size {actual} exceeds limit {limit} for vertex {vertex}")]
76 ProductSizeExceeded {
77 /// The target vertex whose fiber product is too large.
78 vertex: String,
79 /// The actual product size.
80 actual: usize,
81 /// The configured limit.
82 limit: usize,
83 },
84
85 /// Multi-element fiber encountered where only single-element fibers
86 /// are supported (e.g., W-type right Kan extension).
87 #[error("multi-element fiber for vertex {vertex}: {count} source vertices")]
88 MultiElementFiber {
89 /// The target vertex with multiple preimages.
90 vertex: String,
91 /// Number of source vertices in the fiber.
92 count: usize,
93 },
94
95 /// A source node's anchor is neither remapped by `vertex_remap` nor
96 /// present in `surviving_verts`, so a *total* operation (left Kan
97 /// extension `Sigma_F`, right Kan extension `Pi_F`) has no target
98 /// vertex to map it to. These operations never drop data silently;
99 /// they report the unmapped anchor instead.
100 #[error("node {node_id} has anchor `{anchor}` that is neither remapped nor surviving")]
101 UnmappedAnchor {
102 /// The anchor that has no image in the target schema.
103 anchor: Name,
104 /// The offending source node ID.
105 node_id: u32,
106 },
107
108 /// Two source vertices in a right Kan extension (`Pi_F`) fiber contribute
109 /// the same column with conflicting values, so the product-row attribute
110 /// merge would have to silently overwrite one. The merge rejects instead.
111 #[error(
112 "attribute collision on column `{column}` for vertex `{vertex}`: \
113 fiber sources disagree on its value"
114 )]
115 AttributeCollision {
116 /// The target vertex whose fiber product collides.
117 vertex: String,
118 /// The colliding column name.
119 column: String,
120 },
121
122 /// A vertex map sends two or more distinct source vertices to a single
123 /// target vertex. The W-type right Kan extension ([`wtype_pi`]) is only
124 /// defined here for vertex-injective migrations, since a non-injective
125 /// map would require constructing a product of subtrees.
126 ///
127 /// [`wtype_pi`]: crate::wtype_pi
128 #[error(
129 "non-injective vertex map: {} source vertices map to target `{target}`",
130 sources.len()
131 )]
132 NonInjectiveVertexMap {
133 /// The target vertex receiving multiple source vertices.
134 target: Name,
135 /// The source vertices in the fiber (sorted for determinism).
136 sources: Vec<Name>,
137 },
138
139 /// A field transform's expression failed to evaluate.
140 ///
141 /// The transform is reported rather than skipped. A user-authored
142 /// `apply_expr` or `compute_field` whose expression does not evaluate
143 /// is a defect in the lens, and leaving the field untouched makes it
144 /// indistinguishable from a transform that ran and changed nothing.
145 #[error("field transform on `{key}` failed to evaluate: {source}")]
146 FieldTransformFailed {
147 /// The field the transform targets: the edited key for
148 /// `ApplyExpr`, the target key for `ComputeField`, or a
149 /// `<case branch N>` marker for a `Case` predicate, which has no
150 /// single field of its own.
151 key: String,
152 /// The underlying evaluation failure.
153 #[source]
154 source: panproto_expr::ExprError,
155 },
156}
157
158/// Errors from JSON parsing.
159#[derive(Debug, thiserror::Error)]
160#[non_exhaustive]
161pub enum ParseError {
162 /// The root vertex was not found in the schema.
163 #[error("root vertex not found in schema: {0}")]
164 RootVertexNotFound(String),
165
166 /// Expected a JSON object.
167 #[error("expected JSON object at path {path}")]
168 ExpectedObject {
169 /// JSON path where the error occurred.
170 path: String,
171 },
172
173 /// Expected a JSON array.
174 #[error("expected JSON array at path {path}")]
175 ExpectedArray {
176 /// JSON path where the error occurred.
177 path: String,
178 },
179
180 /// An edge references an unknown vertex kind.
181 #[error("unknown edge target at path {path}: {detail}")]
182 UnknownEdgeTarget {
183 /// JSON path where the error occurred.
184 path: String,
185 /// Details.
186 detail: String,
187 },
188
189 /// A value could not be parsed.
190 #[error("invalid value at path {path}: {detail}")]
191 InvalidValue {
192 /// JSON path where the error occurred.
193 path: String,
194 /// Details.
195 detail: String,
196 },
197
198 /// JSON structure error.
199 #[error("JSON error: {0}")]
200 Json(#[from] serde_json::Error),
201}
202
203/// A validation error found when checking a W-type instance against a schema.
204#[derive(Debug, Clone, PartialEq, Eq)]
205#[non_exhaustive]
206pub enum ValidationError {
207 /// A node's anchor vertex is not in the schema.
208 InvalidAnchor {
209 /// The offending node ID.
210 node_id: u32,
211 /// The anchor that was not found.
212 anchor: String,
213 },
214
215 /// An arc's edge is not in the schema.
216 InvalidEdge {
217 /// Parent node ID.
218 parent: u32,
219 /// Child node ID.
220 child: u32,
221 /// Details.
222 detail: String,
223 },
224
225 /// The root node is not present in the node set.
226 MissingRoot,
227
228 /// A child node is unreachable from the root.
229 UnreachableNode {
230 /// The unreachable node ID.
231 node_id: u32,
232 },
233
234 /// A required edge is missing from a node.
235 MissingRequiredEdge {
236 /// The node ID.
237 node_id: u32,
238 /// Description of the missing edge.
239 edge: String,
240 },
241
242 /// Parent map inconsistency.
243 ParentMapInconsistent {
244 /// The node with the inconsistency.
245 node_id: u32,
246 /// Details.
247 detail: String,
248 },
249
250 /// Fan references a nonexistent node.
251 InvalidFan {
252 /// The hyper-edge ID.
253 hyper_edge_id: String,
254 /// Details.
255 detail: String,
256 },
257}
258
259impl fmt::Display for ValidationError {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 match self {
262 Self::InvalidAnchor { node_id, anchor } => {
263 write!(f, "node {node_id} has invalid anchor: {anchor}")
264 }
265 Self::InvalidEdge {
266 parent,
267 child,
268 detail,
269 } => write!(f, "invalid edge ({parent}, {child}): {detail}"),
270 Self::MissingRoot => write!(f, "root node is missing"),
271 Self::UnreachableNode { node_id } => {
272 write!(f, "node {node_id} is unreachable from root")
273 }
274 Self::MissingRequiredEdge { node_id, edge } => {
275 write!(f, "node {node_id} missing required edge: {edge}")
276 }
277 Self::ParentMapInconsistent { node_id, detail } => {
278 write!(f, "parent map inconsistency at node {node_id}: {detail}")
279 }
280 Self::InvalidFan {
281 hyper_edge_id,
282 detail,
283 } => write!(f, "invalid fan for hyper-edge {hyper_edge_id}: {detail}"),
284 }
285 }
286}