pdk-contracts-lib 1.9.1-alpha.2

PDK Contracts Library
Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use crate::api::validator::{PollerError, PollerType};
use crate::implementation::constants::PRIMARY_INACTIVE_TIMEOUT;
use crate::implementation::model::contracts_storage::{
    ContractsLocalStorage, ContractsState, ContractsStorage,
};
use data_storage_lib::{DataStorage, DataStorageError, RemoteDataStorage, StoreMode};
use lock_lib::Lock;
use pdk_core::classy::Clock;
use pdk_core::log::debug;
use std::rc::Rc;

const CONTRACTS_KEY: &str = "contracts";

pub struct ContractsCache {
    clock: Rc<dyn Clock>,
    remote: RemoteDataStorage,
    local: Rc<ContractsLocalStorage>,
}

impl ContractsCache {
    pub fn new(
        clock: Rc<dyn Clock>,
        remote: RemoteDataStorage,
        local: Rc<ContractsLocalStorage>,
    ) -> Self {
        Self {
            clock,
            remote,
            local,
        }
    }

    pub async fn get_state(&self) -> Option<ContractsState> {
        match self.remote.get::<ContractsState>(CONTRACTS_KEY).await {
            Ok(Some((state, _))) => Some(state),
            Err(e) => {
                debug!("Failed to get contracts state from the cache. {e}");
                None
            }
            _ => {
                debug!("No state saved in the cache.");
                None
            }
        }
    }

    pub async fn try_primary(&self, api_lock: &'_ Lock<'_>) -> Result<PollerType, PollerError> {
        debug!("Trying to become primary polling node.");

        let state = self
            .remote
            .get::<ContractsState>(CONTRACTS_KEY)
            .await
            .map_err(|e| {
                debug!("Failed to get contracts state from the cache. {e}");
                PollerError::DataStorageError
            })?;

        if !api_lock.refresh_lock() {
            debug!("Lost the api lock while getting contracts state from the cache.");
            return Err(PollerError::LostLock);
        }

        let mode = match state {
            None => {
                debug!("No current primary found.");
                StoreMode::Absent
            }
            Some((value, cas)) => {
                if value.primary() + PRIMARY_INACTIVE_TIMEOUT > self.clock.get_current_time() {
                    debug!("Primary found, will become Secondary.");
                    self.local.set_primary(false);
                    self.local.set_primary_update(value.primary());
                    return Ok(PollerType::Secondary);
                }
                debug!("Expired primary found.");
                StoreMode::Cas(cas)
            }
        };

        let time = self.clock.get_current_time();
        let mut local_state = self.local.get_state();
        local_state.update_primary(time);

        let response = self.remote.store(CONTRACTS_KEY, &mode, &local_state).await;

        let response = match response {
            Ok(()) => {
                debug!("Successfully became primary.");
                self.local.set_primary(true);
                self.local.set_primary_update(time);
                Ok(PollerType::Primary)
            }
            Err(DataStorageError::CasMismatch) => {
                debug!("Another node became primary.");
                self.local.set_primary(false);
                self.local.set_primary_update(time);
                Ok(PollerType::Secondary)
            }
            Err(e) => {
                debug!("Failed to store contracts update to the cache. {e}");
                Err(PollerError::DataStorageError)
            }
        };

        if !api_lock.refresh_lock() {
            debug!("Lost the api lock while storing contracts state in the cache.");
        }

        response
    }

    pub async fn save_state(&self, state: ContractsState) -> bool {
        debug!("Storing contracts state in the cache.");
        if let Err(result) = self
            .remote
            .store(CONTRACTS_KEY, &StoreMode::Always, &state)
            .await
        {
            debug!("Failed to store contracts state in the cache. {result}");
            false
        } else {
            debug!("Successfully stored contracts state in the cache.");
            true
        }
    }
}