zerocodebreengine / ConditionEvaluator.cs
zombee11's picture
Upload 39 files
af8cc55 verified
Raw
History Blame Contribute Delete
6.77 kB
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using OptimAI.BRE.RuleEngine.Domain;
using OptimAI.BRE.Shared.Domain;
namespace OptimAI.BRE.RuleEngine.Application;
public sealed class ConditionEvaluator : IConditionEvaluator
{
private readonly IDynamicFieldResolver _fieldResolver;
private readonly ILogger<ConditionEvaluator> _logger;
public ConditionEvaluator(IDynamicFieldResolver fieldResolver, ILogger<ConditionEvaluator> logger)
{
_fieldResolver = fieldResolver;
_logger = logger;
}
public bool Evaluate(ConditionNode condition, DynamicDataContext context)
{
try
{
var actualValue = ResolveValue(condition.Field!, context);
var expectedValue = ResolveExpectedValue(condition, context);
return condition.Operator switch
{
ComparisonOperator.Equals => AreEqual(actualValue, expectedValue),
ComparisonOperator.NotEquals => !AreEqual(actualValue, expectedValue),
ComparisonOperator.GreaterThan => Compare(actualValue, expectedValue) > 0,
ComparisonOperator.GreaterThanOrEqual => Compare(actualValue, expectedValue) >= 0,
ComparisonOperator.LessThan => Compare(actualValue, expectedValue) < 0,
ComparisonOperator.LessThanOrEqual => Compare(actualValue, expectedValue) <= 0,
ComparisonOperator.Between => EvaluateBetween(actualValue, condition, context),
ComparisonOperator.NotBetween => !EvaluateBetween(actualValue, condition, context),
ComparisonOperator.In => EvaluateIn(actualValue, condition.Value),
ComparisonOperator.NotIn => !EvaluateIn(actualValue, condition.Value),
ComparisonOperator.Contains => EvaluateContains(actualValue, expectedValue),
ComparisonOperator.NotContains => !EvaluateContains(actualValue, expectedValue),
ComparisonOperator.StartsWith => EvaluateStartsWith(actualValue, expectedValue),
ComparisonOperator.EndsWith => EvaluateEndsWith(actualValue, expectedValue),
ComparisonOperator.IsNull => actualValue == null,
ComparisonOperator.IsNotNull => actualValue != null,
ComparisonOperator.IsTrue => IsTruthy(actualValue),
ComparisonOperator.IsFalse => !IsTruthy(actualValue),
ComparisonOperator.Regex => EvaluateRegex(actualValue, expectedValue?.ToString()),
_ => false
};
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Condition evaluation failed for field {Field}", condition.Field);
return false;
}
}
private object? ResolveValue(string field, DynamicDataContext context)
{
return _fieldResolver.TryResolve(field, context, out var value) ? value : null;
}
private object? ResolveExpectedValue(ConditionNode condition, DynamicDataContext context)
{
return condition.ValueType switch
{
ValueType.Field when condition.ReferenceField != null => ResolveValue(condition.ReferenceField, context),
ValueType.Literal => condition.Value,
_ => condition.Value
};
}
private static bool AreEqual(object? left, object? right)
{
if (left == null && right == null) return true;
if (left == null || right == null) return false;
if (TryToDecimal(left, out var ld) && TryToDecimal(right, out var rd))
return ld == rd;
return left.ToString()?.Equals(right.ToString(), StringComparison.OrdinalIgnoreCase) ?? false;
}
private static int Compare(object? left, object? right)
{
if (left == null && right == null) return 0;
if (left == null) return -1;
if (right == null) return 1;
if (TryToDecimal(left, out var ld) && TryToDecimal(right, out var rd))
return ld.CompareTo(rd);
if (left is DateTime dl && right is DateTime dr)
return dl.CompareTo(dr);
return string.Compare(left.ToString(), right.ToString(), StringComparison.OrdinalIgnoreCase);
}
private bool EvaluateBetween(object? actual, ConditionNode condition, DynamicDataContext context)
{
var value1 = ResolveExpectedValue(condition, context);
var value2 = condition.Value2 != null ? (object)condition.Value2 : null;
if (value1 == null || value2 == null) return false;
return Compare(actual, value1) >= 0 && Compare(actual, value2) <= 0;
}
private static bool EvaluateIn(object? actual, object? values)
{
if (actual == null || values == null) return false;
IEnumerable<object> list = values switch
{
IEnumerable<object> e => e,
string s => s.Split(',').Select(x => (object)x.Trim()),
_ => new[] { values }
};
return list.Any(v => AreEqual(actual, v));
}
private static bool EvaluateContains(object? actual, object? expected)
{
if (actual == null || expected == null) return false;
if (actual is IEnumerable<object> list)
return list.Any(item => AreEqual(item, expected));
return actual.ToString()?.Contains(expected.ToString() ?? "", StringComparison.OrdinalIgnoreCase) ?? false;
}
private static bool EvaluateStartsWith(object? actual, object? expected)
=> actual?.ToString()?.StartsWith(expected?.ToString() ?? "", StringComparison.OrdinalIgnoreCase) ?? false;
private static bool EvaluateEndsWith(object? actual, object? expected)
=> actual?.ToString()?.EndsWith(expected?.ToString() ?? "", StringComparison.OrdinalIgnoreCase) ?? false;
private static bool EvaluateRegex(object? actual, string? pattern)
{
if (actual == null || pattern == null) return false;
return Regex.IsMatch(actual.ToString()!, pattern, RegexOptions.IgnoreCase);
}
private static bool IsTruthy(object? value) => value switch
{
bool b => b,
decimal d => d != 0,
int i => i != 0,
string s => !string.IsNullOrEmpty(s) && s != "0" && s.ToLower() != "false",
null => false,
_ => true
};
private static bool TryToDecimal(object value, out decimal result)
{
result = 0;
return value switch
{
decimal d => (result = d) == d,
double d => (result = (decimal)d) == result,
float f => (result = (decimal)f) == result,
int i => (result = i) == i,
long l => (result = l) == l,
string s => decimal.TryParse(s, out result),
_ => false
};
}
}