Skip to content

Commit

Permalink
feat: plucker support for nested properties (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seddryck authored Aug 21, 2024
1 parent ac4b072 commit 5f0b0e9
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 10 deletions.
38 changes: 28 additions & 10 deletions Streamistry.Core/Pipes/Mappers/Plucker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,40 @@ public Plucker(IChainablePipe<TInput> upstream, Expression<Func<TInput, TOutput?

private static TOutput? RetrieveProperty(TInput? input, Expression<Func<TInput, TOutput?>> lambda)
{
var propInfo = GetPropertyInfo(lambda);
return (TOutput?)propInfo.GetValue(input, null);
if (input is null)
return default;

return (TOutput?)GetNestedPropertyValue(input, lambda);
}

private static PropertyInfo GetPropertyInfo(Expression<Func<TInput, TOutput?>> lambda)
public static object? GetNestedPropertyValue(object target, LambdaExpression lambda)
{
if (lambda.Body is not MemberExpression member)
MemberExpression? expr;
if (lambda.Body is not MemberExpression)
throw new ArgumentException($"Expression '{lambda}' refers to a method, not a property.");

if (member.Member is not PropertyInfo propInfo)
throw new ArgumentException($"Expression '{lambda}' refers to a field, not a property.");
if (lambda.Body is UnaryExpression unaryExpression)
expr = unaryExpression.Operand as MemberExpression ?? throw new InvalidOperationException();
else
expr = lambda.Body as MemberExpression;

var members = new Stack<MemberExpression>();

var type = typeof(TInput);
if (propInfo.ReflectedType != null && type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException($"Expression '{lambda}' refers to a property that is not a property from type {type}.");
while (expr != null)
{
members.Push(expr);
expr = expr.Expression as MemberExpression;
}

return propInfo;
// Now we evaluate them from root to leaf
object? item = target;
while (members.Count > 0)
{
var memberExpression = members.Pop();
var propertyInfo = (memberExpression.Member as PropertyInfo)
?? throw new ArgumentException("Member is not a property");
item = propertyInfo.GetValue(item);
}
return item;
}
}
11 changes: 11 additions & 0 deletions Streamistry.Testing/Pipes/Mappers/PluckerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,15 @@ public void Emit_HumanPluckerOnBirthDay_BirthDay()
pipeline.Emit(new Human("Albert Einstein", new DateOnly(1879, 3, 14)));
Assert.That(sink.State.Last(), Is.EqualTo(new DateOnly(1879, 3, 14)));
}

[Test]
public void Emit_HumanPluckerOnBirthMonth_Integer()
{
var pipeline = new Pipeline<Human>();
var plucker = new Plucker<Human, int>(pipeline, h => h.BirthDay.Month);
var sink = new MemorySink<int>(plucker);

pipeline.Emit(new Human("Albert Einstein", new DateOnly(1879, 3, 14)));
Assert.That(sink.State.Last(), Is.EqualTo(3));
}
}

0 comments on commit 5f0b0e9

Please sign in to comment.