use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DeclineReason {
NotApplicable,
Unknown,
Withheld,
}
impl DeclineReason {
pub const ALL: [Self; 3] = [Self::NotApplicable, Self::Unknown, Self::Withheld];
#[must_use]
pub fn code(self) -> &'static str {
match self {
Self::NotApplicable => "not_applicable",
Self::Unknown => "unknown",
Self::Withheld => "withheld",
}
}
#[must_use]
pub fn worth_asking_again(self) -> bool {
matches!(self, Self::Unknown)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum FieldState {
#[default]
Untouched,
Answered {
value: String,
},
Declined {
reason: DeclineReason,
},
}
impl FieldState {
#[must_use]
pub fn answered(value: impl Into<String>) -> Self {
Self::Answered {
value: value.into(),
}
}
#[must_use]
pub const fn declined(reason: DeclineReason) -> Self {
Self::Declined { reason }
}
#[must_use]
pub fn value(&self) -> Option<&str> {
match self {
Self::Answered { value } => Some(value),
Self::Untouched | Self::Declined { .. } => None,
}
}
#[must_use]
pub const fn is_answered(&self) -> bool {
matches!(self, Self::Answered { .. })
}
#[must_use]
pub const fn decline_reason(&self) -> Option<DeclineReason> {
match self {
Self::Declined { reason } => Some(*reason),
Self::Untouched | Self::Answered { .. } => None,
}
}
#[must_use]
pub const fn is_settled(&self) -> bool {
!matches!(self, Self::Untouched)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TravelerStatus {
#[default]
Draft,
Active,
Archived,
Deleted,
}
impl TravelerStatus {
#[must_use]
pub fn is_editable(self) -> bool {
matches!(self, Self::Draft | Self::Active)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TravelerState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub full_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(default)]
pub loyalty_number: FieldState,
pub status: TravelerStatus,
}
impl TravelerState {
#[must_use]
pub fn open_obligations(&self) -> Vec<TravelerObligation> {
if self.status != TravelerStatus::Draft {
return Vec::new();
}
let mut obligations = Vec::new();
if self.full_name.is_none() {
obligations.push(TravelerObligation::SetName);
}
if self.email.is_none() {
obligations.push(TravelerObligation::SetEmail);
}
if !self.loyalty_number.is_settled() {
obligations.push(TravelerObligation::SetLoyaltyNumber);
}
obligations
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.full_name.is_some() && self.email.is_some() && self.loyalty_number.is_settled()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TravelerPhase {
PreDraft,
Collecting,
AwaitingActivation,
Active,
Archived,
Deleted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TravelerObligation {
SetName,
SetEmail,
SetLoyaltyNumber,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TravelerOutcome {
Archived,
Deleted,
}