use crate::error::TsonError;
use crate::structure::*;
use alloc::{format, string::String, vec, vec::Vec};
const STRREF_SENTINEL: u8 = 0xFF;
const LONG_TAG: u8 = 0xFE;
fn encode_str_inline(buf: &mut Vec<u8>, s: &str) -> Result<(), TsonError> {
let len = s.len();
if len <= 0x7F {
buf.push(len as u8);
} else if len <= 16383 {
buf.push(0x80 | ((len >> 8) as u8 & 0x3F));
buf.push(len as u8);
} else if len <= 0x00FF_FFFF {
buf.push(LONG_TAG);
buf.extend_from_slice(&(len as u32).to_le_bytes()[..3]);
} else {
return Err(TsonError::ParseError(
"String exceeds maximum inline length (16 777 215 bytes)".into(),
));
}
buf.extend_from_slice(s.as_bytes());
Ok(())
}
fn encode_str_ref(buf: &mut Vec<u8>, idx: u32) {
buf.push(STRREF_SENTINEL);
buf.extend_from_slice(&idx.to_le_bytes());
}
pub fn encode_document(doc: &TsonDocument) -> Result<Vec<u8>, TsonError> {
let mut buf = vec![0; TsonHeader::SIZE];
let def_off = TsonHeader::SIZE as u32;
encode_def_block_into(&doc.definitions, &mut buf)?;
let dict_off = buf.len() as u32;
encode_dict_block_into(&doc.dict, &mut buf)?;
let data_off = buf.len() as u32;
buf.reserve(doc.data.len() * 16);
encode_data_block_into(&doc.data, &mut buf)?;
let header = TsonHeader::new(doc.header.version, def_off, dict_off, data_off).to_bytes();
buf[..TsonHeader::SIZE].copy_from_slice(&header);
Ok(buf)
}
fn encode_def_block_into(defs: &[TsonDefinition], buf: &mut Vec<u8>) -> Result<(), TsonError> {
let count = defs.len();
if count > u16::MAX as usize {
return Err(TsonError::ParseError(format!(
"Too many definitions ({}), max {}",
count,
u16::MAX
)));
}
buf.extend_from_slice(&(count as u16).to_le_bytes());
for def in defs {
buf.push(def.def_type as u8);
buf.extend_from_slice(&def.index.to_le_bytes());
match def.def_type {
TsonType::Object => {
let fields = def.fields.as_ref().ok_or_else(|| {
TsonError::ParseError(format!("Object #{} has no fields", def.index))
})?;
let fc = fields.len();
buf.extend_from_slice(&(fc as u16).to_le_bytes());
for (name, ft) in fields {
if name.len() > 255 {
return Err(TsonError::ParseError("Field name too long".into()));
}
buf.push(name.len() as u8);
buf.extend_from_slice(name.as_bytes());
buf.push(*ft as u8);
}
}
TsonType::Array => {
let et = def.elem_type.ok_or_else(|| {
TsonError::ParseError(format!("Array #{} has no elem_type", def.index))
})?;
buf.push(et as u8);
}
_ => {}
}
}
Ok(())
}
fn encode_dict_block_into(dict: &[String], buf: &mut Vec<u8>) -> Result<(), TsonError> {
let count = dict.len();
buf.extend_from_slice(&(count as u32).to_le_bytes());
for s in dict {
encode_str_inline(buf, s)?;
}
Ok(())
}
fn encode_data_block_into(chunks: &[TsonChunk], buf: &mut Vec<u8>) -> Result<(), TsonError> {
let count = chunks.len();
buf.extend_from_slice(&(count as u32).to_le_bytes());
for chunk in chunks {
buf.extend_from_slice(&chunk.definition_index.to_le_bytes());
let len_pos = buf.len();
buf.extend_from_slice(&0u32.to_le_bytes());
let payload_start = buf.len();
encode_value_into(&chunk.data, buf)?;
let payload_len = (buf.len() - payload_start) as u32;
buf[len_pos..len_pos + 4].copy_from_slice(&payload_len.to_le_bytes());
}
Ok(())
}
pub fn encode_value(value: &TsonData) -> Result<Vec<u8>, TsonError> {
let mut buf = Vec::new();
encode_value_into(value, &mut buf)?;
Ok(buf)
}
pub fn encode_value_into(value: &TsonData, buf: &mut Vec<u8>) -> Result<(), TsonError> {
match value {
TsonData::Null => {}
TsonData::Bool(v) => buf.push(*v as u8),
TsonData::Int(v) => buf.extend_from_slice(&v.to_le_bytes()),
TsonData::UInt(v) => buf.extend_from_slice(&v.to_le_bytes()),
TsonData::Float(v) => buf.extend_from_slice(&v.to_le_bytes()),
TsonData::String(s) => encode_str_inline(buf, s)?,
TsonData::StrRef(idx) => encode_str_ref(buf, *idx),
TsonData::Array(self_def, elem_def, items) => {
buf.extend_from_slice(&self_def.to_le_bytes());
buf.extend_from_slice(&elem_def.to_le_bytes());
buf.extend_from_slice(&(items.len() as u16).to_le_bytes());
for item in items {
encode_value_into(item, buf)?;
}
}
TsonData::Object(def_index, fields) => {
buf.extend_from_slice(&def_index.to_le_bytes());
for field in fields {
encode_value_into(field, buf)?;
}
}
}
Ok(())
}