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> {
36 let raw = i32::try_from(index).map_err(|_| StrRefError::IndexOverflow(index))?;
37 Ok(Self(raw))
38 }
39
40 pub const fn raw(self) -> i32 {
42 self.0
43 }
44
45 pub const fn is_invalid(self) -> bool {
47 self.0 == INVALID_STRREF_RAW
48 }
49
50 pub const fn is_valid(self) -> bool {
52 self.0 >= 0
53 }
54
55 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}