Skip to content

Commit

Permalink
Merge pull request #29 from ZEXSM/bugfix/ampersands-in-string-values
Browse files Browse the repository at this point in the history
Bugfix/ampersands in string values
  • Loading branch information
ZEXSM authored Aug 29, 2020
2 parents 922be31 + a379ee5 commit 499108f
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The library primarily targets OData Version 4.01 and provides linq syntax for cr
* [`date`](#date)
* string and collection
* [`contains`](#contains)
* [`substringof`](#substringof)
* [`substringof (deprecated)`](#substringof)
* [`toupper`](#toupper)
* [`tolower`](#tolower)
* [`concat`](#concat)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ namespace OData.QueryBuilder.Conventions.Functions
{
public interface IConvertFunction
{
T ConvertEnumToString<T>(T type) where T : Enum;
T ConvertEnumToString<T>(T value) where T : Enum;

DateTime ConvertDateTimeToString(DateTime dateTime, string format);
DateTime ConvertDateTimeToString(DateTime value, string format);

DateTimeOffset ConvertDateTimeOffsetToString(DateTimeOffset dateTimeOffset, string format);
DateTimeOffset ConvertDateTimeOffsetToString(DateTimeOffset value, string format);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace OData.QueryBuilder.Conventions.Functions
{
public interface IODataFunction : IODataStringAndCollectionFunction, IODataDateFunction, IConvertFunction
public interface IODataFunction : IODataStringAndCollectionFunction, IODataDateFunction, IConvertFunction, IReplaceFunction
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace OData.QueryBuilder.Conventions.Functions
{
public interface IReplaceFunction
{
string ReplaceCharacters(string value, IDictionary<string, string> keyValuePairs);
}
}
2 changes: 1 addition & 1 deletion src/OData.QueryBuilder/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static string ConvertToString(object @object)

return !string.IsNullOrEmpty(stringValuesString) ? $"'{stringValuesString}'" : string.Empty;
default:
return $"{@object}";
return $"'{@object}'";
}
}
}
Expand Down
17 changes: 16 additions & 1 deletion src/OData.QueryBuilder/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
namespace OData.QueryBuilder.Extensions
using System.Collections.Generic;
using System.Text;

namespace OData.QueryBuilder.Extensions
{
internal static class StringExtensions
{
public static bool IsNullOrQuotes(this string value) =>
string.IsNullOrEmpty(value) || value == "null" || value == "''";

public static string ReplaceWithStringBuilder(this string value, IDictionary<string, string> keyValuePairs)
{
var stringBuilder = new StringBuilder(value);

foreach (var keyValuePair in keyValuePairs)
{
stringBuilder.Replace(keyValuePair.Key, keyValuePair.Value);
}

return stringBuilder.ToString();
}
}
}
40 changes: 39 additions & 1 deletion src/OData.QueryBuilder/Visitors/VisitorExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using OData.QueryBuilder.Extensions;
using OData.QueryBuilder.Options;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace OData.QueryBuilder.Visitors
Expand Down Expand Up @@ -149,8 +150,18 @@ protected virtual string VisitMethodCallExpression(MethodCallExpression methodCa
var dateTimeOffset = (DateTimeOffset)GetValueOfExpression(methodCallExpression.Arguments[0]);

return dateTimeOffset.ToString((string)GetValueOfExpression(methodCallExpression.Arguments[1]));
case nameof(IReplaceFunction.ReplaceCharacters):
var @symbol0 = ReflectionExtensions.ConvertToString(GetValueOfExpression(methodCallExpression.Arguments[0]));
var @symbol1 = GetValueOfExpression(methodCallExpression.Arguments[1]);

if (@symbol1 == default)
{
throw new ArgumentException("KeyValuePairs is null");
}

return @symbol0.ReplaceWithStringBuilder(@symbol1 as IDictionary<string, string>);
case nameof(ToString):
return VisitExpression(methodCallExpression.Object);
return ReflectionExtensions.ConvertToString(GetValueOfExpression(methodCallExpression.Object).ToString());
default:
return default;
}
Expand Down Expand Up @@ -221,6 +232,7 @@ protected virtual string VisitParameterExpression(ParameterExpression parameterE
{
MemberExpression memberExpression => GetValueOfMemberExpression(memberExpression),
ConstantExpression constantExpression => GetValueOfConstantExpression(constantExpression),
ListInitExpression listInitExpression => GetValueOfListInitExpression(listInitExpression),
_ => default,
};

Expand All @@ -234,6 +246,32 @@ protected object GetValueOfConstantExpression(ConstantExpression constantExpress
_ => expression.Member.GetValue(),
};

protected object GetValueOfListInitExpression(ListInitExpression listInitExpression)
{
var arguments = new object[listInitExpression.NewExpression.Arguments.Count];

for (var i = 0; i < listInitExpression.NewExpression.Arguments.Count; i++)
{
arguments[i] = GetValueOfExpression(listInitExpression.NewExpression.Arguments[i]);
}

var listInit = listInitExpression.NewExpression.Constructor.Invoke(arguments);

foreach (var elementInit in listInitExpression.Initializers)
{
var parameters = new object[elementInit.Arguments.Count];

for (var index = 0; index < elementInit.Arguments.Count; index++)
{
parameters[index] = GetValueOfExpression(elementInit.Arguments[index]);
}

listInit.GetType().GetMethod(nameof(List<ListInitExpression>.Add)).Invoke(listInit, parameters);
}

return listInit;
}

protected bool IsResourceOfMemberExpression(MemberExpression memberExpression) => memberExpression.Expression switch
{
ParameterExpression pe => pe.Type.GetProperty(memberExpression.Member.Name, memberExpression.Type) != default,
Expand Down
84 changes: 84 additions & 0 deletions test/OData.QueryBuilder.Test/ODataQueryOptionListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,90 @@ public void ODataQueryBuilderList_Skip_Top_Simple_Success()
uri.OriginalString.Should().Be("http://mock/odata/ODataType?$skip=1&$top=1");
}


[Fact(DisplayName = "Filter call ToString => Success")]
public void ODataQueryBuilderList_Filter_Call_ToString_Success()
{
var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter(s => s.TypeCode == 44.ToString())
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=TypeCode eq '44'");
}

[Fact(DisplayName = "Filter string with ReplaceCharacters => Success")]
public void ODataQueryBuilderList_Filter_With_ReplaceCharacters_Success()
{
var dictionary = new Dictionary<string, string>(0)
{
{ "%", "%25" },
{ "/", "%2f" },
{ "?", "%3f" },
{ "#", "%23" },
{ "&", "%26" }
};

var constValue = "3 & 4 / 7 ? 8 % 9 # 1";

var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => s.ODataKind.ODataCode.Code == f.ReplaceCharacters(constValue, dictionary))
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=ODataKind/ODataCode/Code eq '3 %26 4 %2f 7 %3f 8 %25 9 %23 1'");
}

[Fact(DisplayName = "Filter string with ReplaceCharacters new Dictionary => Success")]
public void ODataQueryBuilderList_Filter_With_ReplaceCharacters_new_dictionary_Success()
{
var constValue = "3 & 4 / 7 ? 8 % 9 # 1";

var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => s.ODataKind.ODataCode.Code == f.ReplaceCharacters(
constValue,
new Dictionary<string, string> { { "&", "%26" } }))
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=ODataKind/ODataCode/Code eq '3 %26 4 / 7 ? 8 % 9 # 1'");
}

[Fact(DisplayName = "Filter string with ReplaceCharacters Value => ArgumentException")]
public void ODataQueryBuilderList_Filter_With_ReplaceCharacters_Value_ArgumentException()
{
var constValue = default(string);

var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => s.ODataKind.ODataCode.Code == f.ReplaceCharacters(
constValue,
new Dictionary<string, string> { { "&", "%26" } }))
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=ODataKind/ODataCode/Code eq null");
}

[Fact(DisplayName = "Filter string with ReplaceCharacters KeyValuePairs => ArgumentException")]
public void ODataQueryBuilderList_Filter_With_ReplaceCharacters_KeyValuePairs_ArgumentException()
{
var constValue = "3 & 4 / 7 ? 8 % 9 # 1";

_odataQueryBuilderDefault
.Invoking((r) => r
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => s.ODataKind.ODataCode.Code == f.ReplaceCharacters(
constValue,
null))
.ToUri())
.Should().Throw<ArgumentException>().WithMessage("KeyValuePairs is null");
}

[Fact(DisplayName = "Filter simple const int=> Success")]
public void ODataQueryBuilderList_Filter_Simple_Const_Int_Success()
{
Expand Down

0 comments on commit 499108f

Please sign in to comment.