rakata_core/
strref.rs

1use std::fmt::{Display, Formatter};
2use thiserror::Error;
3
4/// Sentinel value used by KotOR formats to indicate an unset string reference.
5pub const INVALID_STRREF_RAW: i32 = -1;
6
7/// Error returned when converting a value into [`StrRef`] fails.
8#[derive(Debug, Clone, PartialEq, Eq, Error)]
9pub enum StrRefError {
10    /// Unsigned index cannot fit the signed on-disk width.
11    #[error("string reference index {0} exceeds i32 range")]
12    IndexOverflow(u32),
13}
14
15/// TLK string-reference wrapper shared across KotOR formats.
16///
17/// Most formats store string references as signed 32-bit integers where `-1`
18/// means "unset/no string".
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct StrRef(i32);
22
23impl StrRef {
24    /// Creates a string reference from a raw on-disk value.
25    pub const fn from_raw(raw: i32) -> Self {
26        Self(raw)
27    }
28
29    /// Returns the canonical "unset" string-reference value (`-1`).
30    pub const fn invalid() -> Self {
31        Self(INVALID_STRREF_RAW)
32    }
33
34    /// Creates a string reference from a non-negative TLK index.
35    pub fn from_index(index: u32) -> Result<Self, StrRefError> {
36        let raw = i32::try_from(index).map_err(|_| StrRefError::IndexOverflow(index))?;
37        Ok(Self(raw))
38    }
39
40    /// Returns the raw on-disk value.
41    pub const fn raw(self) -> i32 {
42        self.0
43    }
44
45    /// Returns `true` if this value is the unset sentinel (`-1`).
46    pub const fn is_invalid(self) -> bool {
47        self.0 == INVALID_STRREF_RAW
48    }
49
50    /// Returns `true` for non-negative TLK indexes.
51    pub const fn is_valid(self) -> bool {
52        self.0 >= 0
53    }
54
55    /// Returns this value as a TLK index when it is non-negative.
56    pub fn index(self) -> Option<u32> {
57        if self.0 < 0 {
58            None
59        } else {
60            u32::try_from(self.0).ok()
61        }
62    }
63}
64
65impl From<i32> for StrRef {
66    fn from(value: i32) -> Self {
67        Self::from_raw(value)
68    }
69}
70
71impl From<StrRef> for i32 {
72    fn from(value: StrRef) -> Self {
73        value.raw()
74    }
75}
76
77impl TryFrom<u32> for StrRef {
78    type Error = StrRefError;
79
80    fn try_from(value: u32) -> Result<Self, Self::Error> {
81        Self::from_index(value)
82    }
83}
84
85impl Display for StrRef {
86    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87        write!(f, "{}", self.0)
88    }
89}
90
91impl Default for StrRef {
92    fn default() -> Self {
93        Self::invalid()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn invalid_default_is_minus_one() {
103        let strref = StrRef::default();
104        assert_eq!(strref.raw(), -1);
105        assert!(strref.is_invalid());
106        assert!(!strref.is_valid());
107        assert_eq!(strref.index(), None);
108    }
109
110    #[test]
111    fn non_negative_values_are_valid_indexes() {
112        let strref = StrRef::from_raw(123_456);
113        assert!(strref.is_valid());
114        assert!(!strref.is_invalid());
115        assert_eq!(strref.index(), Some(123_456));
116    }
117
118    #[test]
119    fn from_index_rejects_overflow() {
120        let err = StrRef::from_index(u32::MAX).expect_err("must fail");
121        assert_eq!(err, StrRefError::IndexOverflow(u32::MAX));
122    }
123}