use std::io::Write;
use logforth_core::kv::Value;
pub(super) enum FieldName<'a> {
WellFormed(&'a str),
WriteEscaped(&'a str),
}
fn is_valid_key_char(c: char) -> bool {
matches!(c, 'A'..='Z' | '0'..='9' | '_')
}
fn write_escaped_key(key: &str, buffer: &mut Vec<u8>) {
let mut remaining = 64;
let escaped = key
.to_ascii_uppercase()
.replace(|c| !is_valid_key_char(c), "_");
if escaped.starts_with(|c: char| matches!(c, '_' | '0'..='9')) {
buffer.extend_from_slice(b"ESCAPED_");
remaining -= 8;
}
for b in escaped.into_bytes() {
if remaining == 0 {
break;
}
buffer.push(b);
remaining -= 1;
}
}
fn put_field_name(buffer: &mut Vec<u8>, name: FieldName<'_>) {
match name {
FieldName::WellFormed(name) => buffer.extend_from_slice(name.as_bytes()),
FieldName::WriteEscaped("") => buffer.extend_from_slice(b"EMPTY"),
FieldName::WriteEscaped(name) => write_escaped_key(name, buffer),
}
}
pub(super) trait PutAsFieldValue {
fn put_field_value(self, buffer: &mut Vec<u8>);
}
impl PutAsFieldValue for &[u8] {
fn put_field_value(self, buffer: &mut Vec<u8>) {
buffer.extend_from_slice(self)
}
}
impl PutAsFieldValue for &str {
fn put_field_value(self, buffer: &mut Vec<u8>) {
buffer.extend_from_slice(self.as_bytes())
}
}
impl PutAsFieldValue for Value<'_> {
fn put_field_value(self, buffer: &mut Vec<u8>) {
write!(buffer, "{self}").unwrap();
}
}
pub(super) fn put_field_length_encoded<V: PutAsFieldValue>(
buffer: &mut Vec<u8>,
name: FieldName<'_>,
value: V,
) {
put_field_name(buffer, name);
buffer.push(b'\n');
buffer.extend_from_slice(&[0; 8]);
let value_start = buffer.len();
value.put_field_value(buffer);
let value_end = buffer.len();
let length_bytes = ((value_end - value_start) as u64).to_le_bytes();
buffer[value_start - 8..value_start].copy_from_slice(&length_bytes);
buffer.push(b'\n');
}
pub(super) fn put_field_bytes(buffer: &mut Vec<u8>, name: FieldName<'_>, value: &[u8]) {
if value.contains(&b'\n') {
put_field_length_encoded(buffer, name, value);
} else {
put_field_name(buffer, name);
buffer.push(b'=');
buffer.extend_from_slice(value);
buffer.push(b'\n');
}
}
#[cfg(test)]
mod tests {
use FieldName::*;
use super::*;
#[test]
fn test_escape_journal_key() {
for case in ["FOO", "FOO_123"] {
let mut bs = vec![];
write_escaped_key(case, &mut bs);
assert_eq!(String::from_utf8_lossy(&bs), case);
}
let cases = vec![
("foo", "FOO"),
("_foo", "ESCAPED__FOO"),
("1foo", "ESCAPED_1FOO"),
("Hallöchen", "HALL_CHEN"),
];
for (key, expected) in cases {
let mut bs = vec![];
write_escaped_key(key, &mut bs);
assert_eq!(String::from_utf8_lossy(&bs), expected);
}
{
for case in [
"very_long_key_name_that_is_longer_than_64_bytes".repeat(5),
"_need_escape_very_long_key_name_that_is_longer_than_64_bytes".repeat(5),
] {
let mut bs = vec![];
write_escaped_key(&case, &mut bs);
println!("{:?}", String::from_utf8_lossy(&bs));
assert_eq!(bs.len(), 64);
}
}
}
#[test]
fn test_put_field_length_encoded() {
let mut buffer = Vec::new();
put_field_length_encoded(&mut buffer, WellFormed("FOO"), "BAR".as_bytes());
assert_eq!(&buffer, b"FOO\n\x03\0\0\0\0\0\0\0BAR\n");
}
#[test]
fn test_put_field_bytes_no_newline() {
let mut buffer = Vec::new();
put_field_bytes(&mut buffer, WellFormed("FOO"), "BAR".as_bytes());
assert_eq!(&buffer, b"FOO=BAR\n");
}
#[test]
fn test_put_field_bytes_newline() {
let mut buffer = Vec::new();
put_field_bytes(
&mut buffer,
WellFormed("FOO"),
"BAR\nSPAM_WITH_EGGS".as_bytes(),
);
assert_eq!(&buffer, b"FOO\n\x12\0\0\0\0\0\0\0BAR\nSPAM_WITH_EGGS\n");
}
}