Skip to main content

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}