1use std::collections::HashMap;
2
3#[cfg(feature = "rust")]
4use bon::Builder;
5#[cfg(feature = "node")]
6use napi_derive::napi;
7#[cfg(feature = "python")]
8use pyo3::{pyclass, pymethods};
9#[cfg(feature = "python")]
10use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
11use serde::{Deserialize, Serialize};
12
13use crate::{config::KvStoreConfig, errors::SdkError, SdkConfig};
14
15const KV_STORE_BASE_URL: &str = "https://api.quicknode.com/kv/rest/v1/";
16
17pub(crate) struct ResolvedKvStoreConfig {
20 pub(crate) base_url: reqwest::Url,
21}
22
23impl ResolvedKvStoreConfig {
24 pub(crate) fn from_config(config: Option<&KvStoreConfig>) -> Result<Self, SdkError> {
25 let url_str = config
26 .and_then(|s| s.base_url.as_deref())
27 .unwrap_or(KV_STORE_BASE_URL);
28 let mut base_url =
29 reqwest::Url::parse(url_str).map_err(|e| SdkError::Config(e.to_string()))?;
30 if !base_url.path().ends_with('/') {
31 base_url.set_path(&format!("{}/", base_url.path()));
32 }
33 Ok(Self { base_url })
34 }
35}
36
37#[cfg_attr(feature = "rust", derive(Builder))]
41#[cfg_attr(feature = "node", napi(object))]
42#[cfg_attr(not(feature = "node"), derive(Clone))]
43#[derive(Debug, Serialize, Deserialize)]
44pub struct CreateSetParams {
45 pub key: String,
47 pub value: String,
49}
50
51#[cfg_attr(feature = "rust", derive(Builder))]
53#[cfg_attr(feature = "node", napi(object))]
54#[cfg_attr(not(feature = "node"), derive(Clone))]
55#[derive(Debug, Default, Serialize, Deserialize)]
56pub struct GetSetsParams {
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub limit: Option<i64>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub cursor: Option<String>,
63}
64
65#[cfg_attr(feature = "rust", derive(Builder))]
67#[cfg_attr(feature = "node", napi(object))]
68#[cfg_attr(not(feature = "node"), derive(Clone))]
69#[derive(Debug, Serialize, Deserialize)]
70pub struct BulkSetsParams {
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub add_sets: Option<HashMap<String, String>>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub delete_sets: Option<Vec<String>>,
77}
78
79#[cfg_attr(feature = "rust", derive(Builder))]
81#[cfg_attr(feature = "node", napi(object))]
82#[cfg_attr(not(feature = "node"), derive(Clone))]
83#[derive(Debug, Serialize, Deserialize)]
84pub struct CreateListParams {
85 pub key: String,
87 pub items: Vec<String>,
89}
90
91#[cfg_attr(feature = "rust", derive(Builder))]
93#[cfg_attr(feature = "node", napi(object))]
94#[cfg_attr(not(feature = "node"), derive(Clone))]
95#[derive(Debug, Default, Serialize, Deserialize)]
96pub struct GetListsParams {
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub limit: Option<i64>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub cursor: Option<String>,
103}
104
105#[cfg_attr(feature = "rust", derive(Builder))]
107#[cfg_attr(feature = "node", napi(object))]
108#[cfg_attr(not(feature = "node"), derive(Clone))]
109#[derive(Debug, Default, Serialize, Deserialize)]
110pub struct GetListParams {
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub limit: Option<i64>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub cursor: Option<String>,
117}
118
119#[cfg_attr(feature = "rust", derive(Builder))]
121#[cfg_attr(feature = "node", napi(object))]
122#[cfg_attr(not(feature = "node"), derive(Clone))]
123#[derive(Debug, Default, Serialize, Deserialize)]
124pub struct UpdateListParams {
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub add_items: Option<Vec<String>>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub remove_items: Option<Vec<String>>,
131}
132
133#[cfg_attr(feature = "rust", derive(Builder))]
135#[cfg_attr(feature = "node", napi(object))]
136#[cfg_attr(not(feature = "node"), derive(Clone))]
137#[derive(Debug, Serialize, Deserialize)]
138pub struct AddListItemParams {
139 pub item: String,
141}
142
143#[cfg_attr(feature = "python", gen_stub_pyclass)]
147#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
148#[cfg_attr(feature = "node", napi(object))]
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct KvSetEntry {
151 pub key: String,
153 pub value: String,
155}
156
157#[cfg(feature = "python")]
158#[gen_stub_pymethods]
159#[pymethods]
160impl KvSetEntry {
161 #[new]
162 pub fn new(key: String, value: String) -> Self {
163 Self { key, value }
164 }
165}
166
167#[cfg_attr(feature = "python", gen_stub_pyclass)]
170#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
171#[cfg_attr(feature = "node", napi(object))]
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct GetSetsResponse {
174 pub data: Vec<KvSetEntry>,
176 pub cursor: String,
178}
179
180#[cfg(feature = "python")]
181#[gen_stub_pymethods]
182#[pymethods]
183impl GetSetsResponse {
184 #[new]
185 pub fn new(data: Vec<KvSetEntry>, cursor: String) -> Self {
186 Self { data, cursor }
187 }
188}
189
190#[cfg_attr(feature = "python", gen_stub_pyclass)]
193#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
194#[cfg_attr(feature = "node", napi(object))]
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct GetSetResponse {
197 pub value: String,
199}
200
201#[cfg(feature = "python")]
202#[gen_stub_pymethods]
203#[pymethods]
204impl GetSetResponse {
205 #[new]
206 pub fn new(value: String) -> Self {
207 Self { value }
208 }
209}
210
211#[cfg_attr(feature = "python", gen_stub_pyclass)]
214#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
215#[cfg_attr(feature = "node", napi(object))]
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct GetListsData {
218 pub keys: Vec<String>,
220}
221
222#[cfg(feature = "python")]
223#[gen_stub_pymethods]
224#[pymethods]
225impl GetListsData {
226 #[new]
227 pub fn new(keys: Vec<String>) -> Self {
228 Self { keys }
229 }
230}
231
232#[cfg_attr(feature = "python", gen_stub_pyclass)]
235#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
236#[cfg_attr(feature = "node", napi(object))]
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct GetListsResponse {
239 pub data: GetListsData,
241 pub cursor: String,
243}
244
245#[cfg(feature = "python")]
246#[gen_stub_pymethods]
247#[pymethods]
248impl GetListsResponse {
249 #[new]
250 pub fn new(data: GetListsData, cursor: String) -> Self {
251 Self { data, cursor }
252 }
253}
254
255#[cfg_attr(feature = "python", gen_stub_pyclass)]
258#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
259#[cfg_attr(feature = "node", napi(object))]
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct GetListData {
262 pub items: Vec<String>,
264}
265
266#[cfg(feature = "python")]
267#[gen_stub_pymethods]
268#[pymethods]
269impl GetListData {
270 #[new]
271 pub fn new(items: Vec<String>) -> Self {
272 Self { items }
273 }
274}
275
276#[cfg_attr(feature = "python", gen_stub_pyclass)]
279#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
280#[cfg_attr(feature = "node", napi(object))]
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct GetListResponse {
283 pub data: GetListData,
285 pub cursor: String,
287}
288
289#[cfg(feature = "python")]
290#[gen_stub_pymethods]
291#[pymethods]
292impl GetListResponse {
293 #[new]
294 pub fn new(data: GetListData, cursor: String) -> Self {
295 Self { data, cursor }
296 }
297}
298
299#[cfg_attr(feature = "python", gen_stub_pyclass)]
301#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
302#[cfg_attr(feature = "node", napi(object))]
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct ListContainsItemResponse {
305 pub exists: bool,
307}
308
309#[cfg(feature = "python")]
310#[gen_stub_pymethods]
311#[pymethods]
312impl ListContainsItemResponse {
313 #[new]
314 pub fn new(exists: bool) -> Self {
315 Self { exists }
316 }
317}
318
319#[derive(Debug, Serialize, Deserialize)]
322struct ApiResponse<T> {
323 data: T,
324}
325
326#[derive(Debug, Clone)]
332pub struct KvStoreApiClient {
333 config: SdkConfig,
334}
335
336impl KvStoreApiClient {
337 pub fn new(config: SdkConfig) -> Self {
338 Self { config }
339 }
340
341 pub async fn create_set(&self, params: &CreateSetParams) -> Result<(), SdkError> {
345 let url = self.config.kvstore().base_url.join("sets")?;
346 let resp = self
347 .config
348 .http_client()
349 .post(url)
350 .json(params)
351 .send()
352 .await
353 .map_err(SdkError::Http)?;
354 let status = resp.status();
355 if !status.is_success() {
356 let body = resp.text().await.map_err(SdkError::Http)?;
357 return Err(SdkError::Api { status, body });
358 }
359 Ok(())
360 }
361
362 pub async fn get_sets(&self, params: &GetSetsParams) -> Result<GetSetsResponse, SdkError> {
365 let mut url = self.config.kvstore().base_url.join("sets")?;
366 {
367 let mut pairs = url.query_pairs_mut();
368 if let Some(v) = params.limit {
369 pairs.append_pair("limit", &v.to_string());
370 }
371 if let Some(v) = ¶ms.cursor {
372 pairs.append_pair("cursor", v);
373 }
374 }
375 let resp = self
376 .config
377 .http_client()
378 .get(url)
379 .send()
380 .await
381 .map_err(SdkError::Http)?;
382 let status = resp.status();
383 let body = resp.text().await.map_err(SdkError::Http)?;
384 if !status.is_success() {
385 return Err(SdkError::Api { status, body });
386 }
387 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
388 }
389
390 pub async fn get_set(&self, key: &str) -> Result<GetSetResponse, SdkError> {
392 let url = self
393 .config
394 .kvstore()
395 .base_url
396 .join(&format!("sets/{key}"))?;
397 let resp = self
398 .config
399 .http_client()
400 .get(url)
401 .send()
402 .await
403 .map_err(SdkError::Http)?;
404 let status = resp.status();
405 let body = resp.text().await.map_err(SdkError::Http)?;
406 if !status.is_success() {
407 return Err(SdkError::Api { status, body });
408 }
409 let wrapper: ApiResponse<GetSetResponse> =
410 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })?;
411 Ok(wrapper.data)
412 }
413
414 pub async fn bulk_sets(&self, params: &BulkSetsParams) -> Result<(), SdkError> {
417 let url = self.config.kvstore().base_url.join("sets/bulk")?;
418 let resp = self
419 .config
420 .http_client()
421 .post(url)
422 .json(params)
423 .send()
424 .await
425 .map_err(SdkError::Http)?;
426 let status = resp.status();
427 if !status.is_success() {
428 let body = resp.text().await.map_err(SdkError::Http)?;
429 return Err(SdkError::Api { status, body });
430 }
431 Ok(())
432 }
433
434 pub async fn delete_set(&self, key: &str) -> Result<(), SdkError> {
436 let url = self
437 .config
438 .kvstore()
439 .base_url
440 .join(&format!("sets/{key}"))?;
441 let resp = self
442 .config
443 .http_client()
444 .delete(url)
445 .send()
446 .await
447 .map_err(SdkError::Http)?;
448 let status = resp.status();
449 if !status.is_success() {
450 let body = resp.text().await.map_err(SdkError::Http)?;
451 return Err(SdkError::Api { status, body });
452 }
453 Ok(())
454 }
455
456 pub async fn create_list(&self, params: &CreateListParams) -> Result<(), SdkError> {
460 let url = self.config.kvstore().base_url.join("lists")?;
461 let resp = self
462 .config
463 .http_client()
464 .post(url)
465 .json(params)
466 .send()
467 .await
468 .map_err(SdkError::Http)?;
469 let status = resp.status();
470 if !status.is_success() {
471 let body = resp.text().await.map_err(SdkError::Http)?;
472 return Err(SdkError::Api { status, body });
473 }
474 Ok(())
475 }
476
477 pub async fn get_lists(&self, params: &GetListsParams) -> Result<GetListsResponse, SdkError> {
480 let mut url = self.config.kvstore().base_url.join("lists")?;
481 {
482 let mut pairs = url.query_pairs_mut();
483 if let Some(v) = params.limit {
484 pairs.append_pair("limit", &v.to_string());
485 }
486 if let Some(v) = ¶ms.cursor {
487 pairs.append_pair("cursor", v);
488 }
489 }
490 let resp = self
491 .config
492 .http_client()
493 .get(url)
494 .send()
495 .await
496 .map_err(SdkError::Http)?;
497 let status = resp.status();
498 let body = resp.text().await.map_err(SdkError::Http)?;
499 if !status.is_success() {
500 return Err(SdkError::Api { status, body });
501 }
502 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
503 }
504
505 pub async fn get_list(
508 &self,
509 key: &str,
510 params: &GetListParams,
511 ) -> Result<GetListResponse, SdkError> {
512 let mut url = self
513 .config
514 .kvstore()
515 .base_url
516 .join(&format!("lists/{key}"))?;
517 {
518 let mut pairs = url.query_pairs_mut();
519 if let Some(v) = params.limit {
520 pairs.append_pair("limit", &v.to_string());
521 }
522 if let Some(v) = ¶ms.cursor {
523 pairs.append_pair("cursor", v);
524 }
525 }
526 let resp = self
527 .config
528 .http_client()
529 .get(url)
530 .send()
531 .await
532 .map_err(SdkError::Http)?;
533 let status = resp.status();
534 let body = resp.text().await.map_err(SdkError::Http)?;
535 if !status.is_success() {
536 return Err(SdkError::Api { status, body });
537 }
538 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
539 }
540
541 pub async fn update_list(&self, key: &str, params: &UpdateListParams) -> Result<(), SdkError> {
544 let url = self
545 .config
546 .kvstore()
547 .base_url
548 .join(&format!("lists/{key}"))?;
549 let resp = self
550 .config
551 .http_client()
552 .patch(url)
553 .json(params)
554 .send()
555 .await
556 .map_err(SdkError::Http)?;
557 let status = resp.status();
558 if !status.is_success() {
559 let body = resp.text().await.map_err(SdkError::Http)?;
560 return Err(SdkError::Api { status, body });
561 }
562 Ok(())
563 }
564
565 pub async fn add_list_item(
567 &self,
568 key: &str,
569 params: &AddListItemParams,
570 ) -> Result<(), SdkError> {
571 let url = self
572 .config
573 .kvstore()
574 .base_url
575 .join(&format!("lists/{key}/items"))?;
576 let resp = self
577 .config
578 .http_client()
579 .post(url)
580 .json(params)
581 .send()
582 .await
583 .map_err(SdkError::Http)?;
584 let status = resp.status();
585 if !status.is_success() {
586 let body = resp.text().await.map_err(SdkError::Http)?;
587 return Err(SdkError::Api { status, body });
588 }
589 Ok(())
590 }
591
592 pub async fn list_contains_item(
594 &self,
595 key: &str,
596 item: &str,
597 ) -> Result<ListContainsItemResponse, SdkError> {
598 let url = self
599 .config
600 .kvstore()
601 .base_url
602 .join(&format!("lists/{key}/contains/{item}"))?;
603 let resp = self
604 .config
605 .http_client()
606 .get(url)
607 .send()
608 .await
609 .map_err(SdkError::Http)?;
610 let status = resp.status();
611 let body = resp.text().await.map_err(SdkError::Http)?;
612 if !status.is_success() {
613 return Err(SdkError::Api { status, body });
614 }
615 let wrapper: ApiResponse<ListContainsItemResponse> =
616 serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })?;
617 Ok(wrapper.data)
618 }
619
620 pub async fn delete_list_item(&self, key: &str, item: &str) -> Result<(), SdkError> {
622 let url = self
623 .config
624 .kvstore()
625 .base_url
626 .join(&format!("lists/{key}/items/{item}"))?;
627 let resp = self
628 .config
629 .http_client()
630 .delete(url)
631 .send()
632 .await
633 .map_err(SdkError::Http)?;
634 let status = resp.status();
635 if !status.is_success() {
636 let body = resp.text().await.map_err(SdkError::Http)?;
637 return Err(SdkError::Api { status, body });
638 }
639 Ok(())
640 }
641
642 pub async fn delete_list(&self, key: &str) -> Result<(), SdkError> {
644 let url = self
645 .config
646 .kvstore()
647 .base_url
648 .join(&format!("lists/{key}"))?;
649 let resp = self
650 .config
651 .http_client()
652 .delete(url)
653 .send()
654 .await
655 .map_err(SdkError::Http)?;
656 let status = resp.status();
657 if !status.is_success() {
658 let body = resp.text().await.map_err(SdkError::Http)?;
659 return Err(SdkError::Api { status, body });
660 }
661 Ok(())
662 }
663}
664
665#[cfg(test)]
668#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
669mod tests {
670 use super::*;
671 use crate::{KvStoreConfig, QuickNodeSdk, SdkFullConfig};
672 use wiremock::matchers::{method, path};
673 use wiremock::{Mock, MockServer, ResponseTemplate};
674
675 fn make_sdk(base_url: String) -> QuickNodeSdk {
676 QuickNodeSdk::new(&SdkFullConfig {
677 api_key: "test-key".to_string(),
678 http: None,
679 admin: None,
680 streams: None,
681 webhooks: None,
682 kvstore: Some(KvStoreConfig {
683 base_url: Some(base_url),
684 }),
685 })
686 .unwrap()
687 }
688
689 #[tokio::test]
692 async fn create_set_success() {
693 let server = MockServer::start().await;
694 Mock::given(method("POST"))
695 .and(path("/sets"))
696 .respond_with(
697 ResponseTemplate::new(201)
698 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
699 )
700 .mount(&server)
701 .await;
702 let sdk = make_sdk(format!("{}/", server.uri()));
703 sdk.kvstore
704 .create_set(&CreateSetParams {
705 key: "k".to_string(),
706 value: "v".to_string(),
707 })
708 .await
709 .unwrap();
710 }
711
712 #[tokio::test]
713 async fn create_set_api_error() {
714 let server = MockServer::start().await;
715 Mock::given(method("POST"))
716 .and(path("/sets"))
717 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
718 .mount(&server)
719 .await;
720 let sdk = make_sdk(format!("{}/", server.uri()));
721 let err = sdk
722 .kvstore
723 .create_set(&CreateSetParams {
724 key: "k".to_string(),
725 value: "v".to_string(),
726 })
727 .await
728 .unwrap_err();
729 assert!(matches!(err, SdkError::Api { .. }));
730 }
731
732 #[tokio::test]
733 async fn create_set_server_error() {
734 let server = MockServer::start().await;
735 Mock::given(method("POST"))
736 .and(path("/sets"))
737 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
738 .mount(&server)
739 .await;
740 let sdk = make_sdk(format!("{}/", server.uri()));
741 let err = sdk
742 .kvstore
743 .create_set(&CreateSetParams {
744 key: "k".to_string(),
745 value: "v".to_string(),
746 })
747 .await
748 .unwrap_err();
749 assert!(matches!(err, SdkError::Api { .. }));
750 }
751
752 #[tokio::test]
753 async fn get_sets_success() {
754 let server = MockServer::start().await;
755 Mock::given(method("GET"))
756 .and(path("/sets"))
757 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"data": [{"key": "k1", "value": "v1"}, {"key": "k2", "value": "v2"}], "cursor": ""})))
758 .mount(&server)
759 .await;
760 let sdk = make_sdk(format!("{}/", server.uri()));
761 let resp = sdk
762 .kvstore
763 .get_sets(&GetSetsParams::default())
764 .await
765 .unwrap();
766 assert_eq!(resp.data.len(), 2);
767 assert_eq!(resp.data[0].key, "k1");
768 }
769
770 #[tokio::test]
771 async fn get_sets_api_error() {
772 let server = MockServer::start().await;
773 Mock::given(method("GET"))
774 .and(path("/sets"))
775 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
776 .mount(&server)
777 .await;
778 let sdk = make_sdk(format!("{}/", server.uri()));
779 let err = sdk
780 .kvstore
781 .get_sets(&GetSetsParams::default())
782 .await
783 .unwrap_err();
784 assert!(matches!(err, SdkError::Api { .. }));
785 }
786
787 #[tokio::test]
788 async fn get_sets_server_error() {
789 let server = MockServer::start().await;
790 Mock::given(method("GET"))
791 .and(path("/sets"))
792 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
793 .mount(&server)
794 .await;
795 let sdk = make_sdk(format!("{}/", server.uri()));
796 let err = sdk
797 .kvstore
798 .get_sets(&GetSetsParams::default())
799 .await
800 .unwrap_err();
801 assert!(matches!(err, SdkError::Api { .. }));
802 }
803
804 #[tokio::test]
805 async fn get_set_success() {
806 let server = MockServer::start().await;
807 Mock::given(method("GET"))
808 .and(path("/sets/my-key"))
809 .respond_with(
810 ResponseTemplate::new(200)
811 .set_body_json(serde_json::json!({"data": {"value": "my-value"}})),
812 )
813 .mount(&server)
814 .await;
815 let sdk = make_sdk(format!("{}/", server.uri()));
816 let resp = sdk.kvstore.get_set("my-key").await.unwrap();
817 assert_eq!(resp.value, "my-value");
818 }
819
820 #[tokio::test]
821 async fn get_set_not_found() {
822 let server = MockServer::start().await;
823 Mock::given(method("GET"))
824 .and(path("/sets/my-key"))
825 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
826 .mount(&server)
827 .await;
828 let sdk = make_sdk(format!("{}/", server.uri()));
829 let err = sdk.kvstore.get_set("my-key").await.unwrap_err();
830 assert!(matches!(err, SdkError::Api { .. }));
831 }
832
833 #[tokio::test]
834 async fn get_set_server_error() {
835 let server = MockServer::start().await;
836 Mock::given(method("GET"))
837 .and(path("/sets/my-key"))
838 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
839 .mount(&server)
840 .await;
841 let sdk = make_sdk(format!("{}/", server.uri()));
842 let err = sdk.kvstore.get_set("my-key").await.unwrap_err();
843 assert!(matches!(err, SdkError::Api { .. }));
844 }
845
846 #[tokio::test]
847 async fn bulk_sets_success() {
848 let server = MockServer::start().await;
849 Mock::given(method("POST"))
850 .and(path("/sets/bulk"))
851 .respond_with(
852 ResponseTemplate::new(201)
853 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
854 )
855 .mount(&server)
856 .await;
857 let sdk = make_sdk(format!("{}/", server.uri()));
858 let mut add = HashMap::new();
859 add.insert("k1".to_string(), "v1".to_string());
860 sdk.kvstore
861 .bulk_sets(&BulkSetsParams {
862 add_sets: Some(add),
863 delete_sets: None,
864 })
865 .await
866 .unwrap();
867 }
868
869 #[tokio::test]
870 async fn bulk_sets_api_error() {
871 let server = MockServer::start().await;
872 Mock::given(method("POST"))
873 .and(path("/sets/bulk"))
874 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
875 .mount(&server)
876 .await;
877 let sdk = make_sdk(format!("{}/", server.uri()));
878 let err = sdk
879 .kvstore
880 .bulk_sets(&BulkSetsParams {
881 add_sets: None,
882 delete_sets: None,
883 })
884 .await
885 .unwrap_err();
886 assert!(matches!(err, SdkError::Api { .. }));
887 }
888
889 #[tokio::test]
890 async fn bulk_sets_server_error() {
891 let server = MockServer::start().await;
892 Mock::given(method("POST"))
893 .and(path("/sets/bulk"))
894 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
895 .mount(&server)
896 .await;
897 let sdk = make_sdk(format!("{}/", server.uri()));
898 let err = sdk
899 .kvstore
900 .bulk_sets(&BulkSetsParams {
901 add_sets: None,
902 delete_sets: None,
903 })
904 .await
905 .unwrap_err();
906 assert!(matches!(err, SdkError::Api { .. }));
907 }
908
909 #[tokio::test]
910 async fn delete_set_success() {
911 let server = MockServer::start().await;
912 Mock::given(method("DELETE"))
913 .and(path("/sets/my-key"))
914 .respond_with(
915 ResponseTemplate::new(200)
916 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
917 )
918 .mount(&server)
919 .await;
920 let sdk = make_sdk(format!("{}/", server.uri()));
921 sdk.kvstore.delete_set("my-key").await.unwrap();
922 }
923
924 #[tokio::test]
925 async fn delete_set_not_found() {
926 let server = MockServer::start().await;
927 Mock::given(method("DELETE"))
928 .and(path("/sets/my-key"))
929 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
930 .mount(&server)
931 .await;
932 let sdk = make_sdk(format!("{}/", server.uri()));
933 let err = sdk.kvstore.delete_set("my-key").await.unwrap_err();
934 assert!(matches!(err, SdkError::Api { .. }));
935 }
936
937 #[tokio::test]
938 async fn delete_set_server_error() {
939 let server = MockServer::start().await;
940 Mock::given(method("DELETE"))
941 .and(path("/sets/my-key"))
942 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
943 .mount(&server)
944 .await;
945 let sdk = make_sdk(format!("{}/", server.uri()));
946 let err = sdk.kvstore.delete_set("my-key").await.unwrap_err();
947 assert!(matches!(err, SdkError::Api { .. }));
948 }
949
950 #[tokio::test]
953 async fn create_list_success() {
954 let server = MockServer::start().await;
955 Mock::given(method("POST"))
956 .and(path("/lists"))
957 .respond_with(
958 ResponseTemplate::new(201)
959 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
960 )
961 .mount(&server)
962 .await;
963 let sdk = make_sdk(format!("{}/", server.uri()));
964 sdk.kvstore
965 .create_list(&CreateListParams {
966 key: "my-list".to_string(),
967 items: vec!["item1".to_string()],
968 })
969 .await
970 .unwrap();
971 }
972
973 #[tokio::test]
974 async fn create_list_api_error() {
975 let server = MockServer::start().await;
976 Mock::given(method("POST"))
977 .and(path("/lists"))
978 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
979 .mount(&server)
980 .await;
981 let sdk = make_sdk(format!("{}/", server.uri()));
982 let err = sdk
983 .kvstore
984 .create_list(&CreateListParams {
985 key: "my-list".to_string(),
986 items: vec![],
987 })
988 .await
989 .unwrap_err();
990 assert!(matches!(err, SdkError::Api { .. }));
991 }
992
993 #[tokio::test]
994 async fn create_list_server_error() {
995 let server = MockServer::start().await;
996 Mock::given(method("POST"))
997 .and(path("/lists"))
998 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
999 .mount(&server)
1000 .await;
1001 let sdk = make_sdk(format!("{}/", server.uri()));
1002 let err = sdk
1003 .kvstore
1004 .create_list(&CreateListParams {
1005 key: "my-list".to_string(),
1006 items: vec![],
1007 })
1008 .await
1009 .unwrap_err();
1010 assert!(matches!(err, SdkError::Api { .. }));
1011 }
1012
1013 #[tokio::test]
1014 async fn get_lists_success() {
1015 let server = MockServer::start().await;
1016 Mock::given(method("GET"))
1017 .and(path("/lists"))
1018 .respond_with(ResponseTemplate::new(200).set_body_json(
1019 serde_json::json!({"data": {"keys": ["list1", "list2"]}, "cursor": ""}),
1020 ))
1021 .mount(&server)
1022 .await;
1023 let sdk = make_sdk(format!("{}/", server.uri()));
1024 let resp = sdk
1025 .kvstore
1026 .get_lists(&GetListsParams::default())
1027 .await
1028 .unwrap();
1029 assert_eq!(resp.data.keys, vec!["list1", "list2"]);
1030 }
1031
1032 #[tokio::test]
1033 async fn get_lists_api_error() {
1034 let server = MockServer::start().await;
1035 Mock::given(method("GET"))
1036 .and(path("/lists"))
1037 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1038 .mount(&server)
1039 .await;
1040 let sdk = make_sdk(format!("{}/", server.uri()));
1041 let err = sdk
1042 .kvstore
1043 .get_lists(&GetListsParams::default())
1044 .await
1045 .unwrap_err();
1046 assert!(matches!(err, SdkError::Api { .. }));
1047 }
1048
1049 #[tokio::test]
1050 async fn get_lists_server_error() {
1051 let server = MockServer::start().await;
1052 Mock::given(method("GET"))
1053 .and(path("/lists"))
1054 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1055 .mount(&server)
1056 .await;
1057 let sdk = make_sdk(format!("{}/", server.uri()));
1058 let err = sdk
1059 .kvstore
1060 .get_lists(&GetListsParams::default())
1061 .await
1062 .unwrap_err();
1063 assert!(matches!(err, SdkError::Api { .. }));
1064 }
1065
1066 #[tokio::test]
1067 async fn get_list_success() {
1068 let server = MockServer::start().await;
1069 Mock::given(method("GET"))
1070 .and(path("/lists/my-list"))
1071 .respond_with(ResponseTemplate::new(200).set_body_json(
1072 serde_json::json!({"data": {"items": ["item1", "item2"]}, "cursor": ""}),
1073 ))
1074 .mount(&server)
1075 .await;
1076 let sdk = make_sdk(format!("{}/", server.uri()));
1077 let resp = sdk
1078 .kvstore
1079 .get_list("my-list", &GetListParams::default())
1080 .await
1081 .unwrap();
1082 assert_eq!(resp.data.items, vec!["item1", "item2"]);
1083 }
1084
1085 #[tokio::test]
1086 async fn get_list_not_found() {
1087 let server = MockServer::start().await;
1088 Mock::given(method("GET"))
1089 .and(path("/lists/my-list"))
1090 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1091 .mount(&server)
1092 .await;
1093 let sdk = make_sdk(format!("{}/", server.uri()));
1094 let err = sdk
1095 .kvstore
1096 .get_list("my-list", &GetListParams::default())
1097 .await
1098 .unwrap_err();
1099 assert!(matches!(err, SdkError::Api { .. }));
1100 }
1101
1102 #[tokio::test]
1103 async fn get_list_server_error() {
1104 let server = MockServer::start().await;
1105 Mock::given(method("GET"))
1106 .and(path("/lists/my-list"))
1107 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1108 .mount(&server)
1109 .await;
1110 let sdk = make_sdk(format!("{}/", server.uri()));
1111 let err = sdk
1112 .kvstore
1113 .get_list("my-list", &GetListParams::default())
1114 .await
1115 .unwrap_err();
1116 assert!(matches!(err, SdkError::Api { .. }));
1117 }
1118
1119 #[tokio::test]
1120 async fn update_list_success() {
1121 let server = MockServer::start().await;
1122 Mock::given(method("PATCH"))
1123 .and(path("/lists/my-list"))
1124 .respond_with(
1125 ResponseTemplate::new(200)
1126 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1127 )
1128 .mount(&server)
1129 .await;
1130 let sdk = make_sdk(format!("{}/", server.uri()));
1131 sdk.kvstore
1132 .update_list(
1133 "my-list",
1134 &UpdateListParams {
1135 add_items: Some(vec!["item3".to_string()]),
1136 remove_items: None,
1137 },
1138 )
1139 .await
1140 .unwrap();
1141 }
1142
1143 #[tokio::test]
1144 async fn update_list_api_error() {
1145 let server = MockServer::start().await;
1146 Mock::given(method("PATCH"))
1147 .and(path("/lists/my-list"))
1148 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1149 .mount(&server)
1150 .await;
1151 let sdk = make_sdk(format!("{}/", server.uri()));
1152 let err = sdk
1153 .kvstore
1154 .update_list("my-list", &UpdateListParams::default())
1155 .await
1156 .unwrap_err();
1157 assert!(matches!(err, SdkError::Api { .. }));
1158 }
1159
1160 #[tokio::test]
1161 async fn update_list_server_error() {
1162 let server = MockServer::start().await;
1163 Mock::given(method("PATCH"))
1164 .and(path("/lists/my-list"))
1165 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1166 .mount(&server)
1167 .await;
1168 let sdk = make_sdk(format!("{}/", server.uri()));
1169 let err = sdk
1170 .kvstore
1171 .update_list("my-list", &UpdateListParams::default())
1172 .await
1173 .unwrap_err();
1174 assert!(matches!(err, SdkError::Api { .. }));
1175 }
1176
1177 #[tokio::test]
1178 async fn add_list_item_success() {
1179 let server = MockServer::start().await;
1180 Mock::given(method("POST"))
1181 .and(path("/lists/my-list/items"))
1182 .respond_with(
1183 ResponseTemplate::new(201)
1184 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1185 )
1186 .mount(&server)
1187 .await;
1188 let sdk = make_sdk(format!("{}/", server.uri()));
1189 sdk.kvstore
1190 .add_list_item(
1191 "my-list",
1192 &AddListItemParams {
1193 item: "item1".to_string(),
1194 },
1195 )
1196 .await
1197 .unwrap();
1198 }
1199
1200 #[tokio::test]
1201 async fn add_list_item_api_error() {
1202 let server = MockServer::start().await;
1203 Mock::given(method("POST"))
1204 .and(path("/lists/my-list/items"))
1205 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1206 .mount(&server)
1207 .await;
1208 let sdk = make_sdk(format!("{}/", server.uri()));
1209 let err = sdk
1210 .kvstore
1211 .add_list_item(
1212 "my-list",
1213 &AddListItemParams {
1214 item: "item1".to_string(),
1215 },
1216 )
1217 .await
1218 .unwrap_err();
1219 assert!(matches!(err, SdkError::Api { .. }));
1220 }
1221
1222 #[tokio::test]
1223 async fn add_list_item_server_error() {
1224 let server = MockServer::start().await;
1225 Mock::given(method("POST"))
1226 .and(path("/lists/my-list/items"))
1227 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1228 .mount(&server)
1229 .await;
1230 let sdk = make_sdk(format!("{}/", server.uri()));
1231 let err = sdk
1232 .kvstore
1233 .add_list_item(
1234 "my-list",
1235 &AddListItemParams {
1236 item: "item1".to_string(),
1237 },
1238 )
1239 .await
1240 .unwrap_err();
1241 assert!(matches!(err, SdkError::Api { .. }));
1242 }
1243
1244 #[tokio::test]
1245 async fn list_contains_item_success() {
1246 let server = MockServer::start().await;
1247 Mock::given(method("GET"))
1248 .and(path("/lists/my-list/contains/item1"))
1249 .respond_with(
1250 ResponseTemplate::new(200)
1251 .set_body_json(serde_json::json!({"data": {"exists": true}})),
1252 )
1253 .mount(&server)
1254 .await;
1255 let sdk = make_sdk(format!("{}/", server.uri()));
1256 let resp = sdk
1257 .kvstore
1258 .list_contains_item("my-list", "item1")
1259 .await
1260 .unwrap();
1261 assert!(resp.exists);
1262 }
1263
1264 #[tokio::test]
1265 async fn list_contains_item_not_found() {
1266 let server = MockServer::start().await;
1267 Mock::given(method("GET"))
1268 .and(path("/lists/my-list/contains/item1"))
1269 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1270 .mount(&server)
1271 .await;
1272 let sdk = make_sdk(format!("{}/", server.uri()));
1273 let err = sdk
1274 .kvstore
1275 .list_contains_item("my-list", "item1")
1276 .await
1277 .unwrap_err();
1278 assert!(matches!(err, SdkError::Api { .. }));
1279 }
1280
1281 #[tokio::test]
1282 async fn list_contains_item_server_error() {
1283 let server = MockServer::start().await;
1284 Mock::given(method("GET"))
1285 .and(path("/lists/my-list/contains/item1"))
1286 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1287 .mount(&server)
1288 .await;
1289 let sdk = make_sdk(format!("{}/", server.uri()));
1290 let err = sdk
1291 .kvstore
1292 .list_contains_item("my-list", "item1")
1293 .await
1294 .unwrap_err();
1295 assert!(matches!(err, SdkError::Api { .. }));
1296 }
1297
1298 #[tokio::test]
1299 async fn delete_list_item_success() {
1300 let server = MockServer::start().await;
1301 Mock::given(method("DELETE"))
1302 .and(path("/lists/my-list/items/item1"))
1303 .respond_with(
1304 ResponseTemplate::new(200)
1305 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1306 )
1307 .mount(&server)
1308 .await;
1309 let sdk = make_sdk(format!("{}/", server.uri()));
1310 sdk.kvstore
1311 .delete_list_item("my-list", "item1")
1312 .await
1313 .unwrap();
1314 }
1315
1316 #[tokio::test]
1317 async fn delete_list_item_not_found() {
1318 let server = MockServer::start().await;
1319 Mock::given(method("DELETE"))
1320 .and(path("/lists/my-list/items/item1"))
1321 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1322 .mount(&server)
1323 .await;
1324 let sdk = make_sdk(format!("{}/", server.uri()));
1325 let err = sdk
1326 .kvstore
1327 .delete_list_item("my-list", "item1")
1328 .await
1329 .unwrap_err();
1330 assert!(matches!(err, SdkError::Api { .. }));
1331 }
1332
1333 #[tokio::test]
1334 async fn delete_list_item_server_error() {
1335 let server = MockServer::start().await;
1336 Mock::given(method("DELETE"))
1337 .and(path("/lists/my-list/items/item1"))
1338 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1339 .mount(&server)
1340 .await;
1341 let sdk = make_sdk(format!("{}/", server.uri()));
1342 let err = sdk
1343 .kvstore
1344 .delete_list_item("my-list", "item1")
1345 .await
1346 .unwrap_err();
1347 assert!(matches!(err, SdkError::Api { .. }));
1348 }
1349
1350 #[tokio::test]
1351 async fn delete_list_success() {
1352 let server = MockServer::start().await;
1353 Mock::given(method("DELETE"))
1354 .and(path("/lists/my-list"))
1355 .respond_with(
1356 ResponseTemplate::new(200)
1357 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1358 )
1359 .mount(&server)
1360 .await;
1361 let sdk = make_sdk(format!("{}/", server.uri()));
1362 sdk.kvstore.delete_list("my-list").await.unwrap();
1363 }
1364
1365 #[tokio::test]
1366 async fn delete_list_not_found() {
1367 let server = MockServer::start().await;
1368 Mock::given(method("DELETE"))
1369 .and(path("/lists/my-list"))
1370 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1371 .mount(&server)
1372 .await;
1373 let sdk = make_sdk(format!("{}/", server.uri()));
1374 let err = sdk.kvstore.delete_list("my-list").await.unwrap_err();
1375 assert!(matches!(err, SdkError::Api { .. }));
1376 }
1377
1378 #[tokio::test]
1379 async fn delete_list_server_error() {
1380 let server = MockServer::start().await;
1381 Mock::given(method("DELETE"))
1382 .and(path("/lists/my-list"))
1383 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1384 .mount(&server)
1385 .await;
1386 let sdk = make_sdk(format!("{}/", server.uri()));
1387 let err = sdk.kvstore.delete_list("my-list").await.unwrap_err();
1388 assert!(matches!(err, SdkError::Api { .. }));
1389 }
1390}