use std::fmt::{self, Display};
use allocative::Allocative;
use serde_json::{Value as JsonValue, json};
use starlark::starlark_simple_value;
use starlark::values::list::ListRef;
use starlark::values::{
Heap, ProvidesStaticType, StarlarkValue, Trace, Value, ValueLike, starlark_value,
};
#[derive(Debug, Clone, ProvidesStaticType, Allocative)]
pub struct MatchTreeNode {
#[allocative(skip)]
pub json: JsonValue,
}
impl serde::Serialize for MatchTreeNode {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.json.serialize(serializer)
}
}
unsafe impl Trace<'_> for MatchTreeNode {
fn trace(&mut self, _tracer: &starlark::values::Tracer<'_>) {}
}
impl Display for MatchTreeNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MatchTreeNode({})", self.json)
}
}
starlark_simple_value!(MatchTreeNode);
#[starlark_value(type = "MatchTreeNode")]
impl<'v> StarlarkValue<'v> for MatchTreeNode {
fn get_methods() -> Option<&'static starlark::environment::Methods> {
static RES: starlark::environment::MethodsStatic =
starlark::environment::MethodsStatic::new();
RES.methods(match_tree_node_methods)
}
}
#[starlark::starlark_module]
fn match_tree_node_methods(builder: &mut starlark::environment::MethodsBuilder) {
fn on<'v>(
this: &MatchTreeNode,
#[starlark(require = pos)] children: Value<'v>,
) -> anyhow::Result<MatchTreeNode> {
let list = ListRef::from_value(children)
.ok_or_else(|| anyhow::anyhow!(".on() requires a list of nodes"))?;
let mut child_nodes = Vec::new();
for item in list.iter() {
if let Some(node) = item.downcast_ref::<MatchTreeNode>() {
child_nodes.push(node.json.clone());
} else {
anyhow::bail!(
".on() children must be MatchTreeNode values, got {}",
item.get_type()
);
}
}
let mut json = this.json.clone();
set_children_on_deepest_leaf(&mut json, child_nodes);
Ok(MatchTreeNode { json })
}
fn allow(
this: &MatchTreeNode,
#[starlark(require = named)] sandbox: Option<&str>,
) -> anyhow::Result<MatchTreeNode> {
let decision = if let Some(sb) = sandbox {
json!({"decision": {"allow": sb}})
} else {
json!({"decision": {"allow": null}})
};
set_children(this, vec![decision])
}
fn deny(this: &MatchTreeNode) -> anyhow::Result<MatchTreeNode> {
let decision = json!({"decision": "deny"});
set_children(this, vec![decision])
}
fn ask(
this: &MatchTreeNode,
#[starlark(require = named)] sandbox: Option<&str>,
) -> anyhow::Result<MatchTreeNode> {
let decision = if let Some(sb) = sandbox {
json!({"decision": {"ask": sb}})
} else {
json!({"decision": {"ask": null}})
};
set_children(this, vec![decision])
}
}
fn set_children(node: &MatchTreeNode, children: Vec<JsonValue>) -> anyhow::Result<MatchTreeNode> {
let mut json = node.json.clone();
set_children_on_deepest_leaf(&mut json, children);
Ok(MatchTreeNode { json })
}
fn set_children_on_deepest_leaf(json: &mut JsonValue, children: Vec<JsonValue>) {
if let Some(obj) = json.as_object_mut()
&& let Some(cond) = obj.get_mut("condition").and_then(|c| c.as_object_mut())
{
if let Some(existing) = cond.get_mut("children").and_then(|c| c.as_array_mut()) {
if existing.is_empty() {
*existing = children;
return;
}
if existing.len() == 1 && existing[0].get("condition").is_some() {
set_children_on_deepest_leaf(&mut existing[0], children);
return;
}
}
cond.insert("children".into(), serde_json::json!(children));
}
}
#[allow(dead_code)]
pub fn mt_condition(observe: JsonValue, pattern: JsonValue) -> MatchTreeNode {
mt_condition_with_doc(observe, pattern, None, None)
}
pub fn mt_condition_with_doc(
observe: JsonValue,
pattern: JsonValue,
doc: Option<String>,
source: Option<String>,
) -> MatchTreeNode {
let mut condition = json!({
"observe": observe,
"pattern": pattern,
"children": []
});
if let Some(d) = doc {
condition
.as_object_mut()
.unwrap()
.insert("doc".to_string(), json!(d));
}
if let Some(s) = source {
condition
.as_object_mut()
.unwrap()
.insert("source".to_string(), json!(s));
}
MatchTreeNode {
json: json!({"condition": condition}),
}
}
pub fn pattern_to_json<'v>(value: Value<'v>, heap: &'v Heap) -> anyhow::Result<JsonValue> {
if value.is_none() {
return Ok(json!("wildcard"));
}
if let Some(s) = value.unpack_str() {
return Ok(json!({"literal": {"literal": s}}));
}
if let Some(list) = ListRef::from_value(value) {
let items: Result<Vec<_>, _> = list.iter().map(|v| pattern_to_json(v, heap)).collect();
return Ok(json!({"any_of": items?}));
}
if value.get_type() == "struct"
&& let Ok(Some(regex_val)) = value.get_attr("_regex", heap)
&& let Some(s) = regex_val.unpack_str()
{
return Ok(json!({"regex": s}));
}
anyhow::bail!(
"cannot convert {} to a match tree pattern",
value.get_type()
)
}
pub fn path_value_to_json<'v>(value: Value<'v>, heap: &'v Heap) -> anyhow::Result<JsonValue> {
if let Some(s) = value.unpack_str() {
return Ok(json!({"literal": s}));
}
if value.get_type() == "struct" {
if let Ok(Some(env_val)) = value.get_attr("_env", heap)
&& let Some(env_name) = env_val.unpack_str()
{
return Ok(json!({"env": env_name}));
}
if let Ok(Some(join_val)) = value.get_attr("_join", heap)
&& let Some(list) = ListRef::from_value(join_val)
{
let parts: Result<Vec<_>, _> =
list.iter().map(|v| path_value_to_json(v, heap)).collect();
return Ok(json!({"path": parts?}));
}
}
anyhow::bail!("cannot convert {} to a path value", value.get_type())
}