adam_rs/template/error.rs
1//! Construction and binding failures without language source dependencies.
2
3use std::any::TypeId;
4use std::fmt;
5
6use super::{CellSlot, RelationshipSlot, TemplateId};
7use crate::{CellId, Error};
8
9/// Identifies an authored construction boundary before any live relationship exists.
10///
11/// Method indices precede source specialization; policy indices count registrations
12/// on the same local cell. Handles retain definition identity without retaining recipes.
13///
14/// # Examples
15/// ```
16/// use adam_rs::{ConstructionSite, Sheet, TemplateBuilder, TemplateError};
17/// let mut builder = TemplateBuilder::new();
18/// let slot = builder.cell::<i32, _>("value",
19/// || Err(std::io::Error::other("initialization failed").into()))?;
20/// let template = builder.finish()?;
21/// let error = template.instantiate(&mut Sheet::new(), &[]).unwrap_err();
22/// assert!(matches!(error, TemplateError::Construction {
23/// site: ConstructionSite::Cell(ref failed), ..
24/// } if failed == &slot));
25/// # Ok::<(), TemplateError>(())
26/// ```
27#[derive(Debug)]
28pub enum ConstructionSite {
29 /// Constructs one private cell.
30 Cell(CellSlot),
31 /// Attaches one cell's filter or requirement, in registration order.
32 Policy {
33 /// Identifies the governed cell.
34 cell: CellSlot,
35 /// Identifies the cell-local policy registration.
36 index: usize,
37 },
38 /// Constructs one authored method before specialization.
39 Method {
40 /// Identifies the local relationship.
41 relationship: RelationshipSlot,
42 /// Identifies the original authored method.
43 index: usize,
44 },
45 /// Registers the combined relationship after its factories succeed.
46 Relationship(RelationshipSlot),
47}
48
49/// Identifies a malformed recipe, binding, factory, or native graph construction.
50///
51/// Binding and declared-structure errors precede destination mutation. Factory-result
52/// validation, allocation and native construction failures may leave partial structure;
53/// the sheet remains safe to discard. See [`super::SheetTemplate::instantiate`].
54///
55/// # Examples
56/// See [`ConstructionSite`]'s failed-initializer example.
57#[derive(Debug)]
58pub enum TemplateError {
59 /// A binding names no formal parameter.
60 UnknownParameter {
61 /// The unrecognized binding name.
62 name: String,
63 },
64 /// A formal parameter has no binding.
65 MissingParameter {
66 /// The unsupplied formal name.
67 name: String,
68 },
69 /// A formal name is declared or bound more than once.
70 DuplicateParameter {
71 /// The repeated declaration or binding name.
72 name: String,
73 },
74 /// Two formals bind the same actual cell.
75 AliasedArgument {
76 /// The first formal bound to the actual.
77 first: String,
78 /// The second formal bound to the same actual.
79 second: String,
80 },
81 /// A local handle belongs to another recipe.
82 ForeignHandle {
83 /// The local handle from a different owner.
84 slot: CellSlot,
85 },
86 /// An actual handle is absent from the destination.
87 InvalidActual {
88 /// The formal receiving the invalid actual.
89 name: String,
90 /// The absent live handle.
91 actual: CellId,
92 },
93 /// A binding's concrete type differs from its formal declaration.
94 TypeMismatch {
95 /// The formal receiving the wrongly typed argument.
96 name: String,
97 /// The formal's declared concrete type.
98 expected: TypeId,
99 /// The actual's registered concrete type.
100 found: TypeId,
101 },
102 /// An actual is a caller-owned out cell, unsupported even as an input.
103 OutArgument {
104 /// The formal receiving the out cell.
105 name: String,
106 /// The unsupported caller-owned out cell.
107 actual: CellId,
108 },
109 /// A relationship has no methods.
110 EmptyRelationship,
111 /// A component containing private structure is used in a relationship-only site.
112 ConditionalComponent,
113 /// A declaration or factory result violates the recipe's graph contract.
114 InvalidDefinition {
115 /// The violated recipe invariant.
116 detail: &'static str,
117 /// The implicated local output, when a declaration has one.
118 slot: Option<CellSlot>,
119 },
120 /// The expanded graph exceeds representable or reservable capacity.
121 Capacity,
122 /// A repeatable initializer or method factory failed.
123 Factory {
124 /// The failed initializer or method factory.
125 source: anyhow::Error,
126 /// The private cell or first declared method output.
127 slot: CellSlot,
128 },
129 /// A conditional's repeatable match-expression or key factory failed.
130 ConditionalFactory {
131 /// The underlying factory failure.
132 source: anyhow::Error,
133 },
134 /// Retains a construction boundary and only its actual root-to-child inclusion chain.
135 ///
136 /// The boxed original failure preserves concrete error downcasting.
137 /// See [`super::SheetTemplate::instantiate`].
138 Construction {
139 /// Identifies the failed definition.
140 template: TemplateId,
141 /// Identifies the declaration or registration that failed.
142 site: ConstructionSite,
143 /// Retains active inclusion sites, ordered root to child.
144 inclusions: Vec<RelationshipSlot>,
145 /// Retains the original failure without requiring Send or Sync.
146 source: Box<TemplateError>,
147 },
148 /// Retains a failed conditional's definition, local ordinal and active invocation sites.
149 ///
150 /// Context owns identities, not recipes or live state. The original error and
151 /// its typed cause remain available through [`std::error::Error::source`].
152 /// See [`super::SheetTemplate::instantiate`].
153 ConditionalConstruction {
154 /// Identifies the definition containing the failed conditional.
155 template: TemplateId,
156 /// Identifies the zero-based conditional registration ordinal in that definition.
157 conditional: usize,
158 /// Retains only active inclusion sites, ordered from the root to the failed definition.
159 inclusions: Vec<RelationshipSlot>,
160 /// Retains the original concrete construction failure and its cause.
161 source: Box<TemplateError>,
162 },
163 /// An existing native graph mutator rejected construction.
164 Runtime(Error),
165}
166
167impl fmt::Display for TemplateError {
168 /// Describes the error with its local name, handle, or underlying failure.
169 ///
170 /// - Complexity: O(L + D) for L formatted characters and D nested construction
171 /// wrappers, plus underlying factory/native error formatting.
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::UnknownParameter { name } => write!(f, "unknown parameter {name:?}"),
175 Self::MissingParameter { name } => write!(f, "missing parameter {name:?}"),
176 Self::DuplicateParameter { name } => write!(f, "duplicate parameter {name:?}"),
177 Self::AliasedArgument { first, second } => {
178 write!(f, "parameters {first:?} and {second:?} alias one actual")
179 }
180 Self::ForeignHandle { slot } => write!(f, "foreign local cell {slot:?}"),
181 Self::InvalidActual { name, actual } => {
182 write!(f, "invalid actual {actual:?} for {name:?}")
183 }
184 Self::TypeMismatch {
185 name,
186 expected,
187 found,
188 } => {
189 write!(
190 f,
191 "type mismatch for {name:?}: expected {expected:?}, found {found:?}"
192 )
193 }
194 Self::OutArgument { name, actual } => write!(f, "out argument {actual:?} for {name:?}"),
195 Self::EmptyRelationship => f.write_str("relationship requires a method"),
196 Self::ConditionalComponent => f.write_str("component is not relationship-only"),
197 Self::InvalidDefinition { detail, slot } => write!(f, "{detail} at {slot:?}"),
198 Self::Capacity => f.write_str("template expansion exceeds capacity"),
199 Self::Factory { source, slot } => {
200 write!(f, "template factory failed at {slot:?}: {source}")
201 }
202 Self::ConditionalFactory { source } => {
203 write!(f, "template conditional factory failed: {source}")
204 }
205 Self::Construction { source, .. } => source.fmt(f),
206 Self::ConditionalConstruction {
207 conditional,
208 source,
209 ..
210 } => write!(
211 f,
212 "template conditional {conditional} construction failed: {source}"
213 ),
214 Self::Runtime(source) => write!(f, "native template construction failed: {source}"),
215 }
216 }
217}
218
219impl std::error::Error for TemplateError {
220 /// Returns the underlying factory or native construction failure.
221 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
222 match self {
223 Self::Factory { source, .. } => Some(source.as_ref()),
224 Self::ConditionalFactory { source } => Some(source.as_ref()),
225 Self::ConditionalConstruction { source, .. } => Some(source.as_ref()),
226 Self::Construction { source, .. } => Some(source.as_ref()),
227 Self::Runtime(source) => Some(source),
228 _ => None,
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use std::error::Error as _;
237
238 /// Describes every leaf error and exposes causes only for wrapped failures.
239 #[test]
240 fn leaf_errors_preserve_display_and_sources() {
241 let mut builder = super::super::TemplateBuilder::new();
242 let slot = builder.parameter::<i32>("value").unwrap();
243 let cases = [
244 (
245 TemplateError::UnknownParameter {
246 name: "value".into(),
247 },
248 "unknown parameter",
249 ),
250 (
251 TemplateError::MissingParameter {
252 name: "value".into(),
253 },
254 "missing parameter",
255 ),
256 (
257 TemplateError::DuplicateParameter {
258 name: "value".into(),
259 },
260 "duplicate parameter",
261 ),
262 (
263 TemplateError::AliasedArgument {
264 first: "a".into(),
265 second: "b".into(),
266 },
267 "alias",
268 ),
269 (
270 TemplateError::ForeignHandle { slot: slot.clone() },
271 "foreign local cell",
272 ),
273 (
274 TemplateError::InvalidActual {
275 name: "value".into(),
276 actual: CellId::default(),
277 },
278 "invalid actual",
279 ),
280 (
281 TemplateError::TypeMismatch {
282 name: "value".into(),
283 expected: TypeId::of::<i32>(),
284 found: TypeId::of::<bool>(),
285 },
286 "type mismatch",
287 ),
288 (
289 TemplateError::OutArgument {
290 name: "value".into(),
291 actual: CellId::default(),
292 },
293 "out argument",
294 ),
295 (TemplateError::EmptyRelationship, "requires a method"),
296 (TemplateError::ConditionalComponent, "not relationship-only"),
297 (
298 TemplateError::InvalidDefinition {
299 detail: "invalid edges",
300 slot: Some(slot.clone()),
301 },
302 "invalid edges",
303 ),
304 (TemplateError::Capacity, "capacity"),
305 ];
306 for (error, expected) in cases {
307 assert!(error.to_string().contains(expected), "{error}");
308 assert!(error.source().is_none());
309 }
310 let factory = TemplateError::Factory {
311 source: std::io::Error::other("typed cause").into(),
312 slot,
313 };
314 assert!(factory.to_string().contains("typed cause"));
315 assert!(
316 factory
317 .source()
318 .unwrap()
319 .downcast_ref::<std::io::Error>()
320 .is_some()
321 );
322 let conditional = TemplateError::ConditionalFactory {
323 source: std::io::Error::other("key cause").into(),
324 };
325 assert!(conditional.to_string().contains("key cause"));
326 assert!(
327 conditional
328 .source()
329 .unwrap()
330 .downcast_ref::<std::io::Error>()
331 .is_some()
332 );
333 let runtime = TemplateError::Runtime(Error::InvalidId);
334 assert!(runtime.to_string().contains(&Error::InvalidId.to_string()));
335 assert!(runtime.source().unwrap().downcast_ref::<Error>().is_some());
336 }
337
338 /// Retains construction context and concrete nested causes without losing display details.
339 #[test]
340 fn construction_wrappers_preserve_context_and_typed_cause() {
341 let mut builder = super::super::TemplateBuilder::new();
342 let slot = builder.parameter::<i32>("value").unwrap();
343 let template = builder.finish().unwrap();
344 let error = TemplateError::Construction {
345 template: template.id(),
346 site: ConstructionSite::Cell(slot.clone()),
347 inclusions: vec![],
348 source: Box::new(TemplateError::Factory {
349 slot: slot.clone(),
350 source: std::io::Error::other("root cause").into(),
351 }),
352 };
353 assert!(error.to_string().contains("root cause"));
354 assert!(
355 error
356 .source()
357 .unwrap()
358 .source()
359 .unwrap()
360 .downcast_ref::<std::io::Error>()
361 .is_some()
362 );
363 assert!(matches!(&error, TemplateError::Construction {
364 template: owner, site: ConstructionSite::Cell(cell), inclusions, ..
365 } if *owner == template.id() && *cell == slot && inclusions.is_empty()));
366 let error = TemplateError::ConditionalConstruction {
367 template: template.id(),
368 conditional: 2,
369 inclusions: vec![],
370 source: Box::new(error),
371 };
372 assert!(error.to_string().contains("conditional 2"));
373 assert!(error.to_string().contains("root cause"));
374 assert!(
375 error
376 .source()
377 .unwrap()
378 .downcast_ref::<TemplateError>()
379 .is_some()
380 );
381 }
382}