diff --git a/martin/src/sprites/mod.rs b/martin/src/sprites/mod.rs index b8f00c630..64f96f879 100644 --- a/martin/src/sprites/mod.rs +++ b/martin/src/sprites/mod.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use std::fmt::Debug; use std::path::PathBuf; -use actix_web::error::ErrorNotFound; use futures::future::try_join_all; use log::{info, warn}; use spreet::fs::get_svg_input_paths; @@ -16,6 +15,9 @@ use crate::file_config::{FileConfigEnum, FileError}; #[derive(thiserror::Error, Debug)] pub enum SpriteError { + #[error("Sprite {0} not found")] + SpriteNotFound(String), + #[error("IO error {0}: {}", .1.display())] IoError(std::io::Error, PathBuf), @@ -96,10 +98,25 @@ fn add_source(id: String, path: PathBuf, results: &mut SpriteSources) { pub struct SpriteSources(HashMap); impl SpriteSources { - pub fn get_sprite_source(&self, id: &str) -> actix_web::Result<&SpriteSource> { + pub fn get_sprite_source(&self, id: &str) -> Result<&SpriteSource, SpriteError> { self.0 .get(id) - .ok_or_else(|| ErrorNotFound(format!("Sprite {id} does not exist"))) + .ok_or_else(|| SpriteError::SpriteNotFound(id.to_string())) + } + + /// Given a list of IDs in a format "id1,id2,id3", return a spritesheet with them all. + /// `ids` may optionally end with "@2x" to request a high-DPI spritesheet. + pub async fn get_sprites(&self, ids: &str) -> Result { + let (ids, dpi) = if let Some(ids) = ids.strip_suffix("@2x") { + (ids, 2) + } else { + (ids, 1) + }; + let sprite_ids = ids + .split(',') + .map(|id| self.get_sprite_source(id)) + .collect::, SpriteError>>()?; + get_spritesheet(sprite_ids.into_iter(), dpi).await } } diff --git a/martin/src/srv/server.rs b/martin/src/srv/server.rs index 3c0864285..f1813bba6 100755 --- a/martin/src/srv/server.rs +++ b/martin/src/srv/server.rs @@ -13,19 +13,18 @@ use actix_web::http::Uri; use actix_web::middleware::TrailingSlash; use actix_web::web::{Data, Path, Query}; use actix_web::{ - error, middleware, route, web, App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer, + error, middleware, route, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer, Responder, Result, }; use futures::future::try_join_all; use log::error; use martin_tile_utils::{Encoding, Format, TileInfo}; use serde::Deserialize; -use spreet::sprite::Spritesheet; use tilejson::{tilejson, TileJSON}; use crate::config::AllSources; use crate::source::{Source, Sources, UrlQuery, Xyz}; -use crate::sprites::{get_spritesheet, SpriteSources}; +use crate::sprites::{SpriteError, SpriteSources}; use crate::srv::config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT}; use crate::utils::{decode_brotli, decode_gzip, encode_brotli, encode_gzip}; use crate::Error::BindingError; @@ -56,11 +55,19 @@ struct TileRequest { y: u32, } -fn map_internal_error(e: T) -> Error { +pub fn map_internal_error(e: T) -> actix_web::Error { error!("{e}"); error::ErrorInternalServerError(e.to_string()) } +pub fn map_sprite_error(e: SpriteError) -> actix_web::Error { + if let SpriteError::SpriteNotFound(_) = e { + error::ErrorNotFound(e.to_string()) + } else { + map_internal_error(e) + } +} + /// Root path will eventually have a web front. For now, just a stub. #[route("/", method = "GET", method = "HEAD")] #[allow(clippy::unused_async)] @@ -95,10 +102,13 @@ async fn get_sprite_png( path: Path, sprites: Data, ) -> Result { - let ss = get_sprite_int(&path.source_ids, &sprites).await?; + let sheet = sprites + .get_sprites(&path.source_ids) + .await + .map_err(map_sprite_error)?; Ok(HttpResponse::Ok() .content_type(ContentType::png()) - .body(ss.encode_png().map_err(map_internal_error)?)) + .body(sheet.encode_png().map_err(map_internal_error)?)) } #[route("/sprite/{source_ids}.json", method = "GET", method = "HEAD")] @@ -106,23 +116,11 @@ async fn get_sprite_json( path: Path, sprites: Data, ) -> Result { - let ss = get_sprite_int(&path.source_ids, &sprites).await?; - Ok(HttpResponse::Ok().json(ss.get_index())) -} - -async fn get_sprite_int(ids: &str, sprites: &SpriteSources) -> Result { - let (ids, dpi) = if let Some(ids) = ids.strip_suffix("@2x") { - (ids, 2) - } else { - (ids, 1) - }; - let sprite_ids = ids - .split(',') - .map(|id| sprites.get_sprite_source(id)) - .collect::>>()?; - get_spritesheet(sprite_ids.into_iter(), dpi) + let sheet = sprites + .get_sprites(&path.source_ids) .await - .map_err(map_internal_error) + .map_err(map_sprite_error)?; + Ok(HttpResponse::Ok().json(sheet.get_index())) } #[route(