casper_rust_wasm_sdk/sdk/rpcs/
get_dictionary_item.rs

1use crate::types::digest::Digest;
2use crate::{
3    types::{
4        deploy_params::dictionary_item_str_params::{
5            dictionary_item_str_params_to_casper_client, DictionaryItemStrParams,
6        },
7        digest::ToDigest,
8        identifier::dictionary_item_identifier::DictionaryItemIdentifier,
9        sdk_error::SdkError,
10        verbosity::Verbosity,
11    },
12    SDK,
13};
14use casper_client::{
15    cli::get_dictionary_item as get_dictionary_item_cli,
16    get_dictionary_item as get_dictionary_item_lib,
17    rpcs::results::GetDictionaryItemResult as _GetDictionaryItemResult, JsonRpcId, SuccessResponse,
18};
19#[cfg(target_arch = "wasm32")]
20use gloo_utils::format::JsValueSerdeExt;
21use rand::Rng;
22#[cfg(target_arch = "wasm32")]
23use serde::{Deserialize, Serialize};
24#[cfg(target_arch = "wasm32")]
25use wasm_bindgen::prelude::*;
26
27// Define a struct to wrap the GetDictionaryItemResult
28#[cfg(target_arch = "wasm32")]
29#[derive(Debug, Deserialize, Clone, Serialize)]
30#[wasm_bindgen]
31pub struct GetDictionaryItemResult(_GetDictionaryItemResult);
32
33#[cfg(target_arch = "wasm32")]
34impl From<GetDictionaryItemResult> for _GetDictionaryItemResult {
35    fn from(result: GetDictionaryItemResult) -> Self {
36        result.0
37    }
38}
39#[cfg(target_arch = "wasm32")]
40impl From<_GetDictionaryItemResult> for GetDictionaryItemResult {
41    fn from(result: _GetDictionaryItemResult) -> Self {
42        GetDictionaryItemResult(result)
43    }
44}
45
46#[cfg(target_arch = "wasm32")]
47#[wasm_bindgen]
48impl GetDictionaryItemResult {
49    /// Gets the API version as a JsValue.
50    #[wasm_bindgen(getter)]
51    pub fn api_version(&self) -> JsValue {
52        JsValue::from_serde(&self.0.api_version).unwrap()
53    }
54
55    /// Gets the dictionary key as a String.
56    #[wasm_bindgen(getter)]
57    pub fn dictionary_key(&self) -> String {
58        self.0.dictionary_key.clone()
59    }
60
61    /// Gets the stored value as a JsValue.
62    #[wasm_bindgen(getter)]
63    pub fn stored_value(&self) -> JsValue {
64        JsValue::from_serde(&self.0.stored_value).unwrap()
65    }
66
67    /// Gets the merkle proof as a String.
68    #[wasm_bindgen(getter)]
69    pub fn merkle_proof(&self) -> String {
70        self.0.merkle_proof.clone()
71    }
72
73    /// Converts the GetDictionaryItemResult to a JsValue.
74    #[wasm_bindgen(js_name = "toJson")]
75    pub fn to_json(&self) -> JsValue {
76        JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
77    }
78}
79
80/// Options for the `get_dictionary_item` method.
81#[derive(Default, Debug, Deserialize, Clone, Serialize)]
82#[cfg(target_arch = "wasm32")]
83#[wasm_bindgen(js_name = "getDictionaryItemOptions", getter_with_clone)]
84pub struct GetDictionaryItemOptions {
85    pub state_root_hash_as_string: Option<String>,
86    pub state_root_hash: Option<Digest>,
87    pub dictionary_item_params: Option<DictionaryItemStrParams>,
88    pub dictionary_item_identifier: Option<DictionaryItemIdentifier>,
89    pub rpc_address: Option<String>,
90    pub verbosity: Option<Verbosity>,
91}
92
93#[cfg(target_arch = "wasm32")]
94#[wasm_bindgen]
95impl SDK {
96    /// Parses dictionary item options from a JsValue.
97    ///
98    /// # Arguments
99    ///
100    /// * `options` - A JsValue containing dictionary item options to be parsed.
101    ///
102    /// # Returns
103    ///
104    /// Parsed dictionary item options as a `GetDictionaryItemOptions` struct.
105    pub fn get_dictionary_item_options(
106        &self,
107        options: JsValue,
108    ) -> Result<GetDictionaryItemOptions, JsError> {
109        options
110            .into_serde::<GetDictionaryItemOptions>()
111            .map_err(|err| JsError::new(&format!("Error deserializing options: {:?}", err)))
112    }
113
114    /// Retrieves dictionary item information using the provided options.
115    ///
116    /// # Arguments
117    ///
118    /// * `options` - An optional `GetDictionaryItemOptions` struct containing retrieval options.
119    ///
120    /// # Returns
121    ///
122    /// A `Result` containing either a `GetDictionaryItemResult` or a `JsError` in case of an error.
123    ///
124    /// # Errors
125    ///
126    /// Returns a `JsError` if there is an error during the retrieval process.
127    #[wasm_bindgen(js_name = "get_dictionary_item")]
128    pub async fn get_dictionary_item_js_alias(
129        &self,
130        options: Option<GetDictionaryItemOptions>,
131    ) -> Result<GetDictionaryItemResult, JsError> {
132        let GetDictionaryItemOptions {
133            state_root_hash_as_string,
134            state_root_hash,
135            dictionary_item_params,
136            dictionary_item_identifier,
137            verbosity,
138            rpc_address,
139        } = options.unwrap_or_default();
140
141        let dictionary_item = if let Some(identifier) = dictionary_item_identifier {
142            DictionaryItemInput::Identifier(identifier)
143        } else if let Some(params) = dictionary_item_params {
144            DictionaryItemInput::Params(params)
145        } else {
146            let err = "Error: Missing dictionary item identifier or params";
147            return Err(JsError::new(err));
148        };
149
150        let result = if let Some(hash) = state_root_hash {
151            self.get_dictionary_item(dictionary_item, Some(hash), verbosity, rpc_address)
152                .await
153        } else if let Some(hash) = state_root_hash_as_string.clone() {
154            self.get_dictionary_item(dictionary_item, Some(hash.as_str()), verbosity, rpc_address)
155                .await
156        } else {
157            self.get_dictionary_item(dictionary_item, None::<&str>, verbosity, rpc_address)
158                .await
159        };
160
161        match result {
162            Ok(data) => Ok(data.result.into()),
163            Err(err) => {
164                let err = &format!("Error occurred with {:?}", err);
165                Err(JsError::new(err))
166            }
167        }
168    }
169
170    /// JavaScript Alias for `get_dictionary_item`
171    #[deprecated(note = "This function is an alias. Please use `get_dictionary_item` instead.")]
172    #[allow(deprecated)]
173    pub async fn state_get_dictionary_item(
174        &self,
175        options: Option<GetDictionaryItemOptions>,
176    ) -> Result<GetDictionaryItemResult, JsError> {
177        self.get_dictionary_item_js_alias(options).await
178    }
179}
180#[derive(Debug, Clone)]
181pub enum DictionaryItemInput {
182    Identifier(DictionaryItemIdentifier),
183    Params(DictionaryItemStrParams),
184}
185
186impl SDK {
187    /// Retrieves dictionary item information based on the provided options.
188    ///
189    /// # Arguments
190    ///
191    /// * `state_root_hash` - A `ToDigest` implementation for specifying the state root hash.
192    /// * `dictionary_item` - A `DictionaryItemInput` enum specifying the dictionary item to retrieve.
193    /// * `verbosity` - An optional `Verbosity` level for controlling the output verbosity.
194    /// * `rpc_address` - An optional string specifying the rpc address to use for the request.
195    ///
196    /// # Returns
197    ///
198    /// A `Result` containing either a `_GetDictionaryItemResult` or a `SdkError` in case of an error.
199    ///
200    /// # Errors
201    ///
202    /// Returns a `SdkError` if there is an error during the retrieval process.
203    pub async fn get_dictionary_item(
204        &self,
205        dictionary_item_input: DictionaryItemInput,
206        state_root_hash: Option<impl ToDigest>,
207        verbosity: Option<Verbosity>,
208        rpc_address: Option<String>,
209    ) -> Result<SuccessResponse<_GetDictionaryItemResult>, SdkError> {
210        // log("state_get_dictionary_item!");
211
212        let state_root_hash = if let Some(state_root_hash) = state_root_hash {
213            if state_root_hash.is_empty() {
214                let state_root_hash = self
215                    .get_state_root_hash(
216                        None,
217                        None,
218                        Some(self.get_rpc_address(rpc_address.clone())),
219                    )
220                    .await;
221
222                match state_root_hash {
223                    Ok(state_root_hash) => {
224                        let state_root_hash: Digest =
225                            state_root_hash.result.state_root_hash.unwrap().into();
226                        state_root_hash
227                    }
228                    Err(_) => "".to_digest(),
229                }
230            } else {
231                state_root_hash.to_digest()
232            }
233        } else {
234            let state_root_hash = self
235                .get_state_root_hash(None, None, Some(self.get_rpc_address(rpc_address.clone())))
236                .await;
237
238            match state_root_hash {
239                Ok(state_root_hash) => {
240                    let state_root_hash: Digest =
241                        state_root_hash.result.state_root_hash.unwrap().into();
242                    state_root_hash
243                }
244                Err(_) => "".to_digest(),
245            }
246        };
247
248        match dictionary_item_input {
249            DictionaryItemInput::Params(dictionary_item_params) => get_dictionary_item_cli(
250                &rand::thread_rng().gen::<u64>().to_string(),
251                &self.get_rpc_address(rpc_address),
252                self.get_verbosity(verbosity).into(),
253                &state_root_hash.to_string(),
254                dictionary_item_str_params_to_casper_client(&dictionary_item_params),
255            )
256            .await
257            .map_err(SdkError::from),
258            DictionaryItemInput::Identifier(dictionary_item_identifier) => get_dictionary_item_lib(
259                JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
260                &self.get_rpc_address(rpc_address),
261                self.get_verbosity(verbosity).into(),
262                state_root_hash.into(),
263                dictionary_item_identifier.into(),
264            )
265            .await
266            .map_err(SdkError::from),
267        }
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use crate::get_dictionary_item;
275    use sdk_tests::tests::helpers::get_network_constants;
276
277    #[tokio::test]
278    async fn test_get_dictionary_item_with_none_values() {
279        // Arrange
280        let sdk = SDK::new(None, None, None);
281        let error_message = "failed to parse node address as valid URL";
282
283        // Act
284        let result = sdk
285            .get_dictionary_item(
286                get_dictionary_item(false).await,
287                Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), // get_dictionary_item does not support empty string as state_root_hash
288                None,
289                None,
290            )
291            .await;
292
293        // Assert
294        assert!(result.is_err());
295        let err_string = result.err().unwrap().to_string();
296        assert!(err_string.contains(error_message));
297    }
298
299    #[tokio::test]
300    async fn test_get_dictionary_item_with_state_root_hash() {
301        // Arrange
302        let sdk = SDK::new(None, None, None);
303        let verbosity = Some(Verbosity::High);
304        let (rpc_address, _, _, _, _) = get_network_constants();
305        let dictionary_item = get_dictionary_item(false).await;
306        let state_root_hash: Digest = sdk
307            .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
308            .await
309            .unwrap()
310            .result
311            .state_root_hash
312            .unwrap()
313            .into();
314
315        // Act
316        let result = sdk
317            .get_dictionary_item(
318                dictionary_item,
319                Some(state_root_hash),
320                verbosity,
321                Some(rpc_address),
322            )
323            .await;
324
325        // Assert
326        assert!(result.is_ok());
327    }
328
329    #[tokio::test]
330    async fn test_get_dictionary_item_with_empty_state_root_hash() {
331        // Arrange
332        let sdk = SDK::new(None, None, None);
333        let verbosity = Some(Verbosity::High);
334        let (rpc_address, _, _, _, _) = get_network_constants();
335
336        // Act
337        let result = sdk
338            .get_dictionary_item(
339                get_dictionary_item(false).await,
340                None::<&str>,
341                verbosity,
342                Some(rpc_address),
343            )
344            .await;
345
346        // Assert
347        assert!(result.is_ok());
348    }
349
350    #[tokio::test]
351    async fn test_get_dictionary_item_with_valid_identifier_input() {
352        // Arrange
353        let sdk = SDK::new(None, None, None);
354        let verbosity = Some(Verbosity::High);
355        let (rpc_address, _, _, _, _) = get_network_constants();
356
357        // Act
358        let result = sdk
359            .get_dictionary_item(
360                get_dictionary_item(false).await,
361                None::<&str>,
362                verbosity,
363                Some(rpc_address),
364            )
365            .await;
366
367        // Assert
368        assert!(result.is_ok());
369    }
370
371    #[tokio::test]
372    async fn test_get_dictionary_item_with_valid_params_input() {
373        // Arrange
374        let sdk = SDK::new(None, None, None);
375        let verbosity = Some(Verbosity::High);
376        let (rpc_address, _, _, _, _) = get_network_constants();
377
378        // Act
379        let result = sdk
380            .get_dictionary_item(
381                get_dictionary_item(true).await,
382                None::<&str>,
383                verbosity,
384                Some(rpc_address),
385            )
386            .await;
387
388        // Assert
389        assert!(result.is_ok());
390    }
391
392    #[tokio::test]
393    async fn test_get_dictionary_item_with_invalid_params_input() {
394        // Arrange
395        let sdk = SDK::new(None, None, None);
396        let verbosity = Some(Verbosity::High);
397        let (rpc_address, _, _, _, _) = get_network_constants();
398
399        let error_message =
400            "Failed to parse dictionary item address as a key: unknown prefix for key";
401
402        let state_root_hash = "";
403        let params = DictionaryItemStrParams::new();
404
405        // Act
406        let result = sdk
407            .get_dictionary_item(
408                DictionaryItemInput::Params(params),
409                Some(state_root_hash),
410                verbosity,
411                Some(rpc_address),
412            )
413            .await;
414
415        // Assert
416        assert!(result.is_err());
417        let err_string = result.err().unwrap().to_string();
418        assert!(err_string.contains(error_message));
419    }
420
421    #[tokio::test]
422    async fn test_get_dictionary_item_with_error() {
423        // Arrange
424        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
425        let error_message = "error sending request for url (http://localhost/rpc)";
426
427        // Act
428        let result = sdk
429            .get_dictionary_item(
430                get_dictionary_item(false).await,
431                Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), // get_dictionary_item does not support empty string as state_root_hash
432                None,
433                None,
434            )
435            .await;
436
437        // Assert
438        assert!(result.is_err());
439        let err_string = result.err().unwrap().to_string();
440        assert!(err_string.contains(error_message));
441    }
442}