1use std::fmt::{Display, Formatter};
2use thiserror::Error;
3
4pub const INVALID_STRREF_RAW: i32 = -1;
6
7#[derive(Debug, Clone, PartialEq, Eq, Error)]
9pub enum StrRefError {
10 #[error("string reference index {0} exceeds i32 range")]
12 IndexOverflow(u32),
13}
14
15#[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 pub const fn from_raw(raw: i32) -> Self {
26 Self(raw)
27 }
28
29 pub const fn invalid() -> Self {
31 Self(INVALID_STRREF_RAW)
32 }
33
34 pub fn from_index(index: u32) -> Result<Self, StrRefError> {
42 let raw = i32::try_from(index).map_err(|_| StrRefError::IndexOverflow(index))?;
43 Ok(Self(raw))
44 }
45
46 pub const fn raw(self) -> i32 {
48 self.0
49 }
50
51 pub const fn is_invalid(self) -> bool {
53 self.0 == INVALID_STRREF_RAW
54 }
55
56 pub const fn is_valid(self) -> bool {
58 self.0 >= 0
59 }
60
61 pub fn index(self) -> Option<u32> {
63 if self.0 < 0 {
64 None
65 } else {
66 u32::try_from(self.0).ok()
67 }
68 }
69}
70
71impl From<i32> for StrRef {
72 fn from(value: i32) -> Self {
73 Self::from_raw(value)
74 }
75}
76
77impl From<StrRef> for i32 {
78 fn from(value: StrRef) -> Self {
79 value.raw()
80 }
81}
82
83impl TryFrom<u32> for StrRef {
84 type Error = StrRefError;
85
86 fn try_from(value: u32) -> Result<Self, Self::Error> {
87 Self::from_index(value)
88 }
89}
90
91impl Display for StrRef {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 write!(f, "{}", self.0)
94 }
95}
96
97impl Default for StrRef {
98 fn default() -> Self {
99 Self::invalid()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn invalid_default_is_minus_one() {
109 let strref = StrRef::default();
110 assert_eq!(strref.raw(), -1);
111 assert!(strref.is_invalid());
112 assert!(!strref.is_valid());
113 assert_eq!(strref.index(), None);
114 }
115
116 #[test]
117 fn non_negative_values_are_valid_indexes() {
118 let strref = StrRef::from_raw(123_456);
119 assert!(strref.is_valid());
120 assert!(!strref.is_invalid());
121 assert_eq!(strref.index(), Some(123_456));
122 }
123
124 #[test]
125 fn from_index_rejects_overflow() {
126 let err = StrRef::from_index(u32::MAX).expect_err("must fail");
127 assert_eq!(err, StrRefError::IndexOverflow(u32::MAX));
128 }
129}