Skip to main content

turnframe_core/
case.rs

1//! Case references and versioned values (spec §7).
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::ids::{CaseId, CaseRevision, WorkflowKey};
7
8/// Identity of a case without a revision: the key used to group views,
9/// interactions and commands that belong to the same record.
10#[derive(
11    Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
12)]
13pub struct CaseKey {
14    /// Workflow the case belongs to.
15    pub workflow: WorkflowKey,
16    /// Application-owned case identifier.
17    pub case_id: CaseId,
18}
19
20impl CaseKey {
21    /// Builds a key.
22    #[must_use]
23    pub fn new(workflow: impl Into<WorkflowKey>, case_id: impl Into<CaseId>) -> Self {
24        Self {
25            workflow: workflow.into(),
26            case_id: case_id.into(),
27        }
28    }
29
30    /// Attaches an expected revision, producing a [`CaseRef`].
31    #[must_use]
32    pub fn at(self, expected_revision: CaseRevision) -> CaseRef {
33        CaseRef {
34            workflow: self.workflow,
35            case_id: self.case_id,
36            expected_revision,
37        }
38    }
39}
40
41/// A reference to a case at an expected revision (spec §7, I13).
42///
43/// Every mutation names the revision it was planned against; a different
44/// current revision is a conflict, never a blind overwrite.
45#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
46pub struct CaseRef {
47    /// Workflow the case belongs to.
48    pub workflow: WorkflowKey,
49    /// Application-owned case identifier.
50    pub case_id: CaseId,
51    /// Revision the caller believes is current.
52    pub expected_revision: CaseRevision,
53}
54
55impl CaseRef {
56    /// Builds a reference.
57    #[must_use]
58    pub fn new(
59        workflow: impl Into<WorkflowKey>,
60        case_id: impl Into<CaseId>,
61        expected_revision: CaseRevision,
62    ) -> Self {
63        Self {
64            workflow: workflow.into(),
65            case_id: case_id.into(),
66            expected_revision,
67        }
68    }
69
70    /// The revision-less identity of the case.
71    #[must_use]
72    pub fn key(&self) -> CaseKey {
73        CaseKey {
74            workflow: self.workflow.clone(),
75            case_id: self.case_id.clone(),
76        }
77    }
78
79    /// Returns a copy pointing at another revision.
80    #[must_use]
81    pub fn with_revision(&self, revision: CaseRevision) -> Self {
82        Self {
83            workflow: self.workflow.clone(),
84            case_id: self.case_id.clone(),
85            expected_revision: revision,
86        }
87    }
88
89    /// Returns `true` when both references name the same record, regardless of
90    /// revision.
91    #[must_use]
92    pub fn same_case(&self, other: &CaseRef) -> bool {
93        self.workflow == other.workflow && self.case_id == other.case_id
94    }
95}
96
97/// A value read together with the case revision it was read at.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub struct Versioned<T> {
100    /// The value.
101    pub value: T,
102    /// Revision of the case when the value was read.
103    pub revision: CaseRevision,
104}
105
106impl<T> Versioned<T> {
107    /// Pairs a value with its revision.
108    #[must_use]
109    pub const fn new(value: T, revision: CaseRevision) -> Self {
110        Self { value, revision }
111    }
112
113    /// Borrows the inner value keeping the revision.
114    #[must_use]
115    pub const fn as_ref(&self) -> Versioned<&T> {
116        Versioned {
117            value: &self.value,
118            revision: self.revision,
119        }
120    }
121
122    /// Transforms the inner value keeping the revision.
123    #[must_use]
124    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Versioned<U> {
125        Versioned {
126            value: f(self.value),
127            revision: self.revision,
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn case_ref_key_and_revision_helpers() {
138        let r = CaseRef::new("trip", "trip-1", CaseRevision(4));
139        assert_eq!(r.key(), CaseKey::new("trip", "trip-1"));
140        assert_eq!(
141            r.with_revision(CaseRevision(5)).expected_revision,
142            CaseRevision(5)
143        );
144        assert!(r.same_case(&r.with_revision(CaseRevision(9))));
145        assert_eq!(CaseKey::new("trip", "trip-1").at(CaseRevision(4)), r);
146    }
147
148    #[test]
149    fn versioned_map_keeps_revision() {
150        let v = Versioned::new(2_u32, CaseRevision(7)).map(|n| n * 2);
151        assert_eq!(v.value, 4);
152        assert_eq!(v.revision, CaseRevision(7));
153    }
154}