1use crate::{PaginatedResponse, PointOfInterest, Result, RideWithGpsClient};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Visibility {
10 Public,
12
13 Private,
15
16 Unlisted,
18}
19
20#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct TrackPoint {
23 pub x: Option<f64>,
25
26 pub y: Option<f64>,
28
29 pub d: Option<f64>,
31
32 pub e: Option<f64>,
34
35 #[serde(rename = "S")]
37 pub surface: Option<i32>,
38
39 #[serde(rename = "R")]
41 pub highway: Option<i32>,
42}
43
44#[derive(Debug, Clone, Deserialize, Serialize)]
46pub struct CoursePoint {
47 pub x: Option<f64>,
49
50 pub y: Option<f64>,
52
53 pub d: Option<f64>,
55
56 pub t: Option<String>,
58
59 pub n: Option<String>,
61}
62
63#[derive(Debug, Clone, Deserialize, Serialize)]
65pub struct Photo {
66 pub id: u64,
68
69 pub url: Option<String>,
71
72 pub highlighted: Option<bool>,
74
75 pub caption: Option<String>,
77
78 pub created_at: Option<String>,
80}
81
82#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct Route {
85 pub id: u64,
87
88 pub name: Option<String>,
90
91 pub description: Option<String>,
93
94 pub distance: Option<f64>,
96
97 pub elevation_gain: Option<f64>,
99
100 pub elevation_loss: Option<f64>,
102
103 pub visibility: Option<Visibility>,
105
106 pub user_id: Option<u64>,
108
109 pub url: Option<String>,
111
112 pub html_url: Option<String>,
114
115 pub created_at: Option<String>,
117
118 pub updated_at: Option<String>,
120
121 pub locality: Option<String>,
123
124 pub administrative_area: Option<String>,
126
127 pub country_code: Option<String>,
129
130 pub track_type: Option<String>,
132
133 pub has_course_points: Option<bool>,
135
136 pub terrain: Option<String>,
138
139 pub difficulty: Option<String>,
141
142 pub first_lat: Option<f64>,
144
145 pub first_lng: Option<f64>,
147
148 pub last_lat: Option<f64>,
150
151 pub last_lng: Option<f64>,
153
154 pub sw_lat: Option<f64>,
156
157 pub sw_lng: Option<f64>,
159
160 pub ne_lat: Option<f64>,
162
163 pub ne_lng: Option<f64>,
165
166 pub unpaved_pct: Option<f64>,
168
169 pub surface: Option<String>,
171
172 pub archived: Option<bool>,
174
175 pub activity_types: Option<Vec<String>>,
177
178 pub track_points: Option<Vec<TrackPoint>>,
180
181 pub course_points: Option<Vec<CoursePoint>>,
183
184 pub points_of_interest: Option<Vec<PointOfInterest>>,
186
187 pub photos: Option<Vec<Photo>>,
189}
190
191#[derive(Debug, Clone, Deserialize, Serialize)]
193pub struct Polyline {
194 pub polyline: String,
196
197 pub parent_type: Option<String>,
199
200 pub parent_id: Option<u64>,
202}
203
204#[derive(Debug, Clone, Default, Serialize)]
206pub struct ListRoutesParams {
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub name: Option<String>,
210
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub visibility: Option<Visibility>,
214
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub min_distance: Option<f64>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub max_distance: Option<f64>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub min_elevation_gain: Option<f64>,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub max_elevation_gain: Option<f64>,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub page: Option<u32>,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub page_size: Option<u32>,
238}
239
240impl RideWithGpsClient {
241 pub fn list_routes(
267 &self,
268 params: Option<&ListRoutesParams>,
269 ) -> Result<PaginatedResponse<Route>> {
270 let mut url = "/api/v1/routes.json".to_string();
271
272 if let Some(params) = params {
273 let query = serde_json::to_value(params)?;
274 if let Some(obj) = query.as_object() {
275 if !obj.is_empty() {
276 let query_str = serde_urlencoded::to_string(obj).map_err(|e| {
277 crate::Error::ApiError(format!("Failed to encode query: {}", e))
278 })?;
279 url.push('?');
280 url.push_str(&query_str);
281 }
282 }
283 }
284
285 self.get(&url)
286 }
287
288 pub fn get_route(&self, id: u64) -> Result<Route> {
309 #[derive(Deserialize)]
310 struct RouteWrapper {
311 route: Route,
312 }
313
314 let wrapper: RouteWrapper = self.get(&format!("/api/v1/routes/{}.json", id))?;
315 Ok(wrapper.route)
316 }
317
318 pub fn get_route_polyline(&self, id: u64) -> Result<Polyline> {
339 self.get(&format!("/api/v1/routes/{}/polyline.json", id))
340 }
341
342 pub fn delete_route(&self, id: u64) -> Result<()> {
362 self.delete(&format!("/api/v1/routes/{}.json", id))
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn test_route_deserialization() {
372 let json = r#"{
373 "id": 123,
374 "name": "Test Route",
375 "distance": 10000.0,
376 "elevation_gain": 500.0,
377 "visibility": "public"
378 }"#;
379
380 let route: Route = serde_json::from_str(json).unwrap();
381 assert_eq!(route.id, 123);
382 assert_eq!(route.name.as_deref(), Some("Test Route"));
383 assert_eq!(route.distance, Some(10000.0));
384 assert_eq!(route.visibility, Some(Visibility::Public));
385 }
386
387 #[test]
388 fn test_polyline_deserialization() {
389 let json = r#"{
390 "polyline": "encoded_string_here",
391 "parent_type": "route",
392 "parent_id": 123
393 }"#;
394
395 let polyline: Polyline = serde_json::from_str(json).unwrap();
396 assert_eq!(polyline.polyline, "encoded_string_here");
397 assert_eq!(polyline.parent_type.as_deref(), Some("route"));
398 assert_eq!(polyline.parent_id, Some(123));
399 }
400
401 #[test]
402 fn test_list_routes_params() {
403 let params = ListRoutesParams {
404 name: Some("test".to_string()),
405 visibility: Some(Visibility::Public),
406 min_distance: Some(5000.0),
407 ..Default::default()
408 };
409
410 let json = serde_json::to_value(¶ms).unwrap();
411 assert!(json.get("name").is_some());
412 assert!(json.get("visibility").is_some());
413 assert!(json.get("min_distance").is_some());
414 }
415
416 #[test]
417 fn test_route_wrapper_deserialization() {
418 let json = r#"{
419 "route": {
420 "id": 456,
421 "name": "Wrapped Route",
422 "distance": 15000.0
423 }
424 }"#;
425
426 #[derive(Deserialize)]
427 struct RouteWrapper {
428 route: Route,
429 }
430
431 let wrapper: RouteWrapper = serde_json::from_str(json).unwrap();
432 assert_eq!(wrapper.route.id, 456);
433 assert_eq!(wrapper.route.name.as_deref(), Some("Wrapped Route"));
434 assert_eq!(wrapper.route.distance, Some(15000.0));
435 }
436
437 #[test]
438 fn test_track_point_deserialization() {
439 let json = r#"{
440 "x": -122.4194,
441 "y": 37.7749,
442 "d": 1234.5,
443 "e": 100.0,
444 "S": 2,
445 "R": 3
446 }"#;
447
448 let track_point: TrackPoint = serde_json::from_str(json).unwrap();
449 assert_eq!(track_point.x, Some(-122.4194));
450 assert_eq!(track_point.y, Some(37.7749));
451 assert_eq!(track_point.d, Some(1234.5));
452 assert_eq!(track_point.e, Some(100.0));
453 assert_eq!(track_point.surface, Some(2));
454 assert_eq!(track_point.highway, Some(3));
455 }
456
457 #[test]
458 fn test_course_point_deserialization() {
459 let json = r#"{
460 "x": -122.5,
461 "y": 37.8,
462 "d": 5000.0,
463 "n": "Water Stop",
464 "t": "water"
465 }"#;
466
467 let course_point: CoursePoint = serde_json::from_str(json).unwrap();
468 assert_eq!(course_point.x, Some(-122.5));
469 assert_eq!(course_point.y, Some(37.8));
470 assert_eq!(course_point.d, Some(5000.0));
471 assert_eq!(course_point.n.as_deref(), Some("Water Stop"));
472 assert_eq!(course_point.t.as_deref(), Some("water"));
473 }
474
475 #[test]
476 fn test_route_with_nested_structures() {
477 let json = r#"{
478 "id": 999,
479 "name": "Complex Route",
480 "track_points": [
481 {"x": -122.0, "y": 37.0, "d": 0.0},
482 {"x": -122.1, "y": 37.1, "d": 100.0}
483 ],
484 "course_points": [
485 {"id": 1, "n": "Start", "t": "generic"}
486 ]
487 }"#;
488
489 let route: Route = serde_json::from_str(json).unwrap();
490 assert_eq!(route.id, 999);
491 assert!(route.track_points.is_some());
492 assert_eq!(route.track_points.as_ref().unwrap().len(), 2);
493 assert!(route.course_points.is_some());
494 assert_eq!(route.course_points.as_ref().unwrap().len(), 1);
495 }
496
497 #[test]
498 fn test_photo_deserialization() {
499 let json = r#"{
500 "id": 111,
501 "url": "https://example.com/photo.jpg",
502 "thumbnail_url": "https://example.com/thumb.jpg",
503 "caption": "Great view"
504 }"#;
505
506 let photo: Photo = serde_json::from_str(json).unwrap();
507 assert_eq!(photo.id, 111);
508 assert_eq!(photo.url.as_deref(), Some("https://example.com/photo.jpg"));
509 assert_eq!(photo.caption.as_deref(), Some("Great view"));
510 }
511}