1use std::{
16 fmt::Display,
17 num::{
18 NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, ParseIntError,
19 },
20 str::FromStr,
21};
22
23macro_rules! define_one_based {
24 ($name:ident, $itype:ty, $nonzerotype:ty) => {
25 #[doc = concat!(r" Represents 1-based index of ", stringify!($itype), r".")]
26 #[doc = r" ```"]
33 #[doc = r" # fn main() -> Result<(), one_based::OneBasedError> {"]
34 #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
35 #[doc = r" // Creates from 1-based index"]
36 #[doc = concat!(r" let v = ", stringify!($name),r"::from_one_based(5)?;")]
37 #[doc = r" assert_eq!(v.as_zero_based(), 4);"]
38 #[doc = r""]
39 #[doc = r" // Creates from 0-based index"]
40 #[doc = concat!(r" let v = ", stringify!($name),r"::from_zero_based(0)?;")]
41 #[doc = r" assert_eq!(v.as_one_based().get(), 1);"]
42 #[doc = r" # Ok(())"]
43 #[doc = r" # }"]
44 #[doc = r" ```"]
45 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
46 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47 pub struct $name($nonzerotype);
48
49 impl Display for $name {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 self.as_one_based().fmt(f)
52 }
53 }
54
55 impl FromStr for $name {
56 type Err = ParseIntError;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 let v: $nonzerotype = s.parse()?;
60 Ok(Self::from_one_based_nonzero(v))
61 }
62 }
63
64 impl $name {
65 pub fn from_one_based(v: $itype) -> Result<Self, OneBasedError> {
68 let v = <$nonzerotype>::new(v).ok_or(OneBasedError::ZeroIndex)?;
69 Ok($name(v))
70 }
71
72 #[inline]
75 pub fn from_one_based_nonzero(v: $nonzerotype) -> Self {
76 Self(v)
77 }
78
79 pub fn from_zero_based(v: $itype) -> Result<Self, OneBasedError> {
83 if v == <$nonzerotype>::MAX.get() {
84 return Err(OneBasedError::OverflowIndex);
85 }
86 let v = unsafe {
87 <$nonzerotype>::new_unchecked(v + 1)
89 };
90 Ok($name(v))
91 }
92
93 pub fn as_zero_based(&self) -> $itype {
95 self.0.get() - 1
96 }
97
98 pub fn as_one_based(&self) -> $nonzerotype {
100 self.0
101 }
102 }
103 };
104}
105
106define_one_based!(OneBasedU8, u8, NonZeroU8);
107define_one_based!(OneBasedU16, u16, NonZeroU16);
108define_one_based!(OneBasedU32, u32, NonZeroU32);
109define_one_based!(OneBasedU64, u64, NonZeroU64);
110define_one_based!(OneBasedU128, u128, NonZeroU128);
111define_one_based!(OneBasedUsize, usize, NonZeroUsize);
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum OneBasedError {
116 ZeroIndex,
117 OverflowIndex,
118}
119
120impl Display for OneBasedError {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 OneBasedError::ZeroIndex => f.write_str("0 passed as 1-based index"),
124 OneBasedError::OverflowIndex => {
125 f.write_str("unsigned::MAX cannot be used as 0-based index")
126 }
127 }
128 }
129}
130
131impl std::error::Error for OneBasedError {}
132
133#[cfg(test)]
134mod tests {
135 use std::num::IntErrorKind;
136
137 use super::*;
138
139 #[test]
140 fn in_range_ints_converts_each_other() {
141 assert_eq!(
142 OneBasedUsize::from_one_based_nonzero(NonZeroUsize::new(1).unwrap()).as_zero_based(),
143 0
144 );
145 assert_eq!(
146 OneBasedU16::from_zero_based(u16::MAX - 1)
147 .unwrap()
148 .as_one_based(),
149 NonZeroU16::MAX
150 );
151 }
152
153 #[test]
154 fn overflow_fails_on_zero_based() {
155 assert_eq!(
156 Err(OneBasedError::OverflowIndex),
157 OneBasedU8::from_zero_based(u8::MAX)
158 );
159 assert_eq!(
160 Err(OneBasedError::OverflowIndex),
161 OneBasedU16::from_zero_based(u16::MAX)
162 );
163 assert_eq!(
164 Err(OneBasedError::OverflowIndex),
165 OneBasedU32::from_zero_based(u32::MAX)
166 );
167 assert_eq!(
168 Err(OneBasedError::OverflowIndex),
169 OneBasedU64::from_zero_based(u64::MAX)
170 );
171 assert_eq!(
172 Err(OneBasedError::OverflowIndex),
173 OneBasedU128::from_zero_based(u128::MAX)
174 );
175 }
176
177 #[test]
178 fn from_str_and_to_string() {
179 let v: OneBasedU16 = "12345".parse().unwrap();
180 assert_eq!(v.as_zero_based(), 12344u16);
181 assert_eq!(&v.to_string(), "12345");
182 }
183
184 #[test]
185 fn from_str_failures() {
186 let err = OneBasedU8::from_str("0").unwrap_err();
187 assert_eq!(*err.kind(), IntErrorKind::Zero);
188
189 let err = OneBasedU8::from_str("256").unwrap_err();
190 assert_eq!(*err.kind(), IntErrorKind::PosOverflow);
191 }
192}