Current section

Files

Jump to
torque native torque_nif src encoder.rs
Raw

native/torque_nif/src/encoder.rs

use crate::atoms;
use crate::nif_util::make_tuple2;
use crate::types::MAX_DEPTH;
use rustler::sys::{
c_int, c_uint, enif_get_atom, enif_get_atom_length, enif_get_double, enif_get_int64,
enif_get_list_cell, enif_get_tuple, enif_get_uint64, enif_inspect_binary, ErlNifBinary,
ErlNifCharEncoding, ErlNifEnv, ERL_NIF_TERM,
};
use rustler::{Env, MapIterator, NewBinary, Term, TermType};
use std::mem::MaybeUninit;
#[derive(Debug)]
enum EncodeError {
UnsupportedType,
NonFiniteFloat,
InvalidKey,
MalformedProplist,
DepthExceeded,
InvalidUtf8,
}
/// Read an atom's name into a stack buffer without heap allocation.
#[inline]
unsafe fn atom_to_stack_buf(
env_raw: *mut ErlNifEnv,
term_raw: ERL_NIF_TERM,
buf: &mut [u8; 256],
) -> Result<&[u8], EncodeError> {
let mut len: c_uint = 0;
if enif_get_atom_length(
env_raw,
term_raw,
&mut len,
ErlNifCharEncoding::ERL_NIF_LATIN1,
) == 0
{
return Err(EncodeError::UnsupportedType);
}
let alen = (len + 1) as usize;
if alen > 256 {
return Err(EncodeError::UnsupportedType);
}
enif_get_atom(
env_raw,
term_raw,
buf.as_mut_ptr(),
alen as c_uint,
ErlNifCharEncoding::ERL_NIF_LATIN1,
);
Ok(&buf[..len as usize])
}
#[rustler::nif]
fn encode<'a>(env: Env<'a>, term: Term<'a>) -> Term<'a> {
let mut buf: Vec<u8> = Vec::with_capacity(2048);
let env_raw = env.as_c_arg();
match encode_term(env, env_raw, term, &mut buf, MAX_DEPTH) {
Ok(()) => {
let mut binary = NewBinary::new(env, buf.len());
binary.as_mut_slice().copy_from_slice(&buf);
let bin_term: Term = binary.into();
make_tuple2(env, atoms::ok().as_c_arg(), bin_term.as_c_arg())
}
Err(e) => {
let reason = match e {
EncodeError::DepthExceeded => atoms::nesting_too_deep().as_c_arg(),
EncodeError::UnsupportedType => atoms::unsupported_type().as_c_arg(),
EncodeError::NonFiniteFloat => atoms::non_finite_float().as_c_arg(),
EncodeError::InvalidKey => atoms::invalid_key().as_c_arg(),
EncodeError::MalformedProplist => atoms::malformed_proplist().as_c_arg(),
EncodeError::InvalidUtf8 => atoms::invalid_utf8().as_c_arg(),
};
make_tuple2(env, atoms::error().as_c_arg(), reason)
}
}
}
/// Returns the raw binary on success, raises on error.
/// Skips the {:ok, binary} tuple wrapping for maximum throughput.
#[rustler::nif]
fn encode_iodata<'a>(env: Env<'a>, term: Term<'a>) -> Term<'a> {
let mut buf: Vec<u8> = Vec::with_capacity(2048);
let env_raw = env.as_c_arg();
match encode_term(env, env_raw, term, &mut buf, MAX_DEPTH) {
Ok(()) => {
let mut binary = NewBinary::new(env, buf.len());
binary.as_mut_slice().copy_from_slice(&buf);
binary.into()
}
Err(e) => unsafe {
let reason = match e {
EncodeError::DepthExceeded => atoms::nesting_too_deep().as_c_arg(),
EncodeError::UnsupportedType => atoms::unsupported_type().as_c_arg(),
EncodeError::NonFiniteFloat => atoms::non_finite_float().as_c_arg(),
EncodeError::InvalidKey => atoms::invalid_key().as_c_arg(),
EncodeError::MalformedProplist => atoms::malformed_proplist().as_c_arg(),
EncodeError::InvalidUtf8 => atoms::invalid_utf8().as_c_arg(),
};
Term::new(env, rustler::sys::enif_raise_exception(env_raw, reason))
},
}
}
#[inline]
fn encode_term(
env: Env,
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
depth: u32,
) -> Result<(), EncodeError> {
match term.get_type() {
TermType::Map => encode_map(env, env_raw, term, buf, depth),
TermType::List => encode_list(env, env_raw, term, buf, depth),
TermType::Binary => encode_binary(env_raw, term, buf),
TermType::Integer => encode_integer(env_raw, term, buf),
TermType::Float => encode_float(env_raw, term, buf),
TermType::Atom => encode_atom(env_raw, term, buf),
TermType::Tuple => encode_tuple(env, env_raw, term, buf, depth),
_ => Err(EncodeError::UnsupportedType),
}
}
fn encode_map(
env: Env,
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
depth: u32,
) -> Result<(), EncodeError> {
if depth == 0 {
return Err(EncodeError::DepthExceeded);
}
let iter = MapIterator::new(term).ok_or(EncodeError::UnsupportedType)?;
buf.push(b'{');
let mut first = true;
for (key, value) in iter {
if !first {
buf.push(b',');
}
first = false;
encode_map_key(env_raw, key, buf)?;
buf.push(b':');
encode_term(env, env_raw, value, buf, depth - 1)?;
}
buf.push(b'}');
Ok(())
}
#[inline]
fn encode_map_key(
env_raw: *mut ErlNifEnv,
key: Term,
buf: &mut Vec<u8>,
) -> Result<(), EncodeError> {
buf.push(b'"');
match key.get_type() {
TermType::Atom => {
let mut atom_buf = [0u8; 256];
let name = unsafe { atom_to_stack_buf(env_raw, key.as_c_arg(), &mut atom_buf)? };
crate::escape::escape_to_vec(name, buf);
}
TermType::Binary => {
let mut bin = MaybeUninit::<ErlNifBinary>::uninit();
unsafe {
if enif_inspect_binary(env_raw, key.as_c_arg(), bin.as_mut_ptr()) == 0 {
return Err(EncodeError::InvalidKey);
}
let bin = bin.assume_init();
let slice = std::slice::from_raw_parts(bin.data, bin.size);
crate::escape::validate_and_escape_to_vec(slice, buf)
.map_err(|_| EncodeError::InvalidUtf8)?;
}
}
_ => return Err(EncodeError::InvalidKey),
}
buf.push(b'"');
Ok(())
}
fn encode_list(
env: Env,
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
depth: u32,
) -> Result<(), EncodeError> {
if depth == 0 {
return Err(EncodeError::DepthExceeded);
}
buf.push(b'[');
let mut first = true;
let mut current = term.as_c_arg();
let mut head: ERL_NIF_TERM = 0;
let mut tail: ERL_NIF_TERM = 0;
while unsafe { enif_get_list_cell(env_raw, current, &mut head, &mut tail) } != 0 {
if !first {
buf.push(b',');
}
first = false;
let item = unsafe { Term::new(env, head) };
encode_term(env, env_raw, item, buf, depth - 1)?;
current = tail;
}
buf.push(b']');
Ok(())
}
#[inline]
fn encode_binary(
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
) -> Result<(), EncodeError> {
let mut bin = MaybeUninit::<ErlNifBinary>::uninit();
unsafe {
if enif_inspect_binary(env_raw, term.as_c_arg(), bin.as_mut_ptr()) == 0 {
return Err(EncodeError::UnsupportedType);
}
let bin = bin.assume_init();
let slice = std::slice::from_raw_parts(bin.data, bin.size);
buf.push(b'"');
crate::escape::validate_and_escape_to_vec(slice, buf)
.map_err(|_| EncodeError::InvalidUtf8)?;
buf.push(b'"');
}
Ok(())
}
#[inline]
fn encode_integer(
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
) -> Result<(), EncodeError> {
let mut n: i64 = 0;
if unsafe { enif_get_int64(env_raw, term.as_c_arg(), &mut n) } != 0 {
let mut itoa_buf = itoa::Buffer::new();
buf.extend_from_slice(itoa_buf.format(n).as_bytes());
return Ok(());
}
// Fallback for u64 range (i64::MAX + 1 ..= u64::MAX)
let mut u: u64 = 0;
if unsafe { enif_get_uint64(env_raw, term.as_c_arg(), &mut u) } != 0 {
let mut itoa_buf = itoa::Buffer::new();
buf.extend_from_slice(itoa_buf.format(u).as_bytes());
return Ok(());
}
Err(EncodeError::UnsupportedType)
}
#[inline]
fn encode_float(env_raw: *mut ErlNifEnv, term: Term, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
let mut n: f64 = 0.0;
if unsafe { enif_get_double(env_raw, term.as_c_arg(), &mut n) } == 0 {
return Err(EncodeError::UnsupportedType);
}
// ryu panics on non-finite floats; JSON has no representation for them
if !n.is_finite() {
return Err(EncodeError::NonFiniteFloat);
}
let mut ryu_buf = ryu::Buffer::new();
buf.extend_from_slice(ryu_buf.format(n).as_bytes());
Ok(())
}
#[inline]
fn encode_atom(env_raw: *mut ErlNifEnv, term: Term, buf: &mut Vec<u8>) -> Result<(), EncodeError> {
let raw = term.as_c_arg();
if raw == atoms::r#true().as_c_arg() {
buf.extend_from_slice(b"true");
} else if raw == atoms::r#false().as_c_arg() {
buf.extend_from_slice(b"false");
} else if raw == atoms::nil().as_c_arg() {
buf.extend_from_slice(b"null");
} else {
let mut atom_buf = [0u8; 256];
let name = unsafe { atom_to_stack_buf(env_raw, raw, &mut atom_buf)? };
buf.push(b'"');
crate::escape::escape_to_vec(name, buf);
buf.push(b'"');
}
Ok(())
}
/// Get a raw tuple slice without allocating a Vec.
#[inline]
unsafe fn get_tuple_raw<'a>(
env_raw: *mut ErlNifEnv,
term: Term,
) -> Result<&'a [ERL_NIF_TERM], EncodeError> {
let mut arity: c_int = 0;
let mut array_ptr = MaybeUninit::uninit();
if enif_get_tuple(env_raw, term.as_c_arg(), &mut arity, array_ptr.as_mut_ptr()) != 1 {
return Err(EncodeError::UnsupportedType);
}
Ok(std::slice::from_raw_parts(
array_ptr.assume_init(),
arity as usize,
))
}
fn encode_tuple(
env: Env,
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
depth: u32,
) -> Result<(), EncodeError> {
let elements = unsafe { get_tuple_raw(env_raw, term)? };
if elements.len() == 1 {
let inner = unsafe { Term::new(env, elements[0]) };
if inner.get_type() == TermType::List {
return encode_proplist(env, env_raw, inner, buf, depth);
}
}
Err(EncodeError::UnsupportedType)
}
fn encode_proplist(
env: Env,
env_raw: *mut ErlNifEnv,
term: Term,
buf: &mut Vec<u8>,
depth: u32,
) -> Result<(), EncodeError> {
if depth == 0 {
return Err(EncodeError::DepthExceeded);
}
buf.push(b'{');
let mut first = true;
let mut current = term.as_c_arg();
let mut head: ERL_NIF_TERM = 0;
let mut tail: ERL_NIF_TERM = 0;
while unsafe { enif_get_list_cell(env_raw, current, &mut head, &mut tail) } != 0 {
let pair = unsafe {
let pair_term = Term::new(env, head);
get_tuple_raw(env_raw, pair_term).map_err(|_| EncodeError::MalformedProplist)?
};
if pair.len() != 2 {
return Err(EncodeError::MalformedProplist);
}
if !first {
buf.push(b',');
}
first = false;
let key = unsafe { Term::new(env, pair[0]) };
let val = unsafe { Term::new(env, pair[1]) };
encode_map_key(env_raw, key, buf)?;
buf.push(b':');
encode_term(env, env_raw, val, buf, depth - 1)?;
current = tail;
}
buf.push(b'}');
Ok(())
}