scientific_workflow/system_state/error.rs
1//! Errors produced while defining and manipulating system states.
2//!
3//! This module keeps the SystemState error surface in one place so template
4//! loading, layout validation, and typed payload access report failures
5//! consistently. The variants are deliberately specific enough for callers to
6//! inspect programmatically while their display messages retain the field or
7//! path context needed for logs.
8//!
9//! # Error sources
10//!
11//! Filesystem and JSON failures preserve their original errors through
12//! [`std::error::Error::source`]. Semantic template failures and state-access
13//! failures do not wrap another error because they are detected directly by
14//! this crate.
15//!
16//! # Performance
17//!
18//! These errors are constructed only on failure paths. Owned paths and field
19//! names are retained to make an error independent of the state or template
20//! that produced it; successful state access does not allocate error context.
21
22use std::io;
23use std::path::PathBuf;
24
25use thiserror::Error;
26
27/// A failure encountered while loading a state template or accessing a state.
28///
29/// `StateError` is non-exhaustive because later workflow features may add
30/// validation failures without forcing downstream crates to update exhaustive
31/// matches. Callers should match variants of interest and retain a fallback
32/// arm.
33#[derive(Debug, Error)]
34#[non_exhaustive]
35pub enum StateError {
36 /// A JSON state template could not be read from the filesystem.
37 #[error("failed to read state template `{path}`")]
38 TemplateRead {
39 /// Path passed to the template loader.
40 path: PathBuf,
41 /// Underlying filesystem error.
42 #[source]
43 source: io::Error,
44 },
45
46 /// A state template was readable but did not contain valid JSON.
47 #[error("failed to parse state template `{path}` as JSON")]
48 TemplateParse {
49 /// Path of the malformed template.
50 path: PathBuf,
51 /// Underlying JSON syntax or data-model error.
52 #[source]
53 source: serde_json::Error,
54 },
55
56 /// A field definition used an empty or whitespace-only name.
57 #[error("state template field at index {index} has an empty name")]
58 EmptyFieldName {
59 /// Zero-based position of the invalid field in template order.
60 index: usize,
61 },
62
63 /// Two field definitions used the same name.
64 #[error("state template declares duplicate field `{field}`")]
65 DuplicateField {
66 /// Repeated field name.
67 field: String,
68 },
69
70 /// A field definition used an empty or whitespace-only codec type tag.
71 #[error("state template field `{field}` has an empty type tag")]
72 EmptyTypeTag {
73 /// Name of the field with the invalid type tag.
74 field: String,
75 },
76
77 /// An operation addressed a key that is absent from the template layout.
78 #[error("state template does not declare field `{field}`")]
79 UnknownField {
80 /// Requested field name.
81 field: String,
82 },
83
84 /// An operation required a payload from a declared but currently empty
85 /// field.
86 #[error("state field `{field}` does not contain a payload")]
87 MissingValue {
88 /// Declared field whose slot is empty.
89 field: String,
90 },
91
92 /// A typed operation requested a different Rust type from the stored one.
93 #[error("state field `{field}` contains `{actual}`, but the operation requested `{expected}`")]
94 TypeMismatch {
95 /// Field on which the typed operation was attempted.
96 field: String,
97 /// Rust type requested by the caller.
98 expected: &'static str,
99 /// Rust type currently stored in the field.
100 actual: &'static str,
101 },
102
103 /// A reconstructed state supplied a slot count incompatible with its
104 /// template layout.
105 ///
106 /// Normal state creation allocates the correct number of slots. This
107 /// variant primarily protects deserialization and future storage backends
108 /// from constructing structurally invalid states.
109 #[error("state payload has {actual} field slots, but its template declares {expected}")]
110 FieldCountMismatch {
111 /// Number of fields declared by the template.
112 expected: usize,
113 /// Number of payload slots supplied for the state.
114 actual: usize,
115 },
116}