use std::fmt::Display;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Format {
Gif,
Jpeg,
Json,
Mvt,
Png,
Webp,
}
impl Format {
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
Some(match value.to_ascii_lowercase().as_str() {
"gif" => Self::Gif,
"jpg" | "jpeg" => Self::Jpeg,
"json" => Self::Json,
"pbf" | "mvt" => Self::Mvt,
"png" => Self::Png,
"webp" => Self::Webp,
_ => None?,
})
}
#[must_use]
pub fn content_type(&self) -> &str {
match *self {
Self::Gif => "image/gif",
Self::Jpeg => "image/jpeg",
Self::Json => "application/json",
Self::Mvt => "application/x-protobuf",
Self::Png => "image/png",
Self::Webp => "image/webp",
}
}
#[must_use]
pub fn is_detectable(&self) -> bool {
match *self {
Self::Png | Self::Jpeg | Self::Gif | Self::Webp => true,
Self::Mvt | Self::Json => false,
}
}
}
impl Display for Format {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Gif => write!(f, "gif"),
Self::Jpeg => write!(f, "jpeg"),
Self::Json => write!(f, "json"),
Self::Mvt => write!(f, "mvt"),
Self::Png => write!(f, "png"),
Self::Webp => write!(f, "webp"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Encoding {
Uncompressed = 0b0000_0000,
Internal = 0b0000_0001,
Gzip = 0b0000_0010,
Zlib = 0b0000_0100,
Brotli = 0b0000_1000,
Zstd = 0b0001_0000,
}
impl Encoding {
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
Some(match value.to_ascii_lowercase().as_str() {
"none" => Self::Uncompressed,
"gzip" => Self::Gzip,
"zlib" => Self::Zlib,
"brotli" => Self::Brotli,
"zstd" => Self::Zstd,
_ => None?,
})
}
#[must_use]
pub fn content_encoding(&self) -> Option<&str> {
match *self {
Self::Uncompressed | Self::Internal => None,
Self::Gzip => Some("gzip"),
Self::Zlib => Some("deflate"),
Self::Brotli => Some("br"),
Self::Zstd => Some("zstd"),
}
}
#[must_use]
pub fn is_encoded(&self) -> bool {
match *self {
Self::Uncompressed | Self::Internal => false,
Self::Gzip | Self::Zlib | Self::Brotli | Self::Zstd => true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TileInfo {
pub format: Format,
pub encoding: Encoding,
}
impl TileInfo {
#[must_use]
pub fn new(format: Format, encoding: Encoding) -> Self {
Self { format, encoding }
}
#[must_use]
#[allow(clippy::enum_glob_use)]
pub fn detect(value: &[u8]) -> Option<Self> {
use Encoding::*;
use Format::*;
Some(match value {
v if v.starts_with(b"\x1f\x8b") => Self::new(Mvt, Gzip),
v if v.starts_with(b"\x78\x9c") => Self::new(Mvt, Zlib),
v if v.starts_with(b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") => Self::new(Png, Internal),
v if v.starts_with(b"\x47\x49\x46\x38\x39\x61") => Self::new(Gif, Internal),
v if v.starts_with(b"\xFF\xD8\xFF") => Self::new(Jpeg, Internal),
v if v.starts_with(b"RIFF") && v.len() > 8 && v[8..].starts_with(b"WEBP") => {
Self::new(Webp, Internal)
}
v if v.starts_with(b"{") => Self::new(Json, Uncompressed),
_ => None?,
})
}
#[must_use]
pub fn encoding(self, encoding: Encoding) -> Self {
Self { encoding, ..self }
}
}
impl From<Format> for TileInfo {
fn from(format: Format) -> Self {
Self::new(
format,
match format {
Format::Png | Format::Jpeg | Format::Webp | Format::Gif => Encoding::Internal,
Format::Mvt | Format::Json => Encoding::Uncompressed,
},
)
}
}
impl Display for TileInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format.content_type())?;
if let Some(encoding) = self.encoding.content_encoding() {
write!(f, "; encoding={encoding}")?;
} else if self.encoding != Encoding::Uncompressed {
write!(f, "; uncompressed")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::fs::read;
use Encoding::{Internal, Uncompressed};
use Format::{Jpeg, Json, Png, Webp};
use super::*;
fn detect(path: &str) -> Option<TileInfo> {
TileInfo::detect(&read(path).unwrap())
}
#[allow(clippy::unnecessary_wraps)]
fn info(format: Format, encoding: Encoding) -> Option<TileInfo> {
Some(TileInfo::new(format, encoding))
}
#[test]
fn test_data_format_png() {
assert_eq!(detect("./fixtures/world.png"), info(Png, Internal));
}
#[test]
fn test_data_format_jpg() {
assert_eq!(detect("./fixtures/world.jpg"), info(Jpeg, Internal));
}
#[test]
fn test_data_format_webp() {
assert_eq!(detect("./fixtures/dc.webp"), info(Webp, Internal));
assert_eq!(TileInfo::detect(br#"RIFF"#), None);
}
#[test]
fn test_data_format_json() {
assert_eq!(
TileInfo::detect(br#"{"foo":"bar"}"#),
info(Json, Uncompressed)
);
}
}