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 })
700 .unwrap()
701 }
702
703 #[tokio::test]
706 async fn create_set_success() {
707 let server = MockServer::start().await;
708 Mock::given(method("POST"))
709 .and(path("/sets"))
710 .respond_with(
711 ResponseTemplate::new(201)
712 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
713 )
714 .mount(&server)
715 .await;
716 let sdk = make_sdk(format!("{}/", server.uri()));
717 sdk.kvstore
718 .create_set(&CreateSetParams {
719 key: "k".to_string(),
720 value: "v".to_string(),
721 })
722 .await
723 .unwrap();
724 }
725
726 #[tokio::test]
727 async fn create_set_api_error() {
728 let server = MockServer::start().await;
729 Mock::given(method("POST"))
730 .and(path("/sets"))
731 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
732 .mount(&server)
733 .await;
734 let sdk = make_sdk(format!("{}/", server.uri()));
735 let err = sdk
736 .kvstore
737 .create_set(&CreateSetParams {
738 key: "k".to_string(),
739 value: "v".to_string(),
740 })
741 .await
742 .unwrap_err();
743 assert!(matches!(err, SdkError::Api { .. }));
744 }
745
746 #[tokio::test]
747 async fn create_set_server_error() {
748 let server = MockServer::start().await;
749 Mock::given(method("POST"))
750 .and(path("/sets"))
751 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
752 .mount(&server)
753 .await;
754 let sdk = make_sdk(format!("{}/", server.uri()));
755 let err = sdk
756 .kvstore
757 .create_set(&CreateSetParams {
758 key: "k".to_string(),
759 value: "v".to_string(),
760 })
761 .await
762 .unwrap_err();
763 assert!(matches!(err, SdkError::Api { .. }));
764 }
765
766 #[tokio::test]
767 async fn get_sets_success() {
768 let server = MockServer::start().await;
769 Mock::given(method("GET"))
770 .and(path("/sets"))
771 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"data": [{"key": "k1", "value": "v1"}, {"key": "k2", "value": "v2"}], "cursor": ""})))
772 .mount(&server)
773 .await;
774 let sdk = make_sdk(format!("{}/", server.uri()));
775 let resp = sdk
776 .kvstore
777 .get_sets(&GetSetsParams::default())
778 .await
779 .unwrap();
780 assert_eq!(resp.data.len(), 2);
781 assert_eq!(resp.data[0].key, "k1");
782 }
783
784 #[tokio::test]
785 async fn get_sets_null_data_empty_store() {
786 let server = MockServer::start().await;
787 Mock::given(method("GET"))
788 .and(path("/sets"))
789 .respond_with(ResponseTemplate::new(200).set_body_json(
790 serde_json::json!({"code": 200, "msg": "", "data": null, "cursor": ""}),
791 ))
792 .mount(&server)
793 .await;
794 let sdk = make_sdk(format!("{}/", server.uri()));
795 let resp = sdk
796 .kvstore
797 .get_sets(&GetSetsParams::default())
798 .await
799 .unwrap();
800 assert!(resp.data.is_empty());
801 assert_eq!(resp.cursor, "");
802 }
803
804 #[tokio::test]
805 async fn get_sets_api_error() {
806 let server = MockServer::start().await;
807 Mock::given(method("GET"))
808 .and(path("/sets"))
809 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
810 .mount(&server)
811 .await;
812 let sdk = make_sdk(format!("{}/", server.uri()));
813 let err = sdk
814 .kvstore
815 .get_sets(&GetSetsParams::default())
816 .await
817 .unwrap_err();
818 assert!(matches!(err, SdkError::Api { .. }));
819 }
820
821 #[tokio::test]
822 async fn get_sets_server_error() {
823 let server = MockServer::start().await;
824 Mock::given(method("GET"))
825 .and(path("/sets"))
826 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
827 .mount(&server)
828 .await;
829 let sdk = make_sdk(format!("{}/", server.uri()));
830 let err = sdk
831 .kvstore
832 .get_sets(&GetSetsParams::default())
833 .await
834 .unwrap_err();
835 assert!(matches!(err, SdkError::Api { .. }));
836 }
837
838 #[tokio::test]
839 async fn get_set_success() {
840 let server = MockServer::start().await;
841 Mock::given(method("GET"))
842 .and(path("/sets/my-key"))
843 .respond_with(
844 ResponseTemplate::new(200)
845 .set_body_json(serde_json::json!({"data": {"value": "my-value"}})),
846 )
847 .mount(&server)
848 .await;
849 let sdk = make_sdk(format!("{}/", server.uri()));
850 let resp = sdk.kvstore.get_set("my-key").await.unwrap();
851 assert_eq!(resp.value, "my-value");
852 }
853
854 #[tokio::test]
855 async fn get_set_not_found() {
856 let server = MockServer::start().await;
857 Mock::given(method("GET"))
858 .and(path("/sets/my-key"))
859 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
860 .mount(&server)
861 .await;
862 let sdk = make_sdk(format!("{}/", server.uri()));
863 let err = sdk.kvstore.get_set("my-key").await.unwrap_err();
864 assert!(matches!(err, SdkError::Api { .. }));
865 }
866
867 #[tokio::test]
868 async fn get_set_server_error() {
869 let server = MockServer::start().await;
870 Mock::given(method("GET"))
871 .and(path("/sets/my-key"))
872 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
873 .mount(&server)
874 .await;
875 let sdk = make_sdk(format!("{}/", server.uri()));
876 let err = sdk.kvstore.get_set("my-key").await.unwrap_err();
877 assert!(matches!(err, SdkError::Api { .. }));
878 }
879
880 #[tokio::test]
881 async fn bulk_sets_success() {
882 let server = MockServer::start().await;
883 Mock::given(method("POST"))
884 .and(path("/sets/bulk"))
885 .respond_with(
886 ResponseTemplate::new(201)
887 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
888 )
889 .mount(&server)
890 .await;
891 let sdk = make_sdk(format!("{}/", server.uri()));
892 let mut add = HashMap::new();
893 add.insert("k1".to_string(), "v1".to_string());
894 sdk.kvstore
895 .bulk_sets(&BulkSetsParams {
896 add_sets: Some(add),
897 delete_sets: None,
898 })
899 .await
900 .unwrap();
901 }
902
903 #[tokio::test]
904 async fn bulk_sets_api_error() {
905 let server = MockServer::start().await;
906 Mock::given(method("POST"))
907 .and(path("/sets/bulk"))
908 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
909 .mount(&server)
910 .await;
911 let sdk = make_sdk(format!("{}/", server.uri()));
912 let err = sdk
913 .kvstore
914 .bulk_sets(&BulkSetsParams {
915 add_sets: None,
916 delete_sets: None,
917 })
918 .await
919 .unwrap_err();
920 assert!(matches!(err, SdkError::Api { .. }));
921 }
922
923 #[tokio::test]
924 async fn bulk_sets_server_error() {
925 let server = MockServer::start().await;
926 Mock::given(method("POST"))
927 .and(path("/sets/bulk"))
928 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
929 .mount(&server)
930 .await;
931 let sdk = make_sdk(format!("{}/", server.uri()));
932 let err = sdk
933 .kvstore
934 .bulk_sets(&BulkSetsParams {
935 add_sets: None,
936 delete_sets: None,
937 })
938 .await
939 .unwrap_err();
940 assert!(matches!(err, SdkError::Api { .. }));
941 }
942
943 #[tokio::test]
947 async fn bulk_sets_wire_body_add_sets() {
948 let server = MockServer::start().await;
949 Mock::given(method("POST"))
950 .and(path("/sets/bulk"))
951 .and(body_json(serde_json::json!({
952 "addSets": {"k1": "v1"}
953 })))
954 .respond_with(
955 ResponseTemplate::new(201)
956 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
957 )
958 .mount(&server)
959 .await;
960 let sdk = make_sdk(format!("{}/", server.uri()));
961 let mut add = HashMap::new();
962 add.insert("k1".to_string(), "v1".to_string());
963 sdk.kvstore
964 .bulk_sets(&BulkSetsParams {
965 add_sets: Some(add),
966 delete_sets: None,
967 })
968 .await
969 .unwrap();
970 }
971
972 #[tokio::test]
973 async fn bulk_sets_wire_body_delete_sets() {
974 let server = MockServer::start().await;
975 Mock::given(method("POST"))
976 .and(path("/sets/bulk"))
977 .and(body_json(serde_json::json!({
978 "deleteSets": ["k1", "k2"]
979 })))
980 .respond_with(
981 ResponseTemplate::new(201)
982 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
983 )
984 .mount(&server)
985 .await;
986 let sdk = make_sdk(format!("{}/", server.uri()));
987 sdk.kvstore
988 .bulk_sets(&BulkSetsParams {
989 add_sets: None,
990 delete_sets: Some(vec!["k1".to_string(), "k2".to_string()]),
991 })
992 .await
993 .unwrap();
994 }
995
996 #[tokio::test]
997 async fn delete_set_success() {
998 let server = MockServer::start().await;
999 Mock::given(method("DELETE"))
1000 .and(path("/sets/my-key"))
1001 .respond_with(
1002 ResponseTemplate::new(200)
1003 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1004 )
1005 .mount(&server)
1006 .await;
1007 let sdk = make_sdk(format!("{}/", server.uri()));
1008 sdk.kvstore.delete_set("my-key").await.unwrap();
1009 }
1010
1011 #[tokio::test]
1012 async fn delete_set_not_found() {
1013 let server = MockServer::start().await;
1014 Mock::given(method("DELETE"))
1015 .and(path("/sets/my-key"))
1016 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1017 .mount(&server)
1018 .await;
1019 let sdk = make_sdk(format!("{}/", server.uri()));
1020 let err = sdk.kvstore.delete_set("my-key").await.unwrap_err();
1021 assert!(matches!(err, SdkError::Api { .. }));
1022 }
1023
1024 #[tokio::test]
1025 async fn delete_set_server_error() {
1026 let server = MockServer::start().await;
1027 Mock::given(method("DELETE"))
1028 .and(path("/sets/my-key"))
1029 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1030 .mount(&server)
1031 .await;
1032 let sdk = make_sdk(format!("{}/", server.uri()));
1033 let err = sdk.kvstore.delete_set("my-key").await.unwrap_err();
1034 assert!(matches!(err, SdkError::Api { .. }));
1035 }
1036
1037 #[tokio::test]
1040 async fn create_list_success() {
1041 let server = MockServer::start().await;
1042 Mock::given(method("POST"))
1043 .and(path("/lists"))
1044 .respond_with(
1045 ResponseTemplate::new(201)
1046 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1047 )
1048 .mount(&server)
1049 .await;
1050 let sdk = make_sdk(format!("{}/", server.uri()));
1051 sdk.kvstore
1052 .create_list(&CreateListParams {
1053 key: "my-list".to_string(),
1054 items: vec!["item1".to_string()],
1055 })
1056 .await
1057 .unwrap();
1058 }
1059
1060 #[tokio::test]
1061 async fn create_list_api_error() {
1062 let server = MockServer::start().await;
1063 Mock::given(method("POST"))
1064 .and(path("/lists"))
1065 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1066 .mount(&server)
1067 .await;
1068 let sdk = make_sdk(format!("{}/", server.uri()));
1069 let err = sdk
1070 .kvstore
1071 .create_list(&CreateListParams {
1072 key: "my-list".to_string(),
1073 items: vec![],
1074 })
1075 .await
1076 .unwrap_err();
1077 assert!(matches!(err, SdkError::Api { .. }));
1078 }
1079
1080 #[tokio::test]
1081 async fn create_list_server_error() {
1082 let server = MockServer::start().await;
1083 Mock::given(method("POST"))
1084 .and(path("/lists"))
1085 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1086 .mount(&server)
1087 .await;
1088 let sdk = make_sdk(format!("{}/", server.uri()));
1089 let err = sdk
1090 .kvstore
1091 .create_list(&CreateListParams {
1092 key: "my-list".to_string(),
1093 items: vec![],
1094 })
1095 .await
1096 .unwrap_err();
1097 assert!(matches!(err, SdkError::Api { .. }));
1098 }
1099
1100 #[tokio::test]
1101 async fn get_lists_success() {
1102 let server = MockServer::start().await;
1103 Mock::given(method("GET"))
1104 .and(path("/lists"))
1105 .respond_with(ResponseTemplate::new(200).set_body_json(
1106 serde_json::json!({"data": {"keys": ["list1", "list2"]}, "cursor": ""}),
1107 ))
1108 .mount(&server)
1109 .await;
1110 let sdk = make_sdk(format!("{}/", server.uri()));
1111 let resp = sdk
1112 .kvstore
1113 .get_lists(&GetListsParams::default())
1114 .await
1115 .unwrap();
1116 assert_eq!(resp.data.keys, vec!["list1", "list2"]);
1117 }
1118
1119 #[tokio::test]
1120 async fn get_lists_null_data_empty_store() {
1121 let server = MockServer::start().await;
1122 Mock::given(method("GET"))
1123 .and(path("/lists"))
1124 .respond_with(ResponseTemplate::new(200).set_body_json(
1125 serde_json::json!({"code": 200, "msg": "", "data": null, "cursor": ""}),
1126 ))
1127 .mount(&server)
1128 .await;
1129 let sdk = make_sdk(format!("{}/", server.uri()));
1130 let resp = sdk
1131 .kvstore
1132 .get_lists(&GetListsParams::default())
1133 .await
1134 .unwrap();
1135 assert!(resp.data.keys.is_empty());
1136 assert_eq!(resp.cursor, "");
1137 }
1138
1139 #[tokio::test]
1140 async fn get_lists_api_error() {
1141 let server = MockServer::start().await;
1142 Mock::given(method("GET"))
1143 .and(path("/lists"))
1144 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1145 .mount(&server)
1146 .await;
1147 let sdk = make_sdk(format!("{}/", server.uri()));
1148 let err = sdk
1149 .kvstore
1150 .get_lists(&GetListsParams::default())
1151 .await
1152 .unwrap_err();
1153 assert!(matches!(err, SdkError::Api { .. }));
1154 }
1155
1156 #[tokio::test]
1157 async fn get_lists_server_error() {
1158 let server = MockServer::start().await;
1159 Mock::given(method("GET"))
1160 .and(path("/lists"))
1161 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1162 .mount(&server)
1163 .await;
1164 let sdk = make_sdk(format!("{}/", server.uri()));
1165 let err = sdk
1166 .kvstore
1167 .get_lists(&GetListsParams::default())
1168 .await
1169 .unwrap_err();
1170 assert!(matches!(err, SdkError::Api { .. }));
1171 }
1172
1173 #[tokio::test]
1174 async fn get_list_success() {
1175 let server = MockServer::start().await;
1176 Mock::given(method("GET"))
1177 .and(path("/lists/my-list"))
1178 .respond_with(ResponseTemplate::new(200).set_body_json(
1179 serde_json::json!({"data": {"items": ["item1", "item2"]}, "cursor": ""}),
1180 ))
1181 .mount(&server)
1182 .await;
1183 let sdk = make_sdk(format!("{}/", server.uri()));
1184 let resp = sdk
1185 .kvstore
1186 .get_list("my-list", &GetListParams::default())
1187 .await
1188 .unwrap();
1189 assert_eq!(resp.data.items, vec!["item1", "item2"]);
1190 }
1191
1192 #[tokio::test]
1193 async fn get_list_not_found() {
1194 let server = MockServer::start().await;
1195 Mock::given(method("GET"))
1196 .and(path("/lists/my-list"))
1197 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1198 .mount(&server)
1199 .await;
1200 let sdk = make_sdk(format!("{}/", server.uri()));
1201 let err = sdk
1202 .kvstore
1203 .get_list("my-list", &GetListParams::default())
1204 .await
1205 .unwrap_err();
1206 assert!(matches!(err, SdkError::Api { .. }));
1207 }
1208
1209 #[tokio::test]
1210 async fn get_list_server_error() {
1211 let server = MockServer::start().await;
1212 Mock::given(method("GET"))
1213 .and(path("/lists/my-list"))
1214 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1215 .mount(&server)
1216 .await;
1217 let sdk = make_sdk(format!("{}/", server.uri()));
1218 let err = sdk
1219 .kvstore
1220 .get_list("my-list", &GetListParams::default())
1221 .await
1222 .unwrap_err();
1223 assert!(matches!(err, SdkError::Api { .. }));
1224 }
1225
1226 #[tokio::test]
1227 async fn update_list_success() {
1228 let server = MockServer::start().await;
1229 Mock::given(method("PATCH"))
1230 .and(path("/lists/my-list"))
1231 .respond_with(
1232 ResponseTemplate::new(200)
1233 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1234 )
1235 .mount(&server)
1236 .await;
1237 let sdk = make_sdk(format!("{}/", server.uri()));
1238 sdk.kvstore
1239 .update_list(
1240 "my-list",
1241 &UpdateListParams {
1242 add_items: Some(vec!["item3".to_string()]),
1243 remove_items: None,
1244 },
1245 )
1246 .await
1247 .unwrap();
1248 }
1249
1250 #[tokio::test]
1254 async fn update_list_wire_body() {
1255 let server = MockServer::start().await;
1256 Mock::given(method("PATCH"))
1257 .and(path("/lists/my-list"))
1258 .and(body_json(serde_json::json!({
1259 "addItems": ["c"],
1260 "removeItems": ["a"]
1261 })))
1262 .respond_with(
1263 ResponseTemplate::new(200)
1264 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1265 )
1266 .mount(&server)
1267 .await;
1268 let sdk = make_sdk(format!("{}/", server.uri()));
1269 sdk.kvstore
1270 .update_list(
1271 "my-list",
1272 &UpdateListParams {
1273 add_items: Some(vec!["c".to_string()]),
1274 remove_items: Some(vec!["a".to_string()]),
1275 },
1276 )
1277 .await
1278 .unwrap();
1279 }
1280
1281 #[tokio::test]
1282 async fn update_list_api_error() {
1283 let server = MockServer::start().await;
1284 Mock::given(method("PATCH"))
1285 .and(path("/lists/my-list"))
1286 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1287 .mount(&server)
1288 .await;
1289 let sdk = make_sdk(format!("{}/", server.uri()));
1290 let err = sdk
1291 .kvstore
1292 .update_list("my-list", &UpdateListParams::default())
1293 .await
1294 .unwrap_err();
1295 assert!(matches!(err, SdkError::Api { .. }));
1296 }
1297
1298 #[tokio::test]
1299 async fn update_list_server_error() {
1300 let server = MockServer::start().await;
1301 Mock::given(method("PATCH"))
1302 .and(path("/lists/my-list"))
1303 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1304 .mount(&server)
1305 .await;
1306 let sdk = make_sdk(format!("{}/", server.uri()));
1307 let err = sdk
1308 .kvstore
1309 .update_list("my-list", &UpdateListParams::default())
1310 .await
1311 .unwrap_err();
1312 assert!(matches!(err, SdkError::Api { .. }));
1313 }
1314
1315 #[tokio::test]
1316 async fn add_list_item_success() {
1317 let server = MockServer::start().await;
1318 Mock::given(method("POST"))
1319 .and(path("/lists/my-list/items"))
1320 .respond_with(
1321 ResponseTemplate::new(201)
1322 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1323 )
1324 .mount(&server)
1325 .await;
1326 let sdk = make_sdk(format!("{}/", server.uri()));
1327 sdk.kvstore
1328 .add_list_item(
1329 "my-list",
1330 &AddListItemParams {
1331 item: "item1".to_string(),
1332 },
1333 )
1334 .await
1335 .unwrap();
1336 }
1337
1338 #[tokio::test]
1339 async fn add_list_item_api_error() {
1340 let server = MockServer::start().await;
1341 Mock::given(method("POST"))
1342 .and(path("/lists/my-list/items"))
1343 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
1344 .mount(&server)
1345 .await;
1346 let sdk = make_sdk(format!("{}/", server.uri()));
1347 let err = sdk
1348 .kvstore
1349 .add_list_item(
1350 "my-list",
1351 &AddListItemParams {
1352 item: "item1".to_string(),
1353 },
1354 )
1355 .await
1356 .unwrap_err();
1357 assert!(matches!(err, SdkError::Api { .. }));
1358 }
1359
1360 #[tokio::test]
1361 async fn add_list_item_server_error() {
1362 let server = MockServer::start().await;
1363 Mock::given(method("POST"))
1364 .and(path("/lists/my-list/items"))
1365 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1366 .mount(&server)
1367 .await;
1368 let sdk = make_sdk(format!("{}/", server.uri()));
1369 let err = sdk
1370 .kvstore
1371 .add_list_item(
1372 "my-list",
1373 &AddListItemParams {
1374 item: "item1".to_string(),
1375 },
1376 )
1377 .await
1378 .unwrap_err();
1379 assert!(matches!(err, SdkError::Api { .. }));
1380 }
1381
1382 #[tokio::test]
1383 async fn list_contains_item_success() {
1384 let server = MockServer::start().await;
1385 Mock::given(method("GET"))
1386 .and(path("/lists/my-list/contains/item1"))
1387 .respond_with(
1388 ResponseTemplate::new(200)
1389 .set_body_json(serde_json::json!({"data": {"exists": true}})),
1390 )
1391 .mount(&server)
1392 .await;
1393 let sdk = make_sdk(format!("{}/", server.uri()));
1394 let resp = sdk
1395 .kvstore
1396 .list_contains_item("my-list", "item1")
1397 .await
1398 .unwrap();
1399 assert!(resp.exists);
1400 }
1401
1402 #[tokio::test]
1403 async fn list_contains_item_not_found() {
1404 let server = MockServer::start().await;
1405 Mock::given(method("GET"))
1406 .and(path("/lists/my-list/contains/item1"))
1407 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1408 .mount(&server)
1409 .await;
1410 let sdk = make_sdk(format!("{}/", server.uri()));
1411 let err = sdk
1412 .kvstore
1413 .list_contains_item("my-list", "item1")
1414 .await
1415 .unwrap_err();
1416 assert!(matches!(err, SdkError::Api { .. }));
1417 }
1418
1419 #[tokio::test]
1420 async fn list_contains_item_server_error() {
1421 let server = MockServer::start().await;
1422 Mock::given(method("GET"))
1423 .and(path("/lists/my-list/contains/item1"))
1424 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1425 .mount(&server)
1426 .await;
1427 let sdk = make_sdk(format!("{}/", server.uri()));
1428 let err = sdk
1429 .kvstore
1430 .list_contains_item("my-list", "item1")
1431 .await
1432 .unwrap_err();
1433 assert!(matches!(err, SdkError::Api { .. }));
1434 }
1435
1436 #[tokio::test]
1437 async fn delete_list_item_success() {
1438 let server = MockServer::start().await;
1439 Mock::given(method("DELETE"))
1440 .and(path("/lists/my-list/items/item1"))
1441 .respond_with(
1442 ResponseTemplate::new(200)
1443 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1444 )
1445 .mount(&server)
1446 .await;
1447 let sdk = make_sdk(format!("{}/", server.uri()));
1448 sdk.kvstore
1449 .delete_list_item("my-list", "item1")
1450 .await
1451 .unwrap();
1452 }
1453
1454 #[tokio::test]
1455 async fn delete_list_item_not_found() {
1456 let server = MockServer::start().await;
1457 Mock::given(method("DELETE"))
1458 .and(path("/lists/my-list/items/item1"))
1459 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1460 .mount(&server)
1461 .await;
1462 let sdk = make_sdk(format!("{}/", server.uri()));
1463 let err = sdk
1464 .kvstore
1465 .delete_list_item("my-list", "item1")
1466 .await
1467 .unwrap_err();
1468 assert!(matches!(err, SdkError::Api { .. }));
1469 }
1470
1471 #[tokio::test]
1472 async fn delete_list_item_server_error() {
1473 let server = MockServer::start().await;
1474 Mock::given(method("DELETE"))
1475 .and(path("/lists/my-list/items/item1"))
1476 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1477 .mount(&server)
1478 .await;
1479 let sdk = make_sdk(format!("{}/", server.uri()));
1480 let err = sdk
1481 .kvstore
1482 .delete_list_item("my-list", "item1")
1483 .await
1484 .unwrap_err();
1485 assert!(matches!(err, SdkError::Api { .. }));
1486 }
1487
1488 #[tokio::test]
1489 async fn delete_list_success() {
1490 let server = MockServer::start().await;
1491 Mock::given(method("DELETE"))
1492 .and(path("/lists/my-list"))
1493 .respond_with(
1494 ResponseTemplate::new(200)
1495 .set_body_json(serde_json::json!({"code": 0, "msg": "ok", "data": null})),
1496 )
1497 .mount(&server)
1498 .await;
1499 let sdk = make_sdk(format!("{}/", server.uri()));
1500 sdk.kvstore.delete_list("my-list").await.unwrap();
1501 }
1502
1503 #[tokio::test]
1504 async fn delete_list_not_found() {
1505 let server = MockServer::start().await;
1506 Mock::given(method("DELETE"))
1507 .and(path("/lists/my-list"))
1508 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1509 .mount(&server)
1510 .await;
1511 let sdk = make_sdk(format!("{}/", server.uri()));
1512 let err = sdk.kvstore.delete_list("my-list").await.unwrap_err();
1513 assert!(matches!(err, SdkError::Api { .. }));
1514 }
1515
1516 #[tokio::test]
1517 async fn delete_list_server_error() {
1518 let server = MockServer::start().await;
1519 Mock::given(method("DELETE"))
1520 .and(path("/lists/my-list"))
1521 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1522 .mount(&server)
1523 .await;
1524 let sdk = make_sdk(format!("{}/", server.uri()));
1525 let err = sdk.kvstore.delete_list("my-list").await.unwrap_err();
1526 assert!(matches!(err, SdkError::Api { .. }));
1527 }
1528}