Skip to main content

rbx_ds_cloud/api/
datastore_api.rs

1//! All the functions interacting with the Roblox Datastore Open Cloud API
2use 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
35/// List all Datastores in the specified "universe" or game
36pub 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
59/// Returns a list of entry keys within a data store.
60pub 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
92/// Returns the value associated with an entry.
93pub 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
118/// Returns the versions and metadata of an Entry of a datastore
119pub 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
155/// Returns the metadata of a specific version of an entry.
156pub 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
182/// Increments the value for an entry by a given amount, or create a new entry with that amount.
183pub 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
220/// Marks the entry as deleted by creating a tombstone version. Entries are deleted permanently after 30 days.
221pub 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
247/// Sets the value, metadata and user IDs associated with an entry.
248pub 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    // Convert String Data to Json String
268    // let json_data = json!(&set_struct.data).to_string();
269    // println!("{:#?}", json_data.to_string());
270
271    // Convert data to Base64 encoded MD5 Checksum
272    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}