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 {}