From 2beef9379f27067360649fcf5c8ed4f6f04eb31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sat, 13 Jan 2024 17:00:08 +0100 Subject: [PATCH] feat: PredicationBuilder is supporting context arguments --- Expressif.Testing/PredicationBuilderTest.cs | 17 ++ .../Values/Casters/CasterTest.cs | 14 +- Expressif/PredicationBuilder.cs | 257 +++++++++++++----- 3 files changed, 224 insertions(+), 64 deletions(-) diff --git a/Expressif.Testing/PredicationBuilderTest.cs b/Expressif.Testing/PredicationBuilderTest.cs index 36f4c6b..fb7a479 100644 --- a/Expressif.Testing/PredicationBuilderTest.cs +++ b/Expressif.Testing/PredicationBuilderTest.cs @@ -144,6 +144,23 @@ public void Serialize_Negate_CorrectlySerialized() Assert.That(str, Is.EqualTo("{starts-with(ola) |OR !{ends-with(sla)}}")); } + [Test] + public void Chain_MultipleWithContext_CorrectlyEvaluate() + { + var context = new Context(); + var builder = new PredicationBuilder(context) + .Create(ctx => ctx.Variables["myVar"]) + .And(ctx => ctx.CurrentObject[1]); + var predication = builder.Build(); + + context.Variables.Add("myVar", "Nik"); + context.CurrentObject.Set(new List() { "stein", "sla", "Alb" }); + Assert.That(predication.Evaluate("Nikola Tesla"), Is.True); + + context.CurrentObject.Set(new List() { "sla", "stein","Alb" }); + Assert.That(predication.Evaluate("Nikola Tesla"), Is.False); + } + [Test] public void Serialize_NoPredicate_ThrowException() => Assert.Throws(() => new PredicationBuilder().Serialize()); diff --git a/Expressif.Testing/Values/Casters/CasterTest.cs b/Expressif.Testing/Values/Casters/CasterTest.cs index bd2037c..58f1a70 100644 --- a/Expressif.Testing/Values/Casters/CasterTest.cs +++ b/Expressif.Testing/Values/Casters/CasterTest.cs @@ -15,15 +15,23 @@ public void Cast_NullToNullableType_Null() [Test] public void Cast_NullToPrimitive_Null() - => Assert.That(new Caster().Cast(null), Is.Zero); + => Assert.Multiple(() => + { + Assert.That(new Caster().Cast(null), Is.Zero); + Assert.That(new Caster().Cast(null), Is.Null); + }); [Test] public void Cast_DBNullToNullableType_Null() => Assert.That(new Caster().Cast(DBNull.Value), Is.Null); [Test] - public void Cast_DBNullToPrimitive_Null() - => Assert.That(new Caster().Cast(DBNull.Value), Is.Zero); + public void Cast_DBNullToPrimitive_NullOredafult() + => Assert.Multiple(() => + { + Assert.That(new Caster().Cast(DBNull.Value), Is.Zero); + Assert.That(new Caster().Cast(DBNull.Value), Is.Null); + }); [Test] public void Cast_TypedToNullableType_Itself() diff --git a/Expressif/PredicationBuilder.cs b/Expressif/PredicationBuilder.cs index 7545bc4..4b4bdec 100644 --- a/Expressif/PredicationBuilder.cs +++ b/Expressif/PredicationBuilder.cs @@ -8,134 +8,269 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; +using System.Linq.Expressions; namespace Expressif; -public class PredicationBuilder +public class AbstractPredicationBuilder { private IContext Context { get; } private PredicationFactory Factory { get; } private PredicationSerializer Serializer { get; } - + + protected AbstractPredicationBuilder(IContext? context, PredicationFactory? factory = null, PredicationSerializer? serializer = null) + => (Context, Factory, Serializer) = (context ?? new Context(), factory ?? new(), serializer ?? new()); + + protected AbstractPredicationBuilder(AbstractPredicationBuilder builder) + => (Context, Factory, Serializer, Pile) = (builder.Context, builder.Factory, builder.Serializer, builder.Pile); + + protected internal IPredication? Pile { get; set; } + + protected IPredication BuildNot(Type type, object?[] parameters) + => new UnaryPredication(new UnaryOperator("!") + , new SinglePredication(new Function(type.Name, Parametrize(parameters))) + ); + + public IPredicate Build() + { + if (Pile is null) + throw new InvalidOperationException(); + var predicate = Factory.Instantiate(Pile, Context); + return predicate; + } + + protected virtual IParameter[] Parametrize(object?[] parameters) + { + var typedParameters = new List(); + foreach (var parameter in parameters) + { + typedParameters.Add(parameter switch + { + IParameter p => p, + Expression> expression => new ContextParameter(expression.Compile()), + _ => new LiteralParameter(parameter?.ToString() ?? new Null().Keyword) + }); + } + return [.. typedParameters]; + } + + public string Serialize() + { + if (Pile is null) + throw new InvalidOperationException(); + + return Serializer.Serialize(Pile); + } +} + +public class PredicationBuilder : AbstractPredicationBuilder +{ public PredicationBuilder() : this(new Context()) { } public PredicationBuilder(IContext? context = null, PredicationFactory? factory = null, PredicationSerializer? serializer = null) - => (Context, Factory, Serializer) = (context ?? new Context(), factory ?? new(), serializer ?? new()); + : base(context, factory, serializer) { } - private IPredication? Pile { get; set; } + public PredicationBuilderNext Create

() + where P : IPredicate + => Create(typeof(P), []); - public PredicationBuilder Create

(params object?[] parameters) + public PredicationBuilderNext Create

(params object?[] parameters) where P : IPredicate + => Create(typeof(P), parameters); + + public PredicationBuilderNext Create

(params Expression>[] parameters) + where P : IPredicate + => Create(typeof(P), parameters); + + public PredicationBuilderNext Create(Type type, params object?[] parameters) { - Pile = new SinglePredication(new Function(typeof(P).Name, Parametrize(parameters))); - return this; + if (!type.GetInterfaces().Contains(typeof(IPredicate))) + throw new ArgumentException($"The type '{type.FullName}' doesn't implement the interface '{nameof(IPredicate)}'. Only types implementing this interface can be chained to create a predication.", nameof(type)); + + Pile = new SinglePredication(new Function(type.Name, Parametrize(parameters))); + return new(this); } - private UnaryPredication BuildNot

(object?[] parameters) - => new (new UnaryOperator("!") - , new SinglePredication(new Function(typeof(P).Name, Parametrize(parameters))) - ); + public PredicationBuilderNext Not

() + where P : IPredicate + => Not

([]); + + public PredicationBuilderNext Not

(params object?[] parameters) + where P : IPredicate + => Not(typeof(P), parameters); - public PredicationBuilder Not

(params object?[] parameters) + public PredicationBuilderNext Not

(params Expression>[] parameters) where P : IPredicate + => Not(typeof(P), parameters); + + public PredicationBuilderNext Not(Type type, params object?[] parameters) { - Pile = BuildNot

(parameters); - return this; + if (!type.GetInterfaces().Contains(typeof(IPredicate))) + throw new ArgumentException($"The type '{type.FullName}' doesn't implement the interface '{nameof(IPredicate)}'. Only types implementing this interface can be chained to create a predication.", nameof(type)); + + Pile = BuildNot(type, Parametrize(parameters)); + return new(this); } +} + +public class PredicationBuilderNext : AbstractPredicationBuilder +{ + public PredicationBuilderNext(AbstractPredicationBuilder builder) + : base(builder) { } + + #region And + + public PredicationBuilderNext And

() + where P : IPredicate + => And(typeof(P), []); - public PredicationBuilder And

(params object?[] parameters) + public PredicationBuilderNext And

(params object?[] parameters) where P : IPredicate + => And(typeof(P), parameters); + + public PredicationBuilderNext And

(params Expression>[] parameters) + where P : IPredicate + => And(typeof(P), parameters); + + public PredicationBuilderNext And(Type type, params object?[] parameters) { - var right = new SinglePredication(new Function(typeof(P).Name, Parametrize(parameters))); + var right = new SinglePredication(new Function(type.Name, Parametrize(parameters))); Pile = new BinaryPredication(new BinaryOperator("And"), Pile!, right); return this; } - public PredicationBuilder And(PredicationBuilder builder) + public PredicationBuilderNext And(AbstractPredicationBuilder builder) { Pile = new BinaryPredication(new BinaryOperator("And"), Pile!, builder.Pile!); return this; } - public PredicationBuilder AndNot

(params object?[] parameters) + #endregion + + #region AndNot + + public PredicationBuilderNext AndNot

() + where P : IPredicate + => AndNot(typeof(P), []); + + public PredicationBuilderNext AndNot

(params object?[] parameters) where P : IPredicate + => AndNot(typeof(P), parameters); + + public PredicationBuilderNext AndNot

(Expression>[] parameters) + where P : IPredicate + => AndNot(typeof(P), parameters); + + public PredicationBuilderNext AndNot(Type type, params object?[] parameters) { - var right = BuildNot

(parameters); + var right = BuildNot(type, parameters); Pile = new BinaryPredication(new BinaryOperator("And"), Pile!, right); return this; } - public PredicationBuilder Or

(params object?[] parameters) + #endregion + + #region Or + + public PredicationBuilderNext Or

() + where P : IPredicate + => Or(typeof(P), []); + + public PredicationBuilderNext Or

(params object?[] parameters) where P : IPredicate + => Or(typeof(P), parameters); + + public PredicationBuilderNext Or

(Expression>[] parameters) + where P : IPredicate + => Or(typeof(P), parameters); + + public PredicationBuilderNext Or(Type type, params object?[] parameters) { - var right = new SinglePredication(new Function(typeof(P).Name, Parametrize(parameters))); + var right = new SinglePredication(new Function(type.Name, Parametrize(parameters))); Pile = new BinaryPredication(new BinaryOperator("Or"), Pile!, right); - return this; + return new(this); } - public PredicationBuilder Or(PredicationBuilder builder) + public PredicationBuilderNext Or(AbstractPredicationBuilder builder) { Pile = new BinaryPredication(new BinaryOperator("Or"), Pile!, builder.Pile!); return this; } - public PredicationBuilder OrNot

(params object?[] parameters) + #endregion + + #region OrNot + + public PredicationBuilderNext OrNot

() + where P : IPredicate + => OrNot(typeof(P), []); + + public PredicationBuilderNext OrNot

(params object?[] parameters) where P : IPredicate + => OrNot(typeof(P), parameters); + + public PredicationBuilderNext OrNot

(Expression>[] parameters) + where P : IPredicate + => OrNot(typeof(P), parameters); + + public PredicationBuilderNext OrNot(Type type, params object?[] parameters) { - var right = BuildNot

(parameters); + var right = BuildNot(type, parameters); Pile = new BinaryPredication(new BinaryOperator("Or"), Pile!, right); - return this; + return new(this); } - public PredicationBuilder Xor

(params object?[] parameters) + #endregion + + #region Xor + + public PredicationBuilderNext Xor

() + where P : IPredicate + => Xor(typeof(P), []); + + public PredicationBuilderNext Xor

(params object?[] parameters) where P : IPredicate + => Xor(typeof(P), parameters); + + public PredicationBuilderNext Xor

(Expression>[] parameters) + where P : IPredicate + => Xor(typeof(P), parameters); + + public PredicationBuilderNext Xor(Type type, params object?[] parameters) { - var right = new SinglePredication(new Function(typeof(P).Name, Parametrize(parameters))); + var right = new SinglePredication(new Function(type.Name, Parametrize(parameters))); Pile = new BinaryPredication(new BinaryOperator("Xor"), Pile!, right); - return this; + return new(this); } - public PredicationBuilder Xor(PredicationBuilder builder) + public PredicationBuilderNext Xor(AbstractPredicationBuilder builder) { Pile = new BinaryPredication(new BinaryOperator("Xor"), Pile!, builder.Pile!); return this; } - public PredicationBuilder XorNot

(params object?[] parameters) + #endregion + + #region XorNot + + public PredicationBuilderNext XorNot

() where P : IPredicate - { - var right = BuildNot

(parameters); - Pile = new BinaryPredication(new BinaryOperator("Xor"), Pile!, right); - return this; - } + => XorNot(typeof(P), []); - public IPredicate Build() - { - if (Pile is null) - throw new InvalidOperationException(); - var predicate = Factory.Instantiate(Pile, Context); - return predicate; - } + public PredicationBuilderNext XorNot

(params object?[] parameters) + where P : IPredicate + => XorNot(typeof(P), parameters); - private IParameter[] Parametrize(object?[] parameters) - { - var typedParameters = new List(); - foreach (var parameter in parameters) - { - typedParameters.Add(parameter switch - { - IParameter p => p, - _ => new LiteralParameter(parameter?.ToString() ?? new Null().Keyword) - }); - } - return typedParameters.ToArray(); - } + public PredicationBuilderNext XorNot

(Expression>[] parameters) + where P : IPredicate + => XorNot(typeof(P), parameters); - public string Serialize() + public PredicationBuilderNext XorNot(Type type, params object?[] parameters) { - if (Pile is null) - throw new InvalidOperationException(); - - return Serializer.Serialize(Pile); + var right = BuildNot(type, parameters); + Pile = new BinaryPredication(new BinaryOperator("Xor"), Pile!, right); + return new(this); } + + #endregion }