1use super::Codec;
2use crate::{ConfigError, FieldMeta, RowValue, ScalarValue, ValueType};
3use sift_query_algorithm::StableHasher64;
4
5#[derive(Debug, Clone, Copy)]
9pub struct StringCodec {
10 pub name: &'static str,
11 pub field: &'static str,
12 pub trim: bool,
13}
14
15impl Codec for StringCodec {
16 fn name(&self) -> &'static str {
17 self.name
18 }
19 fn supports(&self, field: &FieldMeta) -> bool {
20 field.public_name == self.field && field.value_type == ValueType::Text
21 }
22 fn parse(&self, _: &FieldMeta, input: &str) -> Result<ScalarValue, String> {
23 Ok(ScalarValue::Text(
24 if self.trim { input.trim() } else { input }.to_owned(),
25 ))
26 }
27 fn format_result(&self, _: &FieldMeta, value: RowValue) -> Result<RowValue, String> {
28 match value {
29 RowValue::Null => Ok(RowValue::Null),
30 RowValue::Text(value) => Ok(RowValue::Text(
31 if self.trim { value.trim() } else { &value }.to_owned(),
32 )),
33 _ => Err("string result has an incompatible type".to_owned()),
34 }
35 }
36 fn fingerprint(&self) -> u64 {
37 let mut hash = StableHasher64::default();
38 hash.write_delimited(self.name);
39 hash.write_delimited(self.field);
40 hash.write([u8::from(self.trim)]);
41 hash.finish()
42 }
43}
44
45#[derive(Debug, Clone, Copy)]
49pub struct ScalarTextCodec {
50 pub name: &'static str,
51 pub field: &'static str,
52 pub value_type: ValueType,
53 pub trim: bool,
54}
55
56impl Codec for ScalarTextCodec {
57 fn name(&self) -> &'static str {
58 self.name
59 }
60 fn supports(&self, field: &FieldMeta) -> bool {
61 field.public_name == self.field && field.value_type == self.value_type
62 }
63 fn parse(&self, _: &FieldMeta, input: &str) -> Result<ScalarValue, String> {
64 ScalarValue::parse(
65 self.value_type,
66 if self.trim { input.trim() } else { input },
67 self.field,
68 )
69 .map_err(|error| error.to_string())
70 }
71 fn format_result(&self, _: &FieldMeta, value: RowValue) -> Result<RowValue, String> {
72 if matches!(value, RowValue::Null) {
73 return Ok(RowValue::Null);
74 }
75 if value.value_type() != Some(self.value_type) {
76 return Err("scalar result has an incompatible type".to_owned());
77 }
78 Ok(RowValue::Text(value.to_external_text()))
79 }
80 fn format_input(&self, _: &FieldMeta, value: &ScalarValue) -> Result<String, String> {
81 if value.value_type() != self.value_type {
82 return Err("scalar input has an incompatible type".to_owned());
83 }
84 Ok(value.to_external_text())
85 }
86 fn fingerprint(&self) -> u64 {
87 let mut hash = StableHasher64::default();
88 hash.write_delimited(self.name);
89 hash.write_delimited(self.field);
90 hash.write_delimited(format!("{:?}", self.value_type));
91 hash.write([u8::from(self.trim)]);
92 hash.finish()
93 }
94}
95
96#[cfg(feature = "json")]
97#[derive(Debug, Clone, Copy)]
98pub struct JsonValueCodec {
99 pub name: &'static str,
100 pub field: &'static str,
101 pub max_bytes: usize,
102}
103
104#[cfg(feature = "json")]
105impl Codec for JsonValueCodec {
106 fn name(&self) -> &'static str {
107 self.name
108 }
109 fn supports(&self, field: &FieldMeta) -> bool {
110 field.public_name == self.field && field.value_type == ValueType::Json
111 }
112 fn parse(&self, _: &FieldMeta, input: &str) -> Result<ScalarValue, String> {
113 if self.max_bytes == 0 || input.len() > self.max_bytes {
114 return Err("JSON input exceeds codec budget".to_owned());
115 }
116 serde_json::from_str(input)
117 .map(|value| ScalarValue::Json(Box::new(value)))
118 .map_err(|error| format!("invalid JSON: {error}"))
119 }
120 fn format_result(&self, _: &FieldMeta, value: RowValue) -> Result<RowValue, String> {
121 match value {
122 RowValue::Null => Ok(RowValue::Null),
123 RowValue::Json(value) => {
124 let text = value.to_string();
125 if self.max_bytes == 0 || text.len() > self.max_bytes {
126 return Err("JSON result exceeds codec budget".to_owned());
127 }
128 Ok(RowValue::Text(text))
129 }
130 _ => Err("JSON result has an incompatible type".to_owned()),
131 }
132 }
133 fn format_input(&self, _: &FieldMeta, value: &ScalarValue) -> Result<String, String> {
134 match value {
135 ScalarValue::Json(value) => Ok(value.to_string()),
136 _ => Err("JSON input has an incompatible type".to_owned()),
137 }
138 }
139 fn fingerprint(&self) -> u64 {
140 let mut hash = StableHasher64::default();
141 hash.write_delimited(self.name);
142 hash.write_delimited(self.field);
143 hash.write_delimited(self.max_bytes.to_string());
144 hash.finish()
145 }
146}
147
148#[derive(Debug, Clone)]
151pub struct EnumCodec {
152 name: &'static str,
153 field: &'static str,
154 variants: Vec<(&'static str, i32)>,
155}
156
157impl EnumCodec {
158 pub fn new(
159 name: &'static str,
160 field: &'static str,
161 variants: impl IntoIterator<Item = (&'static str, i32)>,
162 ) -> Result<Self, ConfigError> {
163 let variants = variants.into_iter().collect::<Vec<_>>();
164 if variants.is_empty()
165 || variants.iter().enumerate().any(|(index, (name, code))| {
166 name.is_empty()
167 || variants[..index]
168 .iter()
169 .any(|(other, value)| other == name || value == code)
170 })
171 {
172 return Err(ConfigError::Invalid(
173 "enum codec requires unique nonempty names and codes".to_owned(),
174 ));
175 }
176 Ok(Self {
177 name,
178 field,
179 variants,
180 })
181 }
182}
183
184impl Codec for EnumCodec {
185 fn name(&self) -> &'static str {
186 self.name
187 }
188 fn supports(&self, field: &FieldMeta) -> bool {
189 field.public_name == self.field && field.value_type == ValueType::I32
190 }
191 fn parse(&self, _field: &FieldMeta, input: &str) -> Result<ScalarValue, String> {
192 self.variants
193 .iter()
194 .find(|(name, _)| *name == input)
195 .map(|(_, value)| ScalarValue::I32(*value))
196 .ok_or_else(|| "unknown enum name".to_owned())
197 }
198 fn format_result(&self, _field: &FieldMeta, value: RowValue) -> Result<RowValue, String> {
199 match value {
200 RowValue::Null => Ok(RowValue::Null),
201 RowValue::I32(value) => self
202 .variants
203 .iter()
204 .find(|(_, code)| *code == value)
205 .map(|(name, _)| RowValue::Text((*name).to_owned()))
206 .ok_or_else(|| "unknown enum code".to_owned()),
207 _ => Err("enum result has an incompatible type".to_owned()),
208 }
209 }
210 fn fingerprint(&self) -> u64 {
211 let mut hash = StableHasher64::default();
212 hash.write_delimited(self.name);
213 hash.write_delimited(self.field);
214 for (name, code) in &self.variants {
215 hash.write_delimited(name);
216 hash.write(code.to_le_bytes());
217 }
218 hash.finish()
219 }
220}
221
222#[derive(Debug, Clone, Copy)]
224pub struct BooleanCodec {
225 pub name: &'static str,
226 pub field: &'static str,
227 pub true_name: &'static str,
228 pub false_name: &'static str,
229}
230
231impl Codec for BooleanCodec {
232 fn name(&self) -> &'static str {
233 self.name
234 }
235 fn supports(&self, field: &FieldMeta) -> bool {
236 field.public_name == self.field && field.value_type == ValueType::Bool
237 }
238 fn parse(&self, _field: &FieldMeta, input: &str) -> Result<ScalarValue, String> {
239 if self.true_name == self.false_name
240 || self.true_name.is_empty()
241 || self.false_name.is_empty()
242 {
243 return Err("invalid boolean codec configuration".to_owned());
244 }
245 if input == self.true_name {
246 Ok(ScalarValue::Bool(true))
247 } else if input == self.false_name {
248 Ok(ScalarValue::Bool(false))
249 } else {
250 Err("unknown boolean name".to_owned())
251 }
252 }
253 fn format_result(&self, _field: &FieldMeta, value: RowValue) -> Result<RowValue, String> {
254 match value {
255 RowValue::Null => Ok(RowValue::Null),
256 RowValue::Bool(value) => Ok(RowValue::Text(
257 if value {
258 self.true_name
259 } else {
260 self.false_name
261 }
262 .to_owned(),
263 )),
264 _ => Err("boolean result has an incompatible type".to_owned()),
265 }
266 }
267 fn fingerprint(&self) -> u64 {
268 let mut hash = StableHasher64::default();
269 for part in [self.name, self.field, self.true_name, self.false_name] {
270 hash.write_delimited(part);
271 }
272 hash.finish()
273 }
274}
275
276#[cfg(feature = "chrono")]
279#[derive(Debug, Clone, Copy)]
280pub struct DateCodec {
281 pub name: &'static str,
282 pub field: &'static str,
283 pub format: &'static str,
284}
285
286#[cfg(feature = "chrono")]
287impl Codec for DateCodec {
288 fn name(&self) -> &'static str {
289 self.name
290 }
291 fn supports(&self, field: &FieldMeta) -> bool {
292 field.public_name == self.field && field.value_type == ValueType::Date
293 }
294 fn parse(&self, _field: &FieldMeta, input: &str) -> Result<ScalarValue, String> {
295 chrono::NaiveDate::parse_from_str(input, self.format)
296 .map(ScalarValue::Date)
297 .map_err(|_| "invalid date".to_owned())
298 }
299 fn format_result(&self, _field: &FieldMeta, value: RowValue) -> Result<RowValue, String> {
300 match value {
301 RowValue::Null => Ok(RowValue::Null),
302 RowValue::Date(value) => {
303 use std::fmt::Write;
304 let mut output = String::new();
305 write!(&mut output, "{}", value.format(self.format))
306 .map_err(|_| "invalid date format".to_owned())?;
307 Ok(RowValue::Text(output))
308 }
309 _ => Err("date result has an incompatible type".to_owned()),
310 }
311 }
312 fn fingerprint(&self) -> u64 {
313 let mut hash = StableHasher64::default();
314 for part in [self.name, self.field, self.format] {
315 hash.write_delimited(part);
316 }
317 hash.finish()
318 }
319}