1use std::borrow::Cow;
2
3use bytes::{Buf, BufMut, Bytes, BytesMut};
4use derive_more::Display;
5use serde::{Deserialize, Serialize};
6use tempest_core::encoding::{
7 BufGetLexicalExt, BufGetRawExt, BufPutLexicalExt, BufPutRawExt, LexicalDecodeError,
8 RawDecodeError,
9};
10
11#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13pub enum TempestType {
14 #[display("Int64")]
15 Int64,
16 #[display("Bool")]
17 Bool,
18 #[display("String")]
19 String,
20 #[display("Enum({_0})")]
22 Enum(u32),
23}
24
25impl TempestType {
26 pub fn name(&self) -> &'static str {
27 match self {
28 Self::Int64 => "Int64",
29 Self::Bool => "Bool",
30 Self::String => "String",
31 Self::Enum(_) => "Enum",
32 }
33 }
34}
35
36impl std::str::FromStr for TempestType {
37 type Err = ();
38 fn from_str(s: &str) -> Result<Self, ()> {
39 match s {
40 "Int64" => Ok(Self::Int64),
41 "Bool" => Ok(Self::Bool),
42 "String" => Ok(Self::String),
43 _ => Err(()),
44 }
45 }
46}
47
48#[derive(derive_more::Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord)]
49#[repr(u8)]
50pub enum TempestValue<'a> {
51 Int64(i64) = 0,
52 Bool(bool) = 1,
53 String(Cow<'a, str>) = 2,
54 #[display("Enum({type_id}:{variant_id})")]
55 Enum { type_id: u32, variant_id: u32, fields: Vec<TempestValue<'a>> } = 3,
56}
57
58impl<'a> TempestValue<'a> {
59 pub fn ty(&self) -> TempestType {
60 match self {
61 TempestValue::Int64(_) => TempestType::Int64,
62 TempestValue::Bool(_) => TempestType::Bool,
63 TempestValue::String(_) => TempestType::String,
64 TempestValue::Enum { type_id, .. } => TempestType::Enum(*type_id),
65 }
66 }
67
68 pub fn encode(&self, buf: &mut BytesMut) {
69 match self {
70 &TempestValue::Int64(i) => buf.put_i64_raw(i),
71 &TempestValue::Bool(b) => buf.put_bool_raw(b),
72 TempestValue::String(s) => buf.put_str_raw(s),
73 TempestValue::Enum { variant_id, fields, .. } => {
74 buf.put_u32(*variant_id);
75 for field in fields {
76 field.encode(buf);
77 }
78 }
79 }
80 }
81
82 pub fn decode(
83 buf: &mut Bytes,
84 ty: TempestType,
85 ) -> Result<TempestValue<'static>, RawDecodeError> {
86 match ty {
87 TempestType::Int64 => buf.get_i64_raw().map(TempestValue::Int64),
88 TempestType::Bool => buf.get_bool_raw().map(TempestValue::Bool),
89 TempestType::String => buf
90 .get_str_raw()
91 .map(|s| TempestValue::String(Cow::Owned(s))),
92 TempestType::Enum(type_id) => {
93 let variant_id = buf.get_u32();
94 Ok(TempestValue::Enum { type_id, variant_id, fields: vec![] })
95 }
96 }
97 }
98
99 pub fn encode_lexical(&self, buf: &mut BytesMut) {
100 match self {
101 &TempestValue::Int64(i) => buf.put_i64_lexical(i),
102 &TempestValue::Bool(b) => buf.put_bool_lexical(b),
103 TempestValue::String(s) => buf.put_str_lexical(s),
104 TempestValue::Enum { .. } => panic!("enum values cannot be used as primary key"),
105 }
106 }
107
108 pub fn decode_lexical(
109 buf: &mut Bytes,
110 ty: TempestType,
111 ) -> Result<TempestValue<'static>, LexicalDecodeError> {
112 match ty {
113 TempestType::Int64 => buf.get_i64_lexical().map(TempestValue::Int64),
114 TempestType::Bool => buf.get_bool_lexical().map(TempestValue::Bool),
115 TempestType::String => buf
116 .get_str_lexical()
117 .map(|s| TempestValue::String(Cow::Owned(s))),
118 TempestType::Enum(_) => panic!("enum values cannot be used as primary key"),
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use std::borrow::Cow;
126
127 use bytes::{Bytes, BytesMut};
128
129 use super::*;
130
131 fn roundtrip(val: TempestValue<'_>) -> TempestValue<'static> {
134 let ty = val.ty();
135 let mut buf = BytesMut::new();
136 val.encode(&mut buf);
137 TempestValue::decode(&mut buf.freeze(), ty).unwrap()
138 }
139
140 fn roundtrip_lexical(val: TempestValue<'_>) -> TempestValue<'static> {
141 let ty = val.ty();
142 let mut buf = BytesMut::new();
143 val.encode_lexical(&mut buf);
144 TempestValue::decode_lexical(&mut buf.freeze(), ty).unwrap()
145 }
146
147 fn encode_lexical_bytes(val: TempestValue<'_>) -> Bytes {
148 let mut buf = BytesMut::new();
149 val.encode_lexical(&mut buf);
150 buf.freeze()
151 }
152
153 #[test]
156 fn test_int64_roundtrip() {
157 for val in [0i64, 1, -1, i64::MIN, i64::MAX, 42, -1000] {
158 let result = roundtrip(TempestValue::Int64(val));
159 assert_eq!(result, TempestValue::Int64(val));
160 }
161 }
162
163 #[test]
164 fn test_bool_roundtrip() {
165 for val in [true, false] {
166 let result = roundtrip(TempestValue::Bool(val));
167 assert_eq!(result, TempestValue::Bool(val));
168 }
169 }
170
171 #[test]
172 fn test_string_roundtrip() {
173 for val in ["", "hello", "tempest", "unicode: ??", "hel\x00lo"] {
174 let result = roundtrip(TempestValue::String(Cow::Borrowed(val)));
175 assert_eq!(result, TempestValue::String(Cow::Borrowed(val)));
176 }
177 }
178
179 #[test]
180 fn test_enum_roundtrip() {
181 let val = TempestValue::Enum { type_id: 5, variant_id: 2, fields: vec![] };
182 let result = roundtrip(val.clone());
183 assert_eq!(result, val);
184 }
185
186 #[test]
187 fn test_decode_returns_owned_string() {
188 let result = roundtrip(TempestValue::String(Cow::Borrowed("hello")));
189 if let TempestValue::String(cow) = result {
190 assert!(
191 matches!(cow, Cow::Owned(_)),
192 "decoded string should be Cow::Owned"
193 );
194 } else {
195 panic!("expected String variant");
196 }
197 }
198
199 #[test]
202 fn test_int64_lexical_roundtrip() {
203 for val in [0i64, 1, -1, i64::MIN, i64::MAX, 42, -1000] {
204 let result = roundtrip_lexical(TempestValue::Int64(val));
205 assert_eq!(result, TempestValue::Int64(val));
206 }
207 }
208
209 #[test]
210 fn test_bool_lexical_roundtrip() {
211 for val in [true, false] {
212 let result = roundtrip_lexical(TempestValue::Bool(val));
213 assert_eq!(result, TempestValue::Bool(val));
214 }
215 }
216
217 #[test]
218 fn test_string_lexical_roundtrip() {
219 for val in ["", "hello", "tempest", "hel\x00lo", "\x00\x00\x00"] {
220 let result = roundtrip_lexical(TempestValue::String(Cow::Borrowed(val)));
221 assert_eq!(result, TempestValue::String(Cow::Borrowed(val)));
222 }
223 }
224
225 #[test]
228 fn test_int64_lexical_ordering() {
229 let cases = [i64::MIN, -1000, -1, 0, 1, 1000, i64::MAX];
230 for pair in cases.windows(2) {
231 let (a, b) = (pair[0], pair[1]);
232 assert!(
233 encode_lexical_bytes(TempestValue::Int64(a))
234 < encode_lexical_bytes(TempestValue::Int64(b)),
235 "{} should encode less than {}",
236 a,
237 b
238 );
239 }
240 }
241
242 #[test]
243 fn test_bool_lexical_ordering() {
244 assert!(
245 encode_lexical_bytes(TempestValue::Bool(false))
246 < encode_lexical_bytes(TempestValue::Bool(true))
247 );
248 }
249
250 #[test]
251 fn test_string_lexical_ordering() {
252 let cases = ["", "a", "aa", "ab", "b", "z"];
253 for pair in cases.windows(2) {
254 let (a, b) = (pair[0], pair[1]);
255 assert!(
256 encode_lexical_bytes(TempestValue::String(Cow::Borrowed(a)))
257 < encode_lexical_bytes(TempestValue::String(Cow::Borrowed(b))),
258 "{:?} should encode less than {:?}",
259 a,
260 b
261 );
262 }
263 }
264
265 #[test]
268 fn test_ty_returns_correct_discriminant() {
269 assert_eq!(TempestValue::Int64(Default::default()).ty(), TempestType::Int64);
270 assert_eq!(TempestValue::Bool(Default::default()).ty(), TempestType::Bool);
271 assert_eq!(TempestValue::String(Default::default()).ty(), TempestType::String);
272 assert_eq!(
273 TempestValue::Enum { type_id: 3, variant_id: 0, fields: vec![] }.ty(),
274 TempestType::Enum(3)
275 );
276 }
277
278 #[test]
281 fn test_raw_and_lexical_differ_for_negative_i64() {
282 let val = TempestValue::Int64(-1);
283 let mut raw_buf = BytesMut::new();
284 let mut lex_buf = BytesMut::new();
285 val.encode(&mut raw_buf);
286 val.encode_lexical(&mut lex_buf);
287 assert_ne!(
288 raw_buf, lex_buf,
289 "raw and lexical encodings of -1 must differ"
290 );
291 }
292
293 #[test]
296 fn test_decode_advances_cursor() {
297 let mut buf = BytesMut::new();
298 TempestValue::Int64(42).encode(&mut buf);
299 TempestValue::Bool(true).encode(&mut buf);
300 TempestValue::String(Cow::Borrowed("hi")).encode(&mut buf);
301
302 let mut bytes = buf.freeze();
303 assert_eq!(
304 TempestValue::decode(&mut bytes, TempestType::Int64).unwrap(),
305 TempestValue::Int64(42)
306 );
307 assert_eq!(
308 TempestValue::decode(&mut bytes, TempestType::Bool).unwrap(),
309 TempestValue::Bool(true)
310 );
311 assert_eq!(
312 TempestValue::decode(&mut bytes, TempestType::String).unwrap(),
313 TempestValue::String(Cow::Borrowed("hi"))
314 );
315 assert!(bytes.is_empty());
316 }
317
318 #[test]
319 fn test_decode_lexical_advances_cursor() {
320 let mut buf = BytesMut::new();
321 TempestValue::Int64(-99).encode_lexical(&mut buf);
322 TempestValue::String(Cow::Borrowed("foo")).encode_lexical(&mut buf);
323 TempestValue::Bool(false).encode_lexical(&mut buf);
324
325 let mut bytes = buf.freeze();
326 assert_eq!(
327 TempestValue::decode_lexical(&mut bytes, TempestType::Int64).unwrap(),
328 TempestValue::Int64(-99)
329 );
330 assert_eq!(
331 TempestValue::decode_lexical(&mut bytes, TempestType::String).unwrap(),
332 TempestValue::String(Cow::Borrowed("foo"))
333 );
334 assert_eq!(
335 TempestValue::decode_lexical(&mut bytes, TempestType::Bool).unwrap(),
336 TempestValue::Bool(false)
337 );
338 assert!(bytes.is_empty());
339 }
340}