use crate::activity_summary::ActivitySummaryResponse;
use crate::error::FitbitError;
use crate::sleep::SleepResponseV1_2;
use chrono::NaiveDate;
use std::sync::Arc;
use ureq::Agent;
const API_BASE_URL: &str = "https://api.fitbit.com";
const SLEEP_API_VERSION: &str = "1.2";
const ACTIVITY_API_VERSION: &str = "1";
#[cfg_attr(test, mockall::automock)]
pub trait FitbitClientTrait {
fn fetch_sleep_data(&self, date: NaiveDate) -> Result<SleepResponseV1_2, FitbitError>;
fn fetch_activity_summary(
&self,
date: NaiveDate,
) -> Result<ActivitySummaryResponse, FitbitError>;
}
#[derive(Clone)]
pub struct FitbitClient {
access_token: Arc<String>,
agent: ureq::Agent,
}
impl FitbitClient {
pub fn new(access_token: String) -> Self {
let agent: Agent = Agent::config_builder()
.timeout_global(Some(std::time::Duration::from_secs(30)))
.build()
.into();
Self {
access_token: Arc::new(access_token),
agent,
}
}
pub fn with_agent(access_token: String, agent: ureq::Agent) -> Self {
Self {
access_token: Arc::new(access_token),
agent,
}
}
fn make_api_request<T>(&self, url: &str) -> Result<T, FitbitError>
where
T: serde::de::DeserializeOwned,
{
self.agent
.get(url)
.header("Authorization", &format!("Bearer {}", self.access_token))
.call()
.map_err(FitbitError::RequestError)?
.body_mut()
.read_json()
.map_err(|e| FitbitError::JsonError(e.to_string()))
}
}
impl FitbitClientTrait for FitbitClient {
fn fetch_sleep_data(&self, date: NaiveDate) -> Result<SleepResponseV1_2, FitbitError> {
let url = format!(
"{}/{}/user/-/sleep/date/{}.json",
API_BASE_URL,
SLEEP_API_VERSION,
date.format("%Y-%m-%d")
);
self.make_api_request(&url)
}
fn fetch_activity_summary(
&self,
date: NaiveDate,
) -> Result<ActivitySummaryResponse, FitbitError> {
let url = format!(
"{}/{}/user/-/activities/date/{}.json",
API_BASE_URL,
ACTIVITY_API_VERSION,
date.format("%Y-%m-%d")
);
self.make_api_request(&url)
}
}