panproto_schema/error.rs
1//! Error types for schema operations.
2
3use std::fmt;
4
5/// Errors that can occur during schema construction and validation.
6#[derive(Debug, thiserror::Error)]
7#[non_exhaustive]
8pub enum SchemaError {
9 /// A vertex ID was referenced but not found in the schema.
10 #[error("vertex not found: {0}")]
11 VertexNotFound(String),
12
13 /// A duplicate vertex ID was added to the schema.
14 #[error("duplicate vertex id: {0}")]
15 DuplicateVertex(String),
16
17 /// A duplicate edge was added to the schema.
18 #[error("duplicate edge from {src} to {tgt} of kind {kind}")]
19 DuplicateEdge {
20 /// Source vertex ID.
21 src: String,
22 /// Target vertex ID.
23 tgt: String,
24 /// Edge kind.
25 kind: String,
26 },
27
28 /// A duplicate hyper-edge ID was added to the schema.
29 #[error("duplicate hyper-edge id: {0}")]
30 DuplicateHyperEdge(String),
31
32 /// An edge kind violates the protocol's edge rules.
33 #[error(
34 "invalid edge kind {kind}: source kind {src_kind} not allowed (permitted: {permitted})"
35 )]
36 InvalidEdgeSource {
37 /// The edge kind.
38 kind: String,
39 /// The actual source vertex kind.
40 src_kind: String,
41 /// Comma-separated list of permitted source kinds.
42 permitted: String,
43 },
44
45 /// An edge kind violates the protocol's edge rules (target).
46 #[error(
47 "invalid edge kind {kind}: target kind {tgt_kind} not allowed (permitted: {permitted})"
48 )]
49 InvalidEdgeTarget {
50 /// The edge kind.
51 kind: String,
52 /// The actual target vertex kind.
53 tgt_kind: String,
54 /// Comma-separated list of permitted target kinds.
55 permitted: String,
56 },
57
58 /// An edge kind is not recognized by the protocol.
59 #[error("unknown edge kind: {0}")]
60 UnknownEdgeKind(String),
61
62 /// A vertex kind is not recognized by the protocol's schema theory.
63 #[error("unknown vertex kind: {0}")]
64 UnknownVertexKind(String),
65
66 /// The schema has no vertices.
67 #[error("schema has no vertices")]
68 EmptySchema,
69
70 /// A pushout overlap identified an edge that the schema it is drawn from
71 /// does not contain, so the identification has no endpoints to close over.
72 #[error(
73 "overlap names a {side} edge from {src} to {tgt} of kind {kind} that is not in that schema"
74 )]
75 OverlapEdgeNotFound {
76 /// Which schema the edge was drawn from: `left` or `right`.
77 side: &'static str,
78 /// Source vertex ID.
79 src: String,
80 /// Target vertex ID.
81 tgt: String,
82 /// Edge kind.
83 kind: String,
84 },
85
86 /// A declared entry vertex does not exist in the schema.
87 #[error("entry vertex not found: {0}")]
88 UnknownEntryVertex(String),
89
90 /// `SchemaBuilder::build_abstract` was called on a builder that
91 /// has accumulated constraints in the layout enrichment fibre.
92 /// Abstract schemas must carry no layout witnesses; call
93 /// `build_decorated` if a decorated schema was intended.
94 #[error(
95 "build_abstract called on a builder with layout-fibre constraints; \
96 use build_decorated for a decorated schema"
97 )]
98 LayoutConstraintsOnAbstractBuild,
99
100 /// [`induce`](crate::induce()) produced a sub-schema that fails
101 /// [`validate`](crate::validate) against the protocol it was cut from.
102 ///
103 /// Because induction never invents a vertex, an edge, a kind or a
104 /// constraint, every finding here is inherited from the parent schema:
105 /// either the parent was already invalid, or the cut exposed a
106 /// requirement whose endpoints did not survive.
107 #[error(
108 "induced sub-schema is invalid: {}",
109 .findings.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ")
110 )]
111 InducedSchemaInvalid {
112 /// Every violation reported for the induced sub-schema.
113 findings: Vec<ValidationError>,
114 },
115}
116
117/// An error found during schema validation against a protocol.
118#[derive(Debug, Clone, PartialEq, Eq)]
119#[non_exhaustive]
120pub enum ValidationError {
121 /// An edge violates the protocol's edge rules.
122 InvalidEdge {
123 /// The offending edge's source vertex ID.
124 src: String,
125 /// The offending edge's target vertex ID.
126 tgt: String,
127 /// The edge kind.
128 kind: String,
129 /// Human-readable reason for the violation.
130 reason: String,
131 },
132
133 /// A constraint uses a sort not recognized by the protocol.
134 InvalidConstraintSort {
135 /// The vertex with the invalid constraint.
136 vertex: String,
137 /// The unrecognized sort.
138 sort: String,
139 },
140
141 /// A vertex kind is not recognized by the protocol.
142 InvalidVertexKind {
143 /// The vertex ID.
144 vertex: String,
145 /// The unrecognized kind.
146 kind: String,
147 },
148
149 /// A required edge references a missing vertex.
150 DanglingRequiredEdge {
151 /// The vertex ID.
152 vertex: String,
153 /// The dangling edge description.
154 edge: String,
155 },
156
157 /// A recursion point names a vertex the schema does not have.
158 ///
159 /// Either end can dangle: the marker itself, which is the key the point is
160 /// filed under, or the vertex it unfolds to. Neither is caught anywhere
161 /// else, and inducing a sub-schema silently drops a marker whose ends it
162 /// cannot find, so without this check a schema carrying one validates
163 /// clean and then loses the marker with no diagnostic.
164 DanglingRecursionPoint {
165 /// The marker vertex, which is the key in `recursion_points`.
166 mu: String,
167 /// Which end is missing, for the message.
168 missing: String,
169 },
170}
171
172impl fmt::Display for ValidationError {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 match self {
175 Self::InvalidEdge {
176 src,
177 tgt,
178 kind,
179 reason,
180 } => write!(f, "invalid edge {src} -> {tgt} ({kind}): {reason}"),
181 Self::InvalidConstraintSort { vertex, sort } => {
182 write!(f, "vertex {vertex} has invalid constraint sort: {sort}")
183 }
184 Self::InvalidVertexKind { vertex, kind } => {
185 write!(f, "vertex {vertex} has invalid kind: {kind}")
186 }
187 Self::DanglingRequiredEdge { vertex, edge } => {
188 write!(f, "vertex {vertex} has dangling required edge: {edge}")
189 }
190 Self::DanglingRecursionPoint { mu, missing } => {
191 write!(
192 f,
193 "recursion point {mu} names a vertex the schema does not have: {missing}"
194 )
195 }
196 }
197 }
198}