Skip to main content

pdk_data_storage_lib/distributed/
model.rs

1// Copyright (c) 2026, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5//! Data models used by the distributed storage client
6//!
7//! Public types like [`StoreMode`] and [`Store`] are part of the module API.
8//! Internal transport/data-transfer structs are hidden from docs.
9
10use pdk_core::logger;
11
12use base64::{DecodeError, Engine};
13use std::collections::HashMap;
14use std::convert::TryFrom;
15
16use serde::{Deserialize, Serialize};
17
18/// Maximum TTL in seconds (30 days).
19const MAX_TTL_SECONDS: u32 = 2592000;
20
21#[derive(PartialEq, Eq, Debug)]
22pub enum StoreMode {
23    /// Indicates that the store operation does not depend on any condition.
24    Always,
25    /// Indicates that the store operation should succeed only if no other value was present in the store.
26    Absent,
27    /// Indicates that the store operation should succeed only if the stored value matches the provided cas.
28    Cas(String),
29}
30
31#[allow(dead_code)]
32#[doc(hidden)]
33// Code is actually read in runtime for serialization/deserialization, depending on the response from the OS
34#[derive(Deserialize, Debug)]
35pub struct Keys {
36    pub values: Vec<Key>,
37    next_page_token: Option<String>,
38}
39
40#[allow(dead_code)]
41#[doc(hidden)]
42// Code is actually read in runtime for serialization/deserialization, depending on the response from the OS
43#[derive(Deserialize, Debug)]
44pub struct Key {
45    #[serde(rename = "keyId")]
46    pub key_id: String,
47}
48
49#[allow(dead_code)]
50#[doc(hidden)]
51// Code is actually read in runtime for serialization/deserialization, depending on the response from the OS
52#[derive(Deserialize, Debug)]
53pub struct Partitions {
54    pub values: Vec<String>,
55    next_page_token: Option<String>,
56}
57
58#[allow(dead_code)]
59#[doc(hidden)]
60// Code is actually read in runtime for serialization/deserialization, depending on the response from the OS
61#[derive(Deserialize, Debug)]
62pub struct Stores {
63    pub values: Vec<Store>,
64    next_page_token: Option<String>,
65}
66
67#[derive(Serialize, Deserialize, Debug)]
68pub struct Store {
69    #[serde(rename = "storeId")]
70    store_id: String,
71
72    #[serde(rename = "defaultTtlSeconds")]
73    default_ttl_seconds: u32,
74
75    #[serde(rename = "maximumEntries")]
76    #[serde(skip_serializing_if = "Option::is_none")]
77    max_entries: Option<u32>,
78}
79
80#[allow(dead_code)]
81impl Store {
82    pub fn new(store_id: String, ttl_millis: Option<u32>, max_entries: Option<u32>) -> Self {
83        Self {
84            store_id,
85            default_ttl_seconds: convert_to_seconds(ttl_millis),
86            max_entries,
87        }
88    }
89
90    pub fn store_id(&self) -> &str {
91        self.store_id.as_str()
92    }
93
94    pub fn default_ttl_seconds(&self) -> u32 {
95        self.default_ttl_seconds
96    }
97
98    pub fn max_entries(&self) -> Option<u32> {
99        self.max_entries
100    }
101}
102
103#[derive(Serialize, Deserialize, Debug, PartialEq)]
104#[serde(rename_all = "UPPERCASE")]
105enum Types {
106    Number,
107    Binary,
108    String,
109}
110
111#[derive(Serialize, Deserialize, Debug)]
112#[doc(hidden)]
113pub struct Object {
114    #[serde(rename = "keyId", skip_serializing_if = "Option::is_none")]
115    key_id: Option<String>,
116
117    #[serde(skip_serializing_if = "Option::is_none")]
118    index: Option<u32>,
119
120    #[serde(skip_serializing_if = "Option::is_none")]
121    metadata: Option<HashMap<String, String>>,
122
123    #[serde(rename = "valueType")]
124    value_type: Types,
125
126    #[serde(rename = "stringValue", skip_serializing_if = "Option::is_none")]
127    string_value: Option<String>,
128
129    #[serde(rename = "numberValue", skip_serializing_if = "Option::is_none")]
130    number_value: Option<u64>,
131
132    #[serde(rename = "binaryValue", skip_serializing_if = "Option::is_none")]
133    binary_value: Option<String>,
134}
135
136impl TryFrom<&[u8]> for Object {
137    type Error = serde_json::Error;
138
139    /// Parses error from JSON.
140    fn try_from(value: &[u8]) -> serde_json::Result<Self> {
141        serde_json::from_slice(value)
142    }
143}
144
145impl Object {
146    pub fn new_binary(value: &[u8]) -> Self {
147        let result = base64::engine::general_purpose::STANDARD.encode(value);
148
149        Self {
150            key_id: None,
151            index: None,
152            metadata: None,
153            value_type: Types::Binary,
154            string_value: None,
155            number_value: None,
156            binary_value: Some(result),
157        }
158    }
159
160    pub fn get_binary(&self) -> Result<Vec<u8>, DecodeError> {
161        self.binary_value
162            .as_ref()
163            .map(|binary| base64::engine::general_purpose::STANDARD.decode(binary))
164            .unwrap_or(Ok(vec![]))
165    }
166}
167
168/// Converts TTL from milliseconds to seconds and validates against maximum allowed value.
169/// If the value exceeds MAX_TTL_SECONDS, the TTL will be set to the maximum allowed value.
170fn convert_to_seconds(ttl_millis: Option<u32>) -> u32 {
171    // Convert milliseconds to seconds or set to default max value if not provided.
172    let mut ttl_seconds = ttl_millis.map_or(MAX_TTL_SECONDS, |m| m / 1000);
173
174    // If the TTL is greater than the maximum allowed value, set it to the maximum allowed value.
175    if ttl_seconds > MAX_TTL_SECONDS {
176        logger::warn!("TTL is greater than the maximum allowed value of {MAX_TTL_SECONDS} seconds. Setting TTL to default max value: {MAX_TTL_SECONDS} seconds.");
177        ttl_seconds = MAX_TTL_SECONDS;
178    }
179
180    ttl_seconds
181}
182
183#[cfg(test)]
184mod tests {
185    use super::{convert_to_seconds, MAX_TTL_SECONDS};
186
187    #[test]
188    fn test_convert_to_seconds_normal_ttl_millis() {
189        let result = convert_to_seconds(Some(60000));
190        assert_eq!(result, 60);
191    }
192
193    #[test]
194    fn test_convert_to_seconds_large_ttl_sets_max_ttl_value() {
195        let large_ttl_millis = (MAX_TTL_SECONDS + 1) * 1000;
196        let result = convert_to_seconds(Some(large_ttl_millis));
197        assert_eq!(result, MAX_TTL_SECONDS);
198    }
199
200    #[test]
201    fn test_convert_to_seconds_none_sets_max_ttl_value() {
202        let result = convert_to_seconds(None);
203        assert_eq!(result, MAX_TTL_SECONDS);
204    }
205}