Skip to main content

turnframe_test/workflows/traveler/
command.rs

1//! Typed traveler commands, events and model-facing argument shapes.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::workflows::traveler::state::DeclineReason;
7
8/// Keys of the operations the interpreter may propose.
9pub mod operations {
10    /// Create the draft.
11    pub const CREATE_DRAFT: &str = "traveler.create_draft";
12    /// Set the full name.
13    pub const SET_NAME: &str = "traveler.set_full_name";
14    /// Change the contact address.
15    pub const CHANGE_EMAIL: &str = "traveler.change_email";
16    /// Set the loyalty number.
17    pub const SET_LOYALTY_NUMBER: &str = "traveler.set_loyalty_number";
18    /// Record that the user will not give the loyalty number.
19    pub const DECLINE_LOYALTY_NUMBER: &str = "traveler.decline_loyalty_number";
20    /// Make the traveler usable.
21    pub const ACTIVATE: &str = "traveler.activate";
22    /// Keep the traveler for the record only.
23    pub const ARCHIVE: &str = "traveler.archive";
24    /// Remove the traveler.
25    pub const DELETE: &str = "traveler.delete";
26}
27
28/// One typed traveler command.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31#[non_exhaustive]
32pub enum TravelerCommand {
33    /// Bring the case into existence.
34    CreateDraft,
35    /// Bring the case into existence with its full name.
36    CreateNamedDraft {
37        /// The name.
38        full_name: String,
39    },
40    /// Set the full name.
41    SetName {
42        /// The name.
43        value: String,
44    },
45    /// Change the contact address. Sensitive: it is where notifications go.
46    ChangeEmail {
47        /// The new address.
48        value: String,
49    },
50    /// Set the loyalty number.
51    SetLoyaltyNumber {
52        /// The number.
53        value: String,
54    },
55    /// Record that the user will not give the loyalty number, and
56    /// why. Settles the field without a value, closing its obligation.
57    DeclineLoyaltyNumber {
58        /// Why the user is not giving it.
59        reason: DeclineReason,
60    },
61    /// Make the traveler usable.
62    Activate,
63    /// Keep the traveler for the record only.
64    Archive,
65    /// Remove the traveler.
66    Delete,
67}
68
69/// One committed traveler event.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72#[non_exhaustive]
73pub enum TravelerEvent {
74    /// The draft came into existence.
75    DraftCreated,
76    /// The full name changed.
77    NameSet {
78        /// The new name.
79        value: String,
80    },
81    /// The contact address changed.
82    EmailChanged {
83        /// The address it replaced, when there was one.
84        #[serde(default, skip_serializing_if = "Option::is_none")]
85        previous: Option<String>,
86        /// The new address.
87        value: String,
88    },
89    /// The loyalty number changed.
90    LoyaltyNumberSet {
91        /// The new number.
92        value: String,
93    },
94    /// The user declined to give the loyalty number.
95    LoyaltyNumberDeclined {
96        /// Why.
97        reason: DeclineReason,
98    },
99    /// The traveler became usable.
100    Activated,
101    /// The traveler was archived.
102    Archived,
103    /// The traveler was removed.
104    Deleted,
105}
106
107impl TravelerEvent {
108    /// The stable event type label stored on the committed event.
109    #[must_use]
110    pub fn event_type(&self) -> &'static str {
111        match self {
112            Self::DraftCreated => "traveler.draft_created",
113            Self::NameSet { .. } => "traveler.full_name_set",
114            Self::EmailChanged { .. } => "traveler.email_changed",
115            Self::LoyaltyNumberSet { .. } => "traveler.loyalty_number_set",
116            Self::LoyaltyNumberDeclined { .. } => "traveler.loyalty_number_declined",
117            Self::Activated => "traveler.activated",
118            Self::Archived => "traveler.archived",
119            Self::Deleted => "traveler.deleted",
120        }
121    }
122}
123
124/// Arguments of [`operations::CREATE_DRAFT`]: the full name, when the user gives it.
125#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
126#[serde(deny_unknown_fields)]
127pub struct CreateDraftArgs {
128    /// The full name.
129    #[serde(default)]
130    pub full_name: Option<String>,
131}
132
133/// Arguments of the operations that carry a single string.
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
135#[serde(deny_unknown_fields)]
136pub struct ValueArgs {
137    /// The value to store.
138    pub value: String,
139}
140
141/// Arguments of [`operations::DECLINE_LOYALTY_NUMBER`].
142///
143/// The reason is a closed set rather than free text: the model picks which of
144/// three human meanings the user expressed, and the domain — not the prose —
145/// decides what each one implies.
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
147#[serde(deny_unknown_fields)]
148pub struct DeclineArgs {
149    /// Why the user is not giving the value.
150    pub reason: DeclineReason,
151}