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::implementation::model::abort;
use crate::implementation::platform::shared::{Contract, ContractsRequestParams};
use crate::ClientId;
use pdk_core::classy::shared_data::concurrent_shared_data::ConcurrentSharedData;
use pdk_core::classy::{Clock, SharedData};
use pdk_core::log::debug;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::rc::Rc;
use std::str;
use std::time::SystemTime;

pub trait ContractsStorage {
    fn api_lock_key(&self) -> String;
    fn last_primary_update(&self) -> Option<SystemTime>;
    fn last_update(&self) -> Option<SystemTime>;

    fn save_contracts_request_params(&self, params: ContractsRequestParams);
    fn get_contracts_request_params(&self) -> Option<ContractsRequestParams>;

    fn save_contract(&self, contract: Contract);
    fn remove_contract(&self, contract_id: &str);

    fn get_contract_by_client(&self, client_id: &ClientId) -> Option<Contract>;

    fn get_state(&self) -> ContractsState;
    fn set_state(&self, state: ContractsState);
}

#[derive(Serialize, Deserialize, Clone)]
pub struct ContractsState {
    primary: SystemTime,
    last_update: Option<SystemTime>,
    contracts: Vec<Contract>,
    params: Option<ContractsRequestParams>,
}

impl ContractsState {
    pub fn update_primary(&mut self, now: SystemTime) {
        self.primary = now;
    }

    pub fn primary(&self) -> SystemTime {
        self.primary
    }

    pub fn new(
        primary: SystemTime,
        last_update: Option<SystemTime>,
        contracts: Vec<Contract>,
        params: Option<ContractsRequestParams>,
    ) -> Self {
        Self {
            primary,
            last_update,
            contracts,
            params,
        }
    }
}

pub struct ContractsLocalStorage {
    prefix: String,
    clock: Rc<dyn Clock>,
    shared_data: ConcurrentSharedData,
    reserved_keys: Vec<String>,
}

impl ContractsLocalStorage {
    pub fn new(prefix: &str, clock: Rc<dyn Clock>, shared_data: Rc<dyn SharedData>) -> Self {
        let mut result = Self {
            prefix: format!("{prefix}-CONTRACTS"),
            clock: Rc::clone(&clock),
            shared_data: ConcurrentSharedData::new(clock, shared_data),
            reserved_keys: vec![],
        };

        result.reserved_keys.push(result.api_update_key());
        result.reserved_keys.push(result.api_params_key());
        result.reserved_keys.push(result.api_lock_key());
        result.reserved_keys.push(result.primary_key());
        result.reserved_keys.push(result.primary_update_key());

        result
    }

    pub fn update_last(&self) {
        self.shared_data
            .insert(self.api_update_key(), self.clock.get_current_time(), abort);
    }

    pub fn set_primary_update(&self, time: SystemTime) {
        self.shared_data
            .insert(self.primary_update_key(), time, abort);
    }

    pub fn is_primary(&self) -> Option<bool> {
        self.shared_data.get(self.primary_key().as_str())
    }

    pub fn set_primary(&self, primary: bool) {
        self.shared_data.insert(self.primary_key(), primary, abort);
    }

    fn contract_key(&self, contract_id: &str) -> String {
        format!("{}_{}", self.prefix, contract_id)
    }

    fn api_update_key(&self) -> String {
        format!("{}_UPDATE", self.prefix)
    }

    fn api_params_key(&self) -> String {
        format!("{}_PARAMS", self.prefix)
    }

    fn primary_key(&self) -> String {
        format!("{}_PRIMARY", self.prefix)
    }

    fn primary_update_key(&self) -> String {
        format!("{}_PRIMARY_UPDATE", self.prefix)
    }
}

impl ContractsStorage for ContractsLocalStorage {
    fn api_lock_key(&self) -> String {
        format!("{}_LOCK", self.prefix)
    }

    fn last_primary_update(&self) -> Option<SystemTime> {
        self.shared_data.get(self.primary_update_key().as_str())
    }

    fn last_update(&self) -> Option<SystemTime> {
        self.shared_data.get(self.api_update_key().as_str())
    }

    fn save_contracts_request_params(&self, params: ContractsRequestParams) {
        self.shared_data
            .insert(self.api_params_key(), params, abort);
    }

    fn get_contracts_request_params(&self) -> Option<ContractsRequestParams> {
        self.shared_data.get(self.api_params_key().as_str())
    }

    fn save_contract(&self, contract: Contract) {
        let key = self.contract_key(&contract.client_id);
        self.shared_data.insert(key, contract, abort);
    }

    fn remove_contract(&self, contract_id: &str) {
        let key = self.contract_key(contract_id);
        self.shared_data.remove::<Contract>(key.as_str());
    }

    fn get_contract_by_client(&self, client_id: &ClientId) -> Option<Contract> {
        let key = self.contract_key(client_id.as_str());
        self.shared_data
            .get::<Contract>(&key)
            .filter(|contract| !contract.removed)
    }

    fn get_state(&self) -> ContractsState {
        let contracts = self
            .shared_data
            .keys()
            .into_iter()
            .filter(|key| key.starts_with(&self.prefix) && !self.reserved_keys.contains(key))
            .flat_map(|key| self.shared_data.get::<Contract>(&key))
            .filter(|contract| !contract.removed)
            .collect();

        ContractsState::new(
            self.last_primary_update()
                .unwrap_or(self.clock.get_current_time()),
            self.last_update(),
            contracts,
            self.get_contracts_request_params(),
        )
    }

    fn set_state(&self, state: ContractsState) {
        self.set_primary_update(state.primary);

        let Some(time) = state.last_update else {
            debug!("No last update, current state corresponds to primary assignation event. Skipping update.");
            return;
        };

        debug!("Updating state from cache.");

        self.shared_data.insert(self.api_update_key(), time, abort);

        let mut old_contracts: HashSet<String> = self
            .shared_data
            .keys()
            .into_iter()
            .filter(|key| key.starts_with(&self.prefix) && !self.reserved_keys.contains(key))
            .collect();

        state.contracts.into_iter().for_each(|c| {
            debug!("Updating contract {}", c.client_id);
            let key = self.contract_key(&c.client_id);
            old_contracts.remove(&key);
            self.shared_data.insert(key, c, abort);
        });

        old_contracts.iter().for_each(|key| {
            debug!("Removing old contract {key}");
            self.shared_data.remove::<Contract>(key);
        });

        state
            .params
            .into_iter()
            .for_each(|params| self.save_contracts_request_params(params));
    }
}