wowlab_engine_ports/
game_bugs.rs1use crate::EngineError;
4
5#[derive(Debug)]
7pub struct GameBug {
8 pub slug: &'static str,
9 pub description: &'static str,
10}
11
12inventory::collect!(&'static GameBug);
13
14#[macro_export]
16macro_rules! define_game_bug {
17 (
18 $(#[$meta:meta])*
19 $vis:vis static $name:ident = ($slug:literal, $description:literal $(,)?);
20 ) => {
21 $(#[$meta])*
22 $vis static $name: $crate::GameBug = $crate::GameBug {
23 slug: $slug,
24 description: $description,
25 };
26 inventory::submit! { &$name }
27 };
28}
29
30#[must_use]
32pub fn registered_game_bugs() -> Vec<&'static GameBug> {
33 let mut bugs: Vec<&'static GameBug> = inventory::iter::<&'static GameBug>
34 .into_iter()
35 .copied()
36 .collect();
37
38 bugs.sort_by_key(|bug| bug.slug);
39
40 bugs
41}
42
43#[derive(Clone, Debug)]
45pub struct BugSettings {
46 pub default_enabled: bool,
47 pub overrides: Vec<(String, bool)>,
48}
49
50impl BugSettings {
51 #[must_use]
53 pub fn enabled(&self, bug: &GameBug) -> bool {
54 self.overrides
55 .iter()
56 .find(|(slug, _)| slug == bug.slug)
57 .map_or(self.default_enabled, |(_, enabled)| *enabled)
58 }
59}
60
61impl Default for BugSettings {
62 fn default() -> Self {
63 Self {
64 default_enabled: true,
65 overrides: Vec::new(),
66 }
67 }
68}
69
70#[derive(Clone, Debug)]
72pub struct BugToggles {
73 default_enabled: bool,
74 overrides: Vec<(&'static str, bool)>,
75}
76
77impl BugToggles {
78 #[must_use]
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 #[must_use]
86 pub fn enabled(&self, bug: &GameBug) -> bool {
87 self.overrides
88 .iter()
89 .find(|(slug, _)| *slug == bug.slug)
90 .map_or(self.default_enabled, |(_, enabled)| *enabled)
91 }
92}
93
94impl Default for BugToggles {
95 fn default() -> Self {
96 Self {
97 default_enabled: true,
98 overrides: Vec::new(),
99 }
100 }
101}
102
103pub fn resolve_bug_toggles(settings: &BugSettings) -> Result<BugToggles, EngineError> {
107 let registry = registered_game_bugs();
108
109 if let Some((unknown, _)) = settings
110 .overrides
111 .iter()
112 .find(|(slug, _)| !registry.iter().any(|bug| bug.slug == slug))
113 {
114 return Err(EngineError::intent_validation(format!(
115 "unknown game bug {unknown:?}; registered bugs: {}",
116 registry
117 .iter()
118 .map(|bug| bug.slug)
119 .collect::<Vec<_>>()
120 .join(", ")
121 )));
122 }
123
124 let overrides = settings
125 .overrides
126 .iter()
127 .filter_map(|(slug, enabled)| {
128 registry
129 .iter()
130 .find(|bug| bug.slug == slug)
131 .map(|bug| (bug.slug, *enabled))
132 })
133 .collect();
134
135 Ok(BugToggles {
136 default_enabled: settings.default_enabled,
137 overrides,
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use googletest::prelude::*;
144
145 use super::*;
146
147 crate::define_game_bug! {
148 static TEST_BUG = (
149 "engine_ports_test_bug",
150 "test-only modeled behavior",
151 );
152 }
153
154 #[gtest]
155 fn resolved_toggles_apply_default_and_named_override() -> Result<()> {
156 let toggles = resolve_bug_toggles(&BugSettings {
157 default_enabled: true,
158 overrides: vec![(TEST_BUG.slug.to_string(), false)],
159 })
160 .or_fail()?;
161
162 verify_true!(!toggles.enabled(&TEST_BUG))
163 }
164
165 #[gtest]
166 fn unknown_toggle_reports_registered_slugs() -> Result<()> {
167 let error = resolve_bug_toggles(&BugSettings {
168 default_enabled: true,
169 overrides: vec![("unknown_bug".to_string(), false)],
170 })
171 .err()
172 .or_fail()?;
173
174 verify_true!(error.is_intent_validation())?;
175 verify_that!(error.to_string(), contains_substring("unknown_bug"))?;
176
177 verify_that!(error.to_string(), contains_substring(TEST_BUG.slug))
178 }
179}