Skip to main content

storage/service_line/
training.rs

1//! Training storage projection codes and validated program-duration quantities.
2//!
3//! Training records preserve program choices such as stay-and-study, tutor
4//! sessions, and AKC prep. Duration is validated before persistence so runtime
5//! workflows cannot report impossible zero-week programs as source evidence.
6
7use serde::{Deserialize, Deserializer, Serialize};
8
9use domain::training::program;
10
11use crate::operations::{self, StorageField};
12
13/// Storage shape for a migrated training service rules.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ContractRecord(pub domain::training::Contract);
17
18impl From<domain::training::Contract> for ContractRecord {
19    fn from(value: domain::training::Contract) -> Self {
20        Self(value)
21    }
22}
23
24impl From<ContractRecord> for domain::training::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 training program code, including duration for stay-and-study programs.
33pub enum ProgramRecord {
34    /// Stable storage code for stay and study.
35    StayAndStudy {
36        /// Training duration in weeks for stay-and-study programs.
37        duration_weeks: StoredProgramDurationWeeks,
38    },
39    /// Stable storage code for tutor session.
40    TutorSession,
41    /// Stable storage code for group class.
42    GroupClass,
43    /// Stable storage code for puppy kindergarten.
44    PuppyKindergarten,
45    /// Stable storage code for private lesson.
46    PrivateLesson,
47    /// Stable storage code for akc canine good citizen prep.
48    AkcCanineGoodCitizenPrep,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
52/// Positive training duration persisted in weeks for stay-and-study offerings.
53pub struct StoredProgramDurationWeeks(u8);
54
55impl StoredProgramDurationWeeks {
56    /// Validates and wraps a positive quantity before it is persisted.
57    pub const fn try_new(value: u8) -> std::result::Result<Self, StoredProgramDurationWeeksError> {
58        if value == 0 {
59            return Err(StoredProgramDurationWeeksError::ZeroWeeks);
60        }
61        Ok(Self(value))
62    }
63
64    /// Returns the provider numeric identifier kept on this wrapper.
65    pub const fn get(self) -> u8 {
66        self.0
67    }
68}
69
70impl<'de> Deserialize<'de> for StoredProgramDurationWeeks {
71    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
72    where
73        D: Deserializer<'de>,
74    {
75        Self::try_new(u8::deserialize(deserializer)?).map_err(serde::de::Error::custom)
76    }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
80/// Validation failures for persisted training-program durations.
81pub enum StoredProgramDurationWeeksError {
82    #[error("stored training program duration requires at least one week")]
83    /// Stable storage code for zero weeks.
84    ZeroWeeks,
85}
86
87impl TryFrom<program::DurationWeeks> for StoredProgramDurationWeeks {
88    type Error = operations::Error;
89
90    fn try_from(value: program::DurationWeeks) -> operations::Result<Self> {
91        Self::try_new(value.get()).map_err(|err| operations::Error::InvalidDomainValue {
92            field: StorageField::TrainingProgramDurationWeeks,
93            reason: err.to_string(),
94        })
95    }
96}
97
98impl TryFrom<StoredProgramDurationWeeks> for program::DurationWeeks {
99    type Error = operations::Error;
100
101    fn try_from(value: StoredProgramDurationWeeks) -> operations::Result<Self> {
102        program::DurationWeeks::try_new(value.get()).map_err(|err| {
103            operations::Error::InvalidDomainValue {
104                field: StorageField::TrainingProgramDurationWeeks,
105                reason: err.to_string(),
106            }
107        })
108    }
109}
110
111impl TryFrom<domain::training::Program> for ProgramRecord {
112    type Error = operations::Error;
113
114    fn try_from(value: domain::training::Program) -> operations::Result<Self> {
115        Ok(match value {
116            domain::training::Program::StayAndStudy { duration } => Self::StayAndStudy {
117                duration_weeks: duration.try_into()?,
118            },
119            domain::training::Program::TutorSession => Self::TutorSession,
120            domain::training::Program::GroupClass => Self::GroupClass,
121            domain::training::Program::PuppyKindergarten => Self::PuppyKindergarten,
122            domain::training::Program::PrivateLesson => Self::PrivateLesson,
123            domain::training::Program::AkcCanineGoodCitizenPrep => Self::AkcCanineGoodCitizenPrep,
124        })
125    }
126}
127
128impl TryFrom<ProgramRecord> for domain::training::Program {
129    type Error = operations::Error;
130
131    fn try_from(value: ProgramRecord) -> operations::Result<Self> {
132        Ok(match value {
133            ProgramRecord::StayAndStudy { duration_weeks } => Self::StayAndStudy {
134                duration: duration_weeks.try_into()?,
135            },
136            ProgramRecord::TutorSession => Self::TutorSession,
137            ProgramRecord::GroupClass => Self::GroupClass,
138            ProgramRecord::PuppyKindergarten => Self::PuppyKindergarten,
139            ProgramRecord::PrivateLesson => Self::PrivateLesson,
140            ProgramRecord::AkcCanineGoodCitizenPrep => Self::AkcCanineGoodCitizenPrep,
141        })
142    }
143}