strava_wrapper/
query.rs

1use async_trait::async_trait;
2use reqwest::{Client, StatusCode, Url};
3use serde::de::DeserializeOwned;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::error::Error;
7use std::fmt::Debug;
8pub const API_URL: &str = "https://www.strava.com/api/v3";
9
10pub async fn get<T>(path: &str, token: &str) -> Result<T, ErrorWrapper>
11where
12    T: DeserializeOwned + Debug,
13{
14    let client = Client::new();
15
16    let response = client
17        .get(path)
18        .header("Authorization", format!("Bearer {}", token))
19        .send()
20        .await
21        .map_err(|err| {
22            panic!("Request failed to send: {}", err);
23        })?;
24
25    handle_response::<T>(response).await
26}
27
28pub async fn post<T, B>(path: &str, token: &str, body: B) -> Result<T, ErrorWrapper>
29where
30    T: DeserializeOwned + Debug,
31    B: Serialize + Debug,
32{
33    let client = Client::new();
34
35    let response = client
36        .post(format!("{}/{}", API_URL, path))
37        .header("Authorization", format!("Bearer {}", token))
38        .json(&body) // Serialize the body as JSON
39        .send()
40        .await
41        .map_err(|err| {
42            panic!("Request failed to send: {}", err);
43        })?;
44
45    handle_response::<T>(response).await
46}
47
48async fn handle_response<T>(response: reqwest::Response) -> Result<T, ErrorWrapper>
49where
50    T: DeserializeOwned + Debug,
51{
52    if response.status().is_success() {
53        let raw_body = response.text().await.map_err(|err| {
54            panic!("Failed to read response text: {}", err);
55        })?;
56
57        let content = serde_json::from_str::<T>(&raw_body).map_err(|err| {
58            eprintln!("Failed to parse JSON: {}, Raw response: {}", err, raw_body);
59            panic!("Failed to parse JSON: {}", err);
60        })?;
61
62        Ok(content)
63    } else {
64        let status = response.status();
65        let error_content = response
66            .json::<ErrorResponse>()
67            .await
68            .unwrap_or_else(|_| panic!("Failed to parse error response from server"));
69        Err(ErrorWrapper {
70            status,
71            error: error_content,
72        })
73    }
74}
75
76pub struct Filter {
77    pub query: Vec<(String, String)>,
78    pub path_params: Vec<(String, String)>,
79}
80
81pub trait EndPoint {
82    fn path(&self) -> String;
83}
84
85#[async_trait]
86pub trait Sendable<U> {
87    async fn send(self) -> Result<U, ErrorWrapper>;
88}
89
90pub trait Query: Sized + Clone {
91    fn format_to_query_params(
92        url: &str,
93        params: Vec<(String, String)>,
94    ) -> Result<String, Box<dyn Error>> {
95        Ok(Url::parse_with_params(url, params.iter())?.to_string())
96    }
97
98    fn get_query_params(self) -> Vec<(String, String)>;
99}
100
101pub trait Endpoint: Sized + Clone {
102    fn new(url: impl Into<String>, token: impl Into<String>, path: impl Into<String>) -> Self
103    where
104        Self: Sized;
105
106    fn endpoint(&self) -> String;
107}
108
109pub trait PathQuery: Endpoint {
110    fn get_path_params(&self) -> HashMap<String, String>;
111}
112
113pub trait Page {
114    fn page(self, number: u32) -> Self;
115}
116pub trait PerPage {
117    fn per_page(self, number: u32) -> Self;
118}
119
120pub trait PageSize {
121    fn page_size(self, number: u32) -> Self;
122}
123
124pub trait Before {
125    fn before(self, before: u64) -> Self;
126}
127
128pub trait After {
129    fn after(self, after: u64) -> Self;
130}
131
132pub trait ID {
133    fn id(self, id: u64) -> Self;
134}
135
136pub trait AfterCursor {
137    fn after_cursor(self, cursor: String) -> Self;
138}
139
140pub trait IncludeAllEfforts {
141    fn include_all_efforts(self, should_include: bool) -> Self;
142}
143
144pub trait TimeFilter {
145    fn before(self, timestamp: i64) -> Self;
146
147    fn after(self, timestamp: i64) -> Self;
148}
149
150pub async fn get_with_query<T, U>(inst: T, token: &str) -> Result<U, ErrorWrapper>
151where
152    T: Endpoint + Query + PathQuery + Sendable<U>,
153    U: DeserializeOwned + Debug,
154{
155    let url = T::format_to_query_params(&inst.endpoint(), inst.get_query_params())
156        .expect("Failed to format query params");
157    get(&url, token).await
158}
159
160fn format_path(template: &str, params: &HashMap<String, String>) -> String {
161    let mut path = template.to_string();
162    for (key, value) in params {
163        let placeholder = format!("{{{}}}", key);
164        path = path.replace(&placeholder, value);
165    }
166    path
167}
168
169pub async fn get_with_query_and_path<T, U>(inst: T, token: &str) -> Result<U, ErrorWrapper>
170where
171    T: Query + PathQuery + Endpoint,
172    U: DeserializeOwned + Debug,
173{
174    let url_with_path_params = &format_path(&inst.endpoint(), &inst.get_path_params());
175    let url = T::format_to_query_params(&url_with_path_params, inst.get_query_params())
176        .expect("Failed to format query params");
177    get(&url, token).await
178}
179
180#[derive(Default, Debug, Clone, PartialEq)]
181pub struct ErrorWrapper {
182    status: StatusCode,
183    error: ErrorResponse,
184}
185#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct ErrorResponse {
187    pub errors: Vec<ErrorDetails>,
188    pub message: String,
189}
190
191#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
192pub struct ErrorDetails {
193    pub resource: String,
194    pub field: String,
195    pub code: String,
196}