ip/concrete/prefix/
len.rs

1use core::ops::Neg;
2use core::{fmt, str::FromStr};
3
4use super::impl_try_from_any;
5use crate::{
6    any,
7    error::{err, Error, Kind},
8    traits::{
9        self,
10        primitive::{self, Address as _, Length as _},
11        Afi,
12    },
13    Ipv4, Ipv6,
14};
15
16#[allow(clippy::wildcard_imports)]
17mod private {
18    use super::*;
19
20    /// An IP prefix length guaranteed to be within appropriate bounds for
21    /// address family `A`.
22    #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
23    pub struct PrefixLength<A: Afi>(<A::Primitive as primitive::Address<A>>::Length);
24
25    impl<A: Afi> PrefixLength<A> {
26        /// Minimum valid value of [`PrefixLength<A>`].
27        pub const MIN: Self = Self(A::Primitive::MIN_LENGTH);
28
29        /// Maximum valid value of [`PrefixLength<A>`].
30        pub const MAX: Self = Self(A::Primitive::MAX_LENGTH);
31
32        /// Construct a new [`PrefixLength<A>`] from an integer primitive
33        /// appropriate to `A`.
34        ///
35        /// # Errors
36        ///
37        /// Fails if `n` is outside of the range [`Self::MIN`] to [`Self::MAX`]
38        /// inclusive.
39        pub fn from_primitive(
40            n: <A::Primitive as primitive::Address<A>>::Length,
41        ) -> Result<Self, Error> {
42            if A::Primitive::MIN_LENGTH <= n && n <= A::Primitive::MAX_LENGTH {
43                Ok(Self(n))
44            } else {
45                Err(err!(Kind::PrefixLength))
46            }
47        }
48
49        /// Get the inner integer val, consuming `self`.
50        pub const fn into_primitive(self) -> <A::Primitive as primitive::Address<A>>::Length {
51            self.0
52        }
53    }
54
55    impl<A> PrefixLength<A>
56    where
57        A: Afi,
58        A::Primitive: primitive::Address<A, Length = u8>,
59    {
60        pub(super) const fn as_u8(&self) -> &u8 {
61            &self.0
62        }
63    }
64}
65
66pub use self::private::PrefixLength;
67
68impl<A: Afi> TryFrom<usize> for PrefixLength<A> {
69    type Error = Error;
70
71    fn try_from(value: usize) -> Result<Self, Self::Error> {
72        value
73            .try_into()
74            .map_err(|_| err!(Kind::PrefixLength))
75            .and_then(Self::from_primitive)
76    }
77}
78
79impl<A: Afi> traits::PrefixLength for PrefixLength<A> {
80    fn increment(self) -> Result<Self, Error> {
81        let l = self.into_primitive();
82        if l < <A::Primitive as primitive::Address<A>>::MAX_LENGTH {
83            Self::from_primitive(l + <A::Primitive as primitive::Address<A>>::Length::ONE)
84        } else {
85            Err(err!(Kind::PrefixLength))
86        }
87    }
88    fn decrement(self) -> Result<Self, Error> {
89        let l = self.into_primitive();
90        if l > <A::Primitive as primitive::Address<A>>::Length::ZERO {
91            Self::from_primitive(l - <A::Primitive as primitive::Address<A>>::Length::ONE)
92        } else {
93            Err(err!(Kind::PrefixLength))
94        }
95    }
96}
97
98impl<A: Afi> FromStr for PrefixLength<A> {
99    type Err = Error;
100
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        A::Primitive::parse_length(s).and_then(Self::from_primitive)
103    }
104}
105
106impl<A: Afi> AsRef<u8> for PrefixLength<A>
107where
108    A::Primitive: primitive::Address<A, Length = u8>,
109{
110    fn as_ref(&self) -> &u8 {
111        self.as_u8()
112    }
113}
114
115impl_try_from_any! {
116    any::PrefixLength {
117        any::PrefixLength::Ipv4 => PrefixLength<Ipv4>,
118        any::PrefixLength::Ipv6 => PrefixLength<Ipv6>,
119    }
120}
121
122impl<A: Afi> fmt::Display for PrefixLength<A> {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        self.into_primitive().fmt(f)
125    }
126}
127
128impl<A: Afi> Neg for PrefixLength<A> {
129    type Output = Self;
130
131    fn neg(self) -> Self::Output {
132        // ok to unwrap since 0 <= self.0 <= A::MAX_LENGTH
133        Self::from_primitive(A::Primitive::MAX_LENGTH - self.into_primitive()).unwrap()
134    }
135}
136
137#[cfg(any(test, feature = "arbitrary"))]
138use proptest::{
139    arbitrary::Arbitrary,
140    strategy::{BoxedStrategy, Strategy},
141};
142
143#[cfg(any(test, feature = "arbitrary"))]
144impl<A: Afi> Arbitrary for PrefixLength<A>
145where
146    <A::Primitive as primitive::Address<A>>::Length: 'static,
147    core::ops::RangeInclusive<<A::Primitive as primitive::Address<A>>::Length>:
148        Strategy<Value = <A::Primitive as primitive::Address<A>>::Length>,
149{
150    type Parameters = ();
151    type Strategy = BoxedStrategy<Self>;
152    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
153        (A::Primitive::MIN_LENGTH..=A::Primitive::MAX_LENGTH)
154            .prop_map(|l| Self::from_primitive(l).unwrap())
155            .boxed()
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    mod ipv4 {
164        use super::*;
165
166        #[test]
167        fn parse_valid() {
168            let input = "/8";
169            let length = input.parse::<PrefixLength<Ipv4>>().unwrap();
170            assert_eq!(length.as_ref(), &8);
171        }
172
173        #[test]
174        fn parse_invalid() {
175            let input = "/40";
176            let length = input.parse::<PrefixLength<Ipv4>>();
177            assert!(length.is_err_and(|err| err.kind() == Kind::PrefixLength));
178        }
179    }
180
181    mod ipv6 {
182        use super::*;
183
184        #[test]
185        fn parse_valid() {
186            let input = "/40";
187            let length = input.parse::<PrefixLength<Ipv6>>().unwrap();
188            assert_eq!(length.as_ref(), &40);
189        }
190
191        #[test]
192        fn parse_invalid() {
193            let input = "128";
194            let length = input.parse::<PrefixLength<Ipv6>>();
195            assert!(length.is_err_and(|err| err.kind() == Kind::ParserError));
196        }
197    }
198}