noodles_bgzf/
virtual_position.rs

1//! BGZF virtual position.
2
3use std::{error, fmt};
4
5pub(crate) const MAX_COMPRESSED_POSITION: u64 = (1 << 48) - 1;
6pub(crate) const MAX_UNCOMPRESSED_POSITION: u16 = u16::MAX;
7
8const COMPRESSED_POSITION_SHIFT: u64 = 16;
9const UNCOMPRESSED_POSITION_MASK: u64 = 0xffff;
10
11/// A BGZF virtual position.
12///
13/// A virtual position is a 64-bit unsigned integer representing both the position in the
14/// compressed stream and position in the uncompressed block data. The compressed position is
15/// typically at the start of a block.
16///
17/// The compressed position is the first six most significant bytes; and the uncompressed position,
18/// the last two least significant bytes. For example, for the virtual position
19/// 10253313912875616487:
20///
21/// ```text
22///                       compressed position
23///                        |               |
24/// 10253313912875616487 = 8e 4b 16 ad eb 85 88 e7
25///                                          |   |
26///                                  uncompressed position
27/// ```
28///
29/// The compressed position is at 156453154188165 (`8e 4b 16 ad eb 85`); and the uncompressed
30/// position, 35047 (`88 e7`).
31///
32/// This is also called a virtual file offset; or, simply, a virtual offset.
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
34pub struct VirtualPosition(u64);
35
36impl VirtualPosition {
37    /// The minimum value of a virtual position.
38    pub const MIN: Self = Self(u64::MIN);
39
40    /// The maximum value of a virtual position.
41    pub const MAX: Self = Self(u64::MAX);
42
43    /// Creates a virtual position if the compressed position is valid.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use noodles_bgzf::VirtualPosition;
49    /// assert_eq!(VirtualPosition::new(0, 0), Some(VirtualPosition::MIN));
50    /// assert!(VirtualPosition::new(1 << 48, 0).is_none());
51    /// ```
52    pub const fn new(compressed_pos: u64, uncompressed_pos: u16) -> Option<Self> {
53        if compressed_pos <= MAX_COMPRESSED_POSITION {
54            // SAFETY: 0 <= `uncompressed_pos` <= `u64`
55            let virtual_pos =
56                (compressed_pos << COMPRESSED_POSITION_SHIFT) | uncompressed_pos as u64;
57
58            Some(Self(virtual_pos))
59        } else {
60            None
61        }
62    }
63
64    /// The position in the compressed BGZF stream.
65    ///
66    /// This is typically at the start of a block.
67    ///
68    /// The maximum value of a compressed position is 281474976710655 (2^48 - 1).
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use noodles_bgzf as bgzf;
74    /// let virtual_position = bgzf::VirtualPosition::from(3741638);
75    /// assert_eq!(virtual_position.compressed(), 57);
76    /// ```
77    pub const fn compressed(self) -> u64 {
78        self.0 >> COMPRESSED_POSITION_SHIFT
79    }
80
81    /// The position in the uncompressed block data.
82    ///
83    /// The maximum value of an uncompressed position is 65535 (2^16 - 1).
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use noodles_bgzf as bgzf;
89    /// let virtual_position = bgzf::VirtualPosition::from(3741638);
90    /// assert_eq!(virtual_position.uncompressed(), 6086);
91    /// ```
92    pub const fn uncompressed(self) -> u16 {
93        (self.0 & UNCOMPRESSED_POSITION_MASK) as u16
94    }
95}
96
97impl From<u64> for VirtualPosition {
98    fn from(pos: u64) -> Self {
99        Self(pos)
100    }
101}
102
103/// An error returned when converting a (u64, u16) to a virtual position fails.
104#[derive(Clone, Debug, Eq, PartialEq)]
105pub enum TryFromU64U16TupleError {
106    /// The compressed position is larger than 2^48 - 1.
107    CompressedPositionOverflow,
108}
109
110impl error::Error for TryFromU64U16TupleError {}
111
112impl fmt::Display for TryFromU64U16TupleError {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::CompressedPositionOverflow => {
116                f.write_str("the compressed position is larger than 2^48 - 1")
117            }
118        }
119    }
120}
121
122impl TryFrom<(u64, u16)> for VirtualPosition {
123    type Error = TryFromU64U16TupleError;
124
125    /// Converts a `(compressed position, uncompressed position)` tuple to a virtual position.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use noodles_bgzf as bgzf;
131    /// let virtual_position = bgzf::VirtualPosition::try_from((57, 6086));
132    /// assert_eq!(virtual_position, Ok(bgzf::VirtualPosition::from(3741638)));
133    /// ```
134    fn try_from(pos: (u64, u16)) -> Result<Self, Self::Error> {
135        let (compressed_pos, uncompressed_pos) = pos;
136
137        if compressed_pos > MAX_COMPRESSED_POSITION {
138            return Err(TryFromU64U16TupleError::CompressedPositionOverflow);
139        }
140
141        Ok(Self(
142            (compressed_pos << COMPRESSED_POSITION_SHIFT) | u64::from(uncompressed_pos),
143        ))
144    }
145}
146
147impl From<VirtualPosition> for u64 {
148    fn from(pos: VirtualPosition) -> Self {
149        pos.0
150    }
151}
152
153impl From<VirtualPosition> for (u64, u16) {
154    fn from(pos: VirtualPosition) -> Self {
155        (pos.compressed(), pos.uncompressed())
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_from_u64_for_virtual_position() {
165        let pos = VirtualPosition::from(88384945211);
166        assert_eq!(pos.compressed(), 1348647);
167        assert_eq!(pos.uncompressed(), 15419);
168
169        let pos = VirtualPosition::from(188049630896);
170        assert_eq!(pos.compressed(), 2869409);
171        assert_eq!(pos.uncompressed(), 42672);
172
173        let pos = VirtualPosition::from(26155658182977);
174        assert_eq!(pos.compressed(), 399103671);
175        assert_eq!(pos.uncompressed(), 321);
176    }
177
178    #[test]
179    fn test_try_from_u64_u16_tuple_for_virtual_position() {
180        assert_eq!(
181            VirtualPosition::try_from((1348647, 15419)),
182            Ok(VirtualPosition::from(88384945211))
183        );
184
185        assert_eq!(
186            VirtualPosition::try_from((2869409, 42672)),
187            Ok(VirtualPosition::from(188049630896))
188        );
189
190        assert_eq!(
191            VirtualPosition::try_from((399103671, 321)),
192            Ok(VirtualPosition::from(26155658182977))
193        );
194
195        assert_eq!(
196            VirtualPosition::try_from((281474976710656, 0)),
197            Err(TryFromU64U16TupleError::CompressedPositionOverflow)
198        );
199    }
200
201    #[test]
202    fn test_from_virtual_position_for_u64() {
203        assert_eq!(u64::from(VirtualPosition::from(88384945211)), 88384945211);
204        assert_eq!(u64::from(VirtualPosition::from(188049630896)), 188049630896);
205        assert_eq!(
206            u64::from(VirtualPosition::from(26155658182977)),
207            26155658182977
208        );
209    }
210
211    #[test]
212    fn test_from_virtual_position_for_u64_u16_tuple() {
213        assert_eq!(
214            <(u64, u16)>::from(VirtualPosition::from(88384945211)),
215            (1348647, 15419)
216        );
217
218        assert_eq!(
219            <(u64, u16)>::from(VirtualPosition::from(188049630896)),
220            (2869409, 42672)
221        );
222
223        assert_eq!(
224            <(u64, u16)>::from(VirtualPosition::from(26155658182977)),
225            (399103671, 321)
226        );
227    }
228}