Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PredicationBuilder is supporting context arguments #257

Merged
merged 1 commit into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}