Skip to main content

turnframe_understand/
check.rs

1//! The domain's own check of an understood act, run before the plan exists.
2
3use turnframe_core::error::DomainRejection;
4use turnframe_core::understanding::UnderstoodAct;
5
6/// Dry-runs an act against its record: the domain's pure compile and validation.
7///
8/// A rejection naming an argument gets one repair of the extraction with its
9/// explanation, then the act asks for that argument. A rejection naming none is left
10/// for the reducer, which refuses the act as it does any other.
11pub trait ActChecker: Send + Sync {
12    /// Checks `act`.
13    ///
14    /// # Errors
15    ///
16    /// The domain's rejection.
17    fn check(&self, act: &UnderstoodAct) -> Result<(), DomainRejection>;
18
19    /// Whether it checks anything; one that does not is skipped, and no step says it ran.
20    fn is_active(&self) -> bool {
21        true
22    }
23}
24
25/// Checks nothing: every act passes to the reducer as it is.
26#[derive(Debug, Clone, Copy, Default)]
27pub struct NoChecks;
28
29impl ActChecker for NoChecks {
30    fn check(&self, _act: &UnderstoodAct) -> Result<(), DomainRejection> {
31        Ok(())
32    }
33
34    fn is_active(&self) -> bool {
35        false
36    }
37}
38
39/// The argument name a rejection's JSON Pointer names: `/value/0` names `value`.
40pub(crate) fn argument_of(rejection: &DomainRejection) -> Option<String> {
41    let pointer = rejection.argument.as_deref()?;
42    let name = pointer.trim_start_matches('/').split('/').next()?;
43    (!name.is_empty()).then(|| name.replace("~1", "/").replace("~0", "~"))
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn a_pointer_names_its_first_segment() {
52        let rejection = DomainRejection::new("too_long", "k").on_argument("/value/0");
53        assert_eq!(argument_of(&rejection).as_deref(), Some("value"));
54        assert_eq!(argument_of(&DomainRejection::new("x", "k")), None);
55    }
56}