pdk-lock-lib 1.7.0

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

use crate::TryLock;
use pdk_core::classy::extract::context::ConfigureContext;
use pdk_core::classy::extract::{Extract, FromContext};
use pdk_core::classy::{Clock, SharedData};
use pdk_core::policy_context::api::Metadata;
use std::convert::Infallible;
use std::rc::Rc;
use std::time::Duration;

const DEFAULT_EXPIRATION: Duration = Duration::from_secs(60);

/// Represents the base builder for a [`TryLock`] instance.
pub struct LockBuilder {
    clock: Rc<dyn Clock>,
    shared_data: Rc<dyn SharedData>,
    expiration: Duration,
    prefix: String,
}

/// It can injected by the framework during the configuration phase of the policy lifecycle:
///
/// ``` rust
/// #[entrypoint]
/// async fn configure(
///     launcher: Launcher,
///     Configuration(configuration): Configuration,
///     lock_builder: LockBuilder,
/// ) -> Result<()> {
///     // your code here.
/// }
/// ```
impl FromContext<ConfigureContext> for LockBuilder {
    type Error = Infallible;

    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
        let clock: Rc<dyn Clock> = context.extract()?;
        let shared_data: Rc<dyn SharedData> = context.extract()?;
        let metadata: Metadata = context.extract()?;
        Ok(LockBuilder {
            clock,
            shared_data,
            expiration: DEFAULT_EXPIRATION,
            prefix: format!(
                "isolated-lock-{}-{}",
                metadata.policy_metadata.policy_name, metadata.policy_metadata.policy_namespace
            ),
        })
    }
}

impl LockBuilder {
    /// Returns a [`LockBuilderInstance`] with an ID to uniquely identify the lock.
    ///
    /// _Note: if 2 locks are assigned the same ID in a same policy, they will share the state.
    /// This behavior is extended to other policies when using the
    /// [shared lock option](LockBuilderInstance::shared)._
    #[allow(clippy::new_ret_no_self)]
    pub fn new(&self, lock_id: String) -> LockBuilderInstance {
        LockBuilderInstance {
            clock: Rc::clone(&self.clock),
            shared_data: Rc::clone(&self.shared_data),
            expiration: self.expiration,
            prefix: self.prefix.clone(),
            key: lock_id,
        }
    }
}

/// Represents a particular instance of a [`LockBuilder`]. It includes extra metadata of the
/// policy, required to successfully work in all scenarios involving multiple policies.
pub struct LockBuilderInstance {
    clock: Rc<dyn Clock>,
    shared_data: Rc<dyn SharedData>,
    expiration: Duration,
    prefix: String,
    key: String,
}

impl LockBuilderInstance {
    /// Sets the time expiration time for the lock. An acquired lock will be considered lost
    /// once this time passes even if it was not released.
    pub fn expiration(mut self, expiration: Duration) -> Self {
        self.expiration = expiration;
        self
    }

    /// Indicates that the lock should not be isolated to the policy. The state will be shared
    /// across different policy instances and different policies that use the same lock ID.
    pub fn shared(mut self) -> Self {
        self.prefix = "shared-lock".to_string();
        self
    }

    /// Builds a new [`TryLock`] with the corresponding specifications.
    pub fn build(self) -> TryLock {
        TryLock::new(
            format!("{}-{}", self.prefix, self.key),
            self.expiration,
            self.clock,
            self.shared_data,
        )
    }
}