Skip to main content

pdk_lock_lib/
builder.rs

1// Copyright (c) 2026, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5use crate::TryLock;
6use pdk_core::classy::extract::context::ConfigureContext;
7use pdk_core::classy::extract::{Extract, FromContext};
8use pdk_core::classy::{Clock, SharedData};
9use pdk_core::policy_context::api::Metadata;
10use std::convert::Infallible;
11use std::rc::Rc;
12use std::time::Duration;
13
14const DEFAULT_EXPIRATION: Duration = Duration::from_secs(60);
15
16/// Represents the base builder for a [`TryLock`] instance.
17pub struct LockBuilder {
18    clock: Rc<dyn Clock>,
19    shared_data: Rc<dyn SharedData>,
20    expiration: Duration,
21    prefix: String,
22}
23
24/// It can injected by the framework during the configuration phase of the policy lifecycle:
25///
26/// ``` rust
27/// #[entrypoint]
28/// async fn configure(
29///     launcher: Launcher,
30///     Configuration(configuration): Configuration,
31///     lock_builder: LockBuilder,
32/// ) -> Result<()> {
33///     // your code here.
34/// }
35/// ```
36impl FromContext<ConfigureContext> for LockBuilder {
37    type Error = Infallible;
38
39    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
40        let clock: Rc<dyn Clock> = context.extract()?;
41        let shared_data: Rc<dyn SharedData> = context.extract()?;
42        let metadata: Metadata = context.extract()?;
43        Ok(LockBuilder {
44            clock,
45            shared_data,
46            expiration: DEFAULT_EXPIRATION,
47            prefix: format!(
48                "isolated-lock-{}-{}",
49                metadata.policy_metadata.policy_name, metadata.policy_metadata.policy_namespace
50            ),
51        })
52    }
53}
54
55impl LockBuilder {
56    /// Returns a [`LockBuilderInstance`] with an ID to uniquely identify the lock.
57    ///
58    /// _Note: if 2 locks are assigned the same ID in a same policy, they will share the state.
59    /// This behavior is extended to other policies when using the
60    /// [shared lock option](LockBuilderInstance::shared)._
61    #[allow(clippy::new_ret_no_self)]
62    pub fn new(&self, lock_id: String) -> LockBuilderInstance {
63        LockBuilderInstance {
64            clock: Rc::clone(&self.clock),
65            shared_data: Rc::clone(&self.shared_data),
66            expiration: self.expiration,
67            prefix: self.prefix.clone(),
68            key: lock_id,
69        }
70    }
71}
72
73/// Represents a particular instance of a [`LockBuilder`]. It includes extra metadata of the
74/// policy, required to successfully work in all scenarios involving multiple policies.
75pub struct LockBuilderInstance {
76    clock: Rc<dyn Clock>,
77    shared_data: Rc<dyn SharedData>,
78    expiration: Duration,
79    prefix: String,
80    key: String,
81}
82
83impl LockBuilderInstance {
84    /// Sets the time expiration time for the lock. An acquired lock will be considered lost
85    /// once this time passes even if it was not released.
86    pub fn expiration(mut self, expiration: Duration) -> Self {
87        self.expiration = expiration;
88        self
89    }
90
91    /// Indicates that the lock should not be isolated to the policy. The state will be shared
92    /// across different policy instances and different policies that use the same lock ID.
93    pub fn shared(mut self) -> Self {
94        self.prefix = "shared-lock".to_string();
95        self
96    }
97
98    /// Builds a new [`TryLock`] with the corresponding specifications.
99    pub fn build(self) -> TryLock {
100        TryLock::new(
101            format!("{}-{}", self.prefix, self.key),
102            self.expiration,
103            self.clock,
104            self.shared_data,
105        )
106    }
107}