Skip to main content

wowlab_sentinel/strategy/
mod.rs

1pub(crate) mod factorial;
2
3/// Final output written to the job row.
4#[derive(Debug)]
5pub(crate) struct FinalOutput {
6    pub result_pb: Vec<u8>,
7    pub timeline_pb: Option<Vec<u8>>,
8}
9
10#[derive(thiserror::Error)]
11#[error("{kind}")]
12#[non_exhaustive]
13pub(crate) struct StrategyError {
14    #[source]
15    kind: StrategyErrorKind,
16}
17
18#[derive(Debug, thiserror::Error)]
19enum StrategyErrorKind {
20    #[error("state decode error: {0}")]
21    StateDecode(#[source] prost::DecodeError),
22    #[error("state decode error: tournament payload: {0}")]
23    TournamentPayloadDecode(#[source] prost::DecodeError),
24    #[error("merge error: {0}")]
25    Merge(#[source] wowlab_analytics::MergeError),
26    #[error("merge error: single requires full telemetry")]
27    FullTelemetryRequired,
28    #[error("merge error: unknown tag: {tag}")]
29    UnknownTag { tag: u64 },
30    #[error("merge error: missing baseline run")]
31    MissingBaseline,
32    #[error("result exceeds budget: {size} > {budget}")]
33    BudgetExceeded { size: usize, budget: usize },
34}
35
36impl StrategyError {
37    pub(crate) const fn state_decode(source: prost::DecodeError) -> Self {
38        Self {
39            kind: StrategyErrorKind::StateDecode(source),
40        }
41    }
42
43    pub(crate) const fn tournament_payload_decode(source: prost::DecodeError) -> Self {
44        Self {
45            kind: StrategyErrorKind::TournamentPayloadDecode(source),
46        }
47    }
48
49    pub(crate) const fn merge(source: wowlab_analytics::MergeError) -> Self {
50        Self {
51            kind: StrategyErrorKind::Merge(source),
52        }
53    }
54
55    pub(crate) const fn full_telemetry_required() -> Self {
56        Self {
57            kind: StrategyErrorKind::FullTelemetryRequired,
58        }
59    }
60
61    pub(crate) const fn unknown_tag(tag: u64) -> Self {
62        Self {
63            kind: StrategyErrorKind::UnknownTag { tag },
64        }
65    }
66
67    pub(crate) const fn missing_baseline() -> Self {
68        Self {
69            kind: StrategyErrorKind::MissingBaseline,
70        }
71    }
72
73    const fn budget_exceeded(size: usize, budget: usize) -> Self {
74        Self {
75            kind: StrategyErrorKind::BudgetExceeded { size, budget },
76        }
77    }
78}
79
80impl std::fmt::Debug for StrategyError {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        let category = match &self.kind {
83            StrategyErrorKind::StateDecode(_) => "StateDecode",
84            StrategyErrorKind::TournamentPayloadDecode(_) => "TournamentPayloadDecode",
85            StrategyErrorKind::Merge(_) => "Merge",
86            StrategyErrorKind::FullTelemetryRequired => "FullTelemetryRequired",
87            StrategyErrorKind::UnknownTag { .. } => "UnknownTag",
88            StrategyErrorKind::MissingBaseline => "MissingBaseline",
89            StrategyErrorKind::BudgetExceeded { .. } => "BudgetExceeded",
90        };
91
92        f.debug_struct("StrategyError")
93            .field("category", &category)
94            .finish_non_exhaustive()
95    }
96}
97
98impl From<prost::DecodeError> for StrategyError {
99    fn from(source: prost::DecodeError) -> Self {
100        Self::state_decode(source)
101    }
102}
103
104impl From<wowlab_analytics::MergeError> for StrategyError {
105    fn from(source: wowlab_analytics::MergeError) -> Self {
106        Self::merge(source)
107    }
108}
109
110pub(crate) fn encode_and_check<M>(msg: &M, budget: usize) -> Result<Vec<u8>, StrategyError>
111where
112    M: prost::Message,
113{
114    let bytes = msg.encode_to_vec();
115
116    if bytes.len() > budget {
117        return Err(StrategyError::budget_exceeded(bytes.len(), budget));
118    }
119
120    Ok(bytes)
121}