Skip to main content

everymap_core/domains/
imaging.rs

1use crate::error::EveryMapResult;
2use crate::types::Coordinate;
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5
6/// Options for static map image generation.
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8pub struct ImageOptions {
9    /// Image format (e.g., "png", "jpg")
10    pub format: Option<String>,
11    /// Preferred response language (BCP 47 language tag)
12    pub language: Option<String>,
13    /// Provider-specific options (HERE: style, poi, overlay; Google: maptype, markers, path)
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub provider_extra: Option<serde_json::Value>,
16}
17
18/// Simplified image response from the core trait.
19#[derive(Debug, Clone)]
20pub struct ImageResponse {
21    pub data: Vec<u8>,
22    pub content_type: Option<String>,
23}
24
25impl ImageResponse {
26    /// Create a new image response from raw bytes.
27    pub fn new(data: Vec<u8>, content_type: Option<String>) -> Self {
28        Self { data, content_type }
29    }
30}
31
32#[async_trait]
33pub trait MapImageProvider: Send + Sync {
34    async fn get_image(
35        &self,
36        center: &Coordinate,
37        zoom: u32,
38        size: (u32, u32),
39        options: &ImageOptions,
40    ) -> EveryMapResult<ImageResponse>;
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn test_image_options_default() {
49        let options = ImageOptions::default();
50        assert!(options.format.is_none());
51        assert!(options.language.is_none());
52        assert!(options.provider_extra.is_none());
53    }
54
55    #[test]
56    fn test_image_options_with_fields() {
57        let options = ImageOptions {
58            format: Some("png".to_string()),
59            language: Some("en".to_string()),
60            provider_extra: Some(serde_json::json!({"style": "default"})),
61        };
62        assert_eq!(options.format, Some("png".to_string()));
63    }
64
65    #[test]
66    fn test_image_response_construction() {
67        let response =
68            ImageResponse::new(vec![0x89, 0x50, 0x4E, 0x47], Some("image/png".to_string()));
69        assert_eq!(response.data.len(), 4);
70        assert_eq!(response.content_type, Some("image/png".to_string()));
71    }
72
73    // --- ImageOptions serde roundtrip ---
74
75    #[test]
76    fn test_image_options_serde_roundtrip() {
77        let options = ImageOptions {
78            format: Some("jpg".to_string()),
79            language: Some("ja".to_string()),
80            provider_extra: Some(
81                serde_json::json!({"maptype": "satellite", "markers": [{"lat": 35.6762, "lng": 139.6503}]}),
82            ),
83        };
84        let json = serde_json::to_string(&options).unwrap();
85        let back: ImageOptions = serde_json::from_str(&json).unwrap();
86        assert_eq!(back.format, Some("jpg".to_string()));
87        assert!(back.provider_extra.is_some());
88    }
89
90    // --- ImageResponse edge cases ---
91
92    #[test]
93    fn test_image_response_empty_data() {
94        let response = ImageResponse::new(vec![], None);
95        assert!(response.data.is_empty());
96        assert!(response.content_type.is_none());
97    }
98
99    #[test]
100    fn test_image_response_no_content_type() {
101        let response = ImageResponse::new(vec![0xFF, 0xD8, 0xFF], None);
102        assert_eq!(response.data.len(), 3);
103        assert!(response.content_type.is_none());
104    }
105
106    #[test]
107    fn test_image_response_jpeg_content_type() {
108        let response =
109            ImageResponse::new(vec![0xFF, 0xD8, 0xFF, 0xE0], Some("image/jpeg".to_string()));
110        assert_eq!(response.content_type, Some("image/jpeg".to_string()));
111    }
112
113    #[test]
114    fn test_image_response_large_data() {
115        let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
116        let response = ImageResponse::new(data, Some("image/png".to_string()));
117        assert_eq!(response.data.len(), 1024);
118    }
119
120    // --- ImageResponse Clone ---
121
122    #[test]
123    fn test_image_response_clone() {
124        let response = ImageResponse::new(vec![1, 2, 3], Some("image/png".to_string()));
125        let cloned = response.clone();
126        assert_eq!(cloned.data, response.data);
127        assert_eq!(cloned.content_type, response.content_type);
128    }
129}