1use bytes::Bytes;
2
3use crate::error::{Error, Result};
4
5pub mod flags {
11 pub const F_COMPRESSED: u32 = 2;
13 pub const F_SERIALIZED: u32 = 8;
15
16 pub const MARKER_BYTE: u32 = 1;
18 pub const MARKER_INTEGER: u32 = 4;
20 pub const MARKER_CHARACTER: u32 = 16;
22 pub const MARKER_STRING: u32 = 32;
24 pub const MARKER_STRINGBUFFER: u32 = 64;
26 pub const MARKER_SHORT: u32 = 256;
28 pub const MARKER_DOUBLE: u32 = 512;
30 pub const MARKER_DATE: u32 = 1024;
32 pub const MARKER_STRINGBUILDER: u32 = 2048;
34 pub const MARKER_BYTEARR: u32 = 4096;
36 pub const MARKER_BOOLEAN: u32 = 8192;
38 pub const MARKER_LONG: u32 = 16384;
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Value {
45 data: Bytes,
46 flags: u32,
47}
48
49impl Value {
50 pub fn new(data: impl Into<Bytes>, flags: u32) -> Self {
52 Value {
53 data: data.into(),
54 flags,
55 }
56 }
57
58 pub fn as_bytes(&self) -> &[u8] {
60 &self.data
61 }
62
63 pub fn into_bytes(self) -> Bytes {
65 self.data
66 }
67
68 pub fn flags(&self) -> u32 {
70 self.flags
71 }
72
73 fn reject_opaque(&self) -> Result<()> {
74 if self.flags & flags::F_COMPRESSED != 0 {
75 return Err(Error::Unsupported("QuickLZ compressed value"));
76 }
77 if self.flags & flags::F_SERIALIZED != 0 {
78 return Err(Error::Unsupported("Java-serialized value"));
79 }
80 Ok(())
81 }
82
83 pub fn as_string(&self) -> Result<String> {
88 self.reject_opaque()?;
89 std::str::from_utf8(&self.data)
90 .map(str::to_owned)
91 .map_err(|err| Error::Decode(format!("value is not valid UTF-8: {err}")))
92 }
93
94 pub fn as_i64(&self) -> Result<i64> {
99 self.reject_opaque()?;
100 match self.data.len() {
101 8 => Ok(i64::from_be_bytes(self.data[..8].try_into().unwrap())),
102 4 => Ok(i32::from_be_bytes(self.data[..4].try_into().unwrap()) as i64),
103 2 => Ok(i16::from_be_bytes(self.data[..2].try_into().unwrap()) as i64),
104 1 => Ok(self.data[0] as i64),
105 len => Err(Error::Decode(format!(
106 "cannot decode {len}-byte value as integer"
107 ))),
108 }
109 }
110
111 pub fn as_u64(&self) -> Result<u64> {
113 self.reject_opaque()?;
114 if let Ok(text) = std::str::from_utf8(&self.data)
116 && let Ok(parsed) = text.trim().parse::<u64>()
117 {
118 return Ok(parsed);
119 }
120 match self.data.len() {
121 8 => Ok(u64::from_be_bytes(self.data[..8].try_into().unwrap())),
122 4 => Ok(u32::from_be_bytes(self.data[..4].try_into().unwrap()) as u64),
123 _ => Err(Error::Decode(
124 "cannot decode value as unsigned integer".into(),
125 )),
126 }
127 }
128
129 pub fn as_bool(&self) -> Result<bool> {
131 self.reject_opaque()?;
132 match self.data.first() {
133 Some(&byte) => Ok(byte != 0),
134 None => Err(Error::Decode("empty value cannot decode as bool".into())),
135 }
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct CasValue {
142 pub value: Value,
144 pub cas: u64,
146}
147
148impl CasValue {
149 pub fn new(value: Value, cas: u64) -> Self {
151 CasValue { value, cas }
152 }
153}
154
155pub trait ToMemcacheValue {
162 fn to_memcache_value(self) -> Value;
164}
165
166impl ToMemcacheValue for Value {
167 fn to_memcache_value(self) -> Value {
168 self
169 }
170}
171
172impl ToMemcacheValue for &[u8] {
173 fn to_memcache_value(self) -> Value {
174 Value::new(Bytes::copy_from_slice(self), 0)
175 }
176}
177
178impl ToMemcacheValue for Vec<u8> {
179 fn to_memcache_value(self) -> Value {
180 Value::new(self, 0)
181 }
182}
183
184impl ToMemcacheValue for Bytes {
185 fn to_memcache_value(self) -> Value {
186 Value::new(self, 0)
187 }
188}
189
190impl ToMemcacheValue for &str {
191 fn to_memcache_value(self) -> Value {
192 Value::new(Bytes::copy_from_slice(self.as_bytes()), 0)
193 }
194}
195
196impl ToMemcacheValue for String {
197 fn to_memcache_value(self) -> Value {
198 Value::new(self.into_bytes(), 0)
199 }
200}
201
202impl ToMemcacheValue for i32 {
203 fn to_memcache_value(self) -> Value {
204 Value::new(self.to_be_bytes().to_vec(), flags::MARKER_INTEGER)
205 }
206}
207
208impl ToMemcacheValue for i64 {
209 fn to_memcache_value(self) -> Value {
210 Value::new(self.to_be_bytes().to_vec(), flags::MARKER_LONG)
211 }
212}
213
214impl ToMemcacheValue for u64 {
215 fn to_memcache_value(self) -> Value {
216 Value::new((self as i64).to_be_bytes().to_vec(), flags::MARKER_LONG)
217 }
218}
219
220impl ToMemcacheValue for bool {
221 fn to_memcache_value(self) -> Value {
222 Value::new(vec![self as u8], flags::MARKER_BOOLEAN)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn string_roundtrip() {
232 let value = "hello".to_memcache_value();
233 assert_eq!(value.as_string().unwrap(), "hello");
234 assert_eq!(value.as_bytes(), b"hello");
235 }
236
237 #[test]
238 fn integer_roundtrip_with_marker() {
239 let value = 42_i64.to_memcache_value();
240 assert_eq!(value.flags(), flags::MARKER_LONG);
241 assert_eq!(value.as_i64().unwrap(), 42);
242 }
243
244 #[test]
245 fn bool_roundtrip_with_marker() {
246 let value = true.to_memcache_value();
247 assert_eq!(value.flags(), flags::MARKER_BOOLEAN);
248 assert!(value.as_bool().unwrap());
249 }
250
251 #[test]
252 fn compressed_and_serialized_are_unsupported() {
253 let compressed = Value::new(vec![0, 1, 2], flags::F_COMPRESSED);
254 assert!(matches!(compressed.as_bytes().len(), 3));
255 assert!(compressed.as_string().is_err());
256 let serialized = Value::new(vec![0, 1, 2], flags::F_SERIALIZED);
257 assert!(serialized.as_i64().is_err());
258 }
259
260 #[test]
261 fn counter_decimal_decodes_as_u64() {
262 let value = Value::new(b"12345".to_vec(), 0);
263 assert_eq!(value.as_u64().unwrap(), 12345);
264 }
265}