From 8377166e19fa29483d516b1f19edbf41daa8b25b Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 15 May 2025 13:15:22 +0200 Subject: [PATCH] Add validation when passing ParsingConfig in args --- .../DynamicQueryableExtensions.cs | 67 ++++++++++++++----- .../Validation/Check.cs | 12 ++++ .../ExpressionTests.cs | 4 +- .../QueryableTests.Where.cs | 2 + 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index 65365eae..c5d52287 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -125,6 +125,7 @@ public static bool All(this IQueryable source, ParsingConfig config, string pred Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -176,6 +177,7 @@ public static bool Any(this IQueryable source, ParsingConfig config, string pred Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -246,6 +248,7 @@ public static double Average(this IQueryable source, ParsingConfig config, strin Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -399,6 +402,7 @@ public static int Count(this IQueryable source, ParsingConfig config, string pre Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -529,6 +533,7 @@ public static dynamic First(this IQueryable source, ParsingConfig config, string Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -601,6 +606,7 @@ public static dynamic FirstOrDefault(this IQueryable source, ParsingConfig confi Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -679,8 +685,9 @@ internal static IQueryable InternalGroupBy(IQueryable source, ParsingConfig conf { Check.NotNull(source); Check.NotNull(config); - Check.NotEmpty(keySelector, nameof(keySelector)); - Check.NotEmpty(resultSelector, nameof(resultSelector)); + Check.NotEmpty(keySelector); + Check.NotEmpty(resultSelector); + Check.Args(args); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression keyLambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, keySelector, args); @@ -807,7 +814,8 @@ internal static IQueryable InternalGroupBy(IQueryable source, ParsingConfig conf { Check.NotNull(source); Check.NotNull(config); - Check.NotEmpty(keySelector, nameof(keySelector)); + Check.NotEmpty(keySelector); + Check.Args(args); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression keyLambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, keySelector, args); @@ -932,12 +940,13 @@ private static IEnumerable GroupByManyInternal(IEnumerabl /// An obtained by performing a grouped join on two sequences. public static IQueryable GroupJoin(this IQueryable outer, ParsingConfig config, IEnumerable inner, string outerKeySelector, string innerKeySelector, string resultSelector, params object?[] args) { - Check.NotNull(outer, nameof(outer)); + Check.NotNull(outer); Check.NotNull(config); - Check.NotNull(inner, nameof(inner)); - Check.NotEmpty(outerKeySelector, nameof(outerKeySelector)); - Check.NotEmpty(innerKeySelector, nameof(innerKeySelector)); - Check.NotEmpty(resultSelector, nameof(resultSelector)); + Check.NotNull(inner); + Check.NotEmpty(outerKeySelector); + Check.NotEmpty(innerKeySelector); + Check.NotEmpty(resultSelector); + Check.Args(args); Type outerType = outer.ElementType; Type innerType = inner.AsQueryable().ElementType; @@ -989,12 +998,13 @@ public static IQueryable Join(this IQueryable outer, ParsingConfig config, IEnum { //http://stackoverflow.com/questions/389094/how-to-create-a-dynamic-linq-join-extension-method - Check.NotNull(outer, nameof(outer)); + Check.NotNull(outer); Check.NotNull(config); - Check.NotNull(inner, nameof(inner)); - Check.NotEmpty(outerKeySelector, nameof(outerKeySelector)); - Check.NotEmpty(innerKeySelector, nameof(innerKeySelector)); - Check.NotEmpty(resultSelector, nameof(resultSelector)); + Check.NotNull(inner); + Check.NotEmpty(outerKeySelector); + Check.NotEmpty(innerKeySelector); + Check.NotEmpty(resultSelector); + Check.Args(args); Type outerType = outer.ElementType; Type innerType = inner.AsQueryable().ElementType; @@ -1094,6 +1104,7 @@ public static dynamic Last(this IQueryable source, ParsingConfig config, string Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -1166,6 +1177,7 @@ public static dynamic LastOrDefault(this IQueryable source, ParsingConfig config Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -1244,6 +1256,7 @@ public static long LongCount(this IQueryable source, ParsingConfig config, strin Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -1316,6 +1329,7 @@ public static object Max(this IQueryable source, ParsingConfig config, string pr Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(object), predicate, args); @@ -1388,6 +1402,7 @@ public static object Min(this IQueryable source, ParsingConfig config, string pr Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(object), predicate, args); @@ -1545,6 +1560,8 @@ public static IOrderedQueryable OrderBy(this IQueryable public static IOrderedQueryable OrderBy(this IQueryable source, ParsingConfig config, string ordering, params object?[] args) { + Check.Args(args); + if (args.Length > 0 && args[0] != null && args[0]!.GetType().GetInterfaces().Any(i => i.Name.Contains("IComparer`1"))) { return InternalOrderBy(source, config, ordering, args[0]!, args); @@ -1584,6 +1601,7 @@ internal static IOrderedQueryable InternalOrderBy(IQueryable source, ParsingConf Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(ordering); + Check.Args(args); ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty, config.RenameEmptyParameterExpressionNames)]; var parser = new ExpressionParser(parameters, ordering, args, config, true); @@ -1758,6 +1776,7 @@ public static IQueryable Select(this IQueryable source, ParsingConfig config, st Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(selector); + Check.Args(args); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, selector, args); @@ -1799,6 +1818,7 @@ public static IQueryable Select(this IQueryable source, Parsin Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(selector); + Check.Args(args); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(TResult), selector, args); @@ -1841,8 +1861,9 @@ public static IQueryable Select(this IQueryable source, ParsingConfig config, Ty { Check.NotNull(source); Check.NotNull(config); - Check.NotNull(resultType, nameof(resultType)); + Check.NotNull(resultType); Check.NotEmpty(selector); + Check.Args(args); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, resultType, selector, args); @@ -1907,6 +1928,7 @@ public static IQueryable SelectMany(this IQueryable source, ParsingConfig config Check.NotNull(config); Check.NotNull(resultType); Check.NotEmpty(selector); + Check.Args(args); return SelectManyInternal(source, config, resultType, selector, args); } @@ -1978,6 +2000,7 @@ public static IQueryable SelectMany(this IQueryable source, Pa Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(selector); + Check.Args(args); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, selector, args); @@ -2076,10 +2099,12 @@ public static IQueryable SelectMany( { Check.NotNull(source); Check.NotNull(config); - Check.NotEmpty(collectionSelector, nameof(collectionSelector)); - Check.NotEmpty(collectionParameterName, nameof(collectionParameterName)); - Check.NotEmpty(resultSelector, nameof(resultSelector)); - Check.NotEmpty(resultParameterName, nameof(resultParameterName)); + Check.NotEmpty(collectionSelector); + Check.NotEmpty(collectionParameterName); + Check.NotEmpty(resultSelector); + Check.NotEmpty(resultParameterName); + Check.Args(collectionSelectorArgs); + Check.Args(resultSelectorArgs); bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression sourceSelectLambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, collectionSelector, collectionSelectorArgs); @@ -2227,6 +2252,7 @@ public static dynamic SingleOrDefault(this IQueryable source, ParsingConfig conf Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -2309,6 +2335,7 @@ public static IQueryable SkipWhile(this IQueryable source, ParsingConfig config, Check.NotNull(source); Check.NotNull(config); Check.NotNull(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -2365,6 +2392,7 @@ public static object Sum(this IQueryable source, ParsingConfig config, string pr Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -2439,6 +2467,7 @@ public static IQueryable TakeWhile(this IQueryable source, ParsingConfig config, Check.NotNull(source); Check.NotNull(config); Check.NotNull(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); @@ -2553,6 +2582,7 @@ internal static IOrderedQueryable InternalThenBy(IOrderedQueryable source, Parsi Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(ordering); + Check.Args(args); ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty, config.RenameEmptyParameterExpressionNames) }; ExpressionParser parser = new ExpressionParser(parameters, ordering, args, config); @@ -2649,6 +2679,7 @@ public static IQueryable Where(this IQueryable source, ParsingConfig config, str Check.NotNull(source); Check.NotNull(config); Check.NotEmpty(predicate); + Check.Args(args); bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); diff --git a/src/System.Linq.Dynamic.Core/Validation/Check.cs b/src/System.Linq.Dynamic.Core/Validation/Check.cs index 00994c89..e0bbb32b 100644 --- a/src/System.Linq.Dynamic.Core/Validation/Check.cs +++ b/src/System.Linq.Dynamic.Core/Validation/Check.cs @@ -8,6 +8,18 @@ namespace System.Linq.Dynamic.Core.Validation; [DebuggerStepThrough] internal static class Check { + private const string ParsingConfigError = "The ParsingConfig should be provided as first argument to this method."; + + public static object?[]? Args(object?[]? args, [CallerArgumentExpression("args")] string? parameterName = null) + { + if (args?.Any(a => a is ParsingConfig) == true) + { + throw new ArgumentException(ParsingConfigError, parameterName); + } + + return args; + } + public static T Condition(T value, Predicate predicate, [CallerArgumentExpression("value")] string? parameterName = null) { NotNull(predicate); diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 6ff98396..d71845ea 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -1281,8 +1281,8 @@ public void ExpressionTests_In_Enum() // Act var expected = qry.Where(x => new[] { TestEnum.Var1, TestEnum.Var2 }.Contains(x.TestEnum)).ToArray(); - var result1 = qry.Where("it.TestEnum in (\"Var1\", \"Var2\")", config).ToArray(); - var result2 = qry.Where("it.TestEnum in (0, 1)", config).ToArray(); + var result1 = qry.Where(config, "it.TestEnum in (\"Var1\", \"Var2\")").ToArray(); + var result2 = qry.Where(config, "it.TestEnum in (0, 1)").ToArray(); // Assert Check.That(result1).ContainsExactly(expected); diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs index 1622a54e..66408b41 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs @@ -155,6 +155,8 @@ public void Where_Dynamic_Exceptions() Assert.Throws(() => qry.Where((string?)null)); Assert.Throws(() => qry.Where("")); Assert.Throws(() => qry.Where(" ")); + var parsingConfigException = Assert.Throws(() => qry.Where("UserName == \"x\"", ParsingConfig.Default)); + Assert.Equal("The ParsingConfig should be provided as first argument to this method. (Parameter 'args')", parsingConfigException.Message); } [Fact]