Skip to main content

statum_core/
lib.rs

1//! Core traits and helper types shared by Statum crates.
2//!
3//! Most users reach these through the top-level `statum` crate. This crate
4//! holds the small runtime surface that macro-generated code targets:
5//!
6//! - state marker traits
7//! - transition capability traits
8//! - runtime error and result types
9//! - projection helpers for event-log style rebuilds
10
11use std::borrow::Cow;
12
13#[cfg(doctest)]
14#[doc = include_str!("../README.md")]
15mod readme_doctests {}
16
17mod introspection;
18
19pub mod projection;
20
21#[doc(hidden)]
22pub use introspection::__STATUM_LINKED_MACHINES;
23#[doc(hidden)]
24pub use introspection::__STATUM_LINKED_REFERENCE_TYPES;
25#[doc(hidden)]
26pub use introspection::__STATUM_LINKED_RELATIONS;
27#[doc(hidden)]
28pub use introspection::__STATUM_LINKED_VALIDATOR_ENTRIES;
29#[doc(hidden)]
30pub use introspection::__STATUM_LINKED_VIA_ROUTES;
31
32#[doc(hidden)]
33pub mod __private {
34    pub use crate::{
35        Attested, LinkedMachineGraph, LinkedReferenceTypeDescriptor, LinkedRelationBasis, LinkedRelationDescriptor,
36        LinkedRelationKind, LinkedRelationSource, LinkedRelationTarget, LinkedStateDescriptor,
37        LinkedTransitionDescriptor, LinkedTransitionInventory, LinkedValidatorEntryDescriptor,
38        LinkedViaRouteDescriptor, MachinePresentation,
39        MachinePresentationDescriptor, MachineReference, MachineReferenceTarget, MachineRole,
40        RebuildAttempt, RebuildReport, StateFamily, StateFamilyMember, StatePresentation,
41        StaticMachineLinkDescriptor, TransitionPresentation, TransitionPresentationInventory,
42        __STATUM_LINKED_MACHINES, __STATUM_LINKED_REFERENCE_TYPES, __STATUM_LINKED_RELATIONS,
43        __STATUM_LINKED_VALIDATOR_ENTRIES, __STATUM_LINKED_VIA_ROUTES,
44    };
45    pub use futures;
46    pub use linkme;
47
48    #[derive(Debug)]
49    pub struct TransitionToken {
50        _private: u8,
51    }
52
53    impl Default for TransitionToken {
54        fn default() -> Self {
55            Self::new()
56        }
57    }
58
59    impl TransitionToken {
60        pub const fn new() -> Self {
61            Self { _private: 0 }
62        }
63    }
64
65    pub fn attest<T, Via>(inner: T) -> crate::Attested<T, Via> {
66        crate::Attested::new(inner)
67    }
68}
69
70pub use introspection::{
71    linked_machines, linked_reference_types, linked_relations, linked_validator_entries,
72    linked_via_routes, LinkedMachineGraph, LinkedReferenceTypeDescriptor, LinkedRelationBasis,
73    LinkedRelationDescriptor, LinkedRelationKind, LinkedRelationSource, LinkedRelationTarget,
74    LinkedStateDescriptor, LinkedTransitionDescriptor, LinkedTransitionInventory,
75    LinkedValidatorEntryDescriptor, LinkedViaRouteDescriptor, MachineDescriptor, MachineGraph,
76    MachineIntrospection, MachinePresentation, MachineRole, MachinePresentationDescriptor,
77    MachineStateIdentity, MachineTransitionRecorder, RecordedTransition, StateDescriptor,
78    StatePresentation, StaticMachineLinkDescriptor, TransitionDescriptor, TransitionInventory,
79    TransitionPresentation, TransitionPresentationInventory,
80};
81
82/// Hidden family-level metadata emitted by `#[state]`.
83#[doc(hidden)]
84pub trait StateFamily {
85    /// Rust-facing enum name for this family.
86    const NAME: &'static str;
87
88    /// Number of generated legal state markers in this family.
89    const VARIANT_COUNT: usize;
90}
91
92/// Hidden per-marker metadata emitted by `#[state]`.
93#[doc(hidden)]
94pub trait StateFamilyMember: StateMarker {
95    /// Rust-facing marker name for this state.
96    const RUST_NAME: &'static str;
97
98    /// Whether this state carries data.
99    const HAS_DATA: bool;
100}
101
102/// A generated state marker type.
103///
104/// Every `#[state]` variant produces one marker type that implements
105/// `StateMarker`. The associated `Data` type is `()` for unit states and the
106/// tuple payload type for data-bearing states.
107pub trait StateMarker {
108    /// The payload type stored in machines for this state.
109    type Data;
110}
111
112/// A generated state marker with no payload.
113///
114/// Implemented for unit state variants like `Draft` or `Published`.
115pub trait UnitState: StateMarker<Data = ()> {}
116
117/// A generated state marker that carries payload data.
118///
119/// Implemented for tuple variants like `InReview(Assignment)`.
120pub trait DataState: StateMarker {}
121
122/// One exact target declared for a nominal machine reference type.
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub struct MachineReferenceTarget {
125    /// Exact machine path segments resolved from the declaration target.
126    pub machine_path: &'static [&'static str],
127    /// Target state marker name written in the declaration.
128    pub state: &'static str,
129}
130
131/// One nominal type that carries an exact machine relation declared once.
132pub trait MachineReference {
133    /// Exact target described by this nominal reference type.
134    const TARGET: MachineReferenceTarget;
135}
136
137/// A machine that can transition directly to `Next`.
138///
139/// This is the stable trait-level view of `self.transition()`.
140pub trait CanTransitionTo<Next> {
141    /// The transition result type.
142    type Output;
143
144    /// Perform the transition.
145    fn transition_to(self) -> Self::Output;
146}
147
148/// A machine that can transition using `Data`.
149///
150/// This is the stable trait-level view of `self.transition_with(data)`.
151pub trait CanTransitionWith<Data> {
152    /// The next state selected by this transition.
153    type NextState;
154    /// The transition result type.
155    type Output;
156
157    /// Perform the transition with payload data.
158    fn transition_with_data(self, data: Data) -> Self::Output;
159}
160
161/// A machine that can transition by mapping its current state data into `Next`.
162///
163/// This is the stable trait-level view of `self.transition_map(...)`.
164pub trait CanTransitionMap<Next: StateMarker> {
165    /// The payload type stored in the current state.
166    type CurrentData;
167    /// The transition result type.
168    type Output;
169
170    /// Perform the transition by consuming the current state data and producing the next payload.
171    fn transition_map<F>(self, f: F) -> Self::Output
172    where
173        F: FnOnce(Self::CurrentData) -> Next::Data;
174}
175
176/// Errors returned by Statum runtime helpers.
177#[derive(Debug)]
178pub enum Error {
179    /// Returned when a runtime check determines the current state is invalid.
180    InvalidState,
181}
182
183/// A first-class two-way branching transition result.
184///
185/// This lets a transition expose two concrete machine targets while keeping the
186/// branch alternatives visible to Statum introspection.
187#[derive(Clone, Debug, Eq, PartialEq)]
188pub enum Branch<A, B> {
189    /// The first legal target branch.
190    First(A),
191    /// The second legal target branch.
192    Second(B),
193}
194
195/// A machine value with attached typed provenance for how it was produced.
196///
197/// Statum uses this wrapper for attested transition routes such as
198/// `capture_and_attest()` and generated `from_*()` binders. The public surface
199/// exposes the wrapped machine and the route type, but construction stays on
200/// the macro-generated path so ordinary callers cannot forge attestation
201/// accidentally.
202#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
203pub struct Attested<T, Via> {
204    inner: T,
205    marker: core::marker::PhantomData<Via>,
206}
207
208impl<T, Via> Attested<T, Via> {
209    #[doc(hidden)]
210    pub fn new(inner: T) -> Self {
211        Self {
212            inner,
213            marker: core::marker::PhantomData,
214        }
215    }
216
217    /// Consumes the wrapper and returns the attested inner value.
218    pub fn into_inner(self) -> T {
219        self.inner
220    }
221
222    /// Borrows the attested inner value.
223    #[allow(clippy::should_implement_trait)]
224    pub fn as_ref(&self) -> &T {
225        &self.inner
226    }
227
228    /// Maps the inner value while preserving the attested route marker.
229    pub fn map_inner<U>(self, f: impl FnOnce(T) -> U) -> Attested<U, Via> {
230        Attested::new(f(self.inner))
231    }
232}
233
234impl<T, Via> AsRef<T> for Attested<T, Via> {
235    fn as_ref(&self) -> &T {
236        &self.inner
237    }
238}
239
240/// Convenience result alias used by Statum APIs.
241///
242/// # Example
243///
244/// ```
245/// fn ensure_ready(ready: bool) -> statum_core::Result<()> {
246///     if ready {
247///         Ok(())
248///     } else {
249///         Err(statum_core::Error::InvalidState)
250///     }
251/// }
252///
253/// assert!(ensure_ready(true).is_ok());
254/// assert!(ensure_ready(false).is_err());
255/// ```
256pub type Result<T> = core::result::Result<T, Error>;
257
258/// A structured validator rejection captured during typed rehydration.
259#[derive(Clone, Debug, Eq, PartialEq)]
260pub struct Rejection {
261    /// Stable machine-readable reason key for why the validator rejected.
262    pub reason_key: &'static str,
263    /// Optional human-readable message for debugging and reports.
264    pub message: Option<Cow<'static, str>>,
265}
266
267impl Rejection {
268    /// Create a rejection with a stable reason key and no message.
269    pub const fn new(reason_key: &'static str) -> Self {
270        Self {
271            reason_key,
272            message: None,
273        }
274    }
275
276    /// Attach a human-readable message to this rejection.
277    pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
278        Self {
279            message: Some(message.into()),
280            ..self
281        }
282    }
283}
284
285impl From<&'static str> for Rejection {
286    fn from(reason_key: &'static str) -> Self {
287        Self::new(reason_key)
288    }
289}
290
291impl core::fmt::Display for Rejection {
292    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293        match &self.message {
294            Some(message) => write!(fmt, "{}: {}", self.reason_key, message),
295            None => write!(fmt, "{}", self.reason_key),
296        }
297    }
298}
299
300impl std::error::Error for Rejection {}
301
302/// An opt-in validator result that carries structured rejection details.
303pub type Validation<T> = core::result::Result<T, Rejection>;
304
305/// One validator evaluation recorded during typed rehydration.
306#[derive(Clone, Debug, Eq, PartialEq)]
307pub struct RebuildAttempt {
308    /// Rust method name of the validator that ran.
309    pub validator: &'static str,
310    /// Rust state-marker name the validator was checking.
311    pub target_state: &'static str,
312    /// Whether this validator matched and produced the rebuilt state.
313    pub matched: bool,
314    /// Stable machine-readable rejection key, when the validator exposed one.
315    pub reason_key: Option<&'static str>,
316    /// Optional human-readable rejection message, when the validator exposed one.
317    pub message: Option<Cow<'static, str>>,
318}
319
320/// A typed rehydration result plus the validator attempts that produced it.
321#[derive(Debug)]
322pub struct RebuildReport<M> {
323    /// Validator attempts in evaluation order.
324    pub attempts: Vec<RebuildAttempt>,
325    /// Final rebuild result.
326    pub result: Result<M>,
327}
328
329impl<M> RebuildReport<M> {
330    /// Returns the first matching validator attempt, if any.
331    pub fn matched_attempt(&self) -> Option<&RebuildAttempt> {
332        self.attempts.iter().find(|attempt| attempt.matched)
333    }
334
335    /// Consumes the report and returns the original rebuild result.
336    pub fn into_result(self) -> Result<M> {
337        self.result
338    }
339}
340
341impl<T> From<Error> for core::result::Result<T, Error> {
342    fn from(val: Error) -> Self {
343        Err(val)
344    }
345}
346
347impl core::fmt::Display for Error {
348    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), core::fmt::Error> {
349        write!(fmt, "{self:?}")
350    }
351}
352
353impl std::error::Error for Error {}