Skip to main content

casper_rust_wasm_sdk/sdk/rpcs/
get_entity.rs

1#[cfg(all(feature = "js", target_arch = "wasm32"))]
2use crate::types::identifier::block_identifier::BlockIdentifier;
3use crate::{
4    types::{
5        entity_or_account::EntityOrAccount,
6        identifier::{block_identifier::BlockIdentifierInput, entity_identifier::EntityIdentifier},
7        sdk_error::SdkError,
8        verbosity::Verbosity,
9    },
10    SDK,
11};
12use casper_client::{
13    cli::{get_entity as get_entity_cli, parse::entity_identifier as parse_entity_identifier},
14    get_entity as get_entity_lib,
15    rpcs::results::GetAddressableEntityResult as _GetAddressableEntityResult,
16    JsonRpcId, SuccessResponse,
17};
18#[cfg(all(feature = "js", target_arch = "wasm32"))]
19use gloo_utils::format::JsValueSerdeExt;
20use rand::RngExt;
21use serde::{Deserialize, Serialize};
22#[cfg(feature = "js")]
23use wasm_bindgen::prelude::*;
24
25/// Wrapper around Casper Client `GetAddressableEntityResult`.
26#[derive(Debug, Deserialize, Clone, Serialize)]
27#[cfg_attr(feature = "js", wasm_bindgen)]
28pub struct GetAddressableEntityResult(_GetAddressableEntityResult);
29
30impl From<GetAddressableEntityResult> for _GetAddressableEntityResult {
31    fn from(result: GetAddressableEntityResult) -> Self {
32        result.0
33    }
34}
35
36impl From<_GetAddressableEntityResult> for GetAddressableEntityResult {
37    fn from(result: _GetAddressableEntityResult) -> Self {
38        GetAddressableEntityResult(result)
39    }
40}
41
42impl GetAddressableEntityResult {
43    /// Typed `entity_result` (`AddressableEntity` or legacy `Account`).
44    pub fn entity_result_typed(&self) -> EntityOrAccount {
45        self.0.entity_result.clone().into()
46    }
47}
48
49#[cfg(all(feature = "js", target_arch = "wasm32"))]
50#[cfg_attr(feature = "js", wasm_bindgen)]
51impl GetAddressableEntityResult {
52    #[cfg_attr(feature = "js", wasm_bindgen(getter))]
53    #[cfg(feature = "js")]
54    pub fn api_version(&self) -> JsValue {
55        JsValue::from_serde(&self.0.api_version).unwrap()
56    }
57
58    #[cfg_attr(feature = "js", wasm_bindgen(getter))]
59    #[cfg(feature = "js")]
60    pub fn entity_result(&self) -> JsValue {
61        JsValue::from_serde(&self.0.entity_result).unwrap()
62    }
63
64    /// Typed `entity_result` (`AddressableEntity` or legacy `Account`).
65    #[cfg_attr(feature = "js", wasm_bindgen(js_name = "entityResultTyped"))]
66    pub fn entity_result_typed_js(&self) -> EntityOrAccount {
67        self.entity_result_typed()
68    }
69
70    #[cfg_attr(feature = "js", wasm_bindgen(getter))]
71    pub fn merkle_proof(&self) -> String {
72        self.0.merkle_proof.clone()
73    }
74
75    #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toJson"))]
76    #[cfg(feature = "js")]
77    pub fn to_json(&self) -> JsValue {
78        JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
79    }
80}
81
82// Define options for the `get_entity` function
83#[derive(Debug, Deserialize, Clone, Default, Serialize)]
84#[cfg(all(feature = "js", target_arch = "wasm32"))]
85#[cfg_attr(
86    feature = "js",
87    wasm_bindgen(js_name = "getEntityOptions", getter_with_clone)
88)]
89pub struct GetEntityOptions {
90    pub entity_identifier: Option<EntityIdentifier>,
91    pub entity_identifier_as_string: Option<String>,
92    pub maybe_block_id_as_string: Option<String>,
93    pub maybe_block_identifier: Option<BlockIdentifier>,
94    pub rpc_address: Option<String>,
95    pub verbosity: Option<Verbosity>,
96}
97
98#[cfg(all(feature = "js", target_arch = "wasm32"))]
99#[cfg_attr(feature = "js", wasm_bindgen)]
100impl SDK {
101    // Deserialize options for `get_entity` from a JavaScript object
102    #[cfg(feature = "js")]
103    pub fn get_entity_options(&self, options: JsValue) -> Result<GetEntityOptions, JsError> {
104        options
105            .into_serde::<GetEntityOptions>()
106            .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
107    }
108
109    /// Retrieves entity information using the provided options.
110    ///
111    /// This function is an asynchronous JavaScript binding for the Rust `get_entity` method.
112    ///
113    /// # Arguments
114    ///
115    /// * `options` - An optional `GetEntityOptions` struct containing retrieval options, such as:
116    ///   - `entity_identifier`: Identifier for the entity.
117    ///   - `entity_identifier_as_string`: String representation of the entity identifier.
118    ///   - `maybe_block_id_as_string`: Optional string representation of the block ID.
119    ///   - `maybe_block_identifier`: Optional `BlockIdentifierInput` for specifying the block.
120    ///   - `verbosity`: Verbosity level for the output.
121    ///   - `rpc_address`: Address of the node to query.
122    ///
123    /// # Returns
124    ///
125    /// A `Result` containing either a `GetAddressableEntityResult` on success or a `JsError` on failure.
126    ///
127    /// # Errors
128    ///
129    /// Returns a `JsError` if there is an error during the retrieval process, such as issues with the provided options or network errors.
130    /// ```
131    #[cfg_attr(feature = "js", wasm_bindgen(js_name = "get_entity"))]
132    #[cfg(feature = "js")]
133    pub async fn get_entity_js_alias(
134        &self,
135        options: Option<GetEntityOptions>,
136    ) -> Result<GetAddressableEntityResult, JsError> {
137        let GetEntityOptions {
138            entity_identifier,
139            entity_identifier_as_string,
140            maybe_block_id_as_string,
141            maybe_block_identifier,
142            verbosity,
143            rpc_address,
144        } = options.unwrap_or_default();
145
146        let maybe_block_identifier = if let Some(maybe_block_identifier) = maybe_block_identifier {
147            Some(BlockIdentifierInput::BlockIdentifier(
148                maybe_block_identifier,
149            ))
150        } else {
151            maybe_block_id_as_string.map(BlockIdentifierInput::String)
152        };
153
154        let result = self
155            .get_entity(
156                entity_identifier,
157                entity_identifier_as_string,
158                maybe_block_identifier,
159                verbosity,
160                rpc_address,
161            )
162            .await;
163        match result {
164            Ok(data) => Ok(data.result.into()),
165            Err(err) => {
166                let err = &format!("Error occurred with {err:?}");
167                Err(JsError::new(err))
168            }
169        }
170    }
171
172    // JavaScript alias for `get_entity`
173    #[cfg_attr(feature = "js", wasm_bindgen(js_name = "state_get_entity"))]
174    #[cfg(feature = "js")]
175    pub async fn state_get_entity(
176        &self,
177        options: Option<GetEntityOptions>,
178    ) -> Result<GetAddressableEntityResult, JsError> {
179        self.get_entity_js_alias(options).await
180    }
181}
182
183impl SDK {
184    /// Retrieves account information based on the provided options.
185    ///
186    /// # Arguments
187    ///
188    /// * `entity_identifier` - An optional `EntityIdentifier` for specifying the entity identifier.
189    /// * `entity_identifier_as_string` - An optional string representing the entity identifier.
190    /// * `maybe_block_identifier` - An optional `BlockIdentifierInput` for specifying a block identifier.
191    /// * `verbosity` - An optional `Verbosity` level for controlling the output verbosity.
192    /// * `rpc_address` - An optional string specifying the rpc address to use for the request.
193    ///
194    /// # Returns
195    ///
196    /// A `Result` containing either a `SuccessResponse<_GetAddressableEntityResult>` or a `SdkError` in case of an error.
197    ///
198    /// # Errors
199    ///
200    /// Returns a `SdkError` if there is an error during the retrieval process.
201    pub async fn get_entity(
202        &self,
203        entity_identifier: Option<EntityIdentifier>,
204        entity_identifier_as_string: Option<String>,
205        maybe_block_identifier: Option<BlockIdentifierInput>,
206        verbosity: Option<Verbosity>,
207        rpc_address: Option<String>,
208    ) -> Result<SuccessResponse<_GetAddressableEntityResult>, SdkError> {
209        let entity_identifier = if let Some(entity_identifier) = entity_identifier {
210            entity_identifier
211        } else if let Some(entity_identifier_as_string) = entity_identifier_as_string.clone() {
212            match parse_entity_identifier(&entity_identifier_as_string) {
213                Ok(parsed) => parsed.into(),
214                Err(err) => {
215                    return Err(err.into());
216                }
217            }
218        } else {
219            let err = "Error: Missing entity identifier".to_string();
220            return Err(SdkError::InvalidArgument {
221                context: "get_entity",
222                error: err,
223            });
224        };
225        let random_id = rand::rng().random::<u64>().to_string();
226        if let Some(BlockIdentifierInput::String(maybe_block_id)) = maybe_block_identifier {
227            get_entity_cli(
228                &random_id,
229                &self.get_rpc_address(rpc_address),
230                self.get_verbosity(verbosity).into(),
231                &maybe_block_id,
232                &entity_identifier.to_string(),
233            )
234            .await
235            .map_err(SdkError::from)
236        } else {
237            let maybe_block_identifier =
238                if let Some(BlockIdentifierInput::BlockIdentifier(maybe_block_identifier)) =
239                    maybe_block_identifier
240                {
241                    Some(maybe_block_identifier)
242                } else {
243                    None
244                };
245            get_entity_lib(
246                JsonRpcId::from(random_id),
247                &self.get_rpc_address(rpc_address),
248                self.get_verbosity(verbosity).into(),
249                maybe_block_identifier.map(Into::into),
250                entity_identifier.into(),
251            )
252            .await
253            .map_err(SdkError::from)
254        }
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use crate::{
262        helpers::public_key_from_secret_key,
263        types::{identifier::block_identifier::BlockIdentifier, public_key::PublicKey},
264    };
265    use sdk_tests::tests::helpers::{
266        get_enable_addressable_entity, get_network_constants, get_user_secret_key,
267    };
268
269    fn get_entity_identifier() -> EntityIdentifier {
270        let secret_key = get_user_secret_key(None).unwrap();
271        let account = public_key_from_secret_key(&secret_key).unwrap();
272        let public_key = PublicKey::new(&account).unwrap();
273
274        EntityIdentifier::from_entity_under_public_key(public_key)
275    }
276
277    #[tokio::test]
278    async fn test_get_entity_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        let entity_identifier = get_entity_identifier();
283
284        // Act
285        let result = sdk
286            .get_entity(Some(entity_identifier), None, None, None, None)
287            .await;
288
289        // Assert
290        assert!(result.is_err());
291        let err_string = result.err().unwrap().to_string();
292        assert!(err_string.contains(error_message));
293    }
294
295    #[tokio::test]
296    async fn test_get_entity_with_missing_entity() {
297        // Arrange
298        let sdk = SDK::new(None, None, None);
299        let error_message = "Error: Missing entity identifier";
300
301        // Act
302        let result = sdk.get_entity(None, None, None, None, None).await;
303
304        // Assert
305        assert!(result.is_err());
306        let err_string = result.err().unwrap().to_string();
307        assert!(err_string.contains(error_message));
308    }
309
310    #[tokio::test]
311    async fn test_get_entity_with_entity_identifier() {
312        if !get_enable_addressable_entity() {
313            return;
314        }
315        // Arrange
316        let sdk = SDK::new(None, None, None);
317        let entity_identifier = get_entity_identifier();
318        let verbosity = Some(Verbosity::High);
319        let (rpc_address, _, _, _, _) = get_network_constants();
320
321        // Act
322        let result = sdk
323            .get_entity(
324                Some(entity_identifier),
325                None,
326                None,
327                verbosity,
328                Some(rpc_address),
329            )
330            .await;
331        // Assert
332        assert!(result.is_ok());
333        let typed = GetAddressableEntityResult::from(result.unwrap().result).entity_result_typed();
334        assert_eq!(typed.variant(), "AddressableEntity");
335        assert!(typed.as_addressable_entity().is_some());
336    }
337
338    #[tokio::test]
339    async fn test_get_entity_with_entity_identifier_as_string() {
340        if !get_enable_addressable_entity() {
341            return;
342        }
343        // Arrange
344        let sdk = SDK::new(None, None, None);
345        let entity_identifier_as_string = get_entity_identifier().to_string();
346        let verbosity = Some(Verbosity::High);
347        let (rpc_address, _, _, _, _) = get_network_constants();
348
349        // Act
350        let result = sdk
351            .get_entity(
352                None,
353                Some(entity_identifier_as_string),
354                None,
355                verbosity,
356                Some(rpc_address),
357            )
358            .await;
359
360        // Assert
361        assert!(result.is_ok());
362    }
363
364    #[tokio::test]
365    async fn test_get_entity_with_block_identifier() {
366        if !get_enable_addressable_entity() {
367            return;
368        }
369        // Arrange
370        let sdk = SDK::new(None, None, None);
371        let block_identifier =
372            BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(1));
373        let entity_identifier = get_entity_identifier();
374        let verbosity = Some(Verbosity::High);
375        let (rpc_address, _, _, _, _) = get_network_constants();
376
377        // Act
378        let result = sdk
379            .get_entity(
380                Some(entity_identifier),
381                None,
382                Some(block_identifier),
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_entity_with_error() {
394        // Arrange
395        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
396        let entity_identifier = get_entity_identifier();
397        let error_message = "error sending request";
398
399        // Act
400        let result = sdk
401            .get_entity(Some(entity_identifier), None, None, None, None)
402            .await;
403
404        // Assert
405        assert!(result.is_err());
406        let err_string = result.err().unwrap().to_string();
407        assert!(err_string.contains(error_message));
408    }
409}