1use log::warn;
2use reqwest::header::HeaderMap;
3use reqwest::Client;
4use serde::{Deserialize, Serialize};
5use serde_json::{json, Value};
6use std::time::Duration;
7
8const CF_API_URL: &str = "https://api.cloudflare.com/client/v4/";
9
10async fn convert_string_to_error(s: &str) -> Box<dyn std::error::Error> {
11 Box::new(std::io::Error::new(std::io::ErrorKind::Other, s))
12}
13
14async fn check_success(resp_json: Value) -> Result<bool, Box<dyn std::error::Error>> {
15 match resp_json.get("success") {
16 Some(success) => match success.as_bool() {
17 Some(true) => Ok(true),
18 Some(false) => Ok(false),
19 None => Err(convert_string_to_error(
20 "The returned 'success' field is not a boolean value.",
21 )
22 .await),
23 },
24 None => Err(convert_string_to_error(
25 "The returned JSON does not contain the 'success' field.",
26 )
27 .await),
28 }
29}
30
31#[derive(Clone)]
32pub struct KvClient {
33 pub account_id: String,
34 pub api_key: String,
35 client: Client,
36 url: String,
37 header_map: HeaderMap,
38}
39
40#[derive(Clone, Debug)]
41pub struct Namespace {
42 pub id: String,
43 pub title: String,
44}
45
46impl KvClient {
47 pub fn new(account_id: &str, api_key: &str) -> Self {
48 let headers = HeaderMap::from_iter([
49 (
50 "Authorization".parse().unwrap(),
51 format!("Bearer {}", api_key).parse().unwrap(),
52 ),
53 (
54 "Content-Type".parse().unwrap(),
55 "application/json".parse().unwrap(),
56 ),
57 ]);
58
59 KvClient {
60 account_id: account_id.to_string(),
61 api_key: api_key.to_string(),
62 client: Client::builder()
63 .connect_timeout(Duration::from_secs(5))
64 .build()
65 .unwrap(),
66 url: format!(
67 "{}{}{}{}",
68 CF_API_URL, "accounts/", account_id, "/storage/kv/namespaces"
69 ),
70 header_map: headers,
71 }
72 }
73
74 pub async fn list_namespaces(&self) -> Result<Vec<Namespace>, Box<dyn std::error::Error>> {
75 let resp = self
76 .client
77 .get(self.url.clone())
78 .headers(self.header_map.clone())
79 .send()
80 .await?;
81
82 if resp.status().is_success() == false {
83 warn!("Cloudflare returned an ERROR httpcode.")
84 }
85
86 let resp_json = resp.json::<Value>().await?;
87
88 if check_success(resp_json.clone()).await? == false {
89 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
90 }
91
92 match resp_json.get("result") {
93 Some(result) => match result.as_array() {
94 Some(namespaces) => {
95 let mut namespace_list = Vec::new();
96 for namespace in namespaces {
97 let id = namespace["id"].as_str().unwrap().to_string();
98 let title = namespace["title"].as_str().unwrap().to_string();
99 namespace_list.push(Namespace { id, title });
100 }
101 Ok(namespace_list)
102 }
103 None => Err(convert_string_to_error(
104 "The 'results' field cannot be converted to an array.",
105 )
106 .await),
107 },
108 None => Err(convert_string_to_error(
109 "The returned JSON does not contain the 'result' field.",
110 )
111 .await),
112 }
113 }
114
115 pub async fn create_namespace(
116 &self,
117 title: &str,
118 ) -> Result<Namespace, Box<dyn std::error::Error>> {
119 let payload = json!({
120 "title": title
121 });
122 let resp = self
123 .client
124 .post(self.url.clone())
125 .headers(self.header_map.clone())
126 .json(&payload)
127 .send()
128 .await?;
129
130 if resp.status().is_success() == false {
131 warn!("Cloudflare returned an ERROR httpcode.")
132 }
133
134 let resp_json = resp.json::<Value>().await?;
135
136 if check_success(resp_json.clone()).await? == false {
137 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
138 }
139
140 match resp_json.get("result") {
141 Some(result) => {
142 let id = match result.get("id") {
143 Some(id) => match id.as_str() {
144 Some(id) => id,
145 None => {
146 return Err(convert_string_to_error(
147 "The 'id' field cannot be converted to a string.",
148 )
149 .await)
150 }
151 },
152 None => {
153 return Err(convert_string_to_error(
154 "The 'id' field cannot be found in the 'result' field.",
155 )
156 .await)
157 }
158 };
159
160 let title = match result.get("title") {
161 Some(title) => match title.as_str() {
162 Some(title) => title,
163 None => {
164 return Err(convert_string_to_error(
165 "The 'title' field cannot be converted to a string.",
166 )
167 .await)
168 }
169 },
170 None => {
171 return Err(convert_string_to_error(
172 "The 'title' field cannot be found in the'result' field.",
173 )
174 .await)
175 }
176 };
177
178 Ok(Namespace {
179 id: id.to_string(),
180 title: title.to_string(),
181 })
182 }
183 None => Err(convert_string_to_error(
184 "The returned JSON does not contain the 'result' field.",
185 )
186 .await),
187 }
188 }
189}
190
191#[derive(Clone, Debug)]
192pub struct KvNamespaceClient {
193 pub account_id: String,
194 pub api_key: String,
195 pub namespace_id: String,
196 client: Client,
197 url: String,
198 header_map: HeaderMap,
199}
200
201impl KvNamespaceClient {
202 pub fn new(account_id: &str, api_key: &str, namespace_id: &str) -> Self {
203 let headers = HeaderMap::from_iter([
204 (
205 "Authorization".parse().unwrap(),
206 format!("Bearer {}", api_key).parse().unwrap(),
207 ),
208 (
209 "Content-Type".parse().unwrap(),
210 "application/json".parse().unwrap(),
211 ),
212 ]);
213
214 KvNamespaceClient {
215 account_id: account_id.to_string(),
216 api_key: api_key.to_string(),
217 namespace_id: namespace_id.to_string(),
218 client: Client::builder()
219 .connect_timeout(Duration::from_secs(5))
220 .build()
221 .unwrap(),
222 url: format!(
223 "{}{}{}{}{}",
224 CF_API_URL, "accounts/", account_id, "/storage/kv/namespaces/", namespace_id
225 ),
226 header_map: headers,
227 }
228 }
229
230 pub fn from_kvclient(kvclient: &KvClient, namespace_id: &str) -> Self {
231 KvNamespaceClient {
232 account_id: kvclient.account_id.clone(),
233 api_key: kvclient.api_key.clone(),
234 namespace_id: namespace_id.to_string(),
235 client: kvclient.client.clone(),
236 url: format!("{}/{}", kvclient.url.clone(), namespace_id),
237 header_map: kvclient.header_map.clone(),
238 }
239 }
240
241 pub async fn delete_namespace(&self) -> Result<(), Box<dyn std::error::Error>> {
242 let resp = self
243 .client
244 .delete(self.url.clone())
245 .headers(self.header_map.clone())
246 .send()
247 .await?;
248
249 if resp.status().is_success() == false {
250 warn!("Cloudflare returned an ERROR httpcode.")
251 }
252
253 let resp_json = resp.json::<Value>().await?;
254
255 if check_success(resp_json.clone()).await? == false {
256 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
257 }
258 Ok(())
259 }
260
261 pub async fn rename_namespace(
262 &self,
263 new_title: &str,
264 ) -> Result<(), Box<dyn std::error::Error>> {
265 let payload = json!({
266 "title": new_title
267 });
268
269 let resp = self
270 .client
271 .put(self.url.clone())
272 .headers(self.header_map.clone())
273 .json(&payload)
274 .send()
275 .await?;
276
277 if resp.status().is_success() == false {
278 warn!("Cloudflare returned an ERROR httpcode.")
279 }
280
281 let resp_json = resp.json::<Value>().await?;
282
283 if check_success(resp_json.clone()).await? == false {
284 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
285 }
286
287 Ok(())
288 }
289 pub async fn write(&self, payload: KvRequest) -> Result<(), Box<dyn std::error::Error>> {
290 let url = format!("{}/bulk", self.url);
291
292 let payload_vec = vec![payload];
293
294 let resp = self
295 .client
296 .put(url)
297 .headers(self.header_map.clone())
298 .json(&payload_vec)
299 .send()
300 .await?;
301
302 if resp.status().is_success() == false {
303 warn!("Cloudflare returned an ERROR httpcode.")
304 }
305
306 let resp_json = resp.json::<Value>().await?;
307 if check_success(resp_json.clone()).await? == false {
308 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
309 }
310
311 Ok(())
312 }
313
314 pub async fn write_multiple(
315 &self,
316 payload: Vec<KvRequest>,
317 ) -> Result<(), Box<dyn std::error::Error>> {
318 let url = format!("{}/bulk", self.url);
319 let resp = self
320 .client
321 .put(url)
322 .headers(self.header_map.clone())
323 .json(&payload)
324 .send()
325 .await?;
326
327 if resp.status().is_success() == false {
328 warn!("Cloudflare returned an ERROR httpcode.")
329 }
330
331 let resp_json = resp.json::<Value>().await?;
332
333 if check_success(resp_json.clone()).await? == false {
334 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
335 }
336
337 Ok(())
338 }
339
340 pub async fn delete(&self, key: &str) -> Result<(), Box<dyn std::error::Error>> {
341 let url = format!("{}/bulk/delete", self.url);
342 let payload = json!([key]);
343
344 let resp = self
345 .client
346 .post(url)
347 .headers(self.header_map.clone())
348 .json(&payload)
349 .send()
350 .await?;
351
352 if resp.status().is_success() == false {
353 warn!("Cloudflare returned an ERROR httpcode.")
354 }
355
356 let resp_json = resp.json::<Value>().await?;
357
358 if check_success(resp_json.clone()).await? == false {
359 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
360 }
361
362 Ok(())
363 }
364
365 pub async fn delete_multiple(&self, keys: Vec<&str>) -> Result<(), Box<dyn std::error::Error>> {
366 let url = format!("{}/bulk/delete", self.url);
367 let payload = json!(keys);
368
369 let resp = self
370 .client
371 .post(url)
372 .headers(self.header_map.clone())
373 .json(&payload)
374 .send()
375 .await?;
376
377 if resp.status().is_success() == false {
378 warn!("Cloudflare returned an ERROR httpcode.")
379 }
380
381 let resp_json = resp.json::<Value>().await?;
382
383 if check_success(resp_json.clone()).await? == false {
384 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
385 }
386
387 Ok(())
388 }
389
390 pub async fn list_all_keys(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
391 let url = format!("{}/keys", self.url);
392 let mut keys = Vec::new();
393 let mut cursor = "".to_string();
394 loop {
395 let url = format!("{}?cursor={}", url, cursor);
396 let resp = self
397 .client
398 .get(url.clone())
399 .headers(self.header_map.clone())
400 .send()
401 .await?;
402 if resp.status().is_success() == false {
403 warn!("Cloudflare returned an ERROR httpcode.")
404 }
405 let resp_json = resp.json::<Value>().await?;
406
407 if check_success(resp_json.clone()).await? == false {
408 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
409 }
410
411 let results = match resp_json.get("result") {
412 Some(result) => match result.as_array() {
413 Some(result) => result,
414 None => {
415 return Err(convert_string_to_error("No result found in response.").await);
416 }
417 },
418 None => {
419 return Err(convert_string_to_error("No result found in response.").await);
420 }
421 };
422
423 for result in results {
424 match result.get("name") {
425 Some(name) => {
426 let name = match name.as_str() {
427 Some(name) => name,
428 None => {
429 return Err(
430 convert_string_to_error("No name found in response.").await
431 );
432 }
433 };
434 keys.push(name.to_string());
435 }
436 None => {
437 return Err(convert_string_to_error("No name found in response.").await);
438 }
439 }
440 }
441
442 let (cursor_tmp, _cursor_count) = match resp_json.get("result_info") {
443 Some(result_info) => {
444 let cursor_tmp = match result_info.get("cursor") {
445 Some(cursor) => match cursor.as_str() {
446 Some(cursor) => cursor.to_string(),
447 None => {
448 return Err(convert_string_to_error(
449 "No cursor found in response.",
450 )
451 .await);
452 }
453 },
454 None => {
455 return Err(
456 convert_string_to_error("No cursor found in response.").await
457 );
458 }
459 };
460 let cursor_count = match result_info.get("count") {
461 Some(count) => match count.as_u64() {
462 Some(count) => count,
463 None => {
464 return Err(
465 convert_string_to_error("No count found in response.").await
466 );
467 }
468 },
469 None => {
470 return Err(
471 convert_string_to_error("No count found in response.").await
472 );
473 }
474 };
475 (cursor_tmp, cursor_count)
476 }
477 None => {
478 return Err(convert_string_to_error("No result_info found in response.").await);
479 }
480 };
481
482
483 if cursor_tmp.is_empty() {
484 break;
485 } else {
486 cursor = cursor_tmp;
487 continue;
488 }
489 }
490 Ok(keys)
491 }
492
493 pub async fn read_metadata(&self, key: &str) -> Result<Value, Box<dyn std::error::Error>> {
494 let url = format!("{}/metadata/{}", self.url, key);
495
496 let resp = self
497 .client
498 .get(url)
499 .headers(self.header_map.clone())
500 .send()
501 .await?;
502
503 if resp.status().is_success() == false {
504 warn!("Cloudflare returned an ERROR httpcode.")
505 }
506
507 let resp_json = resp.json::<Value>().await?;
508
509 if check_success(resp_json.clone()).await? == false {
510 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
511 }
512
513 match resp_json.get("result") {
514 Some(result) => Ok(result.clone()),
515 None => {
516 Err(convert_string_to_error("No result found in response.").await)
517 }
518 }
519 }
520
521 pub async fn get(&self, key: &str) -> Result<String, Box<dyn std::error::Error>> {
522 let url = format!("{}/values/{}", self.url, key);
523
524 let resp = self
525 .client
526 .get(url)
527 .headers(self.header_map.clone())
528 .send()
529 .await?;
530
531 if resp.status().is_success() == false {
532 warn!("Cloudflare returned an ERROR httpcode.")
533 }
534
535 if resp.status().as_u16() == 404 {
536 let resp_json = resp.json::<Value>().await?;
537 log::error!("Key: {} Not Found", key);
538 return Err(convert_string_to_error(resp_json.to_string().as_str()).await);
539 }
540
541 let resp_value = resp.text().await?;
542
543 Ok(resp_value)
544 }
545}
546
547#[derive(Serialize, Deserialize, Debug)]
548pub struct KvRequest {
549 key: String,
550 value: String,
551 base64: bool,
552 expiration: Option<u64>,
553 expiration_ttl: Option<u64>,
554 metadata: Option<Value>,
555}
556
557impl KvRequest {
558 pub fn new(key: &str, value: &str) -> Self {
559 KvRequest {
560 key: key.to_string(),
561 value: value.to_string(),
562 base64: false,
563 expiration: None,
564 expiration_ttl: None,
565 metadata: None,
566 }
567 }
568
569 pub fn enable_base64(&self) -> Self {
570 KvRequest {
571 base64: true,
572 key: self.key.clone(),
573 value: self.value.clone(),
574 expiration: self.expiration,
575 expiration_ttl: self.expiration_ttl,
576 metadata: self.metadata.clone(),
577 }
578 }
579
580 pub fn ttl_sec(&self, ttl_sec: u64) -> Self {
581 KvRequest {
582 base64: self.base64,
583 key: self.key.clone(),
584 value: self.value.clone(),
585 expiration: self.expiration,
586 expiration_ttl: Some(ttl_sec),
587 metadata: self.metadata.clone(),
588 }
589 }
590
591 pub fn ttl_timestemp(&self, ttl_timestemp: u64) -> Self {
592 KvRequest {
593 base64: self.base64,
594 key: self.key.clone(),
595 value: self.value.clone(),
596 expiration: Some(ttl_timestemp),
597 expiration_ttl: self.expiration_ttl,
598 metadata: self.metadata.clone(),
599 }
600 }
601
602 pub fn metadata(&self, metadata: Value) -> Self {
603 KvRequest {
604 base64: self.base64,
605 key: self.key.clone(),
606 value: self.value.clone(),
607 expiration: self.expiration,
608 expiration_ttl: self.expiration_ttl,
609 metadata: Some(metadata),
610 }
611 }
612}