1#![cfg_attr(not(feature = "std"), no_std)]
26
27use core::{
28 fmt::Display,
29 num::{
30 NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, ParseIntError,
31 },
32 str::FromStr,
33};
34
35trait OneBased {
36 type IntType;
37 type NonZeroType;
38}
39
40macro_rules! define_one_based {
41 ($name:ident, $itype:ty, $nonzerotype:ty) => {
42 #[doc = concat!(r" Represents 1-based index of ", stringify!($itype), r".")]
43 #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
51 #[doc = r" // Creates from 1-based index"]
52 #[doc = concat!(r" let v = ", stringify!($name),r"::from_one_based(5)?;")]
53 #[doc = r" assert_eq!(v.as_zero_based(), 4);"]
54 #[doc = r""]
55 #[doc = r" // Creates from 0-based index"]
56 #[doc = concat!(r" let v = ", stringify!($name),r"::from_zero_based(0)?;")]
57 #[doc = r" assert_eq!(v.as_one_based().get(), 1);"]
58 #[doc = r" # Ok::<(), one_based::OneBasedError>(())"]
59 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
61 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62 pub struct $name($nonzerotype);
63
64 impl OneBased for $name {
65 type IntType = $itype;
66 type NonZeroType = $nonzerotype;
67 }
68
69 impl Display for $name {
70 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71 self.as_one_based().fmt(f)
72 }
73 }
74
75 impl FromStr for $name {
76 type Err = ParseIntError;
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 let v: $nonzerotype = s.parse()?;
80 Ok(Self::from_one_based_nonzero(v))
81 }
82 }
83
84 impl $name {
85 #[inline]
88 pub const fn from_one_based(v: $itype) -> Result<Self, OneBasedError> {
89 match <$nonzerotype>::new(v) {
90 None => return Err(OneBasedError::ZeroIndex),
91 Some(v) => Ok($name(v)),
92 }
93 }
94
95 #[inline]
101 pub const unsafe fn from_one_based_unchecked(v: $itype) -> Self {
102 $name(<$nonzerotype>::new_unchecked(v))
103 }
104
105 #[inline]
108 pub const fn from_one_based_nonzero(v: $nonzerotype) -> Self {
109 Self(v)
110 }
111
112 #[inline]
116 pub const fn from_zero_based(v: $itype) -> Result<Self, OneBasedError> {
117 if v == <$nonzerotype>::MAX.get() {
118 return Err(OneBasedError::OverflowIndex);
119 }
120 Ok($name(unsafe { <$nonzerotype>::new_unchecked(v + 1) }))
122 }
123
124 #[doc = concat!(r" This function results in undefined behavior when `v == ", stringify!($itype), r"::MAX`.")]
128 #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
130 #[doc = concat!(r" ", stringify!($name), "::from_zero_based_unchecked(", stringify!($itype), r"::MAX);")]
133 #[inline]
136 pub const unsafe fn from_zero_based_unchecked(v: $itype) -> Self {
137 $name(unsafe { <$nonzerotype>::new_unchecked(v + 1) })
139 }
140
141 pub const fn as_zero_based(&self) -> $itype {
143 self.0.get() - 1
144 }
145
146 pub const fn as_one_based(&self) -> $nonzerotype {
148 self.0
149 }
150 }
151 };
152}
153
154define_one_based!(OneBasedU8, u8, NonZeroU8);
155define_one_based!(OneBasedU16, u16, NonZeroU16);
156define_one_based!(OneBasedU32, u32, NonZeroU32);
157define_one_based!(OneBasedU64, u64, NonZeroU64);
158define_one_based!(OneBasedU128, u128, NonZeroU128);
159define_one_based!(OneBasedUsize, usize, NonZeroUsize);
160
161macro_rules! impl_from_one_based {
162 ($source:ty => $($target:ty),+) => {$(
163 impl core::convert::From<$source> for $target {
164 #[doc = concat!(r"Converts [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
165 #[inline]
166 fn from(value: $source) -> Self {
167 use core::convert::Into as _;
168 let v: <$target as OneBased>::NonZeroType = value.as_one_based().into();
169 <$target>::from_one_based_nonzero(v)
170 }
171 }
172 )*};
173}
174
175impl_from_one_based!(OneBasedU8 => OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);
176impl_from_one_based!(OneBasedU16 => OneBasedU32, OneBasedU64, OneBasedU128);
177impl_from_one_based!(OneBasedU32 => OneBasedU64, OneBasedU128);
178impl_from_one_based!(OneBasedU64 => OneBasedU128);
179
180macro_rules! impl_try_from_one_based {
181 ($source:ty => $($target:ty),+) => {$(
182 impl core::convert::TryFrom<$source> for $target {
183 type Error = core::num::TryFromIntError;
184
185 #[doc = concat!(r"Attempts to convert [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
186 #[inline]
187 fn try_from(value: $source) -> Result<Self, Self::Error> {
188 use core::convert::TryInto as _;
189 let v: <$target as OneBased>::NonZeroType = value.as_one_based().try_into()?;
190 Ok(<$target>::from_one_based_nonzero(v))
191 }
192 }
193 )*};
194}
195
196impl_try_from_one_based!(OneBasedU8 => OneBasedUsize);
197impl_try_from_one_based!(OneBasedU16 => OneBasedUsize, OneBasedU8);
198impl_try_from_one_based!(OneBasedU32 => OneBasedUsize, OneBasedU8, OneBasedU16);
199impl_try_from_one_based!(OneBasedU64 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32);
200impl_try_from_one_based!(OneBasedU128 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64);
201impl_try_from_one_based!(OneBasedUsize => OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub enum OneBasedError {
206 ZeroIndex,
207 OverflowIndex,
208}
209
210impl Display for OneBasedError {
211 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212 match self {
213 OneBasedError::ZeroIndex => f.write_str("0 passed as 1-based index"),
214 OneBasedError::OverflowIndex => {
215 f.write_str("unsigned::MAX cannot be used as 0-based index")
216 }
217 }
218 }
219}
220
221#[cfg(feature = "std")]
222impl std::error::Error for OneBasedError {}