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