use anyhow::anyhow;
use anyhow::Result;
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize};
use serde_json::Map;
use serde_json::Value;
use std::collections::HashMap;
use uuid::Uuid;
use crate::arithmetic_operator::ArithmeticOperator;
use crate::comparison_operator::ComparisonOperator;
use crate::node_props::NodePropsType;
use crate::split::SplitAssignmentEntry;
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(tag = "type")]
pub enum Expression {
#[serde(rename = "ApplicationExpression")]
Application(ApplicationExpression),
#[serde(rename = "ObjectExpression")]
Object(ObjectExpression),
#[serde(rename = "BooleanExpression")]
Boolean(BooleanExpression),
#[serde(rename = "FunctionExpression")]
Function(FunctionExpression),
#[serde(rename = "SwitchExpression")]
Switch(SwitchExpression),
#[serde(rename = "ComparisonExpression")]
Comparison(ComparisonExpression),
#[serde(rename = "GetFieldExpression")]
GetField(GetFieldExpression),
#[serde(rename = "ListExpression")]
List(ListExpression),
#[serde(rename = "IntExpression")]
Int(IntExpression),
#[serde(rename = "FloatExpression")]
Float(FloatExpression),
#[serde(rename = "StringExpression")]
String(StringExpression),
#[serde(rename = "VariableExpression")]
Variable(VariableExpression),
#[serde(rename = "LogEventExpression")]
LogEvent(LogEventExpression),
#[serde(rename = "SplitExpression")]
Split(SplitExpression),
#[serde(rename = "EnumSwitchExpression")]
EnumSwitch(EnumSwitchExpression),
#[serde(rename = "ArithmeticExpression")]
Arithmetic(ArithmeticExpression),
#[serde(rename = "NoOpExpression")]
NoOp(NoOpExpression),
#[serde(rename = "EnumExpression")]
Enum(EnumExpression),
#[serde(rename = "RegexExpression")]
Regex(RegexExpression),
#[serde(rename = "UpdateObjectExpression")]
UpdateObject(UpdateObjectExpression),
#[serde(rename = "RoundNumberExpression")]
RoundNumber(RoundNumberExpression),
#[serde(rename = "StringifyNumberExpression")]
StringifyNumber(StringifyNumberExpression),
#[serde(rename = "StringConcatExpression")]
StringConcat(StringConcatExpression),
#[serde(rename = "GetUrlQueryParameterExpression")]
GetUrlQueryParameter(GetUrlQueryParameterExpression),
}
impl Expression {
pub fn merge_logs(mut self, logs: &Option<Logs>) -> Self {
self.set_logs(Logs::merge(self.get_logs(), logs));
self
}
pub fn get_logs(&self) -> &Option<Logs> {
match self {
Expression::Application(e) => &e.logs,
Expression::Object(e) => &e.logs,
Expression::Function(e) => &e.logs,
Expression::Switch(e) => &e.logs,
Expression::GetField(e) => &e.logs,
Expression::List(e) => &e.logs,
Expression::Int(e) => &e.logs,
Expression::Float(e) => &e.logs,
Expression::Comparison(e) => &e.logs,
Expression::String(e) => &e.logs,
Expression::Variable(e) => &e.logs,
Expression::Boolean(e) => &e.logs,
Expression::NoOp(e) => &e.logs,
Expression::LogEvent(e) => &e.logs,
Expression::Split(e) => &e.logs,
Expression::EnumSwitch(e) => &e.logs,
Expression::Arithmetic(e) => &e.logs,
Expression::Enum(e) => &e.logs,
Expression::Regex(e) => &e.logs,
Expression::UpdateObject(e) => &e.logs,
Expression::RoundNumber(e) => &e.logs,
Expression::StringifyNumber(e) => &e.logs,
Expression::StringConcat(e) => &e.logs,
Expression::GetUrlQueryParameter(e) => &e.logs,
}
}
fn set_logs(&mut self, logs: Option<Logs>) {
match self {
Expression::Application(e) => e.logs = logs,
Expression::Object(e) => e.logs = logs,
Expression::Function(e) => e.logs = logs,
Expression::Switch(e) => e.logs = logs,
Expression::GetField(e) => e.logs = logs,
Expression::List(e) => e.logs = logs,
Expression::Int(e) => e.logs = logs,
Expression::Float(e) => e.logs = logs,
Expression::Comparison(e) => e.logs = logs,
Expression::String(e) => e.logs = logs,
Expression::Variable(e) => e.logs = logs,
Expression::Boolean(e) => e.logs = logs,
Expression::NoOp(e) => e.logs = logs,
Expression::LogEvent(e) => e.logs = logs,
Expression::Split(e) => e.logs = logs,
Expression::EnumSwitch(e) => e.logs = logs,
Expression::Arithmetic(e) => e.logs = logs,
Expression::Enum(e) => e.logs = logs,
Expression::Regex(e) => e.logs = logs,
Expression::UpdateObject(e) => e.logs = logs,
Expression::RoundNumber(e) => e.logs = logs,
Expression::StringifyNumber(e) => e.logs = logs,
Expression::StringConcat(e) => e.logs = logs,
Expression::GetUrlQueryParameter(e) => e.logs = logs,
}
}
pub fn is_transient(&self) -> bool {
match self {
Expression::Application(_e) => false,
Expression::Object(e) => e.is_transient,
Expression::Function(_e) => false,
Expression::Switch(_e) => false,
Expression::GetField(_e) => false,
Expression::List(e) => e.is_transient,
Expression::Int(e) => e.is_transient,
Expression::Float(e) => e.is_transient,
Expression::Comparison(_e) => false,
Expression::String(e) => e.is_transient,
Expression::Variable(_e) => false,
Expression::Boolean(e) => e.is_transient,
Expression::NoOp(e) => e.is_transient,
Expression::LogEvent(_e) => false,
Expression::Split(_e) => false,
Expression::EnumSwitch(_e) => false,
Expression::Arithmetic(_e) => false,
Expression::Enum(_e) => false,
Expression::Regex(_e) => false,
Expression::UpdateObject(_e) => false,
Expression::RoundNumber(_e) => false,
Expression::StringifyNumber(_e) => false,
Expression::StringConcat(_e) => false,
Expression::GetUrlQueryParameter(_e) => false,
}
}
pub fn get_id(&self) -> &str {
match self {
Expression::Application(e) => &e.id,
Expression::Object(e) => &e.id,
Expression::Function(e) => &e.id,
Expression::Switch(e) => &e.id,
Expression::GetField(e) => &e.id,
Expression::List(e) => &e.id,
Expression::Int(e) => &e.id,
Expression::Float(e) => &e.id,
Expression::Comparison(e) => &e.id,
Expression::String(e) => &e.id,
Expression::Variable(e) => &e.id,
Expression::Boolean(e) => &e.id,
Expression::NoOp(e) => &e.id,
Expression::LogEvent(e) => &e.id,
Expression::Split(e) => &e.id,
Expression::EnumSwitch(e) => &e.id,
Expression::Arithmetic(e) => &e.id,
Expression::Enum(e) => &e.id,
Expression::Regex(e) => &e.id,
Expression::UpdateObject(e) => &e.id,
Expression::RoundNumber(e) => &e.id,
Expression::StringifyNumber(e) => &e.id,
Expression::StringConcat(e) => &e.id,
Expression::GetUrlQueryParameter(e) => &e.id,
}
}
pub fn set_id(self, id: String) -> Self {
match self {
Expression::Application(mut e) => {
e.id = id;
Expression::Application(e)
}
Expression::Object(mut e) => {
e.id = id;
Expression::Object(e)
}
Expression::Function(mut e) => {
e.id = id;
Expression::Function(e)
}
Expression::Switch(mut e) => {
e.id = id;
Expression::Switch(e)
}
Expression::GetField(mut e) => {
e.id = id;
Expression::GetField(e)
}
Expression::List(mut e) => {
e.id = id;
Expression::List(e)
}
Expression::Int(mut e) => {
e.id = id;
Expression::Int(e)
}
Expression::Float(mut e) => {
e.id = id;
Expression::Float(e)
}
Expression::Comparison(mut e) => {
e.id = id;
Expression::Comparison(e)
}
Expression::String(mut e) => {
e.id = id;
Expression::String(e)
}
Expression::Variable(mut e) => {
e.id = id;
Expression::Variable(e)
}
Expression::Boolean(mut e) => {
e.id = id;
Expression::Boolean(e)
}
Expression::NoOp(mut e) => {
e.id = id;
Expression::NoOp(e)
}
Expression::LogEvent(mut e) => {
e.id = id;
Expression::LogEvent(e)
}
Expression::Split(mut e) => {
e.id = id;
Expression::Split(e)
}
Expression::EnumSwitch(mut e) => {
e.id = id;
Expression::EnumSwitch(e)
}
Expression::Arithmetic(mut e) => {
e.id = id;
Expression::Arithmetic(e)
}
Expression::Enum(mut e) => {
e.id = id;
Expression::Enum(e)
}
Expression::Regex(mut e) => {
e.id = id;
Expression::Regex(e)
}
Expression::UpdateObject(mut e) => {
e.id = id;
Expression::UpdateObject(e)
}
Expression::RoundNumber(mut e) => {
e.id = id;
Expression::RoundNumber(e)
}
Expression::StringifyNumber(mut e) => {
e.id = id;
Expression::StringifyNumber(e)
}
Expression::StringConcat(mut e) => {
e.id = id;
Expression::StringConcat(e)
}
Expression::GetUrlQueryParameter(mut e) => {
e.id = id;
Expression::GetUrlQueryParameter(e)
}
}
}
pub fn get_node_props_type(&self) -> NodePropsType {
match self {
Expression::String(_) => NodePropsType::String,
Expression::Boolean(_) => NodePropsType::Bool,
Expression::Object(ObjectExpression {
object_type_name, ..
}) => NodePropsType::Object {
object_type_name: object_type_name.to_string(),
},
Expression::Int(_) => NodePropsType::Int,
Expression::Float(_) => NodePropsType::Float,
Expression::NoOp(_) => NodePropsType::Void,
Expression::Enum(EnumExpression { value, .. }) => NodePropsType::Enum {
value: value.clone(),
},
_ => NodePropsType::NotFullyReduced,
}
}
#[cfg(test)]
pub fn set_sorted_logs(mut self, logs: Option<Logs>) -> Self {
self.set_logs(logs);
self
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ApplicationExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub function: Box<Expression>,
pub arguments: Vec<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ObjectExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub object_type_name: String,
#[serde(serialize_with = "sort_alphabetically")]
pub fields: HashMap<String, Expression>,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl ObjectExpression {
pub fn transient(fields: HashMap<String, Expression>) -> Self {
Self {
id: Uuid::new_v4().to_string(),
object_type_name: "".to_string(),
fields,
logs: None,
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BooleanExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub value: bool,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl BooleanExpression {
pub fn transient(value: bool) -> Self {
Self {
id: Uuid::new_v4().to_string(),
value,
logs: None,
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
pub struct FunctionExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub body: Box<Expression>,
pub parameters: Vec<FunctionParameter>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
pub struct SwitchExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub control: Box<Expression>,
pub cases: Vec<SwitchCase>,
pub default: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
pub struct ComparisonExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub a: Box<Expression>,
pub b: Box<Expression>,
pub operator: ComparisonOperator,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetFieldExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub field_path: String,
pub object: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ListExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub items: Vec<Expression>,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl ListExpression {
pub fn transient(items: Vec<Expression>) -> Self {
Self {
id: Uuid::new_v4().to_string(),
items,
logs: None,
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct IntExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub value: i64,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl IntExpression {
pub fn transient(value: i64) -> Self {
Self {
id: Uuid::new_v4().to_string(),
logs: None,
value,
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FloatExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub value: OrderedFloat<f64>,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl FloatExpression {
pub fn transient(value: f64) -> Self {
Self {
id: Uuid::new_v4().to_string(),
logs: None,
value: value.into(),
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StringExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub value: String,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl StringExpression {
pub fn transient(value: String) -> Self {
Self {
id: Uuid::new_v4().to_string(),
logs: None,
value,
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NoOpExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
#[serde(default, skip_serializing)]
pub is_transient: bool,
}
impl NoOpExpression {
pub fn transient() -> Self {
Self {
id: Uuid::new_v4().to_string(),
logs: None,
is_transient: true,
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct EnumExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub value: String,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RegexExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub value: String,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UpdateObjectExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub object: Box<Expression>,
pub updates: HashMap<String, Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RoundNumberExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub number: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StringifyNumberExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub number: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StringConcatExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub strings: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GetUrlQueryParameterExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub url: Box<Expression>,
pub query_parameter_name: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct VariableExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub variable_id: String,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LogEventExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub event_object_type_name: String,
pub event_payload: Box<Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SplitExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub split_id: String,
pub dimension_id: String,
pub expose: Box<Expression>,
pub unit_id: Box<Expression>,
pub dimension_mapping: DimensionMapping,
pub event_object_type_name: Option<String>,
pub event_payload: Option<Box<Expression>>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct EnumSwitchExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub control: Box<Expression>,
#[serde(serialize_with = "sort_alphabetically")]
pub cases: HashMap<String, Expression>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ArithmeticExpression {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub logs: Option<Logs>,
pub a: Box<Expression>,
pub b: Box<Expression>,
pub operator: ArithmeticOperator,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum DimensionMapping {
Discrete { cases: HashMap<String, Expression> },
Continuous { function: Box<Expression> },
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
pub struct SwitchCase {
pub when: Expression,
pub then: Expression,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
pub struct FunctionParameter {
pub id: String,
pub name: String,
}
impl TryFrom<Value> for Expression {
type Error = anyhow::Error;
fn try_from(value: Value) -> Result<Self> {
match value {
Value::Bool(value) => Ok(Expression::Boolean(BooleanExpression::transient(value))),
Value::String(value) => Ok(Expression::String(StringExpression::transient(value))),
Value::Number(value) => match value.is_i64() {
true => Ok(Expression::Int(IntExpression::transient(
value
.as_i64()
.ok_or(anyhow!("Failed to fit integer inside i64"))?,
))),
false => Ok(Expression::Float(FloatExpression::transient(
value
.as_f64()
.ok_or(anyhow!("Failed to represent number as f64"))?,
))),
},
Value::Array(array) => {
let items: Vec<Expression> = array
.into_iter()
.map(|value| value.try_into())
.collect::<Result<_>>()?;
Ok(Expression::List(ListExpression::transient(items)))
}
Value::Object(object) => {
let fields: HashMap<String, Expression> = object
.into_iter()
.map(|(key, value)| match value.try_into() {
Ok(value) => Ok((key, value)),
Err(error) => Err(error),
})
.collect::<Result<_>>()?;
Ok(Expression::Object(ObjectExpression::transient(fields)))
}
Value::Null => Err(anyhow!(
"Cannot convert a null JSON value into an expression"
)),
}
}
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Logs {
#[serde(
serialize_with = "sort_alphabetically",
skip_serializing_if = "Option::is_none"
)]
pub evaluations: Option<HashMap<String, u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_list: Option<Vec<Event>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exposure_list: Option<Vec<Exposure>>,
}
fn sort_alphabetically<T: Serialize, S: serde::Serializer>(
value: &T,
serializer: S,
) -> Result<S::Ok, S::Error> {
let value = serde_json::to_value(value).map_err(serde::ser::Error::custom)?;
value.serialize(serializer)
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Exposure {
#[serde(serialize_with = "sort_alphabetically")]
pub assignment: HashMap<String, SplitAssignmentEntry>,
pub event_object_type_name: Option<String>,
#[serde(serialize_with = "sort_alphabetically")]
pub event_payload: Option<Map<String, Value>>,
pub split_id: String,
pub unit_id: String,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Event {
pub event_object_type_name: String,
#[serde(serialize_with = "sort_alphabetically")]
pub event_payload: Map<String, Value>,
}
impl Logs {
pub fn evaluation_logs(expression: &Expression) -> Option<Self> {
match expression.is_transient() {
true => None,
false => {
let id = expression.get_id();
Some(Logs {
event_list: None,
exposure_list: None,
evaluations: Some(HashMap::from([(id.to_string(), 1)])),
})
}
}
}
pub fn exposure_logs(
split_id: &str,
unit_id: &str,
event_object_type_name: &Option<String>,
event_payload: &Option<Map<String, Value>>,
assignment: &HashMap<String, SplitAssignmentEntry>,
) -> Result<Option<Self>> {
let exposure = Exposure {
assignment: assignment.clone(),
event_object_type_name: event_object_type_name.clone(),
event_payload: event_payload.clone(),
split_id: split_id.to_string(),
unit_id: unit_id.to_string(),
};
Ok(Some(Self {
evaluations: None,
event_list: None,
exposure_list: Some(vec![exposure]),
}))
}
pub fn event_logs(
event_object_type_name: String,
event_payload: Map<String, Value>,
) -> Result<Option<Self>> {
let event = Event {
event_object_type_name: event_object_type_name,
event_payload: event_payload.clone(),
};
Ok(Some(Self {
evaluations: None,
event_list: Some(vec![event]),
exposure_list: None,
}))
}
pub fn merge(a: &Option<Self>, b: &Option<Self>) -> Option<Self> {
if a.is_none() {
return b.clone();
}
if b.is_none() {
return a.clone();
}
let a = a.clone().unwrap();
let b = b.clone().unwrap();
let mut result = a.clone();
if result.evaluations.is_none() && b.evaluations.is_some() {
result.evaluations = b.evaluations.clone();
} else if result.evaluations.is_some() && b.evaluations.is_some() {
let mut evaluations = result.evaluations.unwrap();
evaluations.extend(b.evaluations.unwrap().iter().map(|(k, bv)| {
match a.evaluations.as_ref().unwrap().get(k) {
Some(av) => (k.clone(), av + bv),
None => (k.clone(), *bv),
}
}));
result.evaluations = Some(evaluations);
}
if result.event_list.is_none() && b.event_list.is_some() {
result.event_list = b.event_list.clone();
} else if result.event_list.is_some() && b.event_list.is_some() {
let mut events = result.event_list.unwrap();
events.extend(b.event_list.unwrap());
result.event_list = Some(events);
}
if result.exposure_list.is_none() && b.exposure_list.is_some() {
result.exposure_list = b.exposure_list.clone();
} else if result.exposure_list.is_some() && b.exposure_list.is_some() {
let mut exposures = result.exposure_list.unwrap();
exposures.extend(b.exposure_list.unwrap());
result.exposure_list = Some(exposures);
}
Some(result)
}
pub fn merge_all<'a, T>(logs: T) -> Option<Self>
where
T: IntoIterator<Item = &'a Option<Logs>>,
{
logs.into_iter().fold(None, |acc, l| Logs::merge(&acc, l))
}
}