sol-chainsaw 0.0.2

Deserializing Solana accounts using their progam IDL
Documentation
use std::collections::{HashMap, HashSet};

use crate::{
    errors::{ChainsawError, ChainsawResult},
    IdlProvider,
};

#[cfg(feature = "json")]
mod json_uses {
    pub use crate::json::{JsonAccountsDeserializer, JsonSerializationOpts};
    pub use std::fmt::Write;
}
#[cfg(feature = "json")]
use json_uses::*;

#[cfg(feature = "bson")]
mod bson_uses {
    pub use crate::bson::{BsonAccountsDeserializer, BsonSerializationOpts};
    pub use bson::raw::RawBson;
}
#[cfg(feature = "bson")]
use bson_uses::*;

/// Setup to  deserialize accounts for a given program. The accounts are expected to have been
/// serialized using the [borsh] format.
///
/// Uses deserializers defined inside [deserializer] modules under the hood in order to resolve the
/// appropriate [borsh] deserializers for each field.
pub struct ChainsawDeserializer<'opts> {
    #[cfg(feature = "json")]
    /// The deserializers for accounts of for each program
    json_account_deserializers:
        HashMap<String, JsonAccountsDeserializer<'opts>>,

    /// The [JsonSerializationOpts] specifying how specific data types should be deserialized.
    #[cfg(feature = "json")]
    json_serialization_opts: &'opts JsonSerializationOpts,

    #[cfg(feature = "bson")]
    /// The deserializers for accounts of for each program
    bson_account_deserializers:
        HashMap<String, BsonAccountsDeserializer<'opts>>,
    ///
    /// The [BsonSerializationOpts] specifying how specific data types should be deserialized.
    #[cfg(feature = "bson")]
    bson_serialization_opts: &'opts BsonSerializationOpts,
}

impl<'opts> ChainsawDeserializer<'opts> {
    /// Creates an instance of a [ChainsawDeserializer].
    /// Make sure to use [ChainsawDeserializer::add_idl_json] for each program _before_ attempting
    /// to deserialize accounts for it.
    ///
    /// - [serialization_opts] specifying how specific data types should be deserialized.
    pub fn new(
        #[cfg(feature = "json")]
        json_serialization_opts: &'opts JsonSerializationOpts,
        #[cfg(feature = "bson")]
        bson_serialization_opts: &'opts BsonSerializationOpts,
    ) -> Self {
        Self {
            #[cfg(feature = "json")]
            json_account_deserializers: HashMap::new(),
            #[cfg(feature = "json")]
            json_serialization_opts,

            #[cfg(feature = "bson")]
            bson_account_deserializers: HashMap::new(),
            #[cfg(feature = "bson")]
            bson_serialization_opts,
        }
    }

    /// Parses an [IDL] specification from the provided [idl_json] for the [program_id] and adds a
    /// to the json/bson accounts deserializer derived from it.
    pub fn add_idl_json(
        &mut self,
        program_id: String,
        idl_json: &str,
        provider: IdlProvider,
    ) -> ChainsawResult<()> {
        #[cfg(feature = "json")]
        {
            #[cfg(feature = "bson")]
            let (provider, program_id) = (provider.clone(), program_id.clone());

            let json_deserializer = JsonAccountsDeserializer::try_from_idl(
                idl_json,
                provider,
                self.json_serialization_opts,
            )?;
            self.json_account_deserializers
                .insert(program_id, json_deserializer);
        }
        #[cfg(feature = "bson")]
        {
            let bson_deserializer = BsonAccountsDeserializer::try_from_idl(
                idl_json,
                provider,
                self.bson_serialization_opts,
            )?;
            self.bson_account_deserializers
                .insert(program_id, bson_deserializer);
        }
        Ok(())
    }

    #[cfg(any(feature = "json", feature = "bson"))]
    pub fn account_name(
        &self,
        program_id: &str,
        discriminator: &crate::DiscriminatorBytes,
    ) -> Option<&str> {
        #[cfg(feature = "json")]
        {
            self.json_account_deserializers
                .get(program_id)
                .unwrap()
                .account_name(discriminator)
        }
        #[cfg(all(feature = "bson", not(feature = "json")))]
        {
            self.bson_account_deserializers
                .get(program_id)
                .unwrap()
                .account_name(discriminator)
        }
    }

    /// Returns `true` if the IDL of the given [program_id] has been added to the deserializer.
    #[cfg(any(feature = "json", feature = "bson"))]
    pub fn has_idl(&self, program_id: &str) -> bool {
        #[cfg(feature = "json")]
        {
            self.json_account_deserializers.contains_key(program_id)
        }
        #[cfg(all(feature = "bson", not(feature = "json")))]
        {
            self.bson_account_deserializers.contains_key(program_id)
        }
    }

    /// Returns all program ids for which IDLs have been added to the deserializer.
    #[cfg(any(feature = "json", feature = "bson"))]
    pub fn added_idls(&self) -> HashSet<String> {
        #[cfg(feature = "json")]
        {
            self.json_account_deserializers.keys().cloned().collect()
        }
        #[cfg(all(feature = "bson", not(feature = "json")))]
        {
            self.bson_account_deserializers.keys().cloned().collect()
        }
    }

    /// Deserializes an account to a JSON string.
    ///
    /// In order to specify a custom [Write] writer, i.e. a socket connection to write to, use
    /// [deserialize_account] instead.
    ///
    /// - [program_id] is the program id of progra that owns the account.
    ///   make sure to add it's IDL before via [ChainsawDeserializer::add_idl_json].
    /// - [account_data] is the raw account data as a byte array
    #[cfg(feature = "json")]
    pub fn deserialize_account_to_json_string(
        &self,
        program_id: &str,
        account_data: &mut &[u8],
    ) -> ChainsawResult<String> {
        let mut f = String::new();
        self.deserialize_account_to_json(program_id, account_data, &mut f)?;
        Ok(f)
    }

    /// Deserializes an account and writes the resulting JSON to the provided [Write] write [f].
    ///
    /// - [program_id] is the program id of progra that owns the account.
    ///   make sure to add it's IDL before via [ChainsawDeserializer::add_idl_json].
    /// - [account_data] is the raw account data as a byte array
    /// - [f] is the [Write] writer to write the resulting JSON to, i.e. `std::io::stdout()` or
    /// `String::new()`
    #[cfg(feature = "json")]
    pub fn deserialize_account_to_json<W: Write>(
        &self,
        program_id: &str,
        account_data: &mut &[u8],
        f: &mut W,
    ) -> ChainsawResult<()> {
        let deserializer = self
            .json_account_deserializers
            .get(program_id)
            .ok_or_else(|| {
                ChainsawError::CannotFindAccountDeserializerForProgramId(
                    program_id.to_string(),
                )
            })?;

        deserializer.deserialize_account_data(account_data, f)?;
        Ok(())
    }

    /// Deserializes an account and writes it to raw bson which it returns.
    ///
    /// - [program_id] is the program id of progra that owns the account.
    ///   make sure to add it's IDL before via [ChainsawDeserializer::add_idl_json].
    /// - [account_data] is the raw account data as a byte array
    #[cfg(feature = "bson")]
    pub fn deserialize_account_to_bson(
        &self,
        program_id: &str,
        account_data: &mut &[u8],
    ) -> ChainsawResult<RawBson> {
        let deserializer = self
            .bson_account_deserializers
            .get(program_id)
            .ok_or_else(|| {
                ChainsawError::CannotFindAccountDeserializerForProgramId(
                    program_id.to_string(),
                )
            })?;

        deserializer.deserialize_account_data(account_data)
    }
}