reifydb-codec 0.10.1

Unified encoding and decoding for all ReifyDB values, frames, rows, keys, and boundaries
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 ReifyDB

use arrow_buffer::i256;
use reifydb_value::value::{
	Value,
	constraint::{precision::Precision, scale::Scale},
	decimal::Decimal,
	dictionary::DictionaryEntryId,
	duration::Duration,
};

use crate::{
	error::{DecodeError, EncodeError},
	reader::Reader,
	tag::ValueKind,
	unscaled::{NARROW, WIDE, decimal_unscaled, extend_le, read_le, width},
	value::{decode_value, encode_value_into},
};

pub fn encode_decimal_cell(
	value: &Decimal,
	precision: Precision,
	scale: Scale,
	buf: &mut Vec<u8>,
) -> Result<(), EncodeError> {
	let unscaled = decimal_unscaled(value, precision, scale).ok_or_else(|| {
		EncodeError::UnsupportedType(format!(
			"decimal {value} does not fit precision {} and scale {}",
			precision.value(),
			scale.value()
		))
	})?;
	extend_le(unscaled, width(precision), buf);
	Ok(())
}

pub fn decode_decimal_cell(bytes: &[u8], scale: Scale) -> Result<Decimal, DecodeError> {
	let unscaled = read_cell(ValueKind::Decimal, bytes)?;
	Decimal::from_parts(unscaled, scale.value()).ok_or_else(|| {
		DecodeError::InvalidData(format!(
			"decimal cell {unscaled} at scale {} exceeds 76 digits",
			scale.value()
		))
	})
}

fn read_cell(kind: ValueKind, bytes: &[u8]) -> Result<i256, DecodeError> {
	match bytes.len() {
		NARROW | WIDE => Ok(read_le(bytes)),
		other => Err(DecodeError::InvalidData(format!("{kind:?} cell is {other} bytes, expected 16 or 32"))),
	}
}

pub fn encode_any_cell(value: &Value, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
	encode_value_into(value, buf)
}

pub fn decode_any_cell(bytes: &[u8]) -> Result<Value, DecodeError> {
	decode_value(bytes)
}

pub fn encode_dictionary_id_cell(id: &DictionaryEntryId, buf: &mut Vec<u8>) {
	match id {
		DictionaryEntryId::U1(v) => {
			buf.push(1);
			buf.extend_from_slice(&v.to_le_bytes());
		}
		DictionaryEntryId::U2(v) => {
			buf.push(2);
			buf.extend_from_slice(&v.to_le_bytes());
		}
		DictionaryEntryId::U4(v) => {
			buf.push(4);
			buf.extend_from_slice(&v.to_le_bytes());
		}
		DictionaryEntryId::U8(v) => {
			buf.push(8);
			buf.extend_from_slice(&v.to_le_bytes());
		}
		DictionaryEntryId::U16(v) => {
			buf.push(16);
			buf.extend_from_slice(&v.to_le_bytes());
		}
	}
}

pub fn decode_dictionary_id_cell(bytes: &[u8]) -> Result<DictionaryEntryId, DecodeError> {
	let mut r = Reader::new(bytes);
	let width = r.u8()?;
	let id = match width {
		1 => DictionaryEntryId::U1(r.u8()?),
		2 => DictionaryEntryId::U2(r.u16()?),
		4 => DictionaryEntryId::U4(r.u32()?),
		8 => DictionaryEntryId::U8(r.u64()?),
		16 => DictionaryEntryId::U16(r.u128()?),
		other => {
			return Err(DecodeError::InvalidData(format!("invalid dictionary id width: {other}")));
		}
	};
	if !r.is_empty() {
		return Err(DecodeError::TrailingBytes(r.remaining()));
	}
	Ok(id)
}

pub fn encode_duration_cell(value: &Duration, buf: &mut Vec<u8>) {
	buf.extend_from_slice(&value.get_months().to_le_bytes());
	buf.extend_from_slice(&value.get_days().to_le_bytes());
	buf.extend_from_slice(&value.get_nanos().to_le_bytes());
}

pub fn decode_duration_cell(bytes: &[u8]) -> Result<Duration, DecodeError> {
	let mut r = Reader::new(bytes);
	let months = r.i32()?;
	let days = r.i32()?;
	let nanos = r.i64()?;
	if !r.is_empty() {
		return Err(DecodeError::TrailingBytes(r.remaining()));
	}
	Duration::new(months, days, nanos).map_err(|e| DecodeError::InvalidData(format!("invalid duration: {e}")))
}