wowlab_engine_ports/
types.rs1use serde::{Deserialize, Serialize};
4use wowlab_types::sim::SpellIdx;
5
6#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
8#[serde(transparent)]
9#[repr(transparent)]
10pub struct SpellId(i32);
11
12impl SpellId {
13 #[inline]
14 #[must_use]
15 pub const fn new(id: i32) -> Self {
16 Self(id)
17 }
18
19 #[inline]
20 #[must_use]
21 pub const fn as_i32(&self) -> i32 {
22 self.0
23 }
24}
25
26impl std::fmt::Display for SpellId {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 self.0.fmt(f)
29 }
30}
31
32impl From<SpellId> for i32 {
33 #[inline]
34 fn from(id: SpellId) -> Self {
35 id.0
36 }
37}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
41#[error("negative spell id {value} cannot be converted to SpellIdx")]
42pub struct NegativeIdError {
43 value: i32,
44 #[source]
45 source: std::num::TryFromIntError,
46}
47
48impl NegativeIdError {
49 #[must_use]
51 pub const fn value(self) -> i32 {
52 self.value
53 }
54}
55
56impl TryFrom<SpellId> for SpellIdx {
57 type Error = NegativeIdError;
58
59 #[inline]
60 fn try_from(id: SpellId) -> Result<Self, Self::Error> {
61 let raw = u32::try_from(id.0).map_err(|source| NegativeIdError {
62 value: id.0,
63 source,
64 })?;
65
66 Ok(SpellIdx::from_raw(raw))
67 }
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
72#[error("runtime spell id {value} cannot be represented by database int4 SpellId")]
73pub struct SpellIdConversionError {
74 value: u32,
75 #[source]
76 source: std::num::TryFromIntError,
77}
78
79impl SpellIdConversionError {
80 #[must_use]
82 pub const fn value(self) -> u32 {
83 self.value
84 }
85}
86
87impl TryFrom<SpellIdx> for SpellId {
88 type Error = SpellIdConversionError;
89
90 #[inline]
91 fn try_from(idx: SpellIdx) -> Result<Self, Self::Error> {
92 let value = idx.as_u32();
93 let database_id =
94 i32::try_from(value).map_err(|source| SpellIdConversionError { value, source })?;
95
96 Ok(Self(database_id))
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use googletest::prelude::*;
103
104 use super::*;
105
106 #[gtest]
107 fn round_trip_positive() -> Result<()> {
108 let id = SpellId::new(12345);
109 let idx: SpellIdx = id.try_into().or_fail()?;
110
111 verify_that!(idx.as_u32(), eq(12345))?;
112 let back = SpellId::try_from(idx).or_fail()?;
113
114 verify_that!(back, eq(id))
115 }
116
117 #[gtest]
118 fn negative_rejected() -> Result<()> {
119 let id = SpellId::new(-1);
120 let error = SpellIdx::try_from(id).err().or_fail()?;
121
122 verify_that!(error.value(), eq(-1))?;
123
124 verify_that!(std::error::Error::source(&error), some(anything()))
125 }
126
127 #[gtest]
128 fn database_minimum_rejected_by_runtime_boundary() -> Result<()> {
129 let error = SpellIdx::try_from(SpellId::new(i32::MIN)).err().or_fail()?;
130
131 verify_that!(error.value(), eq(i32::MIN))?;
132
133 verify_that!(std::error::Error::source(&error), some(anything()))
134 }
135
136 #[gtest]
137 fn zero_ok() -> Result<()> {
138 let id = SpellId::new(0);
139 let idx: SpellIdx = id.try_into().or_fail()?;
140
141 verify_that!(idx.as_u32(), eq(0))
142 }
143
144 #[gtest]
145 fn runtime_boundary_accepts_database_int4_maximum() -> Result<()> {
146 let idx = SpellIdx::from_raw(i32::MAX as u32);
147 let id = SpellId::try_from(idx).or_fail()?;
148
149 verify_that!(id.as_i32(), eq(i32::MAX))?;
150
151 verify_that!(SpellIdx::try_from(id).or_fail()?, eq(idx))
152 }
153
154 #[gtest]
155 fn runtime_boundary_rejects_first_value_above_database_int4() -> Result<()> {
156 let value = i32::MAX as u32 + 1;
157 let error = SpellId::try_from(SpellIdx::from_raw(value))
158 .err()
159 .or_fail()?;
160
161 verify_that!(error.value(), eq(value))?;
162 verify_that!(
163 error.to_string(),
164 eq("runtime spell id 2147483648 cannot be represented by database int4 SpellId")
165 )?;
166
167 verify_that!(std::error::Error::source(&error), some(anything()))
168 }
169
170 #[gtest]
171 fn runtime_boundary_rejects_u32_maximum() -> Result<()> {
172 let error = SpellId::try_from(SpellIdx::from_raw(u32::MAX))
173 .err()
174 .or_fail()?;
175
176 verify_that!(error.value(), eq(u32::MAX))
177 }
178}