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 Default for BugSettings {
51 fn default() -> Self {
52 Self {
53 default_enabled: true,
54 overrides: Vec::new(),
55 }
56 }
57}
58
59#[derive(Clone, Debug)]
61pub struct BugToggles {
62 default_enabled: bool,
63 overrides: Vec<(&'static str, bool)>,
64}
65
66impl BugToggles {
67 #[must_use]
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 #[must_use]
75 pub fn enabled(&self, bug: &GameBug) -> bool {
76 self.overrides
77 .iter()
78 .find(|(slug, _)| *slug == bug.slug)
79 .map_or(self.default_enabled, |(_, enabled)| *enabled)
80 }
81}
82
83impl Default for BugToggles {
84 fn default() -> Self {
85 Self {
86 default_enabled: true,
87 overrides: Vec::new(),
88 }
89 }
90}
91
92pub fn resolve_bug_toggles(settings: &BugSettings) -> Result<BugToggles, EngineError> {
96 let registry = registered_game_bugs();
97
98 if let Some((unknown, _)) = settings
99 .overrides
100 .iter()
101 .find(|(slug, _)| !registry.iter().any(|bug| bug.slug == slug))
102 {
103 return Err(EngineError::intent_validation(format!(
104 "unknown game bug {unknown:?}; registered bugs: {}",
105 registry
106 .iter()
107 .map(|bug| bug.slug)
108 .collect::<Vec<_>>()
109 .join(", ")
110 )));
111 }
112
113 let overrides = settings
114 .overrides
115 .iter()
116 .filter_map(|(slug, enabled)| {
117 registry
118 .iter()
119 .find(|bug| bug.slug == slug)
120 .map(|bug| (bug.slug, *enabled))
121 })
122 .collect();
123
124 Ok(BugToggles {
125 default_enabled: settings.default_enabled,
126 overrides,
127 })
128}
129
130#[cfg(test)]
131mod tests {
132 use googletest::prelude::*;
133
134 use super::*;
135
136 crate::define_game_bug! {
137 static TEST_BUG = (
138 "engine_ports_test_bug",
139 "test-only modeled behavior",
140 );
141 }
142
143 #[gtest]
144 fn resolved_toggles_apply_default_and_named_override() -> Result<()> {
145 let toggles = resolve_bug_toggles(&BugSettings {
146 default_enabled: true,
147 overrides: vec![(TEST_BUG.slug.to_string(), false)],
148 })
149 .or_fail()?;
150
151 verify_true!(!toggles.enabled(&TEST_BUG))
152 }
153
154 #[gtest]
155 fn unknown_toggle_reports_registered_slugs() -> Result<()> {
156 let error = resolve_bug_toggles(&BugSettings {
157 default_enabled: true,
158 overrides: vec![("unknown_bug".to_string(), false)],
159 })
160 .err()
161 .or_fail()?;
162
163 verify_true!(error.is_intent_validation())?;
164 verify_that!(error.to_string(), contains_substring("unknown_bug"))?;
165
166 verify_that!(error.to_string(), contains_substring(TEST_BUG.slug))
167 }
168}