wowlab_sentinel/strategy/
mod.rs1pub(crate) mod factorial;
2
3#[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}