Skip to content

Commit

Permalink
feat: PredicationBuilder is supporting context arguments (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seddryck committed Jan 13, 2024
1 parent f271180 commit 51aa837
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 64 deletions.
17 changes: 17 additions & 0 deletions Expressif.Testing/PredicationBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<StartsWith>(ctx => ctx.Variables["myVar"])
.And<EndsWith>(ctx => ctx.CurrentObject[1]);
var predication = builder.Build();

context.Variables.Add<string>("myVar", "Nik");
context.CurrentObject.Set(new List<string>() { "stein", "sla", "Alb" });
Assert.That(predication.Evaluate("Nikola Tesla"), Is.True);

context.CurrentObject.Set(new List<string>() { "sla", "stein","Alb" });
Assert.That(predication.Evaluate("Nikola Tesla"), Is.False);
}

[Test]
public void Serialize_NoPredicate_ThrowException()
=> Assert.Throws<InvalidOperationException>(() => new PredicationBuilder().Serialize());
Expand Down
14 changes: 11 additions & 3 deletions Expressif.Testing/Values/Casters/CasterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,23 @@ public void Cast_NullToNullableType_Null()

[Test]
public void Cast_NullToPrimitive_Null()
=> Assert.That(new Caster().Cast<int>(null), Is.Zero);
=> Assert.Multiple(() =>
{
Assert.That(new Caster().Cast<int>(null), Is.Zero);
Assert.That(new Caster().Cast<int?>(null), Is.Null);
});

[Test]
public void Cast_DBNullToNullableType_Null()
=> Assert.That(new Caster().Cast<string>(DBNull.Value), Is.Null);

[Test]
public void Cast_DBNullToPrimitive_Null()
=> Assert.That(new Caster().Cast<int>(DBNull.Value), Is.Zero);
public void Cast_DBNullToPrimitive_NullOredafult()
=> Assert.Multiple(() =>
{
Assert.That(new Caster().Cast<int>(DBNull.Value), Is.Zero);
Assert.That(new Caster().Cast<int?>(DBNull.Value), Is.Null);
});

[Test]
public void Cast_TypedToNullableType_Itself()
Expand Down
257 changes: 196 additions & 61 deletions Expressif/PredicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IParameter>();
foreach (var parameter in parameters)
{
typedParameters.Add(parameter switch
{
IParameter p => p,
Expression<Func<IContext, object?>> 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<P>()
where P : IPredicate
=> Create(typeof(P), []);

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

public PredicationBuilderNext Create<P>(params Expression<Func<IContext, object?>>[] 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<P>(object?[] parameters)
=> new (new UnaryOperator("!")
, new SinglePredication(new Function(typeof(P).Name, Parametrize(parameters)))
);
public PredicationBuilderNext Not<P>()
where P : IPredicate
=> Not<P>([]);

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

public PredicationBuilder Not<P>(params object?[] parameters)
public PredicationBuilderNext Not<P>(params Expression<Func<IContext, object?>>[] parameters)
where P : IPredicate
=> Not(typeof(P), parameters);

public PredicationBuilderNext Not(Type type, params object?[] parameters)
{
Pile = BuildNot<P>(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<P>()
where P : IPredicate
=> And(typeof(P), []);

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

public PredicationBuilderNext And<P>(params Expression<Func<IContext, object?>>[] 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<P>(params object?[] parameters)
#endregion

#region AndNot

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

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

public PredicationBuilderNext AndNot<P>(Expression<Func<IContext, object?>>[] parameters)
where P : IPredicate
=> AndNot(typeof(P), parameters);

public PredicationBuilderNext AndNot(Type type, params object?[] parameters)
{
var right = BuildNot<P>(parameters);
var right = BuildNot(type, parameters);
Pile = new BinaryPredication(new BinaryOperator("And"), Pile!, right);
return this;
}

public PredicationBuilder Or<P>(params object?[] parameters)
#endregion

#region Or

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

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

public PredicationBuilderNext Or<P>(Expression<Func<IContext, object?>>[] 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<P>(params object?[] parameters)
#endregion

#region OrNot

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

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

public PredicationBuilderNext OrNot<P>(Expression<Func<IContext, object?>>[] parameters)
where P : IPredicate
=> OrNot(typeof(P), parameters);

public PredicationBuilderNext OrNot(Type type, params object?[] parameters)
{
var right = BuildNot<P>(parameters);
var right = BuildNot(type, parameters);
Pile = new BinaryPredication(new BinaryOperator("Or"), Pile!, right);
return this;
return new(this);
}

public PredicationBuilder Xor<P>(params object?[] parameters)
#endregion

#region Xor

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

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

public PredicationBuilderNext Xor<P>(Expression<Func<IContext, object?>>[] 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<P>(params object?[] parameters)
#endregion

#region XorNot

public PredicationBuilderNext XorNot<P>()
where P : IPredicate
{
var right = BuildNot<P>(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<P>(params object?[] parameters)
where P : IPredicate
=> XorNot(typeof(P), parameters);

private IParameter[] Parametrize(object?[] parameters)
{
var typedParameters = new List<IParameter>();
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<P>(Expression<Func<IContext, object?>>[] 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
}

0 comments on commit 51aa837

Please sign in to comment.