Skip to main content

everymap_providers_mapbox/
ext.rs

1use async_trait::async_trait;
2use everymap_core::error::EveryMapResult;
3use everymap_core::types::Coordinate;
4
5/// Extension trait for MapBox-specific geocoder capabilities.
6///
7/// These methods are not part of the core `Geocoder` trait because they
8/// are MapBox-specific. Import this trait to access them:
9///
10/// ```ignore
11/// use everymap_providers_mapbox::MapBoxGeocoderExt;
12/// let results = geocoder.permanent_geocode(query, &options).await?;
13/// ```
14#[async_trait]
15pub trait MapBoxGeocoderExt: Send + Sync {
16    /// Use the MapBox permanent geocoding endpoint (batch lookups).
17    async fn permanent_geocode(
18        &self,
19        query: &str,
20        limit: Option<u32>,
21        language: Option<&str>,
22    ) -> EveryMapResult<super::domain::search::MapBoxSearchResponse>;
23
24    /// Batch geocode up to 50 forward/reverse queries.
25    async fn batch_geocode(
26        &self,
27        queries: &[String],
28        language: Option<&str>,
29    ) -> EveryMapResult<Vec<super::domain::search::MapBoxSearchResponse>>;
30}
31
32/// Extension trait for MapBox-specific routing capabilities.
33#[async_trait]
34pub trait MapBoxRouterExt: Send + Sync {
35    /// Get a route with specific profile and options not in the core trait.
36    async fn route_with_profile(
37        &self,
38        coordinates: &[Coordinate],
39        profile: &str,
40        alternatives: Option<u32>,
41    ) -> EveryMapResult<super::domain::routing::MapBoxRouteResponse>;
42}
43
44// --- Extension trait implementations ---
45
46#[async_trait]
47impl MapBoxGeocoderExt for super::domain::search::MapBoxGeocoder {
48    async fn permanent_geocode(
49        &self,
50        query: &str,
51        limit: Option<u32>,
52        language: Option<&str>,
53    ) -> EveryMapResult<super::domain::search::MapBoxSearchResponse> {
54        let url = format!("{}/search/geocode/v6/forward", self.base_url);
55        let mut params: Vec<(&str, String)> = vec![("q", query.to_string())];
56        if let Some(lim) = limit {
57            params.push(("limit", lim.to_string()));
58        }
59        if let Some(lang) = language {
60            params.push(("language", lang.to_string()));
61        }
62
63        let builder = self
64            .client
65            .build_request(reqwest::Method::GET, &url)
66            .query(&params);
67        self.client.request_json(builder).await
68    }
69
70    async fn batch_geocode(
71        &self,
72        queries: &[String],
73        _language: Option<&str>,
74    ) -> EveryMapResult<Vec<super::domain::search::MapBoxSearchResponse>> {
75        // MapBox batch geocoding is a separate paid API; provide individual lookups as fallback
76        let mut results = Vec::with_capacity(queries.len());
77        for query in queries {
78            let url = format!("{}/search/geocode/v6/forward", self.base_url);
79            let params: Vec<(&str, String)> = vec![("q", query.clone())];
80            let builder = self
81                .client
82                .build_request(reqwest::Method::GET, &url)
83                .query(&params);
84            let result: super::domain::search::MapBoxSearchResponse =
85                self.client.request_json(builder).await?;
86            results.push(result);
87        }
88        Ok(results)
89    }
90}
91
92#[async_trait]
93impl MapBoxRouterExt for super::domain::routing::MapBoxRouter {
94    async fn route_with_profile(
95        &self,
96        coordinates: &[Coordinate],
97        profile: &str,
98        alternatives: Option<u32>,
99    ) -> EveryMapResult<super::domain::routing::MapBoxRouteResponse> {
100        if coordinates.len() < 2 {
101            return Err(everymap_core::error::EveryMapError::provider(
102                "mapbox",
103                "INVALID_INPUT",
104                "At least 2 coordinates required for routing",
105            ));
106        }
107
108        let coords: String = coordinates
109            .iter()
110            .map(|c| format!("{},{}", c.lng, c.lat))
111            .collect::<Vec<_>>()
112            .join(";");
113        let url = format!(
114            "{}/directions/v5/mapbox/{}/{}",
115            self.base_url, profile, coords
116        );
117
118        let mut params: Vec<(&str, String)> = vec![
119            ("overview", "full".to_string()),
120            ("geometries", "polyline".to_string()),
121            ("steps", "true".to_string()),
122        ];
123        if let Some(alternative) = alternatives {
124            params.push(("alternatives", alternative.to_string()));
125        }
126
127        let builder = self
128            .client
129            .build_request(reqwest::Method::GET, &url)
130            .query(&params);
131        self.client.request_json(builder).await
132    }
133}