use bumpalo::Bump;
use self_cell::self_cell;
use crate::Result;
use crate::arena::DataValue;
self_cell!(
struct ParsedCell {
owner: Bump,
#[covariant]
dependent: DataValue,
}
);
pub struct ParsedData(ParsedCell);
impl ParsedData {
pub fn from_json(json: &str) -> Result<Self> {
let cell =
ParsedCell::try_new(Bump::with_capacity(json.len().saturating_mul(2)), |arena| {
let stable: &str = arena.alloc_str(json);
DataValue::from_str(stable, arena).map_err(crate::Error::from)
})?;
Ok(Self(cell))
}
#[cfg(feature = "serde_json")]
pub fn from_value(value: &serde_json::Value) -> Self {
Self(ParsedCell::new(Bump::new(), |arena| {
DataValue::from_serde_value_in(value, arena)
}))
}
pub fn from_owned(value: &datavalue::OwnedDataValue) -> Self {
Self(ParsedCell::new(Bump::new(), |arena| value.to_arena(arena)))
}
pub fn value(&self) -> &DataValue<'_> {
self.0.borrow_dependent()
}
pub fn allocated_bytes(&self) -> usize {
self.0.borrow_owner().allocated_bytes()
}
}
impl std::fmt::Debug for ParsedData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ParsedData").field(self.value()).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Engine;
#[test]
fn parses_and_exposes_value() {
let data = ParsedData::from_json(r#"{"a": [1, 2, 3], "b": "text"}"#).unwrap();
let v = data.value();
assert!(v.is_object());
assert_eq!(v.to_string(), r#"{"a":[1,2,3],"b":"text"}"#);
assert!(data.allocated_bytes() > 0);
}
#[cfg(feature = "serde_json")]
#[test]
fn every_constructor_holds_the_same_tree() {
let docs = [
r#"{"a": [1, -2, 3.5, 18446744073709551615], "b": "text"}"#,
r#"{"ключ": {"日本": ["é", "", "😀", "a\"b"]}, "": {"": null}}"#,
r#"[[], {}, [[]], {"x": {}}, true, false, null]"#,
r#""just a string""#,
"42",
];
for doc in docs {
let from_json = ParsedData::from_json(doc).unwrap();
let value: serde_json::Value = serde_json::from_str(doc).unwrap();
let from_value = ParsedData::from_value(&value);
let owned = datavalue::OwnedDataValue::from_json(doc).unwrap();
let from_owned = ParsedData::from_owned(&owned);
assert_eq!(from_value.value(), from_json.value(), "from_value: {doc}");
assert_eq!(from_owned.value(), from_json.value(), "from_owned: {doc}");
assert_eq!(
from_owned.value().to_string(),
from_json.value().to_string()
);
}
}
#[cfg(feature = "serde_json")]
#[test]
fn a_handle_outlives_its_source() {
let engine = Engine::new();
let rule = engine.compile(r#"{"cat": [{"var": "s"}, "!"]}"#).unwrap();
let from_value = {
let value = serde_json::json!({"s": "kept"});
ParsedData::from_value(&value)
};
let from_owned = {
let owned = datavalue::OwnedDataValue::from_json(r#"{"s": "kept"}"#).unwrap();
ParsedData::from_owned(&owned)
};
let arena = bumpalo::Bump::new();
for data in [&from_value, &from_owned] {
let out = engine.evaluate(&rule, data, &arena).unwrap();
assert_eq!(out.as_str(), Some("kept!"));
}
}
#[test]
fn malformed_json_is_a_parse_error() {
let err = ParsedData::from_json("{ not json").unwrap_err();
assert_eq!(err.tag(), "ParseError");
}
#[test]
fn evaluates_through_engine_and_session() {
let engine = Engine::new();
let rule = engine.compile(r#"{"+": [{"var": "x"}, 1]}"#).unwrap();
let data = ParsedData::from_json(r#"{"x": 41}"#).unwrap();
let arena = bumpalo::Bump::new();
for _ in 0..3 {
let out = engine.evaluate(&rule, &data, &arena).unwrap();
assert_eq!(out.as_i64(), Some(42));
}
let mut session = engine.session();
let out = session.eval_borrowed(&rule, &data).unwrap();
assert_eq!(out.as_i64(), Some(42));
}
#[test]
fn outlives_short_lived_eval_arenas() {
let engine = Engine::new();
let rule = engine.compile(r#"{"var": "name"}"#).unwrap();
let data = ParsedData::from_json(r#"{"name": "Ada"}"#).unwrap();
for _ in 0..2 {
let arena = bumpalo::Bump::new();
let out = engine.evaluate(&rule, &data, &arena).unwrap();
assert_eq!(out.to_string(), r#""Ada""#);
}
assert_eq!(data.value().to_string(), r#"{"name":"Ada"}"#);
}
#[test]
fn handle_is_send() {
fn assert_send<T: Send>() {}
assert_send::<ParsedData>();
}
}