turnframe_test/workflows/traveler/
model.rs1use crate::explore::{SimulatedTransition, WorkflowModel};
4use crate::workflows::simulate;
5use crate::workflows::traveler::command::{TravelerCommand, TravelerEvent};
6use crate::workflows::traveler::definition::TravelerWorkflow;
7use crate::workflows::traveler::state::{
8 DeclineReason, FieldState, TravelerOutcome, TravelerState, TravelerStatus,
9};
10
11pub const SAMPLE_NAME: &str = "Marta Bianchi";
13
14pub const SAMPLE_EMAIL: &str = "marta@aurora.example";
16
17pub const OTHER_EMAIL: &str = "marta.bianchi@aurora.example";
19
20pub const SAMPLE_LOYALTY_NUMBER: &str = "AZ1234567";
22
23#[must_use]
25pub fn incomplete_draft() -> TravelerState {
26 TravelerState {
27 full_name: Some(SAMPLE_NAME.to_owned()),
28 ..TravelerState::default()
29 }
30}
31
32#[must_use]
34pub fn awaiting_activation() -> TravelerState {
35 TravelerState {
36 full_name: Some(SAMPLE_NAME.to_owned()),
37 email: Some(SAMPLE_EMAIL.to_owned()),
38 loyalty_number: FieldState::answered(SAMPLE_LOYALTY_NUMBER),
39 status: TravelerStatus::Draft,
40 }
41}
42
43#[must_use]
50pub fn declined_loyalty_number(reason: DeclineReason) -> TravelerState {
51 TravelerState {
52 loyalty_number: FieldState::declined(reason),
53 ..awaiting_activation()
54 }
55}
56
57#[must_use]
59pub fn active_traveler() -> TravelerState {
60 TravelerState {
61 status: TravelerStatus::Active,
62 ..awaiting_activation()
63 }
64}
65
66#[derive(Debug, Clone, Copy, Default)]
68#[non_exhaustive]
69pub struct TravelerModel {
70 pub workflow: TravelerWorkflow,
72}
73
74impl TravelerModel {
75 #[must_use]
77 pub const fn new() -> Self {
78 Self::of(TravelerWorkflow::new())
79 }
80
81 #[must_use]
83 pub const fn of(workflow: TravelerWorkflow) -> Self {
84 Self { workflow }
85 }
86}
87
88impl WorkflowModel<TravelerWorkflow> for TravelerModel {
89 fn initial_states(&self) -> Vec<Option<TravelerState>> {
90 vec![None]
91 }
92
93 fn candidate_commands(&self, state: Option<&TravelerState>) -> Vec<TravelerCommand> {
94 let Some(state) = state else {
95 return vec![TravelerCommand::CreateDraft];
96 };
97 let mut commands = Vec::new();
98 match state.status {
99 TravelerStatus::Draft => {
100 if state.full_name.is_none() {
101 commands.push(TravelerCommand::SetName {
102 value: SAMPLE_NAME.to_owned(),
103 });
104 }
105 if state.email.is_none() {
106 commands.push(TravelerCommand::ChangeEmail {
107 value: SAMPLE_EMAIL.to_owned(),
108 });
109 }
110 if !state.loyalty_number.is_answered() {
114 commands.push(TravelerCommand::SetLoyaltyNumber {
115 value: SAMPLE_LOYALTY_NUMBER.to_owned(),
116 });
117 }
118 match state.loyalty_number.decline_reason() {
119 None => {
120 commands.push(TravelerCommand::DeclineLoyaltyNumber {
121 reason: DeclineReason::NotApplicable,
122 });
123 commands.push(TravelerCommand::DeclineLoyaltyNumber {
124 reason: DeclineReason::Unknown,
125 });
126 }
127 Some(DeclineReason::Unknown) => {
128 commands.push(TravelerCommand::DeclineLoyaltyNumber {
130 reason: DeclineReason::Withheld,
131 });
132 }
133 Some(_) => {}
134 }
135 commands.push(TravelerCommand::Activate);
136 commands.push(TravelerCommand::Delete);
137 }
138 TravelerStatus::Active => {
139 if state.email.as_deref() == Some(SAMPLE_EMAIL) {
140 commands.push(TravelerCommand::ChangeEmail {
141 value: OTHER_EMAIL.to_owned(),
142 });
143 }
144 commands.push(TravelerCommand::Archive);
145 commands.push(TravelerCommand::Delete);
146 }
147 TravelerStatus::Archived | TravelerStatus::Deleted => {}
148 }
149 if state.status.is_editable() {
150 commands.push(TravelerCommand::ChangeEmail {
152 value: "not-an-address".to_owned(),
153 });
154 if state.loyalty_number.is_answered() {
155 commands.push(TravelerCommand::DeclineLoyaltyNumber {
158 reason: DeclineReason::Withheld,
159 });
160 }
161 }
162 commands
163 }
164
165 fn simulate(
166 &self,
167 state: Option<&TravelerState>,
168 command: &TravelerCommand,
169 ) -> SimulatedTransition<TravelerState, TravelerEvent> {
170 simulate(&self.workflow, state, command)
171 }
172
173 fn declared_outcomes(&self) -> Vec<TravelerOutcome> {
174 vec![TravelerOutcome::Archived, TravelerOutcome::Deleted]
175 }
176}