Skip to content
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
64 changes: 64 additions & 0 deletions Mimeo.DynamicUI.Tests/OData/ODataExpressionGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,70 @@ public void CanFilterSimpleString()
expression.Should().Be("tolower(TestProperty) eq 'value'");
}

[Theory]
[InlineData(true, "'", "tolower(TestProperty) eq ''''")]
[InlineData(true, "O'Brien", "tolower(TestProperty) eq 'o''brien'")]
[InlineData(false, "O'Brien", "TestProperty eq 'O''Brien'")]
[InlineData(false, "''", "TestProperty eq ''''''")]
public void CanFilterString_EscapesSingleQuotes(bool ignoreCase, string value, string expectedQuery)
{
// Arrange
var expressionGenerator = new ODataExpressionGenerator(Mock.Of<IDateTimeConverter>());
var definition = new DataFieldDefinition("TestClass", typeof(string), "TestProperty");
var filter = new DataQueryFilter(definition, DataFilterOperator.Equals, value)
{
IgnoreCase = ignoreCase
};
var query = new DataQuery
{
Filters = [filter]
};

// Act
var expression = expressionGenerator.GenerateODataFilter(query);

// Assert
expression.Should().Be(expectedQuery);
}

[Fact]
public void CanFilterStringIn_EscapesSingleQuotes()
{
// Arrange
var expressionGenerator = new ODataExpressionGenerator(Mock.Of<IDateTimeConverter>());
var definition = new DataFieldDefinition("TestClass", typeof(string), "TestProperty");
var filter = new DataQueryFilter(definition, DataFilterOperator.In, new List<string> { "O'Brien", "Smith" });
var query = new DataQuery
{
Filters = [filter]
};

// Act
var expression = expressionGenerator.GenerateODataFilter(query);

// Assert
expression.Should().Be("tolower(TestProperty) in ('o''brien','smith')");
}

[Fact]
public void CanFilterListOfString_EscapesSingleQuotes()
{
// Arrange
var expressionGenerator = new ODataExpressionGenerator(Mock.Of<IDateTimeConverter>());
var definition = new DataFieldDefinition("TestClass", typeof(List<string>), "TestProperty");
var filter = new DataQueryFilter(definition, DataFilterOperator.Contains, "'");
var query = new DataQuery
{
Filters = [filter]
};

// Act
var expression = expressionGenerator.GenerateODataFilter(query);

// Assert
expression.Should().Be("TestProperty/any(i: contains(tolower(i), ''''))");
}

[Fact]
public void CanFilterListOfString()
{
Expand Down
26 changes: 17 additions & 9 deletions Mimeo.DynamicUI/Data/OData/ODataExpressionGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,11 @@ protected virtual string GenerateStringODataFilterExpression(string property, Da
{
if (ignoreCase)
{
propertyValueString = $"'{propertyValueString.ToLower()}'";
propertyValueString = $"'{EscapeODataString(propertyValueString.ToLower())}'";
}
else
{
propertyValueString = $"'{propertyValueString}'";
propertyValueString = $"'{EscapeODataString(propertyValueString)}'";
}

if (!string.IsNullOrEmpty(propertyValueString) && @operator == DataFilterOperator.Contains)
Expand Down Expand Up @@ -347,8 +347,8 @@ protected virtual string GenerateStringODataFilterExpression(string property, Da
else if (propertyValue is IEnumerable<string> propertyValueEnumerable)
{
string propertyValueExpression = ignoreCase
? $"({string.Join(",", propertyValueEnumerable.Select(v => $"'{v.ToLower()}'"))})"
: $"({string.Join(",", propertyValueEnumerable.Select(v => $"'{v}'"))})";
? $"({string.Join(",", propertyValueEnumerable.Select(v => $"'{EscapeODataString(v.ToLower())}'"))})"
: $"({string.Join(",", propertyValueEnumerable.Select(v => $"'{EscapeODataString(v)}'"))})";

if (@operator == DataFilterOperator.In)
{
Expand Down Expand Up @@ -377,11 +377,11 @@ protected virtual string GenerateEnumerableODataFilter(string property, DataFilt
var enumerableValue = ((IEnumerable)(propertyValue != null ? propertyValue : Enumerable.Empty<object>())).AsQueryable();

var enumerableValueAsString = "(" + string.Join(",",
(enumerableValue.ElementType == typeof(string) ? enumerableValue.Cast<string>().Select(i => $@"'{i}'").Cast<object>() : enumerableValue.Cast<object>())) + ")";
(enumerableValue.ElementType == typeof(string) ? enumerableValue.Cast<string>().Select(i => $@"'{EscapeODataString(i)}'").Cast<object>() : enumerableValue.Cast<object>())) + ")";

var enumerableValueAsStringOrForAny = string.Join(" or ",
(enumerableValue.ElementType == typeof(string)
? enumerableValue.Cast<string>().Select(i => $@"i eq '{i}'").Cast<object>()
? enumerableValue.Cast<string>().Select(i => $@"i eq '{EscapeODataString(i)}'").Cast<object>()
: enumerableValue.Cast<object>().Select(i => $@"i eq {i}").Cast<object>()));

if (enumerableValue.Any() && @operator == DataFilterOperator.Contains)
Expand All @@ -408,7 +408,7 @@ protected virtual string GenerateEnumerableODataFilter(string property, DataFilt
{
return "(" + string.Join(" and ",
(enumerableValue.ElementType == typeof(string)
? enumerableValue.Cast<string>().Select(i => $@"{property}/any(i: i eq '{i}')").Cast<object>()
? enumerableValue.Cast<string>().Select(i => $@"{property}/any(i: i eq '{EscapeODataString(i)}')").Cast<object>()
: enumerableValue.Cast<object>().Select(i => $@"{property}/any(i: i eq {i})").Cast<object>()))
+ ")";
}
Expand All @@ -417,7 +417,7 @@ protected virtual string GenerateEnumerableODataFilter(string property, DataFilt
{
return "not (" + string.Join(" and ",
(enumerableValue.ElementType == typeof(string)
? enumerableValue.Cast<string>().Select(i => $@"{property}/any(i: i eq '{i}')").Cast<object>()
? enumerableValue.Cast<string>().Select(i => $@"{property}/any(i: i eq '{EscapeODataString(i)}')").Cast<object>()
: enumerableValue.Cast<object>().Select(i => $@"{property}/any(i: i eq {i})").Cast<object>()))
+ ")";
}
Expand Down Expand Up @@ -539,7 +539,7 @@ protected string GetODataValue(object value)

return value switch
{
string => $"'{value}'",
string stringValue => $"'{EscapeODataString(stringValue)}'",
Comment thread
evandixon marked this conversation as resolved.
DateTimeOffset dateTimeOffset => dateTimeOffset.UtcDateTime.ToString("o"),
DateTime dateTime => dateTime.ToString("o"),
_ => value.ToString()!
Expand Down Expand Up @@ -602,6 +602,14 @@ protected static bool IsNumeric(Type source)
}
}

/// <summary>
/// Escapes a value for use inside a single-quoted OData string literal
/// </summary>
protected static string EscapeODataString(string value)
{
return value.Replace("'", "''");
}

protected static string? GetValueAsString(object? value)
{
if (value == null)
Expand Down
Loading