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) .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<T, 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 endpoint(&self) -> String;
103}
104
105pub trait PathQuery: Endpoint {
106 fn get_path_params(&self) -> HashMap<String, String>;
107}
108
109pub trait Page {
110 fn page(self, number: u32) -> Self;
111}
112pub trait PerPage {
113 fn per_page(self, number: u32) -> Self;
114}
115
116pub trait PageSize {
117 fn page_size(self, number: u32) -> Self;
118}
119
120pub trait ID {
121 fn id(self, id: u64) -> Self;
122}
123
124pub trait AfterCursor {
125 fn after_cursor(self, cursor: String) -> Self;
126}
127
128pub trait IncludeAllEfforts {
129 fn include_all_efforts(self, should_include: bool) -> Self;
130}
131
132pub trait TimeFilter {
133 fn before(self, timestamp: i64) -> Self;
134
135 fn after(self, timestamp: i64) -> Self;
136}
137
138pub async fn get_with_query<T, U>(inst: T, token: &str) -> Result<U, ErrorWrapper>
139where
140 T: Endpoint + Query + PathQuery + Sendable<T, U>,
141 U: DeserializeOwned + Debug,
142{
143 let url = T::format_to_query_params(&inst.endpoint(), inst.get_query_params())
144 .expect("Failed to format query params");
145 get(&url, token).await
146}
147
148fn format_path(template: &str, params: &HashMap<String, String>) -> String {
149 let mut path = template.to_string();
150 for (key, value) in params {
151 let placeholder = format!("{{{}}}", key);
152 path = path.replace(&placeholder, value);
153 }
154 path
155}
156
157pub async fn get_with_query_and_path<T, U>(inst: T, token: &str) -> Result<U, ErrorWrapper>
158where
159 T: Query + PathQuery + Endpoint,
160 U: DeserializeOwned + Debug,
161{
162 let url_with_path_params = &format_path(&inst.endpoint(), &inst.get_path_params());
163 let url = T::format_to_query_params(&url_with_path_params, inst.get_query_params())
164 .expect("Failed to format query params");
165 get(&url, token).await
166}
167
168#[derive(Default, Debug, Clone, PartialEq)]
169pub struct ErrorWrapper {
170 status: StatusCode,
171 error: ErrorResponse,
172}
173#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct ErrorResponse {
175 pub errors: Vec<ErrorDetails>,
176 pub message: String,
177}
178
179#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
180pub struct ErrorDetails {
181 pub resource: String,
182 pub field: String,
183 pub code: String,
184}