#[cfg(all(feature = "mlt", feature = "_tiles"))]
mod to_mlt;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
mod to_mvt;
use martin_core::tiles::Tile;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use martin_tile_utils::Format;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use mlt_core::encoder::EncoderConfig;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use to_mlt::convert_mvt_to_mlt;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use to_mvt::convert_mlt_to_mvt;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use crate::config::file::{MltProcessConfig, MvtProcessConfig, ProcessConfig};
#[derive(thiserror::Error, Debug)]
pub enum ProcessError {
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[error("MVT to MLT conversion failed: {0}")]
MltConversion(String),
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[error("MLT encoding failed: {0}")]
MltEncoding(String),
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[error("MLT to MVT conversion failed: {0}")]
MvtConversion(String),
#[error("Tile decompression failed: {0}")]
DecompressionFailed(String),
}
impl From<ProcessError> for actix_web::Error {
fn from(e: ProcessError) -> Self {
actix_web::error::ErrorInternalServerError(e.to_string())
}
}
pub fn apply_pre_cache_processors(
tile: Tile,
#[cfg(all(feature = "mlt", feature = "_tiles"))] config: &ProcessConfig,
#[cfg(all(feature = "mlt", feature = "_tiles"))] accepted: Option<Format>,
) -> Result<Tile, ProcessError> {
if tile.data.is_empty() {
return Ok(tile);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
let tile = if accepted == Some(Format::Mlt) && tile.info.format == Format::Mvt {
match config.convert_to_mlt.as_ref() {
None | Some(MltProcessConfig::Auto) => {
convert_mvt_to_mlt(tile, EncoderConfig::default())?
}
Some(MltProcessConfig::Explicit(cfg)) => {
convert_mvt_to_mlt(tile, EncoderConfig::from(cfg.clone()))?
}
Some(MltProcessConfig::Disabled) => tile,
}
} else if accepted == Some(Format::Mvt)
&& tile.info.format == Format::Mlt
&& !config
.convert_to_mvt
.as_ref()
.is_some_and(MvtProcessConfig::is_disabled)
{
convert_mlt_to_mvt(tile)?
} else {
tile
};
Ok(tile)
}
#[cfg(test)]
mod tests {
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use martin_core::tiles::Tile;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use martin_tile_utils::{Encoding, Format, TileInfo};
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use rstest::rstest;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use super::to_mvt::{empty_layer_mvt_bytes, mvt_with_feature_bytes};
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use super::*;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
fn make_tile(data: Vec<u8>, format: Format, encoding: Encoding) -> Tile {
Tile::new_hash_etag(data, TileInfo::new(format, encoding))
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[rstest]
#[case::mvt_unc_mlt(Format::Mvt, Encoding::Uncompressed, Format::Mlt)]
#[case::mlt_unc_mvt(Format::Mlt, Encoding::Uncompressed, Format::Mvt)]
fn empty_tile_is_noop(
#[case] format: Format,
#[case] encoding: Encoding,
#[case] target: Format,
) {
let tile = make_tile(Vec::new(), format, encoding);
let result =
apply_pre_cache_processors(tile, &ProcessConfig::default(), Some(target)).unwrap();
assert!(result.data.is_empty());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mvt_request_is_noop() {
let tile = make_tile(vec![1, 2, 3], Format::Mvt, Encoding::Uncompressed);
let config = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Auto),
..Default::default()
};
let result = apply_pre_cache_processors(tile, &config, Some(Format::Mvt)).unwrap();
assert_eq!(result.data, vec![1, 2, 3]);
assert_eq!(result.info.format, Format::Mvt);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn no_accept_header_is_noop() {
let tile = make_tile(vec![1, 2, 3], Format::Mvt, Encoding::Uncompressed);
let result = apply_pre_cache_processors(tile, &ProcessConfig::default(), None).unwrap();
assert_eq!(result.data, vec![1, 2, 3]);
assert_eq!(result.info.format, Format::Mvt);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn non_mvt_source_with_mlt_accept_is_noop() {
let tile = make_tile(vec![1, 2, 3], Format::Png, Encoding::Internal);
let result =
apply_pre_cache_processors(tile, &ProcessConfig::default(), Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Png);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_accept_converts_mvt_with_default_encoder() {
let tile = make_tile(empty_layer_mvt_bytes(), Format::Mvt, Encoding::Uncompressed);
let result =
apply_pre_cache_processors(tile, &ProcessConfig::default(), Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Mlt);
assert_eq!(result.info.encoding, Encoding::Uncompressed);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_accept_uses_explicit_encoder_overrides() {
let tile = make_tile(empty_layer_mvt_bytes(), Format::Mvt, Encoding::Uncompressed);
let config = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Auto),
..Default::default()
};
let result = apply_pre_cache_processors(tile, &config, Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Mlt);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_accept_with_disabled_serves_mvt_unchanged() {
let tile = make_tile(empty_layer_mvt_bytes(), Format::Mvt, Encoding::Uncompressed);
let config = ProcessConfig {
convert_to_mlt: Some(MltProcessConfig::Disabled),
..Default::default()
};
let result = apply_pre_cache_processors(tile, &config, Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Mvt);
assert_eq!(result.data, empty_layer_mvt_bytes());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn compressed_mvt_decompressed_and_converted() {
use martin_tile_utils::encode_gzip;
let gzipped = encode_gzip(&empty_layer_mvt_bytes()).unwrap();
let tile = make_tile(gzipped, Format::Mvt, Encoding::Gzip);
let result =
apply_pre_cache_processors(tile, &ProcessConfig::default(), Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Mlt);
assert_eq!(result.info.encoding, Encoding::Uncompressed);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
fn mvt_with_feature() -> Vec<u8> {
mvt_with_feature_bytes()
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_to_mvt_round_trip() {
let original = make_tile(mvt_with_feature(), Format::Mvt, Encoding::Uncompressed);
let encoded =
apply_pre_cache_processors(original, &ProcessConfig::default(), Some(Format::Mlt))
.unwrap();
assert_eq!(encoded.info.format, Format::Mlt);
assert!(!encoded.data.is_empty(), "MLT tile should have data");
let decoded =
apply_pre_cache_processors(encoded, &ProcessConfig::default(), Some(Format::Mvt))
.unwrap();
assert_eq!(decoded.info.format, Format::Mvt);
assert_eq!(decoded.info.encoding, Encoding::Uncompressed);
assert!(!decoded.data.is_empty());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_source_with_mvt_accept_converts() {
let original = make_tile(mvt_with_feature(), Format::Mvt, Encoding::Uncompressed);
let encoded =
apply_pre_cache_processors(original, &ProcessConfig::default(), Some(Format::Mlt))
.unwrap();
assert!(!encoded.data.is_empty());
let tile = make_tile(encoded.data, Format::Mlt, Encoding::Uncompressed);
let result =
apply_pre_cache_processors(tile, &ProcessConfig::default(), Some(Format::Mvt)).unwrap();
assert_eq!(result.info.format, Format::Mvt);
}
}