use anyhow::anyhow;
use minijinja::Environment;
use std::collections::HashMap;
use crate::instance::SandboxInstance;
use crate::renderer_env::prepare_renderer;
use crate::repository::{ReadOnlyLoader, ReadOnlyTransaction};
struct RendererContext<'a> {
cache: HashMap<String, serde_json::value::Value>,
env: Environment<'a>,
}
pub fn render_entity_html(
instance: &SandboxInstance,
tx: &ReadOnlyTransaction,
obj: &serde_json::Value,
) -> anyhow::Result<(String, String)> {
let mut env = Environment::new();
prepare_renderer(&mut env, instance);
if let Some(class_spec) = obj["class"]
.as_str()
.and_then(|name| instance.classes.get(name))
{
if let (Some(html_body), Some(html_header)) =
(&class_spec.html_body, &class_spec.html_header)
{
let rendered_header = env
.render_str(
html_header.as_str(),
render_entity(instance, tx, obj, true)?,
)
.map_err(anyhow::Error::new)?;
let rendered_body = env
.render_str(html_body.as_str(), render_entity(instance, tx, obj, true)?)
.map_err(anyhow::Error::new)?;
return Ok((rendered_header, rendered_body));
}
}
Ok((String::new(), String::new()))
}
pub fn render_entity<T: ReadOnlyLoader>(
instance: &SandboxInstance,
tx: &T,
obj: &serde_json::Value,
is_root: bool,
) -> anyhow::Result<serde_json::Value> {
let env = {
let mut env = Environment::new();
env.set_undefined_behavior(minijinja::UndefinedBehavior::Chainable);
prepare_renderer(&mut env, instance);
env
};
recursive_entity_renderer(
&mut RendererContext {
cache: HashMap::new(),
env,
},
instance,
tx,
obj,
is_root,
None,
)
}
fn recursive_entity_renderer<T: ReadOnlyLoader>(
context: &mut RendererContext,
instance: &SandboxInstance,
tx: &T,
obj: &serde_json::Value,
is_root: bool,
stopper: Option<&str>,
) -> anyhow::Result<serde_json::Value> {
let uuid = obj["uid"].as_str().unwrap().to_string();
if context.cache.contains_key(&uuid) && !is_root {
return Ok(context.cache.get(&uuid).unwrap().clone());
}
let class_name = obj["class"].as_str().unwrap();
let class_spec = &instance.classes[class_name];
let mut ctx = serde_json::json!({
"uuid" : obj["uid"]
});
let mut ret = serde_json::json!({
"class": obj["class"],
"uuid": obj["uid"]
});
for spec in class_spec.collects.iter() {
if let Some(attr) = &spec.virtual_attribute {
if attr.is_optional && !is_root {
continue;
}
let frame = tx.retrieve(&format!("{}_frame", uuid))?;
let unused = &frame.value["$collections"]["$unused"][&spec.class_name];
ctx[&attr.attr_name] = serde_json::Value::from(
unused
.as_array()
.unwrap()
.iter()
.map(|unused_id| {
let next = tx.retrieve(unused_id.as_str().unwrap())?;
recursive_entity_renderer(context, instance, tx, &next.value, false, None)
})
.collect::<Result<Vec<serde_json::Value>, _>>()?,
);
if attr.is_public || is_root {
ret[&attr.attr_name] = ctx[&attr.attr_name].clone();
}
}
}
for (attr_name, raw_value) in obj.as_object().unwrap() {
if attr_name.starts_with('$') {
continue;
}
let (is_optional, is_public, is_array) =
if let Some(attr_spec) = &class_spec.attrs.get(attr_name) {
(
attr_spec.is_optional,
attr_spec.is_public,
attr_spec.is_array,
)
} else {
(false, false, false)
};
if is_optional && !is_root {
continue;
}
ctx[attr_name] = match raw_value {
serde_json::Value::Bool(_) | serde_json::Value::Number(_) => obj[attr_name].clone(),
serde_json::Value::String(_) => serde_json::Value::String(
context
.env
.render_str(obj[attr_name].as_str().unwrap(), &ctx)
.map_err(|e| {
anyhow::anyhow!(
"Failed to render string template {} for uid {} attr {} with error {:#}",
obj[attr_name].as_str().unwrap(),
uuid,
attr_name,
e
)
})?,
),
serde_json::Value::Array(_) => {
if is_array {
serde_json::Value::from(
raw_value
.as_array()
.unwrap()
.iter()
.map(|child_uid| {
let next = tx.retrieve(child_uid.as_str().unwrap())?;
recursive_entity_renderer(
context,
instance,
tx,
&next.value,
false,
None,
)
})
.collect::<Result<Vec<serde_json::Value>, _>>()?,
)
} else if let Some(id) = raw_value.as_array().unwrap().iter().next() {
let next = tx.retrieve(id.as_str().unwrap())?;
recursive_entity_renderer(context, instance, tx, &next.value, false, None)?
} else {
serde_json::json!({})
}
}
serde_json::Value::Object(_) => {
render_indirections(context, instance, tx, obj, attr_name)?
}
serde_json::Value::Null => {
let tmpl_str = class_spec.attrs[attr_name].cmd.value().unwrap();
serde_json::Value::String(context.env.render_str(&tmpl_str, &ctx).map_err(|e| {
anyhow::anyhow!(
"Failed to render string template {} for uid {} attr {} with error {:#}",
tmpl_str,
uuid,
attr_name,
e
)
})?,
)
}
};
if is_public || is_root {
ret[attr_name] = ctx[attr_name].clone();
}
if let Some(stopper) = stopper {
if stopper == attr_name {
break;
}
} else {
context.cache.insert(uuid.clone(), ret.clone());
}
}
Ok(ret)
}
fn render_pointer_attribute<T: ReadOnlyLoader>(
context: &mut RendererContext,
instance: &SandboxInstance,
tx: &T,
uid: &str,
attr: &str,
) -> anyhow::Result<serde_json::Value> {
let pointed_entity = tx.retrieve(uid)?.value;
let pointed_render =
recursive_entity_renderer(context, instance, tx, &pointed_entity, true, Some(attr))?;
Ok(pointed_render[attr].clone())
}
fn render_parent_attribute<T: ReadOnlyLoader>(
context: &mut RendererContext,
instance: &SandboxInstance,
tx: &T,
pid: &str,
parent_class: &str,
parent_attr: &str,
) -> anyhow::Result<serde_json::Value> {
let parent = tx.retrieve(pid)?.value;
let class = parent["class"].as_str().unwrap();
let Some(class_spec) = &instance.classes.get(class) else {
return Err(anyhow!("Could not find class {} in entity {}", class, pid));
};
if class_spec.hierarchy.contains(&parent_class.to_string()) {
let v = &parent[parent_attr];
return if v.is_array() && !v.as_array().unwrap().is_empty() {
let data_uid = v.as_array().unwrap().first().unwrap().as_str().unwrap();
let data = tx.retrieve(data_uid)?.value;
recursive_entity_renderer(context, instance, tx, &data, false, Some(parent_attr))
} else if v.is_object() {
render_indirections(context, instance, tx, &parent, parent_attr)
} else {
Ok(parent[parent_attr].clone())
};
} else {
let my_pid = &parent["parent_uid"];
if my_pid != "root" {
return render_parent_attribute(
context,
instance,
tx,
my_pid.as_str().unwrap(),
parent_class,
parent_attr,
);
}
}
Ok(serde_json::json!(false))
}
fn render_indirections<T: ReadOnlyLoader>(
context: &mut RendererContext,
instance: &SandboxInstance,
tx: &T,
obj: &serde_json::Value,
attr_name: &str,
) -> Result<serde_json::Value, anyhow::Error> {
let indirection = &obj[attr_name];
if indirection["type"] == "context" {
let pid = obj["parent_uid"].as_str().unwrap();
let spec = &indirection["spec"];
let parent_attr = spec["attr"].as_str().unwrap();
return render_parent_attribute(
context,
instance,
tx,
pid,
spec["parent"].as_str().unwrap(),
parent_attr,
);
} else if indirection["type"] == "pointer" {
let spec = &indirection["spec"];
let attr = spec["attr"].as_str().unwrap();
return render_pointer_attribute(
context,
instance,
tx,
spec["uid"].as_str().unwrap(),
attr,
);
} else {
return Err(anyhow!(
"Unknown obj detected {}, {}",
indirection,
attr_name
));
}
}