reallyme_valkey_kit/
value.rs1use 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#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
17pub struct ValkeyKey(Vec<u8>);
18
19impl ValkeyKey {
20 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 pub fn as_bytes(&self) -> &[u8] {
41 self.0.as_slice()
42 }
43}
44
45#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
47pub struct ValkeyValue(Vec<u8>);
48
49impl ValkeyValue {
50 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 pub fn as_bytes(&self) -> &[u8] {
64 self.0.as_slice()
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct ValkeyTimeToLive {
71 milliseconds: u64,
72}
73
74impl ValkeyTimeToLive {
75 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 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}