Skip to main content

libvault_rs/secrets/key_value/
requests.rs

1use super::responses::{KeyValueResponse, KeyValueV1Response, KeyValueV2Response};
2use crate::client::vault::{AuthenticatedVaultClient, VaultClientError};
3
4pub enum KeyValueVersion {
5    One,
6    Two,
7}
8
9pub struct KeyValue {
10    version: KeyValueVersion,
11    mount: String,
12}
13
14impl KeyValue {
15    pub fn new(version: KeyValueVersion, mount: impl Into<String>) -> Self {
16        Self {
17            version,
18            mount: mount.into(),
19        }
20    }
21
22    pub async fn read(
23        &self,
24        vault_client: &AuthenticatedVaultClient,
25        path: &str,
26    ) -> Result<KeyValueResponse, VaultClientError> {
27        let response = vault_client
28            .client
29            .read(
30                self.url(vault_client.client.base_url(), path),
31                Some(vault_client.token.clone()),
32            )
33            .await;
34
35        match response {
36            Err(e) => Err(e),
37            Ok(value) => {
38                match self.version {
39                    KeyValueVersion::One => {
40                        let convert: Result<KeyValueV1Response, serde_json::Error> =
41                            serde_json::from_value(value);
42                        match convert {
43                            Ok(value) => Ok(KeyValueResponse::new(value.data)),
44                            Err(e) => Err(VaultClientError::new(e.to_string())),
45                        }
46                    }
47                    KeyValueVersion::Two => {
48                        let convert: Result<KeyValueV2Response, serde_json::Error> =
49                            serde_json::from_value(value);
50                        match convert {
51                            Ok(value) => Ok(KeyValueResponse::new(value.data.data)),
52                            Err(e) => Err(VaultClientError::new(e.to_string())),
53                        }
54                    }
55                }                        
56            },
57        }
58    }
59
60    fn url(&self, base_url: String, path: &str) -> String {
61        match self.version {
62            KeyValueVersion::One => format!("{}/{}/{}", base_url, self.mount, path),
63            KeyValueVersion::Two => format!("{}/{}/data/{}", base_url, self.mount, path),
64        }
65    }
66}
67
68#[cfg(test)]
69mod test {
70    use super::{KeyValue, KeyValueVersion};
71    use crate::{
72        client::vault::{AuthenticatedVaultClient, Parameters, VaultClient, VaultClientError},
73        secrets::key_value::{
74            test_helpers::{
75                successful_key_value_response, successful_v1_json_response,
76                successful_v2_json_response,
77            },
78        },
79    };
80    use futures::{future::BoxFuture, FutureExt};
81    use serde_json::json;
82    
83
84    struct StubClient {
85        response: serde_json::Value,
86    }
87
88    impl VaultClient for StubClient {
89        fn read(&self, _: String, _: Option<String>) -> BoxFuture<Result<serde_json::Value, VaultClientError>> {
90            async { Ok(self.response.to_owned()) }.boxed()
91        }
92
93        fn create(&self, _: String, _: Option<String>, _: Parameters) -> BoxFuture<Result<serde_json::Value, VaultClientError>> {
94            unimplemented!()
95        }
96
97        fn base_url(&self) -> String {
98            "test".into()
99        }
100    }
101
102    #[tokio::test]
103    async fn v1_success() {
104        let vault_client: StubClient = StubClient {
105            response: successful_v1_json_response(),
106        };
107        let key_value: KeyValue = KeyValue::new(KeyValueVersion::One, "secrets");
108        let authenticated_client: AuthenticatedVaultClient =
109            AuthenticatedVaultClient::new(Box::new(vault_client), "token".into());
110
111        assert_eq!(
112            Ok(successful_key_value_response()),
113            key_value.read(&authenticated_client, "test").await
114        );
115    }
116
117    #[tokio::test]
118    async fn v2_success() {
119        let vault_client: StubClient = StubClient {
120            response: successful_v2_json_response(),
121        };
122        let key_value: KeyValue = KeyValue::new(KeyValueVersion::Two, "secrets");
123        let authenticated_client: AuthenticatedVaultClient =
124            AuthenticatedVaultClient::new(Box::new(vault_client), "token".into());
125
126        assert_eq!(
127            Ok(successful_key_value_response()),
128            key_value.read(&authenticated_client, "test").await
129        );
130    }
131
132    #[tokio::test]
133    async fn v1_deserialization_failure() {
134        let vault_client: StubClient = StubClient {
135            response: json!({}),
136        };
137        let key_value: KeyValue = KeyValue::new(KeyValueVersion::One, "secrets");
138        let authenticated_client: AuthenticatedVaultClient =
139            AuthenticatedVaultClient::new(Box::new(vault_client), "token".into());
140
141        assert_eq!(
142            Err(VaultClientError::new("missing field `data`".into())),
143            key_value.read(&authenticated_client, "test").await
144        );
145    }
146
147    #[tokio::test]
148    async fn v2_deserialization_failure() {
149        let vault_client: StubClient = StubClient {
150            response: json!({}),
151        };
152        let key_value: KeyValue = KeyValue::new(KeyValueVersion::Two, "secrets");
153        let authenticated_client: AuthenticatedVaultClient =
154            AuthenticatedVaultClient::new(Box::new(vault_client), "token".into());
155
156        assert_eq!(
157            Err(VaultClientError::new("missing field `data`".into())),
158            key_value.read(&authenticated_client, "test").await
159        );
160    }
161}