1use reqwest::{Client, Response};
3
4use crate::api::endpoint_structs::*;
5use crate::api::error_types::*;
6
7use md5::{Digest, Md5};
8
9async fn build_endpoint_url(universe_id: &u64, endpoint: &str) -> String {
10 let mut ep_url = format!(
11 "https://apis.roblox.com/datastores/v1/universes/{}",
12 universe_id
13 );
14 ep_url.push_str(endpoint);
15 ep_url
16}
17
18async fn res_error_check(res: Response) -> Result<Response, Error> {
19 if res.status().is_success() {
20 Ok(res)
21 } else {
22 let rbx_error = res.json::<DataStoreErrorResponse>().await?;
23 Err(Error::DataStoreAPIError(rbx_error))
24 }
25}
26
27async fn vec_to_strlist(v: &Option<Vec<u64>>) -> String {
28 v.as_ref()
29 .iter()
30 .map(|i| format!("{:#?}", i))
31 .collect::<Vec<String>>()
32 .join(",")
33}
34
35pub async fn list_data_stores(lds_struct: &ListDataStores) -> Result<Response, Error> {
37 let mut target = build_endpoint_url(&lds_struct.universe_id, "/standard-datastores").await;
38 target.push_str(format!("?limit={}", &lds_struct.limit).as_str());
39
40 if let Some(v) = &lds_struct.prefix {
41 target.push_str(format!("&prefix={}", v).as_str());
42 }
43 if let Some(v) = &lds_struct.cursor {
44 target.push_str(format!("&cursor={}", v).as_str());
45 }
46
47 let res = res_error_check(
48 Client::new()
49 .get(target)
50 .header("x-api-key", &lds_struct.api_key)
51 .send()
52 .await?,
53 )
54 .await?;
55
56 Ok(res)
57}
58
59pub async fn list_entries(ls_struct: &ListEntries) -> Result<Response, Error> {
61 let mut target = build_endpoint_url(
62 &ls_struct.universe_id,
63 "/standard-datastores/datastore/entries",
64 )
65 .await;
66 target.push_str(format!("?limit={}", &ls_struct.limit).as_str());
67 target.push_str(format!("&datastoreName={}", &ls_struct.datastore_name).as_str());
68 target.push_str(format!("&AllScopes={}", &ls_struct.all_scopes.unwrap_or(true)).as_str());
69
70 if let Some(v) = &ls_struct.prefix {
71 target.push_str(format!("&prefix={}", v).as_str());
72 }
73 if let Some(v) = &ls_struct.cursor {
74 target.push_str(format!("&cursor={}", v).as_str());
75 }
76 if let Some(v) = &ls_struct.scope {
77 target.push_str(format!("&scope={}", v).as_str());
78 }
79
80 let res = res_error_check(
81 Client::new()
82 .get(target)
83 .header("x-api-key", &ls_struct.api_key)
84 .send()
85 .await?,
86 )
87 .await?;
88
89 Ok(res)
90}
91
92pub async fn get_entry(entry_struct: &GetEntry) -> Result<Response, Error> {
94 let mut target = build_endpoint_url(
95 &entry_struct.universe_id,
96 "/standard-datastores/datastore/entries/entry",
97 )
98 .await;
99 target.push_str(format!("?datastoreName={}", &entry_struct.datastore_name).as_str());
100 target.push_str(format!("&entryKey={}", &entry_struct.key).as_str());
101
102 if let Some(v) = &entry_struct.scope {
103 target.push_str(format!("&scope={}", v).as_str());
104 }
105
106 let res = res_error_check(
107 Client::new()
108 .get(target)
109 .header("x-api-key", &entry_struct.api_key)
110 .send()
111 .await?,
112 )
113 .await?;
114
115 Ok(res)
116}
117
118pub async fn list_entries_version(lev_struct: &ListEntryVersions) -> Result<Response, Error> {
120 let mut target = build_endpoint_url(
121 &lev_struct.universe_id,
122 "/standard-datastores/datastore/entries/entry/versions",
123 )
124 .await;
125 target.push_str(format!("?limit={}", &lev_struct.limit).as_str());
126 target.push_str(format!("&datastoreName={}", &lev_struct.datastore_name).as_str());
127 target.push_str(format!("&entryKey={}", &lev_struct.key).as_str());
128 target.push_str(format!("&sortOrder={:?}", &lev_struct.sort_order).as_str());
129
130 if let Some(v) = &lev_struct.cursor {
131 target.push_str(format!("&cursor={}", v).as_str());
132 }
133 if let Some(v) = &lev_struct.start_time {
134 target.push_str(format!("&startTime={}", v).as_str());
135 }
136 if let Some(v) = &lev_struct.end_time {
137 target.push_str(format!("&endTime={}", v).as_str());
138 }
139 if let Some(v) = &lev_struct.scope {
140 target.push_str(format!("&scope={}", v).as_str());
141 }
142
143 let res = res_error_check(
144 Client::new()
145 .get(target)
146 .header("x-api-key", &lev_struct.api_key)
147 .send()
148 .await?,
149 )
150 .await?;
151
152 Ok(res)
153}
154
155pub async fn get_entry_version(gev_struct: &GetEntryVersion) -> Result<Response, Error> {
157 let mut target = build_endpoint_url(
158 &gev_struct.universe_id,
159 "/standard-datastores/datastore/entries/entry/versions/version",
160 )
161 .await;
162 target.push_str(format!("?datastoreName={}", &gev_struct.datastore_name).as_str());
163 target.push_str(format!("&entryKey={}", &gev_struct.key).as_str());
164 target.push_str(format!("&versionId={}", &gev_struct.version_id).as_str());
165
166 if let Some(v) = &gev_struct.scope {
167 target.push_str(format!("&scope={}", v).as_str());
168 }
169
170 let res = res_error_check(
171 Client::new()
172 .get(target)
173 .header("x-api-key", &gev_struct.api_key)
174 .send()
175 .await?,
176 )
177 .await?;
178
179 Ok(res)
180}
181
182pub async fn increment_entry(increment_struct: &IncrementEntry) -> Result<Response, Error> {
184 let mut target = build_endpoint_url(
185 &increment_struct.universe_id,
186 "/standard-datastores/datastore/entries/entry/increment",
187 )
188 .await;
189 target.push_str(format!("?datastoreName={}", &increment_struct.datastore_name).as_str());
190 target.push_str(format!("&entryKey={}", &increment_struct.key).as_str());
191 target.push_str(format!("&incrementBy={}", &increment_struct.increment_by).as_str());
192
193 if let Some(v) = &increment_struct.scope {
194 target.push_str(format!("&scope={}", v).as_str());
195 }
196
197 let res = res_error_check(
198 Client::new()
199 .post(target)
200 .header("x-api-key", &increment_struct.api_key)
201 .header(
202 "roblox-entry-userids",
203 vec_to_strlist(&increment_struct.user_ids).await,
204 )
205 .header(
206 "roblox-entry-attributes",
207 increment_struct
208 .attributes
209 .as_ref()
210 .unwrap_or(&String::from("{}")),
211 )
212 .send()
213 .await?,
214 )
215 .await?;
216
217 Ok(res)
218}
219
220pub async fn delete_entry(delete_struct: &DeleteEntry) -> Result<Response, Error> {
222 let mut target = build_endpoint_url(
223 &delete_struct.universe_id,
224 "/standard-datastores/datastore/entries/entry",
225 )
226 .await;
227
228 target.push_str(format!("?datastoreName={}", &delete_struct.datastore_name).as_str());
229 target.push_str(format!("&entryKey={}", &delete_struct.key).as_str());
230
231 if let Some(v) = &delete_struct.scope {
232 target.push_str(format!("&scope={}", v).as_str());
233 }
234
235 let res = res_error_check(
236 Client::new()
237 .delete(target)
238 .header("x-api-key", &delete_struct.api_key)
239 .send()
240 .await?,
241 )
242 .await?;
243
244 Ok(res)
245}
246
247pub async fn set_entry(set_struct: &SetEntry) -> Result<Response, Error> {
249 let mut target = build_endpoint_url(
250 &set_struct.universe_id,
251 "/standard-datastores/datastore/entries/entry",
252 )
253 .await;
254 target.push_str(format!("?datastoreName={}", &set_struct.datastore_name).as_str());
255 target.push_str(format!("&entryKey={}", &set_struct.key).as_str());
256
257 if let Some(v) = &set_struct.scope {
258 target.push_str(format!("&scope={}", v).as_str());
259 }
260 if let Some(v) = &set_struct.match_version {
261 target.push_str(format!("&matchVersion={}", v).as_str());
262 }
263 if let Some(v) = &set_struct.exclusive_create {
264 target.push_str(format!("&exclusiveCreate={}", v).as_str());
265 }
266
267 let mut hasher = Md5::new();
273 hasher.update(&set_struct.data.as_bytes());
274 let hasher = base64::encode(hasher.finalize());
275
276 let res = res_error_check(
277 Client::new()
278 .post(target)
279 .header("x-api-key", &set_struct.api_key)
280 .header(
281 "roblox-entry-userids",
282 vec_to_strlist(&set_struct.user_ids).await,
283 )
284 .header(
285 "roblox-entry-attributes",
286 set_struct
287 .attributes
288 .as_ref()
289 .unwrap_or(&String::from("{}")),
290 )
291 .header("content-md5", hasher)
292 .body(set_struct.data.clone())
293 .send()
294 .await?,
295 )
296 .await?;
297
298 Ok(res)
299}