aph_core/
human_not_present_flow.rs1#[derive(
15 std::fmt::Debug,
16 std::clone::Clone,
17 std::marker::Copy,
18 std::cmp::PartialEq,
19 std::cmp::Eq,
20 std::hash::Hash,
21 serde::Serialize,
22 serde::Deserialize,
23)]
24#[serde(rename_all = "camelCase")]
25pub enum HumanNotPresentNotarizationState {
26 Drafted,
28 MandateIssued,
30 EnvelopeIssued,
32 Delivered,
34 Denied,
36}
37
38impl HumanNotPresentNotarizationState {
39 pub fn is_terminal(&self) -> bool {
41 matches!(self, Self::Delivered | Self::Denied)
42 }
43
44 pub fn valid_transitions(&self) -> &'static [Self] {
46 match self {
47 Self::Drafted => &[Self::MandateIssued, Self::Denied],
48 Self::MandateIssued => &[Self::EnvelopeIssued],
49 Self::EnvelopeIssued => &[Self::Delivered],
50 Self::Delivered => &[],
51 Self::Denied => &[],
52 }
53 }
54
55 pub fn can_transition_to(&self, next: Self) -> bool {
57 self.valid_transitions().contains(&next)
58 }
59}
60
61#[derive(std::fmt::Debug, std::clone::Clone, std::cmp::PartialEq, std::cmp::Eq, serde::Serialize, serde::Deserialize)]
64pub struct HumanNotPresentNotarizationFlow {
65 state: HumanNotPresentNotarizationState,
66 delegation_mandate_id: String,
67 communication_mandate_id: std::option::Option<String>,
68 envelope_id: std::option::Option<String>,
69}
70
71impl HumanNotPresentNotarizationFlow {
72 pub fn new(delegation_mandate_id: impl std::convert::Into<String>) -> Self {
75 Self {
76 state: HumanNotPresentNotarizationState::Drafted,
77 delegation_mandate_id: delegation_mandate_id.into(),
78 communication_mandate_id: std::option::Option::None,
79 envelope_id: std::option::Option::None,
80 }
81 }
82
83 pub fn state(&self) -> HumanNotPresentNotarizationState {
85 self.state
86 }
87
88 pub fn delegation_mandate_id(&self) -> &str {
90 &self.delegation_mandate_id
91 }
92
93 pub fn communication_mandate_id(&self) -> std::option::Option<&str> {
95 self.communication_mandate_id.as_deref()
96 }
97
98 pub fn envelope_id(&self) -> std::option::Option<&str> {
100 self.envelope_id.as_deref()
101 }
102
103 pub fn set_communication_mandate_id(&mut self, id: impl std::convert::Into<String>) {
105 self.communication_mandate_id = std::option::Option::Some(id.into());
106 }
107
108 pub fn set_envelope_id(&mut self, id: impl std::convert::Into<String>) {
110 self.envelope_id = std::option::Option::Some(id.into());
111 }
112
113 pub fn transition_to(
116 &mut self,
117 next: HumanNotPresentNotarizationState,
118 ) -> std::result::Result<(), super::errors::AphError> {
119 if self.state.can_transition_to(next) {
120 self.state = next;
121 std::result::Result::Ok(())
122 } else {
123 std::result::Result::Err(super::errors::AphError::invalid_flow_transition(
124 format!("{:?}", self.state),
125 format!("{:?}", next),
126 ))
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 #[test]
134 fn terminal_states() {
135 std::assert!(super::HumanNotPresentNotarizationState::Delivered.is_terminal());
139 std::assert!(super::HumanNotPresentNotarizationState::Denied.is_terminal());
140 std::assert!(!super::HumanNotPresentNotarizationState::Drafted.is_terminal());
141 std::assert!(!super::HumanNotPresentNotarizationState::MandateIssued.is_terminal());
142 std::assert!(!super::HumanNotPresentNotarizationState::EnvelopeIssued.is_terminal());
143 }
144
145 #[test]
146 fn full_transition_walk_delivered_path() {
147 let mut flow = super::HumanNotPresentNotarizationFlow::new("dm-1");
151 std::assert_eq!(
152 flow.state(),
153 super::HumanNotPresentNotarizationState::Drafted
154 );
155 std::assert_eq!(flow.delegation_mandate_id(), "dm-1");
156
157 flow
158 .transition_to(super::HumanNotPresentNotarizationState::MandateIssued)
159 .unwrap();
160 flow
161 .transition_to(super::HumanNotPresentNotarizationState::EnvelopeIssued)
162 .unwrap();
163 flow
164 .transition_to(super::HumanNotPresentNotarizationState::Delivered)
165 .unwrap();
166
167 std::assert_eq!(
168 flow.state(),
169 super::HumanNotPresentNotarizationState::Delivered
170 );
171 std::assert!(flow.state().is_terminal());
172 }
173
174 #[test]
175 fn denial_path() {
176 let mut flow = super::HumanNotPresentNotarizationFlow::new("dm-deny");
180 flow
181 .transition_to(super::HumanNotPresentNotarizationState::Denied)
182 .unwrap();
183 std::assert_eq!(
184 flow.state(),
185 super::HumanNotPresentNotarizationState::Denied
186 );
187 std::assert!(flow.state().is_terminal());
188 }
189
190 #[test]
191 fn terminal_no_transitions() {
192 std::assert!(
194 super::HumanNotPresentNotarizationState::Delivered
195 .valid_transitions()
196 .is_empty()
197 );
198 std::assert!(
199 super::HumanNotPresentNotarizationState::Denied
200 .valid_transitions()
201 .is_empty()
202 );
203 }
204
205 #[test]
206 fn invalid_transition_returns_error() {
207 let mut flow = super::HumanNotPresentNotarizationFlow::new("dm-bad");
211 let err = flow
212 .transition_to(super::HumanNotPresentNotarizationState::EnvelopeIssued)
213 .unwrap_err();
214 std::assert_eq!(err.code(), "APH_E002");
215 std::assert_eq!(
216 flow.state(),
217 super::HumanNotPresentNotarizationState::Drafted
218 );
219 }
220
221 #[test]
222 fn set_communication_mandate_id_works() {
223 let mut flow = super::HumanNotPresentNotarizationFlow::new("dm-cm");
226 std::assert!(flow.communication_mandate_id().is_none());
227 flow.set_communication_mandate_id("cm-7");
228 std::assert_eq!(
229 flow.communication_mandate_id(),
230 std::option::Option::Some("cm-7")
231 );
232 }
233
234 #[test]
235 fn set_envelope_id_works() {
236 let mut flow = super::HumanNotPresentNotarizationFlow::new("dm-env");
239 std::assert!(flow.envelope_id().is_none());
240 flow.set_envelope_id("env-9");
241 std::assert_eq!(flow.envelope_id(), std::option::Option::Some("env-9"));
242 }
243
244 #[test]
245 fn state_serde_roundtrip() {
246 let variants = [
249 super::HumanNotPresentNotarizationState::Drafted,
250 super::HumanNotPresentNotarizationState::MandateIssued,
251 super::HumanNotPresentNotarizationState::EnvelopeIssued,
252 super::HumanNotPresentNotarizationState::Delivered,
253 super::HumanNotPresentNotarizationState::Denied,
254 ];
255 for v in &variants {
256 let json = serde_json::to_string(v).unwrap();
257 let back: super::HumanNotPresentNotarizationState = serde_json::from_str(&json).unwrap();
258 std::assert_eq!(*v, back, "round-trip mismatch for {:?}", v);
259 }
260 }
261}