Skip to main content

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}