Use proper query parameters

This commit is contained in:
2024-12-15 12:34:50 -05:00
parent 24c2e86396
commit 58155ff838
2 changed files with 71 additions and 40 deletions

View File

@@ -29,7 +29,7 @@ async fn main() {
} }
let d = CreateDatapoint::new(1.0) let d = CreateDatapoint::new(1.0)
.with_comment("Test datapoint") .with_comment("Test #hashtag datapoint")
.with_requestid("unique-id-42"); .with_requestid("unique-id-42");
match client.create_datapoint("me", "meditation", &d).await { match client.create_datapoint("me", "meditation", &d).await {
Ok(datapoint) => println!("Added: {datapoint:#?}"), Ok(datapoint) => println!("Added: {datapoint:#?}"),

View File

@@ -1,5 +1,5 @@
pub mod types; pub mod types;
use crate::types::{CreateDatapoint, Datapoint, UserInfo, UserInfoDiff}; use crate::types::{CreateDatapoint, Datapoint, Goal, UserInfo, UserInfoDiff};
use reqwest::Client; use reqwest::Client;
use time::OffsetDateTime; use time::OffsetDateTime;
@@ -16,29 +16,42 @@ pub struct BeeminderClient {
} }
impl BeeminderClient { impl BeeminderClient {
async fn request<T>(&self, endpoint: &str) -> Result<T, Error> async fn request<T>(
&self,
endpoint: &str,
params: Option<Vec<(&str, &str)>>,
) -> Result<T, Error>
where where
T: serde::de::DeserializeOwned, T: serde::de::DeserializeOwned,
{ {
let mut query = vec![("auth_token", self.api_key.as_str())];
if let Some(additional_params) = params {
query.extend(additional_params);
}
let response = self let response = self
.client .client
.get(format!("{}{}", self.base_url, endpoint)) .get(format!("{}{}", self.base_url, endpoint))
.query(&[("auth_token", &self.api_key)]) .query(&query)
.send() .send()
.await? .await?
.error_for_status()?; .error_for_status()?;
response.json().await.map_err(Error::from) response.json().await.map_err(Error::from)
} }
async fn post<T>(&self, endpoint: &str) -> Result<T, Error> async fn post<T>(&self, endpoint: &str, params: Option<Vec<(&str, &str)>>) -> Result<T, Error>
where where
T: serde::de::DeserializeOwned, T: serde::de::DeserializeOwned,
{ {
let mut query = vec![("auth_token", self.api_key.as_str())];
if let Some(additional_params) = params {
query.extend(additional_params);
}
let response = self let response = self
.client .client
.post(format!("{}{}", self.base_url, endpoint)) .post(format!("{}{}", self.base_url, endpoint))
.query(&[("auth_token", &self.api_key)]) .query(&query)
.send() .send()
.await? .await?
.error_for_status()?; .error_for_status()?;
@@ -61,7 +74,7 @@ impl BeeminderClient {
/// # Errors /// # Errors
/// Returns an error if the HTTP request fails or response cannot be parsed. /// Returns an error if the HTTP request fails or response cannot be parsed.
pub async fn get_user(&self, username: &str) -> Result<UserInfo, Error> { pub async fn get_user(&self, username: &str) -> Result<UserInfo, Error> {
self.request(&format!("users/{username}.json")).await self.request(&format!("users/{username}.json"), None).await
} }
/// Retrieves detailed user information with changes since the specified timestamp. /// Retrieves detailed user information with changes since the specified timestamp.
@@ -73,8 +86,9 @@ impl BeeminderClient {
username: &str, username: &str,
diff_since: OffsetDateTime, diff_since: OffsetDateTime,
) -> Result<UserInfoDiff, Error> { ) -> Result<UserInfoDiff, Error> {
let timestamp = diff_since.unix_timestamp(); let diff_since = diff_since.unix_timestamp().to_string();
self.request(&format!("users/{username}.json?diff_since={timestamp}")) let params = vec![("diff_since", diff_since.as_str())];
self.request(&format!("users/{username}.json"), Some(params))
.await .await
} }
@@ -89,25 +103,17 @@ impl BeeminderClient {
sort: Option<&str>, sort: Option<&str>,
count: Option<u64>, count: Option<u64>,
) -> Result<Vec<Datapoint>, Error> { ) -> Result<Vec<Datapoint>, Error> {
let mut endpoint = format!("users/{username}/goals/{goal}/datapoints.json"); let mut params = Vec::new();
params.push(("sort", sort.unwrap_or("timestamp")));
let mut query = Vec::new();
if let Some(sort) = sort {
query.push(format!("sort={sort}"));
} else {
query.push("sort=timestamp".to_string());
}
let count_str;
if let Some(count) = count { if let Some(count) = count {
query.push(format!("count={count}")); count_str = count.to_string();
params.push(("count", &count_str));
} }
if !query.is_empty() { let endpoint = format!("users/{username}/goals/{goal}/datapoints.json");
endpoint = format!("{}?{}", endpoint, query.join("&")); self.request(&endpoint, Some(params)).await
}
self.request(&endpoint).await
} }
/// Creates a new datapoint for a goal. /// Creates a new datapoint for a goal.
@@ -120,23 +126,48 @@ impl BeeminderClient {
goal: &str, goal: &str,
datapoint: &CreateDatapoint, datapoint: &CreateDatapoint,
) -> Result<Datapoint, Error> { ) -> Result<Datapoint, Error> {
let mut query = Vec::new(); let mut params = Vec::new();
query.push(format!("value={}", datapoint.value));
let value_str = datapoint.value.to_string();
params.push(("value", value_str.as_str()));
let timestamp_str;
if let Some(ts) = datapoint.timestamp { if let Some(ts) = datapoint.timestamp {
query.push(format!("timestamp={}", ts.unix_timestamp())); timestamp_str = ts.unix_timestamp().to_string();
} params.push(("timestamp", timestamp_str.as_str()));
if let Some(ds) = &datapoint.daystamp {
query.push(format!("daystamp={ds}"));
}
if let Some(c) = &datapoint.comment {
query.push(format!("comment={c}"));
}
if let Some(rid) = &datapoint.requestid {
query.push(format!("requestid={rid}"));
} }
let mut endpoint = format!("users/{username}/goals/{goal}/datapoints.json"); if let Some(ds) = &datapoint.daystamp {
endpoint = format!("{}?{}", endpoint, query.join("&")); params.push(("daystamp", ds.as_str()));
self.post(&endpoint).await }
if let Some(c) = &datapoint.comment {
params.push(("comment", c.as_str()));
}
if let Some(rid) = &datapoint.requestid {
params.push(("requestid", rid.as_str()));
}
let endpoint = format!("users/{username}/goals/{goal}/datapoints.json");
self.post(&endpoint, Some(params)).await
}
/// Retrieves all goals for a user.
///
/// # Errors
/// Returns an error if the HTTP request fails or response cannot be parsed.
pub async fn get_goals(&self, username: &str) -> Result<Vec<Goal>, Error> {
self.request(&format!("users/{username}/goals.json"), None)
.await
}
/// Retrieves archived goals for a user.
///
/// # Errors
/// Returns an error if the HTTP request fails or response cannot be parsed.
pub async fn get_archived_goals(&self, username: &str) -> Result<Vec<Goal>, Error> {
self.request(&format!("users/{username}/goals/archived.json"), None)
.await
} }
} }