gmaps_static/
lib.rs

1mod color;
2mod credentials;
3mod format;
4mod icon_anchor;
5mod location;
6mod maptype;
7mod marker;
8mod marker_appearence;
9mod marker_icon;
10mod marker_label;
11mod marker_scale;
12mod marker_size;
13mod marker_style;
14mod relative_position;
15mod scale;
16mod signature;
17mod size;
18mod zoom;
19
20pub use color::*;
21pub use credentials::*;
22pub use format::*;
23pub use icon_anchor::*;
24pub use location::*;
25pub use maptype::*;
26pub use marker::*;
27pub use marker_appearence::*;
28pub use marker_icon::*;
29pub use marker_label::*;
30pub use marker_scale::*;
31pub use marker_size::*;
32pub use marker_style::*;
33pub use relative_position::*;
34pub use scale::*;
35use signature::*;
36pub use size::*;
37pub use zoom::*;
38
39use url::Url;
40
41#[macro_use]
42extern crate lazy_static;
43
44#[derive(Clone)]
45pub struct UrlBuilder<S: AsRef<str> + Clone> {
46    credentials: Credentials<S>,
47    size: Size,
48    center: Option<Location>,
49    zoom: Option<&'static Zoom>,
50    scale: Option<&'static Scale>,
51    format: Option<&'static Format>,
52    maptype: Option<&'static MapType>,
53    language: Option<S>,
54    region: Option<S>,
55    markers: Vec<Marker<S>>,
56}
57
58static BASE_URL: &str = "https://maps.googleapis.com/maps/api/staticmap";
59
60impl<S: AsRef<str> + Clone> UrlBuilder<S> {
61    pub fn new(credentials: Credentials<S>, size: Size) -> Self {
62        UrlBuilder {
63            credentials,
64            size,
65            center: None,
66            zoom: None,
67            scale: None,
68            format: None,
69            maptype: None,
70            language: None,
71            region: None,
72            markers: vec![],
73        }
74    }
75
76    pub fn center(&self, center: Location) -> Self {
77        UrlBuilder {
78            center: Some(center),
79            ..(*self).clone()
80        }
81    }
82
83    pub fn zoom(&self, zoom: &'static Zoom) -> Self {
84        UrlBuilder {
85            zoom: Some(zoom),
86            ..(*self).clone()
87        }
88    }
89
90    pub fn scale(&self, scale: &'static Scale) -> Self {
91        UrlBuilder {
92            scale: Some(scale),
93            ..(*self).clone()
94        }
95    }
96
97    pub fn format(&self, format: &'static Format) -> Self {
98        UrlBuilder {
99            format: Some(format),
100            ..(*self).clone()
101        }
102    }
103
104    pub fn maptype(&self, maptype: &'static MapType) -> Self {
105        UrlBuilder {
106            maptype: Some(maptype),
107            ..(*self).clone()
108        }
109    }
110
111    pub fn language(&self, language: S) -> Self {
112        UrlBuilder {
113            language: Some(language),
114            ..(*self).clone()
115        }
116    }
117
118    pub fn region(&self, region: S) -> Self {
119        UrlBuilder {
120            region: Some(region),
121            ..(*self).clone()
122        }
123    }
124
125    pub fn markers(&self, markers: Vec<Marker<S>>) -> Self {
126        UrlBuilder {
127            markers,
128            ..(*self).clone()
129        }
130    }
131
132    pub fn add_marker(&self, marker: Marker<S>) -> Self {
133        let mut new_markers = self.markers.clone();
134        new_markers.push(marker);
135
136        UrlBuilder {
137            markers: new_markers,
138            ..(*self).clone()
139        }
140    }
141
142    pub fn make_url(&self) -> String {
143        // TODO: make this method fallible and return an error if there's no (center+zoom) and no marker
144
145        let mut url = Url::parse(BASE_URL).unwrap();
146        url.query_pairs_mut()
147            .append_pair("size", self.size.to_string().as_str());
148
149        if let Some(center) = self.center.as_ref() {
150            url.query_pairs_mut()
151                .append_pair("center", center.to_string().as_str());
152        }
153
154        if let Some(scale) = &self.scale {
155            url.query_pairs_mut()
156                .append_pair("scale", scale.to_string().as_str());
157        }
158
159        if let Some(format) = self.format {
160            url.query_pairs_mut()
161                .append_pair("format", format.to_string().as_str());
162        }
163
164        if let Some(maptype) = self.maptype {
165            url.query_pairs_mut()
166                .append_pair("maptype", maptype.to_string().as_str());
167        }
168
169        if let Some(language) = self.language.as_ref() {
170            url.query_pairs_mut()
171                .append_pair("language", language.as_ref());
172        }
173
174        if let Some(region) = self.region.as_ref() {
175            url.query_pairs_mut().append_pair("region", region.as_ref());
176        }
177
178        if let Some(zoom) = self.zoom {
179            url.query_pairs_mut()
180                .append_pair("zoom", zoom.to_string().as_ref());
181        }
182
183        for marker in &self.markers {
184            url.query_pairs_mut()
185                .append_pair("markers", marker.to_string().as_ref());
186        }
187
188        url.query_pairs_mut()
189            .append_pair("key", self.credentials.api_key.as_ref());
190
191        // If credentials has a secret_key, calculate and appends the signature
192        // This must be the last query string parameters to be added (all the others need to
193        // calculate the signature)
194        if let Some(secret) = &self.credentials.secret_key {
195            let signature = sign(&url, secret.as_ref());
196            url.query_pairs_mut()
197                .append_pair("signature", signature.as_str());
198        }
199
200        url.to_string()
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    mod test_utils;
207
208    use super::*;
209    use test_utils::*;
210
211    #[test]
212    fn it_builds_a_simple_url() {
213        let map = UrlBuilder::new("YOUR_API_KEY".into(), (50, 50).into());
214
215        let generated_url = qs_from_url(map.make_url());
216        let expected_url = qs_from_url(
217            "https://maps.googleapis.com/maps/api/staticmap?size=50x50&key=YOUR_API_KEY"
218                .to_string(),
219        );
220        assert_eq!(generated_url, expected_url);
221    }
222
223    #[test]
224    fn it_builds_a_url_with_a_signature_if_secret_is_used() {
225        let credentials =
226            Credentials::with_secret_key("YOUR_API_KEY", "X8XXXxxxxxXwrIEQfguOVNGv2jY=");
227        let map = UrlBuilder::new(credentials, (50, 50).into());
228
229        let generated_url = qs_from_url(map.make_url());
230        let expected_url = qs_from_url(
231            "https://maps.googleapis.com/maps/api/staticmap?size=50x50&key=YOUR_API_KEY&signature=Ig1D2O-jLfIGKJaO7SWeWVvLwR4%3D"
232                .to_string(),
233        );
234        assert_eq!(generated_url, expected_url);
235    }
236
237    #[test]
238    fn it_builds_a_more_complete_url() {
239        let map = UrlBuilder::new("YOUR_API_KEY".into(), (400, 300).into())
240            .scale(SCALE2)
241            .center("Colosseo".into())
242            .zoom(STREETS)
243            .format(GIF)
244            .maptype(HYBRID)
245            .region("it")
246            .language("it");
247
248        let generated_url = qs_from_url(map.make_url());
249        let expected_url = qs_from_url(
250            "https://maps.googleapis.com/maps/api/staticmap?\
251            size=400x300\
252            &center=Colosseo&\
253            scale=2&\
254            zoom=15&\
255            format=gif&\
256            maptype=hybrid&\
257            language=it&\
258            region=it&\
259            key=YOUR_API_KEY"
260                .to_string(),
261        );
262        assert_eq!(generated_url, expected_url);
263    }
264
265    #[test]
266    fn it_builds_a_more_complete_url_2() {
267        let marker1 = Marker::simple(&Color::Blue, 'S', (40.702147, -74.015794).into());
268        let marker2 = Marker::simple(&Color::Green, 'G', (40.711614, -74.012318).into());
269        let marker3 = Marker::simple(&Color::Red, 'C', (40.718217, -73.998284).into());
270
271        let map = UrlBuilder::new("YOUR_API_KEY".into(), (600, 300).into())
272            .center("Brooklyn Bridge,New York,NY".into())
273            .zoom(&ZOOM_13)
274            .maptype(&MapType::RoadMap)
275            .add_marker(marker1)
276            .add_marker(marker2)
277            .add_marker(marker3);
278
279        let generated_url = qs_from_url(map.make_url());
280        let expected_url = qs_from_url(
281            "https://maps.googleapis.com/maps/api/staticmap?\
282        center=Brooklyn+Bridge%2CNew+York%2CNY\
283        &zoom=13\
284        &size=600x300\
285        &maptype=roadmap\
286        &markers=color%3Ablue%7Clabel%3AS%7C40.702147%2C-74.015794\
287        &markers=color%3Agreen%7Clabel%3AG%7C40.711614%2C-74.012318\
288        &markers=color%3Ared%7Clabel%3AC%7C40.718217%2C-73.998284\
289        &key=YOUR_API_KEY"
290                .to_string(),
291        );
292
293        assert_eq!(generated_url, expected_url);
294    }
295}