Skip to main content

reallyme_valkey_kit/
value.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use std::time::Duration;
6
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9use crate::{ValkeyDataErrorReason, ValkeyDataKind, ValkeyError, ValkeyResult};
10
11const MAX_KEY_BYTES: usize = 512;
12const MAX_VALUE_BYTES: usize = 8 * 1_024 * 1_024;
13const MAX_TTL_MILLIS: u64 = 604_800_000;
14
15/// Validated binary key suffix.
16#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
17pub struct ValkeyKey(Vec<u8>);
18
19impl ValkeyKey {
20    /// Validates and constructs a binary key suffix.
21    pub fn new(mut value: Vec<u8>) -> ValkeyResult<Self> {
22        if value.is_empty() {
23            value.zeroize();
24            return Err(data_error(
25                ValkeyDataKind::Key,
26                ValkeyDataErrorReason::Empty,
27            ));
28        }
29        if value.len() > MAX_KEY_BYTES {
30            value.zeroize();
31            return Err(data_error(
32                ValkeyDataKind::Key,
33                ValkeyDataErrorReason::TooLarge,
34            ));
35        }
36        Ok(Self(value))
37    }
38
39    /// Returns the validated binary key suffix.
40    pub fn as_bytes(&self) -> &[u8] {
41        self.0.as_slice()
42    }
43}
44
45/// Validated binary value that clears its allocation on drop.
46#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
47pub struct ValkeyValue(Vec<u8>);
48
49impl ValkeyValue {
50    /// Validates and constructs a bounded binary value.
51    pub fn new(mut value: Vec<u8>) -> ValkeyResult<Self> {
52        if value.len() > MAX_VALUE_BYTES {
53            value.zeroize();
54            return Err(data_error(
55                ValkeyDataKind::Value,
56                ValkeyDataErrorReason::TooLarge,
57            ));
58        }
59        Ok(Self(value))
60    }
61
62    /// Returns the binary value.
63    pub fn as_bytes(&self) -> &[u8] {
64        self.0.as_slice()
65    }
66}
67
68/// Positive, bounded key expiration interval.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct ValkeyTimeToLive {
71    milliseconds: u64,
72}
73
74impl ValkeyTimeToLive {
75    /// Constructs a TTL from a duration.
76    pub fn new(value: Duration) -> ValkeyResult<Self> {
77        let milliseconds = u64::try_from(value.as_millis()).map_err(|_| {
78            data_error(
79                ValkeyDataKind::TimeToLive,
80                ValkeyDataErrorReason::OutOfRange,
81            )
82        })?;
83        if milliseconds == 0 || milliseconds > MAX_TTL_MILLIS {
84            return Err(data_error(
85                ValkeyDataKind::TimeToLive,
86                ValkeyDataErrorReason::OutOfRange,
87            ));
88        }
89        Ok(Self { milliseconds })
90    }
91
92    /// Returns the TTL in whole milliseconds.
93    pub const fn milliseconds(self) -> u64 {
94        self.milliseconds
95    }
96}
97
98const fn data_error(kind: ValkeyDataKind, reason: ValkeyDataErrorReason) -> ValkeyError {
99    ValkeyError::InvalidData { kind, reason }
100}
101
102#[cfg(test)]
103mod tests {
104    use std::time::Duration;
105
106    use super::{ValkeyKey, ValkeyTimeToLive, ValkeyValue};
107    use crate::{ValkeyDataErrorReason, ValkeyDataKind, ValkeyError};
108
109    #[test]
110    fn key_rejects_empty_input() {
111        assert_eq!(
112            ValkeyKey::new(Vec::new()).err(),
113            Some(ValkeyError::InvalidData {
114                kind: ValkeyDataKind::Key,
115                reason: ValkeyDataErrorReason::Empty,
116            })
117        );
118    }
119
120    #[test]
121    fn value_accepts_empty_payload_but_rejects_oversized_payload() {
122        assert!(ValkeyValue::new(Vec::new()).is_ok());
123        let oversized = vec![0_u8; 8 * 1_024 * 1_024 + 1];
124        assert_eq!(
125            ValkeyValue::new(oversized).err(),
126            Some(ValkeyError::InvalidData {
127                kind: ValkeyDataKind::Value,
128                reason: ValkeyDataErrorReason::TooLarge,
129            })
130        );
131    }
132
133    #[test]
134    fn ttl_rejects_zero_and_unbounded_values() {
135        assert!(ValkeyTimeToLive::new(Duration::ZERO).is_err());
136        assert!(ValkeyTimeToLive::new(Duration::from_secs(604_801)).is_err());
137        assert_eq!(
138            ValkeyTimeToLive::new(Duration::from_secs(60))
139                .expect("TTL fixture should be valid")
140                .milliseconds(),
141            60_000
142        );
143    }
144}