Skip to main content

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            system: Base::NAME,
77            inner: error,
78            value: s.to_string(),
79        })?)
80    }
81}
82
83impl TryFrom<Number> for Position {
84    type Error = Error;
85
86    fn try_from(value: Number) -> Result<Self> {
87        Self::try_new(value)
88    }
89}
90
91impl From<NonZero<Number>> for Position {
92    fn from(value: NonZero<Number>) -> Self {
93        // SAFETY: because [`try_new()`] will only throw an error when zero
94        // (`0`) is passed in and `value` here is a non-zero number, this will
95        // always [`unwrap()`].
96        Self::try_new(value.get()).unwrap()
97    }
98}
99
100/// Creates implementations to convert from smaller numbers to a position.
101macro_rules! position_from_smaller_number {
102    ($from:ty) => {
103        impl From<NonZero<$from>> for Position {
104            fn from(value: NonZero<$from>) -> Self {
105                // SAFETY: because [`try_from()`] will only throw an error when zero
106                // (`0`) is passed in and `value` here is a non-zero number, this will
107                // always [`unwrap()`].
108                Self::try_new(value.get() as Number).unwrap()
109            }
110        }
111
112        impl TryFrom<$from> for Position {
113            type Error = Error;
114
115            fn try_from(value: $from) -> Result<Self> {
116                Self::try_new(value as Number)
117            }
118        }
119    };
120}
121
122#[cfg(feature = "position-u64")]
123position_from_smaller_number!(u32);
124position_from_smaller_number!(u16);
125position_from_smaller_number!(u8);
126
127#[cfg(test)]
128mod tests {
129    use std::num::NonZeroU8;
130    use std::num::NonZeroU16;
131    #[cfg(feature = "position-u64")]
132    use std::num::NonZeroU32;
133
134    use crate::Position;
135    use crate::position::Error;
136    use crate::position::Number;
137    use crate::position::Result;
138    use crate::system::Base;
139
140    #[test]
141    fn from_number() {
142        let error: Result<Position<Base>> = 0u32.try_into();
143        assert_eq!(
144            error.unwrap_err(),
145            Error::IncompatibleValue {
146                system: Base::NAME,
147                value: 0,
148            }
149        );
150
151        let position: Position<Base> = 1u32.try_into().unwrap();
152        assert_eq!(position.get(), 1);
153    }
154
155    #[test]
156    fn distance() {
157        // Zero distance between two positions.
158        let a = Position::<Base>::try_from(10u8).unwrap();
159        let b = Position::<Base>::try_from(10u8).unwrap();
160        assert_eq!(a.distance_unchecked(&b), 0);
161        assert_eq!(b.distance_unchecked(&a), 0);
162
163        // Non-zero distance between two Number positions.
164        let a = Position::<Base>::try_from(Number::MAX).unwrap();
165        let b = Position::<Base>::try_from(1u8).unwrap();
166        assert_eq!(a.distance_unchecked(&b), Number::MAX - 1);
167        assert_eq!(b.distance_unchecked(&a), Number::MAX - 1);
168    }
169
170    #[test]
171    fn parse() {
172        let err = "0".parse::<Position<Base>>().unwrap_err();
173        assert_eq!(
174            err.to_string(),
175            "incompatible value for system \"base coordinate system\": `0`"
176        );
177
178        let position = "1".parse::<Position<Base>>().unwrap();
179        assert_eq!(position.get(), 1);
180
181        let err = "a".parse::<Position<Base>>().unwrap_err();
182        assert_eq!(
183            err.to_string(),
184            "parse error: failed to parse base coordinate system position from `a`: invalid digit \
185             found in string"
186        );
187    }
188
189    #[test]
190    fn from_smaller_types() -> Result<()> {
191        #[cfg(feature = "position-u64")]
192        {
193            // u32
194            let position = Position::<Base>::try_from(1u32)?;
195            assert_eq!(position.get(), 1);
196
197            let position = Position::<Base>::from(NonZeroU32::new(1).unwrap());
198            assert_eq!(position.get(), 1);
199        }
200
201        // u16
202        let position = Position::<Base>::try_from(1u16)?;
203        assert_eq!(position.get(), 1);
204
205        let position = Position::<Base>::from(NonZeroU16::new(1).unwrap());
206        assert_eq!(position.get(), 1);
207
208        // u8
209        let position = Position::<Base>::try_from(1u8)?;
210        assert_eq!(position.get(), 1);
211
212        let position = Position::<Base>::from(NonZeroU8::new(1).unwrap());
213        assert_eq!(position.get(), 1);
214
215        Ok(())
216    }
217}