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