scientific_workflow/time_series/error.rs
1//! Errors produced by the in-memory state-series collection.
2//!
3//! This module describes only failures that arise while organizing owned
4//! [`SystemState`](crate::system_state::SystemState) values for analysis. JSON
5//! encoding, payload reconstruction, metadata validation, filesystem access,
6//! chunk management, and writer lifecycles belong to the separate storage
7//! module and deliberately do not appear in [`StateSeriesError`].
8//!
9//! # Invariant failures
10//!
11//! A [`StateSeries`](super::state_series::StateSeries) accepts a state only when it
12//! shares the series' exact immutable layout allocation and has a simulation
13//! iteration greater than the current final iteration. These requirements make layout
14//! checks constant-time and preserve one unambiguous iteration order while
15//! still allowing gaps between sampled indices.
16//!
17//! # Analysis access failures
18//!
19//! Immutable state lookup follows ordinary slice conventions and returns an
20//! `Option`. The narrow mutable analysis boundary needs richer diagnostics
21//! because it validates both a series position and a typed state field.
22//! [`StateSeriesError::PositionOutOfBounds`] identifies the former, while
23//! [`StateSeriesError::PayloadAccess`] adds the series position to the original
24//! [`StateError`] without discarding its source-chain information.
25
26use thiserror::Error;
27
28use crate::system_state::StateError;
29
30/// A failure produced while maintaining or mutating an in-memory state series.
31///
32/// The enum is intentionally small and independent of persistence. Every
33/// variant is either a collection invariant violation or contextualized typed
34/// access into one already-stored state.
35///
36/// `StateSeriesError` is non-exhaustive so additional analysis invariants can be
37/// introduced without forcing downstream crates to use exhaustive matches.
38/// Callers should therefore retain a fallback match arm.
39#[derive(Debug, Error)]
40#[non_exhaustive]
41pub enum StateSeriesError {
42 /// The rejected state does not share the series' canonical layout.
43 ///
44 /// Structural equality is insufficient: accepted states must derive from
45 /// the same [`SystemStateSchema`](crate::system_state::SystemStateSchema) allocation. The
46 /// rejected state's iteration is retained for diagnostics without
47 /// inspecting or formatting any scientific payload.
48 #[error("state at iteration {iteration} does not share the series specification")]
49 SchemaMismatch {
50 /// Iteration carried by the rejected state.
51 iteration: u64,
52 },
53
54 /// The rejected state would violate strictly increasing simulation order.
55 ///
56 /// Iteration gaps are permitted, but an equal or decreasing value would make
57 /// ordered iteration ambiguous. Physical time is not used for ordering
58 /// because it is optional and may follow an application-specific scale.
59 #[error("state iteration {next} must be greater than the previous iteration {previous}")]
60 NonIncreasingIteration {
61 /// Iteration of the series' current final state.
62 previous: u64,
63 /// Iteration carried by the rejected state.
64 next: u64,
65 },
66
67 /// A mutable analysis request selected no stored state.
68 ///
69 /// `position` is a zero-based position in the series rather than a
70 /// simulation iteration. Both the attempted position and current length
71 /// are recorded so callers can diagnose stale analysis selections.
72 #[error("state-series position {position} is out of bounds for length {len}")]
73 PositionOutOfBounds {
74 /// Zero-based series position requested by the caller.
75 position: usize,
76 /// Number of states stored when the request was evaluated.
77 len: usize,
78 },
79
80 /// Typed mutable access failed inside the selected state.
81 ///
82 /// The source distinguishes an undeclared key, an empty field, and a
83 /// concrete payload type mismatch. Wrapping it here adds the series
84 /// position while preserving [`std::error::Error::source`] traversal.
85 #[error("cannot access state-series position {position}: {source}")]
86 PayloadAccess {
87 /// Zero-based position of the state containing the requested field.
88 position: usize,
89 /// Original typed access failure reported by `SystemState::payload_mut`.
90 #[source]
91 source: StateError,
92 },
93}