Skip to content

Commit

Permalink
Allow $filtering with enum integer value in squote and without squote (
Browse files Browse the repository at this point in the history
…#3014)

* Allow comparison with underlying integral for Enum member and add capability to convert integral value to enum member name

* Bump with tests

* Move helper methods to EnumHelper, use them to check for enum values and adds more tests to handle float, long and int.

* Use explicit/exact type instead of 'var'

* Add more tests to handle huge, long values

* Add assertion for comparing enum member name with the result

* Move extension methods from Edm EnumHelper.cs to Core in EdmExtensionMethods.cs
  • Loading branch information
WanjohiSammy committed Jul 22, 2024
1 parent 4373f04 commit 50ab60d
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 8 deletions.
42 changes: 42 additions & 0 deletions src/Microsoft.OData.Core/EdmExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,47 @@ public static bool HasKey(IEdmNavigationSource currentNavigationSource, IEdmStru

return false;
}

/// <summary>
/// Parse an enum integral value to enum member.
/// </summary>
/// <param name="enumType">edm enum type</param>
/// <param name="value">input integral value.</param>
/// <param name="enumMember">parsed result.</param>
/// <returns>true if parse succeeds, false if parse fails.</returns>
public static bool TryParse(this IEdmEnumType enumType, long value, out IEdmEnumMember enumMember)
{
enumMember = null;
foreach (IEdmEnumMember member in enumType.Members)
{
if (member.Value.Value == value)
{
enumMember = member;
return true;
}
}

return false;
}

/// <summary>
/// Checks if the given member name exists in the enum type.
/// </summary>
/// <param name="enumType">The enum type.</param>
/// <param name="memberName">The member name to check.</param>
/// <param name="comparison">The comparison type to use for string comparison. Default is Ordinal.</param>
/// <returns>True if the member name exists in the enum type; otherwise, false.</returns>
public static bool ContainsMember(this IEdmEnumType enumType, string memberName, StringComparison comparison = StringComparison.Ordinal)
{
foreach (IEdmEnumMember member in enumType.Members)
{
if (string.Equals(member.Name, memberName, comparison))
{
return true;
}
}

return false;
}
}
}
17 changes: 12 additions & 5 deletions src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,26 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
}

ConstantNode constantNode = source as ConstantNode;
if (constantNode != null && constantNode.Value != null && source.TypeReference.IsString() && targetTypeReference.IsEnum())
// Check if the source node is a constant node, not null, and the source type is either string or integral
// and the target type is an enum.
if (constantNode != null && constantNode.Value != null && (source.TypeReference.IsString() || source.TypeReference.IsIntegral()) && targetTypeReference.IsEnum())
{
string memberName = constantNode.Value.ToString();
IEdmEnumType enumType = targetTypeReference.Definition as IEdmEnumType;
if (enumType.Members.Any(m => string.Compare(m.Name, memberName, StringComparison.Ordinal) == 0))
if(enumType.ContainsMember(memberName, StringComparison.Ordinal))
{
string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion));
return new ConstantNode(new ODataEnumValue(constantNode.Value.ToString(), targetTypeReference.Definition.ToString()), literalText, targetTypeReference);
return new ConstantNode(new ODataEnumValue(memberName, enumType.ToString()), literalText, targetTypeReference);
}
else

// If the member name is an integral value, we should try to convert it to the enum member name and find the enum member with the matching integral value
if (long.TryParse(memberName, out long memberIntegralValue) && enumType.TryParse(memberIntegralValue, out IEdmEnumMember enumMember))
{
throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(memberName));
string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default(ODataVersion));
return new ConstantNode(new ODataEnumValue(enumMember.Name, enumType.ToString()), literalText, targetTypeReference);
}

throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(memberName));
}

if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference))
Expand Down
7 changes: 4 additions & 3 deletions src/Microsoft.OData.Core/UriParser/TypePromotionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,15 @@ internal static bool PromoteOperandTypes(
return true;
}

// Comparing an enum with a string is valid
if (left != null && right != null && left.IsEnum() && right.IsString())
// Comparing an enum with a string or int is valid
if (left != null && right != null && left.IsEnum() && (right.IsString() || right.IsIntegral()))
{
right = left;
return true;
}

