use std::collections::HashMap;
use std::sync::Arc;
use anyhow::anyhow;
use anyhow::Result;
use parking_lot::RwLock;
use serde_json::Value;
use crate::context::Context;
use crate::evaluate::EvaluationError;
use crate::expression::ListExpression;
use crate::expression::Logs;
use crate::expression::{Expression, ObjectExpression};
use crate::node_props::NodeProps;
use crate::node_props::NodePropsError;
use crate::node_props::NodePropsType;
use crate::query::*;
#[derive(Clone)]
pub enum UpdateStep {
GetField {
field_name: String,
field_arguments: Value,
},
GetItem {
index: usize,
},
}
struct NodeInner {
pub props: NodeProps,
}
impl NodeInner {
fn new(props: NodeProps) -> Self {
Self { props }
}
fn check_for_update(&self) -> bool {
let context = self.props.context.read();
match &context.init_data {
None => false,
Some(init_data) => match &self.props.commit_hash {
Some(commit_hash) => commit_hash != &init_data.hash,
None => true,
},
}
}
fn get_path(&self) -> String {
let prefix = match self.props.parent {
Some(ref parent) => format!("{} > ", parent.get_path()),
None => "".to_string(),
};
match self.props.step {
Some(UpdateStep::GetItem { ref index }) => format!("{}[{}]", prefix, index),
Some(UpdateStep::GetField {
ref field_name,
ref field_arguments,
}) => format!("{}{}({})", prefix, field_name, field_arguments),
None => format!("{}{{}}", prefix),
}
}
fn update(&mut self) -> Result<()> {
match &self.props.parent {
Some(parent) => match self.props.step {
Some(UpdateStep::GetField {
ref field_name,
ref field_arguments,
}) => {
self.props = parent.get_field(field_name, field_arguments.clone())?;
Ok(())
}
Some(UpdateStep::GetItem { index }) => {
let items = parent.get_items()?;
let props = items.get(index).ok_or(anyhow!(
"Child node with index is no longer present in parent items"
))?;
self.props = props.clone();
Ok(())
}
None => Err(anyhow!(
"Could not recreate child from parent as Node step was missing"
)),
},
None => {
let context = self.props.context.read();
let init_data = context.init_data.as_ref().ok_or(anyhow!("Could not get init data despite having already determined node needs to update"))?;
self.props.expression = Some(init_data.reduced_expression.clone());
self.props.commit_hash = Some(init_data.hash.clone());
Ok(())
}
}
}
}
#[derive(Clone)]
pub struct Node {
inner: Arc<RwLock<NodeInner>>,
}
impl Node {
pub fn new(props: NodeProps) -> Self {
Self {
inner: Arc::new(RwLock::new(NodeInner::new(props))),
}
}
#[cfg(test)]
pub fn new_with_hash(
context: Arc<RwLock<Context>>,
expression: Expression,
commit_hash: String,
) -> Self {
let props = NodeProps {
r#type: expression.get_node_props_type(),
expression: Some(expression),
context,
commit_hash: Some(commit_hash),
parent: None,
step: None,
};
Self::new(props)
}
pub fn wait_for_initialization(&self) {
let node = self.inner.read();
Context::wait_for_initialization(node.props.context.clone());
}
pub async fn flush_logs(&self) {
let node = self.inner.read();
Context::flush_logs(node.props.context.clone()).await;
}
pub fn close(&self) {
let node = self.inner.read();
Context::close(node.props.context.clone());
}
pub fn get_field(
&self,
field_name: &str,
field_arguments: Value,
) -> Result<NodeProps, NodePropsError> {
match self.get_field_inner(field_name, &field_arguments) {
Ok(props) => Ok(props),
Err(error) => {
let node = self.inner.read();
Context::log_error(node.props.context.clone(), format!("{}", error));
Err(NodePropsError::GetField(NodeProps {
r#type: NodePropsType::Error,
context: node.props.context.clone(),
expression: None,
commit_hash: None,
parent: Some(self.clone()),
step: Some(UpdateStep::GetField {
field_name: field_name.to_string(),
field_arguments,
}),
}))
}
}
}
fn get_field_inner(&self, field_name: &str, field_arguments: &Value) -> Result<NodeProps> {
let mut node = self.inner.write();
if node.check_for_update() {
node.update()?;
}
match &node.props.expression {
Some(Expression::Object(expression)) => {
let selection: Selection = HashMap::from([(
field_name.to_string(),
SelectionField {
field_query: None,
field_arguments: Some(match field_arguments {
Value::Object(ref object) => object
.into_iter()
.map(|(key, value)| match value.clone().try_into() {
Ok(value) => Ok((key.clone(), value)),
Err(error) => Err(error),
})
.collect::<Result<_>>(),
_ => Err(anyhow!("field arguments must be an object value")),
})
.transpose()?,
},
)]);
let fragment = Fragment {
object_type_name: expression.object_type_name.clone(),
selection,
};
let query: Query = HashMap::from([(expression.object_type_name.clone(), fragment)]);
let expression = {
let context = node.props.context.read();
context.reduce(Some(&query), Expression::Object(expression.clone()))?
};
if let Expression::Object(ObjectExpression { fields, .. }) = expression {
fields
.get(field_name)
.ok_or(anyhow!(format!(
"Object expression does not contain field {}",
field_name
)))
.map(|expression| NodeProps {
r#type: expression.get_node_props_type(),
expression: Some(expression.clone()),
context: node.props.context.clone(),
commit_hash: node.props.commit_hash.clone(),
parent: Some(self.clone()),
step: Some(UpdateStep::GetField {
field_name: field_name.to_string(),
field_arguments: field_arguments.clone(),
}),
})
} else {
Err(anyhow!(format!(
"Cannot get field {} as expression type is {:?}",
field_name, node.props.expression
)))
}
}
_ => Err(anyhow!(format!(
"Cannot get field {} as expression type is {:?}",
field_name, node.props.expression
))),
}
}
pub fn get_items(&self) -> Result<Vec<NodeProps>> {
match self.get_items_inner() {
Ok(props) => Ok(props),
Err(error) => {
let node = self.inner.read();
Context::log_error(node.props.context.clone(), format!("{}", error));
Err(error)
}
}
}
fn get_items_inner(&self) -> Result<Vec<NodeProps>> {
let mut node = self.inner.write();
if node.check_for_update() {
node.update()?;
}
match &node.props.expression {
Some(Expression::List(ListExpression { items, logs, .. })) => {
let list_logs = Logs::merge(
logs,
&Logs::evaluation_logs(node.props.expression.as_ref().unwrap()),
);
Ok(items
.iter()
.enumerate()
.map(|(index, item)| {
let expression = item.clone().merge_logs(&list_logs);
NodeProps {
r#type: expression.get_node_props_type(),
expression: Some(expression),
context: node.props.context.clone(),
commit_hash: node.props.commit_hash.clone(),
parent: Some(self.clone()),
step: Some(UpdateStep::GetItem { index }),
}
})
.collect())
}
_ => Err(anyhow!(format!(
"Cannot get items as expression type is {:?}",
node.props.expression
))),
}
}
pub fn evaluate(&self) -> std::result::Result<Value, EvaluationError> {
match self.evaluate_inner() {
Ok(props) => Ok(props),
Err(error) => {
let node = self.inner.read();
Context::log_error(node.props.context.clone(), format!("{}", error));
Err(error)
}
}
}
fn evaluate_inner(&self) -> std::result::Result<Value, EvaluationError> {
let node = self.inner.read();
match &node.props.expression {
Some(expression) => match expression.evaluate() {
Ok((value, logs)) => {
node.props
.context
.write()
.backend_logger
.reduction_logs(&logs);
Ok(value)
}
Err(error) => Err(error),
},
None => Err(EvaluationError::MissingExpression),
}
}
pub fn get_error_node_props(&self) -> NodeProps {
let node = self.inner.read();
NodeProps {
r#type: NodePropsType::Error,
context: node.props.context.clone(),
expression: None,
commit_hash: None,
parent: Some(self.clone()),
step: None,
}
}
pub fn log_unexpected_type_error(&self) {
let path = self.get_path();
let node = self.inner.read();
Context::log_error(
node.props.context.clone(),
format!("Unexpected type error at {}", path),
);
}
pub fn log_unexpected_value_error(&self, value: Result<Value, EvaluationError>) {
let path = self.get_path();
let node = self.inner.read();
match node.props.r#type {
NodePropsType::Error => (),
_ => Context::log_error(
node.props.context.clone(),
format!("Unexpected value error at {}: {:?}", path, value),
),
};
}
fn get_path(&self) -> String {
self.inner.read().get_path()
}
}
#[cfg(test)]
mod tests {
use parking_lot::RwLock;
use serde_json::Value;
use std::sync::Arc;
use super::Node;
use crate::context::Context;
use crate::expression::{BooleanExpression, Expression};
#[test]
fn test_get_field() {
let json = r#"
{
"id": "gZDqtndJoiI8RQ7q4CuCo",
"logs": {
"evaluations": {
"gy-OTD4_b0zH2XOmaXctc": 1
},
"events": {},
"exposures": {}
},
"type": "ObjectExpression",
"fields": {
"unreducedFlag": {
"id": "ej8Xy-9QFcd29W22D7poY",
"body": {
"id": "usSc5C1in4PECg9FtttwZ",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "SwitchExpression",
"cases": [
{
"when": {
"a": {
"id": "D30a6QkgwwZqZirsvUv44",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "GetFieldExpression",
"object": {
"id": "LLtvzKMh9aUUiuOQ5aTfu",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "VariableExpression",
"valueType": {
"type": "ObjectValueType",
"objectTypeName": "Root_unreducedFlag_args"
},
"variableId": "IRjkAecIF-NGJEuHgIc4r"
},
"fieldPath": "num",
"valueType": {
"type": "IntValueType"
}
},
"b": {
"id": "TphPzOq00QBZWn4J6c1sa",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "ListExpression",
"items": [
{
"id": "7aneXIcBqW0lrl02VxDUz",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "IntExpression",
"value": 42,
"valueType": {
"type": "IntValueType"
}
}
],
"valueType": {
"type": "ListValueType",
"itemValueType": {
"type": "IntValueType"
}
}
},
"id": "toCWw-idWeRnLG0ZDEp2U",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "ComparisonExpression",
"operator": "in",
"valueType": {
"type": "BooleanValueType"
}
},
"then": {
"id": "mPQwUTHRjFLfXIuw-Lf5L",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "BooleanExpression",
"value": true,
"valueType": {
"type": "BooleanValueType"
}
}
}
],
"control": {
"id": "lf6BDeESCHuGUMFVv3Bj6",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "BooleanExpression",
"value": true,
"valueType": {
"type": "BooleanValueType"
}
},
"default": {
"id": "5yMNttnvgNyqzD0GN07kV",
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "BooleanExpression",
"value": false,
"valueType": {
"type": "BooleanValueType"
}
},
"valueType": {
"type": "BooleanValueType"
}
},
"logs": {
"events": {},
"exposures": {},
"evaluations": {}
},
"type": "FunctionExpression",
"valueType": {
"type": "FunctionValueType",
"returnValueType": {
"type": "BooleanValueType"
},
"parameterValueTypes": [
{
"type": "ObjectValueType",
"objectTypeName": "Root_unreducedFlag_args"
}
]
},
"parameters": [
{
"id": "IRjkAecIF-NGJEuHgIc4r",
"name": "unreducedFlagArgs"
}
]
}
},
"valueType": {
"type": "ObjectValueType",
"objectTypeName": "Root"
},
"objectTypeName": "Root"
}"#;
let expression: Expression = serde_json::from_str(json).unwrap();
let node = Node::new_with_hash(
Arc::new(RwLock::new(Context::test())),
expression,
"".to_string(),
);
let field_arguments: Value = serde_json::from_str(
r#"
{
"num": 42
}"#,
)
.unwrap();
let field = node.get_field("unreducedFlag", field_arguments).unwrap();
if let Some(Expression::Boolean(BooleanExpression { value, .. })) = field.get_expression() {
assert!(value);
} else {
panic!("Expected BooleanExpression");
}
let field_arguments: Value = serde_json::from_str(
r#"
{
"num": 22
}"#,
)
.unwrap();
let field = node.get_field("unreducedFlag", field_arguments).unwrap();
if let Some(Expression::Boolean(BooleanExpression { value, .. })) = field.get_expression() {
assert!(!value);
} else {
panic!("Expected BooleanExpression");
}
}
}