1use serde_json::Value;
6
7pub use tauq::error::TauqError;
9
10pub struct TauqEncoder;
12
13impl TauqEncoder {
14 pub fn encode(value: &Value) -> String {
16 tauq::format_to_tauq(value)
17 }
18}
19
20pub struct TauqDecoder;
22
23impl TauqDecoder {
24 pub fn decode(input: &str) -> Result<Value, TauqError> {
26 tauq::compile_tauq(input)
27 }
28
29 pub fn decode_batch(input: &str) -> Result<Vec<Value>, TauqError> {
31 let value = tauq::compile_tauq(input)?;
32 match value {
33 Value::Array(arr) => Ok(arr),
34 _ => Ok(vec![value]),
35 }
36 }
37}
38
39pub struct TauqBatchEncoder;
41
42impl TauqBatchEncoder {
43 pub fn encode_all(values: &[Value]) -> Result<String, TauqError> {
45 let array = Value::Array(values.to_vec());
46 Ok(tauq::format_to_tauq(&array))
47 }
48}
49
50pub fn ensure_tauq_format(payload: &str) -> String {
75 let trimmed = payload.trim();
76
77 if trimmed.starts_with('{') || trimmed.starts_with('[') {
78 match serde_json::from_str::<Value>(trimmed) {
79 Ok(json_value) => TauqEncoder::encode(&json_value),
80 Err(_) => payload.to_string(),
81 }
82 } else {
83 payload.to_string()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use serde_json::json;
91
92 fn assert_json_eq(left: &Value, right: &Value) {
93 if left == right {
94 return;
95 }
96
97 match (left, right) {
98 (Value::Number(n1), Value::Number(n2)) => {
99 let f1 = n1.as_f64().unwrap_or(0.0);
100 let f2 = n2.as_f64().unwrap_or(0.0);
101 if (f1 - f2).abs() > 1e-9 {
102 panic!("Numbers mismatch: {:?} != {:?}", n1, n2);
103 }
104 }
105 (Value::Object(o1), Value::Object(o2)) => {
106 if o1.len() != o2.len() {
107 panic!("Object length mismatch: {:?} != {:?}", o1, o2);
108 }
109 for (k, v1) in o1 {
110 if let Some(v2) = o2.get(k) {
111 assert_json_eq(v1, v2);
112 } else {
113 panic!("Key missing in right: {}", k);
114 }
115 }
116 }
117 (Value::Array(a1), Value::Array(a2)) => {
118 if a1.len() != a2.len() {
119 panic!("Array length mismatch: {:?} != {:?}", a1, a2);
120 }
121 for (v1, v2) in a1.iter().zip(a2.iter()) {
122 assert_json_eq(v1, v2);
123 }
124 }
125 _ => panic!("Mismatch: {:?} != {:?}", left, right),
126 }
127 }
128
129 #[test]
130 fn test_encode_simple_string() {
131 let json = json!("hello world");
132 let tauq = TauqEncoder::encode(&json);
133 assert!(tauq.contains("hello world"));
134 }
135
136 #[test]
137 fn test_roundtrip_simple_object() {
138 let json = json!({
139 "Response": {
140 "text": "Hello world",
141 "confidence": 0.95,
142 "timestamp": 1735689600
143 }
144 });
145
146 let tauq = TauqEncoder::encode(&json);
147 let decoded = TauqDecoder::decode(&tauq).unwrap();
148 assert_json_eq(&json, &decoded);
149 }
150
151 #[test]
152 fn test_roundtrip_with_special_chars() {
153 let json = json!({
154 "Response": {
155 "text": "Line 1\nLine 2\twith\ttabs",
156 "quote": "He said \"hello\""
157 }
158 });
159
160 let tauq = TauqEncoder::encode(&json);
161 let decoded = TauqDecoder::decode(&tauq).unwrap();
162 assert_json_eq(&json, &decoded);
163 }
164
165 #[test]
166 fn test_roundtrip_complex() {
167 let json = json!({
168 "Response": {
169 "text": "To reset your password, go to Settings > Security and click Reset Password",
170 "confidence": 0.99,
171 "timestamp": 1735689600,
172 "source": "knowledge_base",
173 "tokens_used": 42,
174 "metadata": {
175 "cached": true,
176 "ttl": 3600
177 },
178 "alternatives": ["option1", "option2"]
179 }
180 });
181
182 let tauq = TauqEncoder::encode(&json);
183 let decoded = TauqDecoder::decode(&tauq).unwrap();
184 assert_json_eq(&json, &decoded);
185 }
186
187 #[test]
188 fn test_compression_ratio_single_object() {
189 let json = json!({
190 "Response": {
191 "text": "To reset your password, go to Settings > Security and click Reset Password. You will receive an email with a reset link. The link expires in 24 hours.",
192 "confidence": 0.99,
193 "timestamp": 1735689600,
194 "source": "knowledge_base",
195 "tokens_used": 42
196 }
197 });
198
199 let json_str = serde_json::to_string(&json).unwrap();
200 let tauq = TauqEncoder::encode(&json);
201
202 let json_bytes = json_str.len();
203 let tauq_bytes = tauq.len();
204
205 println!(
206 "Single object - JSON: {} bytes, Tauq: {} bytes",
207 json_bytes, tauq_bytes
208 );
209
210 assert!(
211 tauq_bytes as f64 <= json_bytes as f64 * 1.1,
212 "Tauq should not be significantly larger than JSON"
213 );
214 }
215
216 #[test]
217 fn test_compression_ratio_batch_with_shared_schema() {
218 let values = vec![
219 json!({"Response": {"confidence": 0.95, "source": "cache", "text": "First response text here with some content", "timestamp": 1735689600}}),
220 json!({"Response": {"confidence": 0.87, "source": "cache", "text": "Second response with different text content", "timestamp": 1735689700}}),
221 json!({"Response": {"confidence": 0.92, "source": "cache", "text": "Third response also quite lengthy text data", "timestamp": 1735689800}}),
222 json!({"Response": {"confidence": 0.88, "source": "cache", "text": "Fourth response with more example content", "timestamp": 1735689900}}),
223 json!({"Response": {"confidence": 0.91, "source": "cache", "text": "Fifth response completing our batch example", "timestamp": 1735690000}}),
224 ];
225
226 let tauq_batch = TauqBatchEncoder::encode_all(&values).unwrap();
227
228 let json_entries: Vec<String> = values
229 .iter()
230 .map(|v| serde_json::to_string(v).unwrap())
231 .collect();
232 let _json_batch = json_entries.join("\n");
233
234 let decoded = TauqDecoder::decode_batch(&tauq_batch).unwrap();
235 assert_eq!(values.len(), decoded.len());
236 for (original, decoded_value) in values.iter().zip(decoded.iter()) {
237 assert_json_eq(original, decoded_value);
238 }
239 }
240}