//! Sans-IO byte-range reading traits.
//!
//! Generic over a `Ctx` type that provides additional context for each read
//! (e.g. a volume filename). Defaults to `()` when no context is needed.
//!
//! # Examples
//!
//! A simple in-memory source:
//!
//! ```
//! use std::ops::Range;
//! use fskit::ReadAt;
//!
//! struct MemSource(Vec<u8>);
//!
//! impl ReadAt for MemSource {
//!     fn read_at(&self, _ctx: &(), range: Range<u64>) -> std::io::Result<impl AsRef<[u8]>> {
//!         Ok(&self.0[range.start as usize..range.end as usize])
//!     }
//! }
//! ```
//!
//! A multi-volume source:
//!
//! ```
//! use std::collections::HashMap;
//! use std::ops::Range;
//! use fskit::ReadAt;
//!
//! struct MultiVolume {
//!     volumes: HashMap<String, Vec<u8>>,
//! }
//!
//! impl ReadAt<String> for MultiVolume {
//!     fn read_at(&self, volume: &String, range: Range<u64>) -> std::io::Result<impl AsRef<[u8]>> {
//!         let data = self.volumes.get(volume)
//!             .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "volume not found"))?;
//!         Ok(&data[range.start as usize..range.end as usize])
//!     }
//! }
//! ```

use std::ops::Range;

/// Synchronous byte-range read from a backing data source.
///
/// `Ctx` is additional context passed on each read. Defaults to `()`.
pub trait ReadAt<Ctx = ()> {
    fn read_at(&self, ctx: &Ctx, range: Range<u64>) -> std::io::Result<impl AsRef<[u8]>>;
}

/// Async counterpart of [`ReadAt`].
pub trait AsyncReadAt<Ctx = ()>: Send + Sync {
    fn read_at(
        &self,
        ctx: &Ctx,
        range: Range<u64>,
    ) -> impl std::future::Future<Output = std::io::Result<impl AsRef<[u8]>>> + Send;
}

impl<T: ReadAt<Ctx> + Send + Sync, Ctx: Send + Sync> AsyncReadAt<Ctx> for T {
    async fn read_at(&self, ctx: &Ctx, range: Range<u64>) -> std::io::Result<impl AsRef<[u8]>> {
        ReadAt::read_at(self, ctx, range)
    }
}