if (left != null && right != null && right.IsEnum() && left.IsString())
// Comparing an enum with a string or int is valid
if (left != null && right != null && right.IsEnum() && (left.IsString() || left.IsIntegral()))
{
left = right;
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.OData.Edm;
using Xunit;
using ODataErrorStrings = Microsoft.OData.Strings;
using System.Linq;

namespace Microsoft.OData.Tests.UriParser.Binders
{
Expand Down Expand Up @@ -50,6 +51,199 @@ public void IfTypesCannotPromoteErrorIsThrown()
Action convertMethod = () => MetadataBindingUtils.ConvertToTypeIfNeeded(node, targetType);
convertMethod.Throws<ODataException>(ODataErrorStrings.MetadataBinder_CannotConvertToType(node.TypeReference.FullName(), targetType.FullName()));
}

[Fact]
public void IfTypePromotionNeeded_SourceIsIntegerMemberValueAndTargetIsEnum_ConstantNodeIsCreated()
{
// Arrange
int enumValue = 3;
bool success = WeekDayEmumType.TryParse(enumValue, out IEdmEnumMember expectedMember);

SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);

// Act
ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;

// Assert
Assert.True(success);
result.ShouldBeEnumNode(WeekDayEmumType, expectedMember.Name);
Assert.Equal(expectedMember.Name, result.Value.ToString());
}

[Fact]
public void IfTypePromotionNeeded_SourceIsLongMemberValueAndTargetIsEnum_ConstantNodeIsCreated()
{
// Arrange
long enumValue = 7L;
bool success = WeekDayEmumType.TryParse(enumValue, out IEdmEnumMember expectedMember);

SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);

// Act
ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;

// Assert
Assert.True(success);
result.ShouldBeEnumNode(WeekDayEmumType, expectedMember.Name);
Assert.Equal(expectedMember.Name, result.Value.ToString()); // Compare the enum member name
}

[Fact]
public void IfTypePromotionNeeded_SourceIsHugeLongMemberValueAndTargetIsEnum_ConstantNodeIsCreated()
{
// Arrange
long enumValue = 2147483657; // ((long)int.MaxValue + 10L).ToString()
bool success = EmployeeType.TryParse(enumValue, out IEdmEnumMember expectedMember);

SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(EmployeeType, false);

// Act
ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;

// Assert
Assert.True(success);
result.ShouldBeEnumNode(EmployeeType, expectedMember.Name);
Assert.Equal(expectedMember.Name, result.Value.ToString()); // Compare the enum member name
}

[Fact]
public void IfTypePromotionNeeded_SourceIsLongAsStringMemberValueAndTargetIsEnum_ConstantNodeIsCreated()
{
// Arrange
string enumValue = "4294967294"; // ((long)int.MaxValue + (long)int.MaxValue).ToString();
bool success = EmployeeType.TryParse(long.Parse(enumValue), out IEdmEnumMember expectedMember);

SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(EmployeeType, false);

// Act
ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;

// Assert
Assert.True(success);
result.ShouldBeEnumNode(EmployeeType, expectedMember.Name);
Assert.Equal(expectedMember.Name, result.Value.ToString()); // Compare the enum member name
}

[Fact]
public void IfTypePromotionNeededForEnum_SourceIsIntegralMemberValueInStringAndTargetIsEnumType_ConstantNodeIsCreated()
{
// Arrange
string enumValue = "5";
bool success = WeekDayEmumType.TryParse(long.Parse(enumValue), out IEdmEnumMember expectedMember);

SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);

// Act
ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;

// Assert
Assert.True(success);
result.ShouldBeEnumNode(WeekDayEmumType, expectedMember.Name);
Assert.Equal(expectedMember.Name, result.Value.ToString()); // compare the enum member name
}

[Fact]
public void IfTypePromotionNeededForEnum_SourceIsMemberName_ConstantNodeIsCreated()
{
// Arrange
string enumValue = "Monday";
SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);
// Act
ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;

// Assert
result.ShouldBeEnumNode(WeekDayEmumType, enumValue);
Assert.Equal(enumValue, result.Value.ToString());
}

[Fact]
public void IfTypePromotionNeededForEnum_SourceIsIntegerExceedingDefinedIntegralLimits_ValueIsNotValidEnumConstantExceptionIsThrown()
{
// Arrange
int enumValue = 10;
SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);

// Act
Action convertIfNeeded = () => MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference);

