use simdjson_sys::{SIMDJSON_PADDING, dom_ffi};
use snafu::prelude::*;
use crate::{error::SimdJsonError, tape::TapeRef};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
#[snafu(display("simdjson error: {source}"))]
SimdJson { source: SimdJsonError },
#[snafu(display(
"the JSON input buffer must have at least {} bytes of extra capacity",
SIMDJSON_PADDING
))]
InsufficientPadding,
#[snafu(display("tape error: {source}"))]
Tape {
#[snafu(backtrace)]
source: crate::tape::Error,
},
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
impl From<crate::tape::Error> for Error {
fn from(source: crate::tape::Error) -> Self {
Error::Tape { source }
}
}
pub struct Parser {
inner: cxx::UniquePtr<dom_ffi::parser>,
padded: Vec<u8>,
valid: bool,
}
impl Default for Parser {
fn default() -> Self {
Self::new(simdjson_sys::SIMDJSON_MAXSIZE_BYTES)
}
}
impl Parser {
pub fn new(max_capacity: usize) -> Self {
Self {
inner: dom_ffi::parser_new(max_capacity),
padded: Vec::new(),
valid: false,
}
}
pub unsafe fn parse_bytes_with_padding(&mut self, json: &[u8]) -> Result<TapeRef<'_>> {
self.valid = false;
let code = unsafe { dom_ffi::parser_parse(self.inner.pin_mut(), json, false) };
SimdJsonError::check_code(code).context(SimdJsonSnafu)?;
self.valid = true;
Ok(self.tape_ref())
}
pub fn parse_str(&mut self, json: &str) -> Result<TapeRef<'_>> {
self.parse_bytes(json.as_bytes())
}
pub fn parse_bytes(&mut self, json: &[u8]) -> Result<TapeRef<'_>> {
self.valid = false;
let json_len = json.len();
self.padded.clear();
self.padded.reserve(json_len + SIMDJSON_PADDING);
self.padded.extend_from_slice(json);
self.padded
.extend(std::iter::repeat_n(0u8, SIMDJSON_PADDING));
let code =
unsafe { dom_ffi::parser_parse(self.inner.pin_mut(), &self.padded[..json_len], false) };
SimdJsonError::check_code(code).context(SimdJsonSnafu)?;
self.valid = true;
Ok(self.tape_ref())
}
pub fn parse_padded(&mut self, json: &mut String) -> Result<TapeRef<'_>> {
self.valid = false;
ensure!(
json.capacity() - json.len() >= SIMDJSON_PADDING,
InsufficientPaddingSnafu
);
let len = json.len();
json.extend(std::iter::repeat_n('\0', SIMDJSON_PADDING));
json.truncate(len);
let code = unsafe { dom_ffi::parser_parse(self.inner.pin_mut(), json.as_bytes(), false) };
SimdJsonError::check_code(code).context(SimdJsonSnafu)?;
self.valid = true;
Ok(self.tape_ref())
}
pub fn tape_ref(&self) -> TapeRef<'_> {
if !self.valid {
return TapeRef::new(&[], &[]);
}
let view = unsafe {
dom_ffi::parser_get_tape_view(self.inner.as_ref().expect("parser must not be null"))
};
TapeRef::new(view.tape, view.string_buf)
}
pub fn parse_to_value(&mut self, json: &str) -> Result<crate::tape::Value<'_>> {
let mut tape = self.parse_str(json)?;
Ok(tape.parse_value()?)
}
pub fn parse_bytes_to_value(&mut self, json: &[u8]) -> Result<crate::tape::Value<'_>> {
let mut tape = self.parse_bytes(json)?;
Ok(tape.parse_value()?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_null_tape() -> Result<()> {
let mut p = Parser::default();
let tape = p.parse_str("null")?;
assert_eq!(tape.tape_type(), Some(crate::tape::TapeType::Root));
Ok(())
}
#[test]
fn parse_object_tape() -> Result<()> {
let mut p = Parser::default();
let tape = p.parse_str(r#"{"answer":42}"#)?;
assert_eq!(tape.tape_type(), Some(crate::tape::TapeType::Root));
Ok(())
}
#[test]
fn parse_to_value_works() -> Result<()> {
let mut p = Parser::default();
let v = p.parse_to_value(r#"{"answer":42,"ok":true,"name":"rust"}"#)?;
assert_eq!(v.get("answer").unwrap().as_i64(), Some(42));
assert_eq!(v.get("ok").unwrap().as_bool(), Some(true));
assert_eq!(v.get("name").unwrap().as_str(), Some("rust"));
Ok(())
}
}