diff --git a/src/async_reader.rs b/src/async_reader.rs index f8201a5..bb9412f 100644 --- a/src/async_reader.rs +++ b/src/async_reader.rs @@ -10,7 +10,7 @@ use reqwest::{Client, IntoUrl}; use tokio::io::AsyncReadExt; use crate::directory::{Directory, Entry}; -use crate::error::Error; +use crate::error::PmtError; use crate::header::{HEADER_SIZE, MAX_INITIAL_BYTES}; #[cfg(feature = "http-async")] use crate::http::HttpBackend; @@ -29,11 +29,11 @@ impl AsyncPmTilesReader { /// Creates a new reader from a specified source and validates the provided PMTiles archive is valid. /// /// Note: Prefer using new_with_* methods. - pub async fn try_from_source(backend: B) -> Result { + pub async fn try_from_source(backend: B) -> Result { // Read the first 127 and up to 16,384 bytes to ensure we can initialize the header and root directory. let mut initial_bytes = backend.read(0, MAX_INITIAL_BYTES).await?; if initial_bytes.len() < HEADER_SIZE { - return Err(Error::InvalidHeader); + return Err(PmtError::InvalidHeader); } let header = Header::try_from_bytes(initial_bytes.split_to(HEADER_SIZE))?; @@ -73,7 +73,7 @@ impl AsyncPmTilesReader { /// /// Note: by spec, this should be valid JSON. This method currently returns a [String]. /// This may change in the future. - pub async fn get_metadata(&self) -> Result { + pub async fn get_metadata(&self) -> Result { let offset = self.header.metadata_offset as _; let length = self.header.metadata_length as _; let metadata = self.backend.read_exact(offset, length).await?; @@ -85,13 +85,16 @@ impl AsyncPmTilesReader { } #[cfg(feature = "tilejson")] - pub async fn parse_tilejson(&self, sources: Vec) -> Result { + pub async fn parse_tilejson( + &self, + sources: Vec, + ) -> Result { use serde_json::Value; let meta = self.get_metadata().await?; - let meta: Value = serde_json::from_str(&meta).map_err(|_| Error::InvalidMetadata)?; + let meta: Value = serde_json::from_str(&meta).map_err(|_| PmtError::InvalidMetadata)?; let Value::Object(meta) = meta else { - return Err(Error::InvalidMetadata); + return Err(PmtError::InvalidMetadata); }; let mut tj = self.header.get_tilejson(sources); @@ -117,7 +120,7 @@ impl AsyncPmTilesReader { if let Ok(v) = serde_json::from_value::>(value) { tj.vector_layers = Some(v); } else { - return Err(Error::InvalidMetadata); + return Err(PmtError::InvalidMetadata); } } else { tj.other.insert(key, value); @@ -159,7 +162,7 @@ impl AsyncPmTilesReader { entry.cloned() } - async fn read_directory(&self, offset: usize, length: usize) -> Result { + async fn read_directory(&self, offset: usize, length: usize) -> Result { let data = self.backend.read_exact(offset, length).await?; Self::read_compressed_directory(self.header.internal_compression, data).await } @@ -167,12 +170,12 @@ impl AsyncPmTilesReader { async fn read_compressed_directory( compression: Compression, bytes: Bytes, - ) -> Result { + ) -> Result { let decompressed_bytes = Self::decompress(compression, bytes).await?; Directory::try_from(decompressed_bytes) } - async fn decompress(compression: Compression, bytes: Bytes) -> Result { + async fn decompress(compression: Compression, bytes: Bytes) -> Result { let mut decompressed_bytes = Vec::with_capacity(bytes.len() * 2); match compression { Compression::Gzip => { @@ -192,7 +195,7 @@ impl AsyncPmTilesReader { /// Creates a new PMTiles reader from a URL using the Reqwest backend. /// /// Fails if [url] does not exist or is an invalid archive. (Note: HTTP requests are made to validate it.) - pub async fn new_with_url(client: Client, url: U) -> Result { + pub async fn new_with_url(client: Client, url: U) -> Result { let backend = HttpBackend::try_from(client, url)?; Self::try_from_source(backend).await @@ -204,7 +207,7 @@ impl AsyncPmTilesReader { /// Creates a new PMTiles reader from a file path using the async mmap backend. /// /// Fails if [p] does not exist or is an invalid archive. - pub async fn new_with_path>(path: P) -> Result { + pub async fn new_with_path>(path: P) -> Result { let backend = MmapBackend::try_from(path).await?; Self::try_from_source(backend).await @@ -214,10 +217,10 @@ impl AsyncPmTilesReader { #[async_trait] pub trait AsyncBackend { /// Reads exactly `length` bytes starting at `offset` - async fn read_exact(&self, offset: usize, length: usize) -> Result; + async fn read_exact(&self, offset: usize, length: usize) -> Result; /// Reads up to `length` bytes starting at `offset`. - async fn read(&self, offset: usize, length: usize) -> Result; + async fn read(&self, offset: usize, length: usize) -> Result; } #[cfg(test)] diff --git a/src/directory.rs b/src/directory.rs index ccceab0..ad8a36f 100644 --- a/src/directory.rs +++ b/src/directory.rs @@ -3,7 +3,7 @@ use std::fmt::{Debug, Formatter}; use bytes::{Buf, Bytes}; use varint_rs::VarintReader; -use crate::error::Error; +use crate::error::PmtError; pub(crate) struct Directory { entries: Vec, @@ -38,9 +38,9 @@ impl Directory { } impl TryFrom for Directory { - type Error = Error; + type Error = PmtError; - fn try_from(buffer: Bytes) -> Result { + fn try_from(buffer: Bytes) -> Result { let mut buffer = buffer.reader(); let n_entries = buffer.read_usize_varint()?; @@ -68,7 +68,7 @@ impl TryFrom for Directory { for entry in entries.iter_mut() { let offset = buffer.read_u64_varint()?; entry.offset = if offset == 0 { - let e = last_entry.ok_or(Error::InvalidEntry)?; + let e = last_entry.ok_or(PmtError::InvalidEntry)?; e.offset + e.length as u64 } else { offset - 1 diff --git a/src/error.rs b/src/error.rs index df9b01b..ec718d2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ use std::string::FromUtf8Error; use thiserror::Error; #[derive(Debug, Error)] -pub enum Error { +pub enum PmtError { #[error("Invalid magic number")] InvalidMagicNumber, #[error("Invalid PMTiles version")] @@ -27,12 +27,12 @@ pub enum Error { UnableToOpenMmapFile, #[cfg(feature = "http-async")] #[error("{0}")] - Http(#[from] HttpError), + Http(#[from] PmtHttpError), } #[cfg(feature = "http-async")] #[derive(Debug, Error)] -pub enum HttpError { +pub enum PmtHttpError { #[error("Unexpected number of bytes returned [expected: {0}, received: {1}].")] UnexpectedNumberOfBytesReturned(usize, usize), #[error("Range requests unsupported")] @@ -47,8 +47,8 @@ pub enum HttpError { // This is required because thiserror #[from] does not support two-level conversion. #[cfg(feature = "http-async")] -impl From for Error { +impl From for PmtError { fn from(e: reqwest::Error) -> Self { - Self::Http(HttpError::Http(e)) + Self::Http(PmtHttpError::Http(e)) } } diff --git a/src/header.rs b/src/header.rs index e0c3457..2f704f5 100644 --- a/src/header.rs +++ b/src/header.rs @@ -3,7 +3,7 @@ use std::panic::catch_unwind; use bytes::{Buf, Bytes}; -use crate::error::Error; +use crate::error::PmtError; #[cfg(any(feature = "http-async", feature = "mmap-async-tokio"))] pub(crate) const MAX_INITIAL_BYTES: usize = 16_384; @@ -59,7 +59,7 @@ impl Compression { } impl TryInto for u8 { - type Error = Error; + type Error = PmtError; fn try_into(self) -> Result { match self { @@ -68,7 +68,7 @@ impl TryInto for u8 { 2 => Ok(Compression::Gzip), 3 => Ok(Compression::Brotli), 4 => Ok(Compression::Zstd), - _ => Err(Error::InvalidCompression), + _ => Err(PmtError::InvalidCompression), } } } @@ -125,7 +125,7 @@ impl TileType { } impl TryInto for u8 { - type Error = Error; + type Error = PmtError; fn try_into(self) -> Result { match self { @@ -134,7 +134,7 @@ impl TryInto for u8 { 2 => Ok(TileType::Png), 3 => Ok(TileType::Jpeg), 4 => Ok(TileType::Webp), - _ => Err(Error::InvalidTileType), + _ => Err(PmtError::InvalidTileType), } } } @@ -147,15 +147,15 @@ impl Header { buf.get_i32_le() as f32 / 10_000_000. } - pub fn try_from_bytes(mut bytes: Bytes) -> Result { + pub fn try_from_bytes(mut bytes: Bytes) -> Result { let magic_bytes = bytes.split_to(V3_MAGIC.len()); // Assert magic if magic_bytes != V3_MAGIC { return Err(if magic_bytes.starts_with(V2_MAGIC.as_bytes()) { - Error::UnsupportedPmTilesVersion + PmtError::UnsupportedPmTilesVersion } else { - Error::InvalidMagicNumber + PmtError::InvalidMagicNumber }); } @@ -189,7 +189,7 @@ impl Header { center_latitude: Self::read_coordinate_part(&mut bytes), }) }) - .map_err(|_| Error::InvalidHeader)? + .map_err(|_| PmtError::InvalidHeader)? } } diff --git a/src/http.rs b/src/http.rs index 97bb507..8484890 100644 --- a/src/http.rs +++ b/src/http.rs @@ -4,7 +4,7 @@ use reqwest::header::{HeaderValue, RANGE}; use reqwest::{Client, IntoUrl, Method, Request, StatusCode, Url}; use crate::async_reader::AsyncBackend; -use crate::error::{Error, HttpError}; +use crate::error::{PmtError, PmtHttpError}; pub struct HttpBackend { client: Client, @@ -12,7 +12,7 @@ pub struct HttpBackend { } impl HttpBackend { - pub fn try_from(client: Client, url: U) -> Result { + pub fn try_from(client: Client, url: U) -> Result { Ok(HttpBackend { client, pmtiles_url: url.into_url()?, @@ -22,32 +22,32 @@ impl HttpBackend { #[async_trait] impl AsyncBackend for HttpBackend { - async fn read_exact(&self, offset: usize, length: usize) -> Result { + async fn read_exact(&self, offset: usize, length: usize) -> Result { let data = self.read(offset, length).await?; if data.len() == length { Ok(data) } else { - Err(HttpError::UnexpectedNumberOfBytesReturned(length, data.len()).into()) + Err(PmtHttpError::UnexpectedNumberOfBytesReturned(length, data.len()).into()) } } - async fn read(&self, offset: usize, length: usize) -> Result { + async fn read(&self, offset: usize, length: usize) -> Result { let end = offset + length - 1; let range = format!("bytes={offset}-{end}"); - let range = HeaderValue::try_from(range).map_err(HttpError::from)?; + let range = HeaderValue::try_from(range).map_err(PmtHttpError::from)?; let mut req = Request::new(Method::GET, self.pmtiles_url.clone()); req.headers_mut().insert(RANGE, range); let response = self.client.execute(req).await?.error_for_status()?; if response.status() != StatusCode::PARTIAL_CONTENT { - return Err(HttpError::RangeRequestsUnsupported.into()); + return Err(PmtHttpError::RangeRequestsUnsupported.into()); } let response_bytes = response.bytes().await?; if response_bytes.len() > length { - Err(HttpError::ResponseBodyTooLong(response_bytes.len(), length).into()) + Err(PmtHttpError::ResponseBodyTooLong(response_bytes.len(), length).into()) } else { Ok(response_bytes) } diff --git a/src/lib.rs b/src/lib.rs index b0de680..43c8476 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,12 @@ pub use crate::header::{Compression, Header, TileType}; mod directory; -pub mod error; + +mod error; +pub use error::PmtError; +#[cfg(feature = "http-async")] +pub use error::PmtHttpError; + mod header; #[cfg(feature = "http-async")] diff --git a/src/mmap.rs b/src/mmap.rs index 70270e8..a82cdce 100644 --- a/src/mmap.rs +++ b/src/mmap.rs @@ -6,23 +6,23 @@ use bytes::{Buf, Bytes}; use fmmap::tokio::{AsyncMmapFile, AsyncMmapFileExt as _, AsyncOptions}; use crate::async_reader::AsyncBackend; -use crate::error::Error; +use crate::error::PmtError; pub struct MmapBackend { file: AsyncMmapFile, } impl MmapBackend { - pub async fn try_from>(p: P) -> Result { + pub async fn try_from>(p: P) -> Result { Ok(Self { file: AsyncMmapFile::open_with_options(p, AsyncOptions::new().read(true)) .await - .map_err(|_| Error::UnableToOpenMmapFile)?, + .map_err(|_| PmtError::UnableToOpenMmapFile)?, }) } } -impl From for Error { +impl From for PmtError { fn from(_: fmmap::error::Error) -> Self { Self::Reading(io::Error::from(io::ErrorKind::UnexpectedEof)) } @@ -30,17 +30,17 @@ impl From for Error { #[async_trait] impl AsyncBackend for MmapBackend { - async fn read_exact(&self, offset: usize, length: usize) -> Result { + async fn read_exact(&self, offset: usize, length: usize) -> Result { if self.file.len() >= offset + length { Ok(self.file.reader(offset)?.copy_to_bytes(length)) } else { - Err(Error::Reading(io::Error::from( + Err(PmtError::Reading(io::Error::from( io::ErrorKind::UnexpectedEof, ))) } } - async fn read(&self, offset: usize, length: usize) -> Result { + async fn read(&self, offset: usize, length: usize) -> Result { let reader = self.file.reader(offset)?; let read_length = length.min(reader.len());