Skip to main content

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