1pub mod models;
20
21use paste::paste;
22use reqwest::blocking::Client;
23use serde::{Deserialize, Serialize};
24use typed_builder::TypedBuilder;
25
26use crate::models::*;
27
28const API_BASE_URL: &str = "https://api.ouraring.com/v2/usercollection";
29
30#[derive(Serialize, TypedBuilder)]
32pub struct DateQuery<'a> {
33 #[builder(default = None, setter(strip_option))]
34 start_date: Option<&'a str>,
35 #[builder(default = None, setter(strip_option))]
36 end_date: Option<&'a str>,
37 #[builder(default = None, setter(strip_option))]
38 next_token: Option<&'a str>,
39}
40
41#[derive(Serialize, TypedBuilder)]
43pub struct DatetimeQuery<'a> {
44 #[builder(default = None, setter(strip_option))]
45 start_datetime: Option<&'a str>,
46 #[builder(default = None, setter(strip_option))]
47 end_datetime: Option<&'a str>,
48 #[builder(default = None, setter(strip_option))]
49 next_token: Option<&'a str>,
50}
51
52#[derive(Deserialize, Debug, PartialEq)]
54pub struct ListResponse<T> {
55 pub data: Vec<T>,
57 pub next_token: Option<String>,
59}
60
61macro_rules! generic_endpoint {
62 ($(#[$m:meta])*, $name: ident, $type: ty, $path: literal) => {
63 $(#[$m])*
64 pub fn $name(&self) -> Result<$type, reqwest::Error> {
65 let url = format!("{}/{}", &self.base_url, $path);
66 let response = self
67 .client
68 .get(&url)
69 .bearer_auth(&self.token)
70 .send()?
71 .error_for_status()?
72 .json::<$type>()?;
73 Ok(response)
74 }
75 };
76}
77
78macro_rules! list_endpoint {
79 ($(#[$m:meta])*, $name: ident, $type: ty, $path: literal, $query: ty) => {
80 $(#[$m])*
81 pub fn $name(&self, query: $query) -> Result<ListResponse<$type>, reqwest::Error> {
82 let url = format!("{}/{}", &self.base_url, $path);
83 let response = self
84 .client
85 .get(&url)
86 .bearer_auth(&self.token)
87 .query(&query)
88 .send()?
89 .error_for_status()?
90 .json::<ListResponse<$type>>()?;
91 Ok(response)
92 }
93 };
94}
95
96macro_rules! get_endpoint {
97 ($(#[$m:meta])*, $name: ident, $type: ty, $path: literal) => {
98 $(#[$m])*
99 pub fn $name(&self, id: &str) -> Result<$type, reqwest::Error> {
100 let url = format!("{}/{}/{}", &self.base_url, $path, id);
101 let response = self
102 .client
103 .get(&url)
104 .bearer_auth(&self.token)
105 .send()?
106 .error_for_status()?
107 .json::<$type>()?;
108 Ok(response)
109 }
110 };
111}
112
113macro_rules! endpoint_set {
114 ($name: ident, $type: ty, $path: literal, $params: ty) => {
115 paste! {
116 get_endpoint!(#[doc = "Gets a single [" $type "] item by id."], [<get_ $name>], $type, $path);
117 list_endpoint!(#[doc = "Returns a [ListResponse] of [" $type "] items based on the supplied query."], [<list_ $name>], $type, $path, $params);
118 }
119 };
120}
121
122pub struct OuraClient<'a> {
126 token: &'a str,
127 base_url: &'a str,
128 client: Client,
129}
130
131impl<'a> OuraClient<'a> {
132 pub fn new(token: &'a str) -> Self {
134 let client = Client::new();
135 Self {
136 token,
137 client,
138 base_url: API_BASE_URL,
139 }
140 }
141
142 pub fn build_with_base_url(token: &'a str, base_url: &'a str) -> Self {
146 let client = Client::new();
147 Self {
148 token,
149 client,
150 base_url,
151 }
152 }
153
154 endpoint_set!(daily_activity, DailyActivity, "daily_activity", DateQuery);
155
156 endpoint_set!(
157 daily_readiness,
158 DailyReadiness,
159 "daily_readiness",
160 DateQuery
161 );
162
163 endpoint_set!(daily_sleep, DailySleep, "daily_sleep", DateQuery);
164
165 endpoint_set!(daily_spo2, DailySpO2, "daily_spo2", DateQuery);
166
167 list_endpoint!(#[doc = "Returns a [ListResponse] of [HeartRate] items based on the supplied query."], list_heart_rate, HeartRate, "heartrate", DatetimeQuery);
168
169 generic_endpoint!(#[doc = "Returns a [PersonalInfo] based on the caller."], get_personal_info, PersonalInfo, "personal_info");
170
171 endpoint_set!(
172 rest_mode_period,
173 RestModePeriod,
174 "rest_mode_period",
175 DateQuery
176 );
177
178 endpoint_set!(
179 ring_configuration,
180 RingConfiguration,
181 "ring_configuration",
182 DateQuery
183 );
184
185 endpoint_set!(session, Session, "session", DateQuery);
186
187 endpoint_set!(sleep, Sleep, "sleep", DateQuery);
188
189 endpoint_set!(sleep_time, SleepTime, "sleep_time", DateQuery);
190
191 endpoint_set!(tag, Tag, "tag", DateQuery);
192
193 endpoint_set!(workout, Workout, "workout", DateQuery);
194
195 endpoint_set!(tag_v2, TagV2, "tag/v2", DateQuery);
196}