omics_coordinate/position/
base.rs

1//! Base positions.
2
3mod addition;
4mod subtraction;
5
6use std::num::NonZero;
7
8use crate::position::Error;
9use crate::position::Number;
10use crate::position::ParseError;
11use crate::position::Result;
12use crate::system::Base;
13
14////////////////////////////////////////////////////////////////////////////////////////
15// Assertions
16////////////////////////////////////////////////////////////////////////////////////////
17
18const _: () = {
19    // Ensure that a value is only ever as big as the internal, numerical
20    // representation.
21    assert!(size_of::<Position>() == size_of::<Number>());
22
23    /// A function to ensure that types are `Copy`.
24    const fn is_copy<T: Copy>() {}
25    is_copy::<Position>();
26};
27
28////////////////////////////////////////////////////////////////////////////////////////
29// Position
30////////////////////////////////////////////////////////////////////////////////////////
31
32/// A base position.
33///
34/// Base positions start at one (`1`).
35pub type Position = crate::Position<Base>;
36
37impl Position {
38    /// Create a new base position if the value is not zero.
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// use omics_coordinate::position::base::Position;
44    ///
45    /// let position = Position::try_new(1)?;
46    /// assert_eq!(position.get(), 1);
47    ///
48    /// # Ok::<(), omics_coordinate::position::Error>(())
49    /// ```
50    pub const fn try_new(value: Number) -> Result<Self> {
51        if value == 0 {
52            return Err(Error::IncompatibleValue {
53                system: Base::NAME,
54                value,
55            });
56        }
57
58        Ok(Self {
59            system: Base,
60            value,
61        })
62    }
63}
64
65////////////////////////////////////////////////////////////////////////////////////////
66// Trait implementations
67////////////////////////////////////////////////////////////////////////////////////////
68
69impl super::r#trait::Position<Base> for Position {}
70
71impl std::str::FromStr for Position {
72    type Err = Error;
73
74    fn from_str(s: &str) -> Result<Self> {
75        Self::try_new(s.parse::<Number>().map_err(|error| ParseError::Int {
76            inner: error,
77            value: s.to_string(),
78        })?)
79    }
80}
81
82impl TryFrom<Number> for Position {
83    type Error = Error;
84
85    fn try_from(value: Number) -> Result<Self> {
86        Self::try_new(value)
87    }
88}
89
90impl From<NonZero<Number>> for Position {
91    fn from(value: NonZero<Number>) -> Self {
92        // SAFETY: because [`try_new()`] will only throw an error when zero
93        // (`0`) is passed in and `value` here is a non-zero number, this will
94        // always [`unwrap()`].
95        Self::try_new(value.get()).unwrap()
96    }
97}
98
99/// Creates implementations to convert from smaller numbers to a position.
100macro_rules! position_from_smaller_number {
101    ($from:ty) => {
102        impl From<NonZero<$from>> for Position {
103            fn from(value: NonZero<$from>) -> Self {
104                // SAFETY: because [`try_from()`] will only throw an error when zero
105                // (`0`) is passed in and `value` here is a non-zero number, this will
106                // always [`unwrap()`].
107                Self::try_new(value.get() as Number).unwrap()
108            }
109        }
110
111        impl TryFrom<$from> for Position {
112            type Error = Error;
113
114            fn try_from(value: $from) -> Result<Self> {
115                Self::try_new(value as Number)
116            }
117        }
118    };
119}
120
121#[cfg(feature = "position-u64")]
122position_from_smaller_number!(u32);
123position_from_smaller_number!(u16);
124position_from_smaller_number!(u8);
125
126#[cfg(test)]
127mod tests {
128    use std::num::NonZeroU8;
129    use std::num::NonZeroU16;
130    #[cfg(feature = "position-u64")]
131    use std::num::NonZeroU32;
132
133    use crate::Position;
134    use crate::position::Error;
135    use crate::position::Number;
136    use crate::position::Result;
137    use crate::system::Base;
138
139    #[test]
140    fn from_number() {
141        let error: Result<Position<Base>> = 0u32.try_into();
142        assert_eq!(error.unwrap_err(), Error::IncompatibleValue {
143            system: Base::NAME,
144            value: 0,
145        });
146
147        let position: Position<Base> = 1u32.try_into().unwrap();
148        assert_eq!(position.get(), 1);
149    }
150
151    #[test]
152    fn distance() {
153        // Zero distance between two positions.
154        let a = Position::<Base>::try_from(10u8).unwrap();
155        let b = Position::<Base>::try_from(10u8).unwrap();
156        assert_eq!(a.distance_unchecked(&b), 0);
157        assert_eq!(b.distance_unchecked(&a), 0);
158
159        // Non-zero distance between two Number positions.
160        let a = Position::<Base>::try_from(Number::MAX).unwrap();
161        let b = Position::<Base>::try_from(1u8).unwrap();
162        assert_eq!(a.distance_unchecked(&b), Number::MAX - 1);
163        assert_eq!(b.distance_unchecked(&a), Number::MAX - 1);
164    }
165
166    #[test]
167    fn parse() {
168        let err = "0".parse::<Position<Base>>().unwrap_err();
169        assert_eq!(
170            err.to_string(),
171            "incompatible value for system \"base coordinate system\": `0`"
172        );
173
174        let position = "1".parse::<Position<Base>>().unwrap();
175        assert_eq!(position.get(), 1);
176
177        let err = "a".parse::<Position<Base>>().unwrap_err();
178        assert_eq!(
179            err.to_string(),
180            "parse error: invalid digit found in string: `a`"
181        );
182    }
183
184    #[test]
185    fn from_smaller_types() -> Result<()> {
186        #[cfg(feature = "position-u64")]
187        {
188            // u32
189            let position = Position::<Base>::try_from(1u32)?;
190            assert_eq!(position.get(), 1);
191
192            let position = Position::<Base>::from(NonZeroU32::new(1).unwrap());
193            assert_eq!(position.get(), 1);
194        }
195
196        // u16
197        let position = Position::<Base>::try_from(1u16)?;
198        assert_eq!(position.get(), 1);
199
200        let position = Position::<Base>::from(NonZeroU16::new(1).unwrap());
201        assert_eq!(position.get(), 1);
202
203        // u8
204        let position = Position::<Base>::try_from(1u8)?;
205        assert_eq!(position.get(), 1);
206
207        let position = Position::<Base>::from(NonZeroU8::new(1).unwrap());
208        assert_eq!(position.get(), 1);
209
210        Ok(())
211    }
212}