Skip to main content

oxigeo_core/io/
traits.rs

1//! I/O traits for data sources
2//!
3//! This module provides abstract traits for reading and writing geospatial data
4//! from various sources (local files, HTTP, cloud storage, etc.).
5
6#[cfg(not(feature = "std"))]
7#[allow(unused_imports)]
8use crate::compat::*;
9use crate::error::Result;
10
11/// Byte range for partial reads
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ByteRange {
14    /// Start offset (inclusive)
15    pub start: u64,
16    /// End offset (exclusive)
17    pub end: u64,
18}
19
20impl ByteRange {
21    /// Creates a new byte range
22    #[must_use]
23    pub const fn new(start: u64, end: u64) -> Self {
24        Self { start, end }
25    }
26
27    /// Creates a byte range from an offset and length
28    #[must_use]
29    pub const fn from_offset_length(offset: u64, length: u64) -> Self {
30        Self {
31            start: offset,
32            end: offset + length,
33        }
34    }
35
36    /// Returns the length of this range
37    #[must_use]
38    pub const fn len(&self) -> u64 {
39        self.end - self.start
40    }
41
42    /// Returns true if the range is empty
43    #[must_use]
44    pub const fn is_empty(&self) -> bool {
45        self.start >= self.end
46    }
47
48    /// Returns true if this range overlaps with another
49    #[must_use]
50    pub const fn overlaps(&self, other: &Self) -> bool {
51        self.start < other.end && self.end > other.start
52    }
53
54    /// Returns true if this range is adjacent to another
55    #[must_use]
56    pub const fn is_adjacent(&self, other: &Self) -> bool {
57        self.end == other.start || self.start == other.end
58    }
59
60    /// Merges two overlapping or adjacent ranges
61    #[must_use]
62    pub fn merge(&self, other: &Self) -> Option<Self> {
63        if self.overlaps(other) || self.is_adjacent(other) {
64            Some(Self {
65                start: self.start.min(other.start),
66                end: self.end.max(other.end),
67            })
68        } else {
69            None
70        }
71    }
72}
73
74/// Converts `range.len()` to a `usize`, the length of the buffer a read of that
75/// range needs.
76///
77/// Shared by every in-tree [`DataSource`] so they agree on what an
78/// unrepresentable length means: on a 64-bit target the conversion is infallible,
79/// on a 32-bit one (`wasm32`, `thumbv7em`, …) a range wider than `usize::MAX`
80/// is reported rather than silently truncated to a shorter read.
81///
82/// # Errors
83/// Returns [`OxiGeoError::OutOfBounds`] if the range is wider than `usize::MAX`.
84pub(crate) fn range_len_usize(range: ByteRange) -> Result<usize> {
85    usize::try_from(range.len()).map_err(|_| crate::error::OxiGeoError::OutOfBounds {
86        message: format!(
87            "byte range {}..{} is {} bytes long, which exceeds usize::MAX ({})",
88            range.start,
89            range.end,
90            range.len(),
91            usize::MAX
92        ),
93    })
94}
95
96/// Converts a range to the `(offset, length)` pair an in-memory source indexes
97/// with, rejecting values that do not fit a `usize`.
98///
99/// # Errors
100/// Returns [`OxiGeoError::OutOfBounds`] if either value exceeds `usize::MAX`.
101// Only the memory-mapped sources index by `(offset, len)`, and those are std-only.
102#[cfg(feature = "std")]
103pub(crate) fn range_bounds_usize(range: ByteRange) -> Result<(usize, usize)> {
104    let start =
105        usize::try_from(range.start).map_err(|_| crate::error::OxiGeoError::OutOfBounds {
106            message: format!(
107                "byte range start {} exceeds usize::MAX ({})",
108                range.start,
109                usize::MAX
110            ),
111        })?;
112    Ok((start, range_len_usize(range)?))
113}
114
115/// Builds the error every [`DataSource::read_range_into`] implementation returns
116/// when the caller's destination buffer cannot hold the whole range.
117pub(crate) fn dst_too_small(needed: usize, available: usize) -> crate::error::OxiGeoError {
118    crate::error::OxiGeoError::invalid_parameter(
119        "dst",
120        format!(
121            "destination buffer is {available} bytes but the requested range needs {needed}; \
122             size it with ByteRange::len()"
123        ),
124    )
125}
126
127/// Trait for synchronous data sources
128pub trait DataSource: Send + Sync {
129    /// Returns the total size of the data source in bytes
130    fn size(&self) -> Result<u64>;
131
132    /// Reads bytes from the specified range
133    fn read_range(&self, range: ByteRange) -> Result<Vec<u8>>;
134
135    /// Reads `range` directly into `dst`, avoiding the intermediate allocation of
136    /// [`DataSource::read_range`]. Returns the number of bytes written.
137    ///
138    /// This is the allocation-free entry point for block-oriented readers: a
139    /// caller walking thousands of tiles or strips can size one scratch buffer
140    /// up front and reuse it, instead of paying a heap allocation per block
141    /// (cool-japan/oxigeo#14).
142    ///
143    /// # Buffer sizing
144    ///
145    /// * `dst` **longer** than the range is fine — only `dst[..n]` is written and
146    ///   the tail keeps whatever the caller left there.
147    /// * `dst` **shorter** than `range.len()` is rejected with
148    ///   [`OxiGeoError::InvalidParameter`](crate::error::OxiGeoError::InvalidParameter)
149    ///   *before* any I/O happens, leaving `dst` untouched. Truncating silently
150    ///   would hand back a partial block that looks like a complete read.
151    /// * An **empty** range writes nothing and returns `Ok(0)`; `dst` may itself
152    ///   be empty in that case.
153    ///
154    /// The returned count is what the source actually produced. It equals
155    /// `range.len()` for every source that reports a short read as an error
156    /// (all of the built-in ones); a source whose `read_range` clamps to its own
157    /// end returns the same clamped length here.
158    ///
159    /// On error the contents of `dst` are unspecified — an implementation is free
160    /// to write directly into it and fail part-way through.
161    ///
162    /// # Errors
163    /// Returns exactly what [`DataSource::read_range`] returns for `range`, plus
164    /// the `dst`-too-short error described above.
165    fn read_range_into(&self, range: ByteRange, dst: &mut [u8]) -> Result<usize> {
166        let needed = range_len_usize(range)?;
167        if dst.len() < needed {
168            return Err(dst_too_small(needed, dst.len()));
169        }
170        let data = self.read_range(range)?;
171        // A source whose `read_range` clamps may return fewer bytes than the
172        // range asked for; never more than `dst` can hold, because `needed` is an
173        // upper bound on a well-behaved source's output and `dst` holds `needed`.
174        let written = data.len().min(dst.len());
175        dst[..written].copy_from_slice(&data[..written]);
176        Ok(written)
177    }
178
179    /// Returns a borrowed view of `range` when this source can serve it without
180    /// copying — a memory-mapped file or a fully in-memory buffer, for instance.
181    ///
182    /// The default implementation returns `None`, meaning "copy me instead", so
183    /// callers must always keep a copying fallback ([`DataSource::read_range`] or
184    /// [`DataSource::read_range_into`]). An implementation that returns `Some`
185    /// must return exactly the bytes [`DataSource::read_range`] would have
186    /// returned; any range it cannot serve in full (out of bounds, inverted, or
187    /// wider than `usize`) must yield `None` so the fallback reports the error.
188    fn range_slice(&self, range: ByteRange) -> Option<&[u8]> {
189        let _ = range;
190        None
191    }
192
193    /// Reads bytes from multiple ranges (for optimization)
194    fn read_ranges(&self, ranges: &[ByteRange]) -> Result<Vec<Vec<u8>>> {
195        ranges.iter().map(|r| self.read_range(*r)).collect()
196    }
197
198    /// Returns true if this data source supports range requests
199    fn supports_range_requests(&self) -> bool {
200        true
201    }
202}
203
204/// Trait for async data sources
205#[cfg(feature = "async")]
206#[async_trait::async_trait]
207pub trait AsyncDataSource: Send + Sync {
208    /// Returns the total size of the data source in bytes
209    async fn size(&self) -> Result<u64>;
210
211    /// Reads bytes from the specified range
212    async fn read_range(&self, range: ByteRange) -> Result<Vec<u8>>;
213
214    /// Reads `range` directly into `dst`, avoiding the intermediate allocation of
215    /// [`AsyncDataSource::read_range`]. Returns the number of bytes written.
216    ///
217    /// The buffer-sizing and error contract is identical to the synchronous
218    /// [`DataSource::read_range_into`]: a longer `dst` is fine, a `dst` shorter
219    /// than `range.len()` is rejected before any I/O, and an empty range returns
220    /// `Ok(0)`.
221    ///
222    /// # Errors
223    /// Returns exactly what [`AsyncDataSource::read_range`] returns for `range`,
224    /// plus the `dst`-too-short error.
225    async fn read_range_into(&self, range: ByteRange, dst: &mut [u8]) -> Result<usize> {
226        let needed = range_len_usize(range)?;
227        if dst.len() < needed {
228            return Err(dst_too_small(needed, dst.len()));
229        }
230        let data = self.read_range(range).await?;
231        let written = data.len().min(dst.len());
232        dst[..written].copy_from_slice(&data[..written]);
233        Ok(written)
234    }
235
236    /// Reads bytes from multiple ranges concurrently
237    async fn read_ranges(&self, ranges: &[ByteRange]) -> Result<Vec<Vec<u8>>> {
238        let mut results = Vec::with_capacity(ranges.len());
239        for range in ranges {
240            results.push(self.read_range(*range).await?);
241        }
242        Ok(results)
243    }
244
245    /// Returns true if this data source supports range requests
246    fn supports_range_requests(&self) -> bool {
247        true
248    }
249}
250
251/// Trait for seekable byte-level writes
252pub trait DataSink: Send + Sync {
253    /// Writes bytes at the specified offset
254    fn write_at(&mut self, offset: u64, data: &[u8]) -> Result<()>;
255
256    /// Appends bytes to the end
257    fn append(&mut self, data: &[u8]) -> Result<u64>;
258
259    /// Flushes any buffered data
260    fn flush(&mut self) -> Result<()>;
261
262    /// Truncates the data to the specified size
263    fn truncate(&mut self, size: u64) -> Result<()>;
264
265    /// Returns the current size
266    fn size(&self) -> Result<u64>;
267}
268
269/// Read capability for raster datasets
270pub trait RasterRead {
271    /// The buffer type returned by read operations
272    type Buffer;
273
274    /// Reads a region of the raster
275    fn read_region(
276        &self,
277        band: u32,
278        x_offset: u64,
279        y_offset: u64,
280        width: u64,
281        height: u64,
282    ) -> Result<Self::Buffer>;
283
284    /// Reads a single tile (for tiled datasets)
285    fn read_tile(&self, band: u32, tile_col: u32, tile_row: u32) -> Result<Self::Buffer>;
286}
287
288/// Write capability for raster datasets
289pub trait RasterWrite {
290    /// The buffer type for write operations
291    type Buffer;
292
293    /// Writes a region to the raster
294    fn write_region(
295        &mut self,
296        band: u32,
297        x_offset: u64,
298        y_offset: u64,
299        data: &Self::Buffer,
300    ) -> Result<()>;
301
302    /// Writes a single tile
303    fn write_tile(
304        &mut self,
305        band: u32,
306        tile_col: u32,
307        tile_row: u32,
308        data: &Self::Buffer,
309    ) -> Result<()>;
310}
311
312/// Async read capability for raster datasets
313#[cfg(feature = "async")]
314#[async_trait::async_trait]
315pub trait AsyncRasterRead: Send + Sync {
316    /// The buffer type returned by read operations
317    type Buffer: Send;
318
319    /// Reads a region of the raster asynchronously
320    async fn read_region(
321        &self,
322        band: u32,
323        x_offset: u64,
324        y_offset: u64,
325        width: u64,
326        height: u64,
327    ) -> Result<Self::Buffer>;
328
329    /// Reads a single tile asynchronously
330    async fn read_tile(&self, band: u32, tile_col: u32, tile_row: u32) -> Result<Self::Buffer>;
331}
332
333/// Overview (pyramid) level support
334pub trait OverviewSupport {
335    /// Returns the number of overview levels
336    fn overview_count(&self) -> u32;
337
338    /// Returns the dimensions of an overview level
339    fn overview_dimensions(&self, level: u32) -> Option<(u64, u64)>;
340}
341
342/// COG-specific operations
343pub trait CogSupport: OverviewSupport {
344    /// Returns the tile size
345    fn tile_size(&self) -> (u32, u32);
346
347    /// Returns the number of tiles in X and Y
348    fn tile_count(&self) -> (u32, u32);
349
350    /// Returns the byte range for a specific tile
351    fn tile_byte_range(&self, level: u32, tile_col: u32, tile_row: u32) -> Option<ByteRange>;
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_byte_range() {
360        let range = ByteRange::new(100, 200);
361        assert_eq!(range.len(), 100);
362        assert!(!range.is_empty());
363
364        let empty = ByteRange::new(100, 100);
365        assert!(empty.is_empty());
366    }
367
368    #[test]
369    fn test_byte_range_overlap() {
370        let a = ByteRange::new(0, 100);
371        let b = ByteRange::new(50, 150);
372        let c = ByteRange::new(200, 300);
373
374        assert!(a.overlaps(&b));
375        assert!(b.overlaps(&a));
376        assert!(!a.overlaps(&c));
377    }
378
379    #[test]
380    fn test_byte_range_merge() {
381        let a = ByteRange::new(0, 100);
382        let b = ByteRange::new(100, 200);
383        let c = ByteRange::new(50, 150);
384
385        // Adjacent merge
386        let merged_adj = a.merge(&b);
387        assert!(merged_adj.is_some());
388        let merged = merged_adj.expect("merge should work");
389        assert_eq!(merged.start, 0);
390        assert_eq!(merged.end, 200);
391
392        // Overlapping merge
393        let merged_overlap = a.merge(&c);
394        assert!(merged_overlap.is_some());
395        let merged2 = merged_overlap.expect("merge should work");
396        assert_eq!(merged2.start, 0);
397        assert_eq!(merged2.end, 150);
398
399        // Non-overlapping - no merge
400        let d = ByteRange::new(300, 400);
401        assert!(a.merge(&d).is_none());
402    }
403
404    #[test]
405    fn test_from_offset_length() {
406        let range = ByteRange::from_offset_length(100, 50);
407        assert_eq!(range.start, 100);
408        assert_eq!(range.end, 150);
409        assert_eq!(range.len(), 50);
410    }
411}