Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow $filtering with enum integer value in squote and without squote #3014

Merged
merged 9 commits into from
Jul 22, 2024
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())
gathogojr marked this conversation as resolved.
Show resolved Hide resolved
{
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()))
gathogojr marked this conversation as resolved.
Show resolved Hide resolved
{
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