1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5#[repr(transparent)]
6pub struct SpellLabel(pub i32);
8
9impl SpellLabel {
10 pub const CLASS_SPELLS: Self = Self(16);
11 pub const MAGE: Self = Self(17);
12 pub const PRIEST: Self = Self(18);
13 pub const WARLOCK: Self = Self(19);
14 pub const ROGUE: Self = Self(20);
15 pub const DRUID: Self = Self(21);
16 pub const MONK: Self = Self(22);
17 pub const HUNTER: Self = Self(23);
18 pub const SHAMAN: Self = Self(24);
19 pub const WARRIOR: Self = Self(25);
20 pub const PALADIN: Self = Self(26);
21 pub const DEATH_KNIGHT: Self = Self(27);
22 pub const MAGE_SECONDARY: Self = Self(28);
23 pub const DEMON_HUNTER: Self = Self(66);
24 pub const EVOKER: Self = Self(1216);
25
26 pub const EVOKER_RED: Self = Self(1464);
28 pub const EVOKER_BLUE: Self = Self(1465);
30 pub const EVOKER_GREEN: Self = Self(1466);
32 pub const EVOKER_BRONZE: Self = Self(1467);
34 pub const EVOKER_BLACK: Self = Self(1468);
36
37 pub const TALENTS: Self = Self(52);
38 pub const TELEPORT: Self = Self(64);
39 pub const RANGED_DPS: Self = Self(71);
40 pub const MAJOR_OFFENSIVE_CD: Self = Self(115);
41 pub const DISPEL_UTILITY: Self = Self(118);
42 pub const DIE_ON_FAILURE: Self = Self(237);
43 pub const INTERNAL_DNT: Self = Self(292);
45 pub const SPEC_CORE: Self = Self(295);
46 pub const TALENT_ROW_A: Self = Self(374);
47 pub const TALENT_ROW_B: Self = Self(375);
48 pub const MAJOR_COOLDOWNS: Self = Self(690);
49 pub const HEALING_SPELLS: Self = Self(741);
50 pub const ROGUE_LETHAL_POISONS: Self = Self(869);
51 pub const ROGUE_NON_LETHAL_POISONS: Self = Self(870);
52 pub const PVP: Self = Self(999);
53 pub const NPC_ABILITIES: Self = Self(2832);
54
55 pub const LEGENDARY_ITEMS: Self = Self(187);
56 pub const ARTIFACT_TRAITS: Self = Self(277);
57 pub const ARTIFACT_PASSIVES: Self = Self(293);
58 pub const LEGENDARY_EFFECTS: Self = Self(294);
59
60 pub const AZERITE_ESSENCES: Self = Self(640);
61
62 pub const SHADOWLANDS_GENERAL: Self = Self(627);
63 pub const SHADOWLANDS_COMBAT: Self = Self(630);
64 pub const CONDUITS: Self = Self(931);
65 pub const COVENANT: Self = Self(976);
66
67 pub const DRAGON_RACING: Self = Self(1447);
68 pub const DRAGONFLIGHT_CRAFTED_GEAR: Self = Self(1565);
69 pub const PRIMORDIAL_STONE: Self = Self(2513);
70 pub const SINGING_CITRINE: Self = Self(3922);
71
72 pub const BRANN_DELVES: Self = Self(617);
73 pub const HERO_TALENTS: Self = Self(3022);
74 pub const ITEM_EFFECTS: Self = Self(3959);
75 pub const FURNITURE_CRAFTS: Self = Self(5702);
76
77 pub const BLACKSMITHING: Self = Self(565);
78 pub const LEATHERWORKING: Self = Self(590);
79 pub const TAILORING: Self = Self(592);
80 pub const SKINNING: Self = Self(600);
81 pub const ALCHEMY: Self = Self(602);
82 pub const JEWELCRAFTING: Self = Self(609);
83 pub const ALL_PROFESSIONS: Self = Self(969);
84 pub const COOKING: Self = Self(971);
85 pub const MODIFIED_CRAFTING_REAGENTS: Self = Self(972);
86 pub const ENCHANTING: Self = Self(974);
87 pub const INSCRIPTION: Self = Self(975);
88 pub const ALL_CRAFTED: Self = Self(1396);
89 pub const ENGINEERING: Self = Self(1530);
90 pub const MINING: Self = Self(1762);
91 pub const HERBALISM: Self = Self(1930);
92
93 pub const PROF_CLASSIC: Self = Self(2409);
94 pub const PROF_TBC: Self = Self(2410);
95 pub const PROF_WOTLK: Self = Self(2411);
96 pub const PROF_CATA: Self = Self(2412);
97 pub const PROF_MOP: Self = Self(2413);
98 pub const PROF_WOD: Self = Self(2414);
99 pub const PROF_LEGION: Self = Self(2415);
100 pub const PROF_BFA: Self = Self(2416);
101 pub const PROF_DF: Self = Self(3232);
102
103 pub const KNOWN: &'static [(Self, &'static str)] = &[
104 (Self::CLASS_SPELLS, "ClassSpells"),
105 (Self::MAGE, "Mage"),
106 (Self::PRIEST, "Priest"),
107 (Self::WARLOCK, "Warlock"),
108 (Self::ROGUE, "Rogue"),
109 (Self::DRUID, "Druid"),
110 (Self::MONK, "Monk"),
111 (Self::HUNTER, "Hunter"),
112 (Self::SHAMAN, "Shaman"),
113 (Self::WARRIOR, "Warrior"),
114 (Self::PALADIN, "Paladin"),
115 (Self::DEATH_KNIGHT, "DeathKnight"),
116 (Self::MAGE_SECONDARY, "MageSecondary"),
117 (Self::DEMON_HUNTER, "DemonHunter"),
118 (Self::EVOKER, "Evoker"),
119 (Self::EVOKER_RED, "EvokerRed"),
120 (Self::EVOKER_BLUE, "EvokerBlue"),
121 (Self::EVOKER_GREEN, "EvokerGreen"),
122 (Self::EVOKER_BRONZE, "EvokerBronze"),
123 (Self::EVOKER_BLACK, "EvokerBlack"),
124 (Self::TALENTS, "Talents"),
125 (Self::TELEPORT, "Teleport"),
126 (Self::RANGED_DPS, "RangedDps"),
127 (Self::MAJOR_OFFENSIVE_CD, "MajorOffensiveCd"),
128 (Self::DISPEL_UTILITY, "DispelUtility"),
129 (Self::DIE_ON_FAILURE, "DieOnFailure"),
130 (Self::INTERNAL_DNT, "InternalDnt"),
131 (Self::SPEC_CORE, "SpecCore"),
132 (Self::TALENT_ROW_A, "TalentRowA"),
133 (Self::TALENT_ROW_B, "TalentRowB"),
134 (Self::MAJOR_COOLDOWNS, "MajorCooldowns"),
135 (Self::HEALING_SPELLS, "HealingSpells"),
136 (Self::ROGUE_LETHAL_POISONS, "RogueLethalPoisons"),
137 (Self::ROGUE_NON_LETHAL_POISONS, "RogueNonLethalPoisons"),
138 (Self::PVP, "Pvp"),
139 (Self::NPC_ABILITIES, "NpcAbilities"),
140 (Self::LEGENDARY_ITEMS, "LegendaryItems"),
141 (Self::ARTIFACT_TRAITS, "ArtifactTraits"),
142 (Self::ARTIFACT_PASSIVES, "ArtifactPassives"),
143 (Self::LEGENDARY_EFFECTS, "LegendaryEffects"),
144 (Self::AZERITE_ESSENCES, "AzeriteEssences"),
145 (Self::SHADOWLANDS_GENERAL, "ShadowlandsGeneral"),
146 (Self::SHADOWLANDS_COMBAT, "ShadowlandsCombat"),
147 (Self::CONDUITS, "Conduits"),
148 (Self::COVENANT, "Covenant"),
149 (Self::DRAGON_RACING, "DragonRacing"),
150 (Self::DRAGONFLIGHT_CRAFTED_GEAR, "DragonflightCraftedGear"),
151 (Self::PRIMORDIAL_STONE, "PrimordialStone"),
152 (Self::SINGING_CITRINE, "SingingCitrine"),
153 (Self::BRANN_DELVES, "BrannDelves"),
154 (Self::HERO_TALENTS, "HeroTalents"),
155 (Self::ITEM_EFFECTS, "ItemEffects"),
156 (Self::FURNITURE_CRAFTS, "FurnitureCrafts"),
157 (Self::BLACKSMITHING, "Blacksmithing"),
158 (Self::LEATHERWORKING, "Leatherworking"),
159 (Self::TAILORING, "Tailoring"),
160 (Self::SKINNING, "Skinning"),
161 (Self::ALCHEMY, "Alchemy"),
162 (Self::JEWELCRAFTING, "Jewelcrafting"),
163 (Self::ALL_PROFESSIONS, "AllProfessions"),
164 (Self::COOKING, "Cooking"),
165 (Self::MODIFIED_CRAFTING_REAGENTS, "ModifiedCraftingReagents"),
166 (Self::ENCHANTING, "Enchanting"),
167 (Self::INSCRIPTION, "Inscription"),
168 (Self::ALL_CRAFTED, "AllCrafted"),
169 (Self::ENGINEERING, "Engineering"),
170 (Self::MINING, "Mining"),
171 (Self::HERBALISM, "Herbalism"),
172 (Self::PROF_CLASSIC, "ProfClassic"),
173 (Self::PROF_TBC, "ProfTbc"),
174 (Self::PROF_WOTLK, "ProfWotlk"),
175 (Self::PROF_CATA, "ProfCata"),
176 (Self::PROF_MOP, "ProfMop"),
177 (Self::PROF_WOD, "ProfWod"),
178 (Self::PROF_LEGION, "ProfLegion"),
179 (Self::PROF_BFA, "ProfBfa"),
180 (Self::PROF_DF, "ProfDf"),
181 ];
182
183 pub fn known() -> impl Iterator<Item = (Self, &'static str)> + 'static {
184 Self::KNOWN.iter().copied()
185 }
186
187 #[must_use]
188 pub fn is_known(self) -> bool {
189 Self::KNOWN.iter().any(|(label, _)| *label == self)
190 }
191
192 #[must_use]
193 pub fn name(self) -> Option<&'static str> {
194 Self::KNOWN
195 .iter()
196 .find(|(label, _)| *label == self)
197 .map(|(_, name)| *name)
198 }
199}
200
201impl From<i32> for SpellLabel {
202 fn from(value: i32) -> Self {
203 Self(value)
204 }
205}
206
207impl From<SpellLabel> for i32 {
208 fn from(label: SpellLabel) -> Self {
209 label.0
210 }
211}
212
213impl std::fmt::Display for SpellLabel {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self.name() {
216 Some(name) => f.write_str(name),
217 None => write!(f, "Unknown({})", self.0),
218 }
219 }
220}
221
222impl Serialize for SpellLabel {
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: serde::Serializer,
226 {
227 serializer.serialize_i32(self.0)
228 }
229}
230
231impl<'de> Deserialize<'de> for SpellLabel {
232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233 where
234 D: serde::Deserializer<'de>,
235 {
236 let value = i32::deserialize(deserializer)?;
237
238 Ok(Self(value))
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use googletest::prelude::*;
245 use rstest::rstest;
246
247 use super::*;
248
249 #[gtest]
250 #[rstest]
251 #[case::rogue(SpellLabel::ROGUE, Some("Rogue"))]
252 #[case::mage(SpellLabel::MAGE, Some("Mage"))]
253 #[case::evoker_bronze(SpellLabel::EVOKER_BRONZE, Some("EvokerBronze"))]
254 #[case::unknown(SpellLabel(999_999), None)]
255 fn name(#[case] label: SpellLabel, #[case] expected: Option<&'static str>) -> Result<()> {
256 if let Some(name) = expected {
257 verify_that!(label.name(), some(eq(name)))
258 } else {
259 verify_that!(label.name(), none())
260 }
261 }
262
263 #[gtest]
264 #[rstest]
265 #[case::mage(SpellLabel::MAGE, true)]
266 #[case::rogue(SpellLabel::ROGUE, true)]
267 #[case::unknown(SpellLabel(999_999), false)]
268 fn is_known(#[case] label: SpellLabel, #[case] expected: bool) -> Result<()> {
269 verify_that!(label.is_known(), eq(expected))
270 }
271
272 #[gtest]
273 #[rstest]
274 #[case::known(SpellLabel::ROGUE, "Rogue")]
275 #[case::unknown(SpellLabel(42), "Unknown(42)")]
276 fn display(#[case] label: SpellLabel, #[case] expected: &str) -> Result<()> {
277 verify_that!(format!("{label}"), eq(expected))
278 }
279
280 #[gtest]
281 fn from_i32() -> Result<()> {
282 verify_that!(SpellLabel::from(20).0, eq(20))
283 }
284
285 #[gtest]
286 fn into_i32() -> Result<()> {
287 verify_that!(i32::from(SpellLabel::ROGUE), eq(20))
288 }
289
290 #[gtest]
291 fn serialize_as_bare_i32() -> Result<()> {
292 verify_that!(
293 serde_json::to_string(&SpellLabel::ROGUE).ok(),
294 some(eq("20"))
295 )
296 }
297
298 #[gtest]
299 fn deserialize_known() -> Result<()> {
300 verify_that!(
301 serde_json::from_str::<SpellLabel>("20").ok(),
302 some(eq(SpellLabel(20)))
303 )
304 }
305
306 #[gtest]
307 fn round_trip_unknown() -> Result<()> {
308 let label = SpellLabel(88);
309 let json = serde_json::to_string(&label).or_fail()?;
310
311 verify_that!(json, eq("88"))?;
312
313 verify_that!(
314 serde_json::from_str::<SpellLabel>(&json).ok(),
315 some(eq(label))
316 )
317 }
318}