// Assert
convertIfNeeded.Throws<ODataException>(ODataErrorStrings.Binder_IsNotValidEnumConstant(enumValue.ToString()));
}

[Fact]
public void IfTypePromotionNeeded_SourceIsFloatMemberValuesAndTargetIsEnum_CannotConvertToTypeExceptionIsThrown()
{
// Arrange
float[] floatValues = new float[] { 1.0F, 3.3F, 5.0F, 6.0F };

foreach (float enumValue in floatValues)
{
SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);

// Act
Action convertIfNeeded = () => MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference);

// Assert
convertIfNeeded.Throws<ODataException>(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.FullName(), targetTypeReference.FullName()));
}
}

[Fact]
public void IfTypePromotionNeeded_SourceIsFloatMemberValuesInStringAndTargetIsEnum_ValueIsNotValidEnumConstantExceptionIsThrown()
{
// Arrange
string[] floatValues = new string[] { "1.0", "3.1", "5.5", "7.0" };

foreach (string enumValue in floatValues)
{
SingleValueNode source = new ConstantNode(enumValue);
IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(WeekDayEmumType, false);

// Act
Action convertIfNeeded = () => MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference);

// Assert
convertIfNeeded.Throws<ODataException>(ODataErrorStrings.Binder_IsNotValidEnumConstant(enumValue));
}
}

private static EdmEnumType WeekDayEmumType
{
get
{
EdmEnumType weekDayType = new EdmEnumType("NS", "WeekDay");
weekDayType.AddMember("Monday", new EdmEnumMemberValue(1L));
weekDayType.AddMember("Tuesday", new EdmEnumMemberValue(2L));
weekDayType.AddMember("Wednesday", new EdmEnumMemberValue(3L));
weekDayType.AddMember("Thursday", new EdmEnumMemberValue(4L));
weekDayType.AddMember("Friday", new EdmEnumMemberValue(5L));
weekDayType.AddMember("Saturday", new EdmEnumMemberValue(6L));
weekDayType.AddMember("Sunday", new EdmEnumMemberValue(7L));

return weekDayType;
}
}

private static EdmEnumType EmployeeType
{
get
{
EdmEnumType employeeType = new EdmEnumType("NS", "EmployeeType");
employeeType.AddMember("FullTime", new EdmEnumMemberValue((long)int.MaxValue));
employeeType.AddMember("PartTime", new EdmEnumMemberValue((long)int.MaxValue + 10L));
employeeType.AddMember("Contractor", new EdmEnumMemberValue((long)int.MaxValue + (long)int.MaxValue));

return employeeType;
}
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,24 @@ public void EqualsOnEnumAndStringIsSupported()
Assert.True(left.IsEquivalentTo(HardCodedTestModel.GetPet2PetColorPatternProperty().Type));
Assert.True(right.IsEquivalentTo(HardCodedTestModel.GetPet2PetColorPatternProperty().Type));
}

[Fact]
public void EqualsOnEnumAndIntegralMemberValueIsSupported()
{
// Arrange
IEdmTypeReference left = HardCodedTestModel.GetPet2PetColorPatternProperty().Type;
IEdmTypeReference right = EdmCoreModel.Instance.GetInt32(true);
SingleValueNode leftNode = new SingleValuePropertyAccessNode(new ConstantNode(null)/*parent*/, new EdmStructuralProperty(new EdmEntityType("MyNamespace", "MyEntityType"), "myPropertyName", left));
SingleValueNode rightNode = new SingleValuePropertyAccessNode(new ConstantNode(null)/*parent*/, new EdmStructuralProperty(new EdmEntityType("MyNamespace", "MyEntityType"), "myPropertyName", right));

// Act
bool result = TypePromotionUtils.PromoteOperandTypes(BinaryOperatorKind.Equal, leftNode, rightNode, out left, out right, new TypeFacetsPromotionRules());

// Assert
Assert.True(result);
Assert.True(left.IsEquivalentTo(HardCodedTestModel.GetPet2PetColorPatternProperty().Type));
Assert.True(right.IsEquivalentTo(HardCodedTestModel.GetPet2PetColorPatternProperty().Type));
}
#endregion

#region PromoteOperandType Tests (For Unary Operators)
Expand Down

0 comments on commit 50ab60d

Please sign in to comment.