Skip to main content

storage/service_line/
grooming.rs

1//! Grooming storage projection codes and validated cadence quantities.
2//!
3//! Grooming records can persist service codes and known repeat cadence in weeks
4//! for rebooking workflows. Unknown or groomer-recommended cadence remains a
5//! domain decision rather than a fabricated storage value.
6
7use serde::{Deserialize, Deserializer, Serialize};
8
9use domain::grooming::rebooking;
10
11use crate::operations::{self, StorageField};
12
13/// Storage shape for a migrated grooming service rules.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ContractRecord(pub domain::grooming::Contract);
17
18impl From<domain::grooming::Contract> for ContractRecord {
19    fn from(value: domain::grooming::Contract) -> Self {
20        Self(value)
21    }
22}
23
24impl From<ContractRecord> for domain::grooming::Contract {
25    fn from(record: ContractRecord) -> Self {
26        record.0
27    }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32/// Storage-facing grooming service code used by service-offering records.
33pub enum ServiceCode {
34    /// Stable storage code for mini groom.
35    MiniGroom,
36    /// Stable storage code for full groom.
37    FullGroom,
38    /// Stable storage code for exit bath.
39    ExitBath,
40    /// Stable storage code for full bath.
41    FullBath,
42    /// Stable storage code for premium bath.
43    PremiumBath,
44    /// Stable storage code for nail trim.
45    NailTrim,
46    /// Stable storage code for nail dremel.
47    NailDremel,
48    /// Stable storage code for ear cleaning.
49    EarCleaning,
50    /// Stable storage code for coat skin specific product.
51    CoatSkinSpecificProduct,
52    /// Stable storage code for first time grooming offer.
53    FirstTimeGroomingOffer,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
57/// Positive grooming cadence interval persisted in weeks.
58pub struct StoredCadenceWeeks(u8);
59
60impl StoredCadenceWeeks {
61    /// Validates and wraps a positive quantity before it is persisted.
62    pub const fn try_new(value: u8) -> std::result::Result<Self, StoredCadenceWeeksError> {
63        if value == 0 {
64            return Err(StoredCadenceWeeksError::ZeroWeeks);
65        }
66        Ok(Self(value))
67    }
68
69    /// Returns the provider numeric identifier kept on this wrapper.
70    pub const fn get(self) -> u8 {
71        self.0
72    }
73}
74
75impl<'de> Deserialize<'de> for StoredCadenceWeeks {
76    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
77    where
78        D: Deserializer<'de>,
79    {
80        Self::try_new(u8::deserialize(deserializer)?).map_err(serde::de::Error::custom)
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
85/// Validation failures for persisted grooming cadence intervals.
86pub enum StoredCadenceWeeksError {
87    #[error("stored grooming cadence requires at least one week")]
88    /// Stable storage code for zero weeks.
89    ZeroWeeks,
90}
91
92impl TryFrom<rebooking::CadenceWeeks> for StoredCadenceWeeks {
93    type Error = operations::Error;
94
95    fn try_from(value: rebooking::CadenceWeeks) -> operations::Result<Self> {
96        Self::try_new(value.get()).map_err(|err| operations::Error::InvalidDomainValue {
97            field: StorageField::GroomingCadenceWeeks,
98            reason: err.to_string(),
99        })
100    }
101}
102
103impl TryFrom<StoredCadenceWeeks> for rebooking::CadenceWeeks {
104    type Error = operations::Error;
105
106    fn try_from(value: StoredCadenceWeeks) -> operations::Result<Self> {
107        rebooking::CadenceWeeks::try_new(value.get()).map_err(|err| {
108            operations::Error::InvalidDomainValue {
109                field: StorageField::GroomingCadenceWeeks,
110                reason: err.to_string(),
111            }
112        })
113    }
114}
115
116impl From<ServiceCode> for domain::grooming::Service {
117    fn from(value: ServiceCode) -> Self {
118        match value {
119            ServiceCode::MiniGroom => Self::MiniGroom,
120            ServiceCode::FullGroom => Self::FullGroom,
121            ServiceCode::ExitBath => Self::ExitBath,
122            ServiceCode::FullBath => Self::FullBath,
123            ServiceCode::PremiumBath => Self::PremiumBath,
124            ServiceCode::NailTrim => Self::NailTrim,
125            ServiceCode::NailDremel => Self::NailDremel,
126            ServiceCode::EarCleaning => Self::EarCleaning,
127            ServiceCode::CoatSkinSpecificProduct => Self::CoatSkinSpecificProduct,
128            ServiceCode::FirstTimeGroomingOffer => Self::FirstTimeGroomingOffer,
129        }
130    }
131}
132
133impl From<domain::grooming::Service> for ServiceCode {
134    fn from(value: domain::grooming::Service) -> Self {
135        match value {
136            domain::grooming::Service::MiniGroom => Self::MiniGroom,
137            domain::grooming::Service::FullGroom => Self::FullGroom,
138            domain::grooming::Service::ExitBath => Self::ExitBath,
139            domain::grooming::Service::FullBath => Self::FullBath,
140            domain::grooming::Service::PremiumBath => Self::PremiumBath,
141            domain::grooming::Service::NailTrim => Self::NailTrim,
142            domain::grooming::Service::NailDremel => Self::NailDremel,
143            domain::grooming::Service::EarCleaning => Self::EarCleaning,
144            domain::grooming::Service::CoatSkinSpecificProduct => Self::CoatSkinSpecificProduct,
145            domain::grooming::Service::FirstTimeGroomingOffer => Self::FirstTimeGroomingOffer,
146        }
147    }
148}