Skip to content

Commit

Permalink
Fixes #3120: Throws exception if non defined enum member used in 'in'…
Browse files Browse the repository at this point in the history
… operator
  • Loading branch information
xuzhg committed Nov 18, 2024
1 parent 937d3d9 commit a50dd31
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 5 deletions.
11 changes: 10 additions & 1 deletion src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ internal sealed class InBinder
/// </summary>
private readonly Func<QueryToken, QueryNode> bindMethod;

/// <summary>
/// Resolver for parsing
/// </summary>
private readonly ODataUriResolver resolver;

/// <summary>
/// Constructs a InBinder with the given method to be used binding the parent token if needed.
/// </summary>
/// <param name="bindMethod">Method to use for binding the parent token, if needed.</param>
internal InBinder(Func<QueryToken, QueryNode> bindMethod)
/// <param name="resolver">Resolver for parsing.</param>
internal InBinder(Func<QueryToken, QueryNode> bindMethod, ODataUriResolver resolver)
{
this.bindMethod = bindMethod;
this.resolver = resolver;
}

/// <summary>
Expand Down Expand Up @@ -65,6 +72,8 @@ internal QueryNode BindInOperator(InToken inToken, BindingState state)
left = MetadataBindingUtils.ConvertToTypeIfNeeded(left, right.ItemType);
}

MetadataBindingUtils.VerifyCollectionNode(right, this.resolver.EnableCaseInsensitive);

return new InNode(left, right);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ protected virtual QueryNode BindIn(InToken inToken)
return this.Bind(queryToken);
};

InBinder inBinder = new InBinder(InBinderMethod);
InBinder inBinder = new InBinder(InBinderMethod, this.BindingState.Configuration.Resolver);
return inBinder.BindInOperator(inToken, this.BindingState);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
{
string memberName = constantNode.Value.ToString();
IEdmEnumType enumType = targetTypeReference.Definition as IEdmEnumType;
if(enumType.ContainsMember(memberName, StringComparison.Ordinal))
if (enumType.ContainsMember(memberName, StringComparison.Ordinal))
{
string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion));
return new ConstantNode(new ODataEnumValue(memberName, enumType.ToString()), literalText, targetTypeReference);
Expand Down Expand Up @@ -183,5 +183,30 @@ internal static IEdmTypeReference GetEdmTypeReference(this QueryNode segment)

return null;
}

internal static void VerifyCollectionNode(CollectionNode node, bool enableCaseInsensitive = false)
{
if (node == null ||
!(node is CollectionConstantNode collectionConstantNode) ||
!collectionConstantNode.ItemType.IsEnum()
)
{
return;
}

IEdmEnumType enumType = collectionConstantNode.ItemType.Definition as IEdmEnumType;

StringComparison comparison = enableCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
foreach (ConstantNode item in collectionConstantNode.Collection)
{
if (item != null && item.Value != null && item.Value is ODataEnumValue enumValue)
{
if (!enumType.ContainsMember(enumValue.Value, comparison))
{
throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(enumValue.Value));
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//---------------------------------------------------------------------

using System;
using Microsoft.OData.UriParser;
using Xunit;

namespace Microsoft.OData.Tests.ScenarioTests.UriBuilder
Expand All @@ -20,6 +21,48 @@ public void BuildFilterEnum()
Assert.Equal(queryUri, actualUri,new UriComparer<Uri>());
}

[Fact]
public void FilterUsingNonDefinedEnumMemberThrows()
{
Uri queryUri = new Uri("http://gobbledygook/Pet2Set?$filter=Shape eq 'NonDefinedShape'");

// Use default ODataUriResolver
ODataException exception1 = Assert.Throws<ODataException>(
() => UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings));

// Use StringAsEnumResolver
ODataException exception2 = Assert.Throws<ODataException>(
() => UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings, new StringAsEnumResolver { EnableCaseInsensitive = true }));

Assert.Equal("The string 'NonDefinedShape' is not a valid enumeration type constant.", exception1.Message);
Assert.Equal(exception1.Message, exception2.Message);
}

[Fact]
public void BuildFilterUsingEnumInInOperator()
{
Uri queryUri = new Uri("http://gobbledygook/Pet2Set?$filter=Shape in ('Rectangle', 'Triangle')");
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings);
Assert.Equal(new Uri("http://gobbledygook/Pet2Set?$filter=Shape in %28%27Rectangle%27%2C %27Triangle%27%29"), actualUri, new UriComparer<Uri>());
}

[Fact]
public void BuildFilterUsingNonDefinedEnumInInOperatorThrows()
{
Uri queryUri = new Uri("http://gobbledygook/Pet2Set?$filter=Shape in ('NonDefinedShape', 'Triangle')");

// Use default ODataUriResolver
ODataException exception1 = Assert.Throws<ODataException>(
() => UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings));

// Use StringAsEnumResolver
ODataException exception2 = Assert.Throws<ODataException>(
() => UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings, new StringAsEnumResolver { EnableCaseInsensitive = true }));

Assert.Equal("The string 'NonDefinedShape' is not a valid enumeration type constant.", exception1.Message);
Assert.Equal(exception1.Message, exception2.Message);
}

[Fact]
public void BuildFilterLongValuesWithOptionalSuffix()
{
Expand Down Expand Up @@ -442,6 +485,15 @@ public void BuildFilterWithInOperatorUsingCollectionConstant()
Assert.Equal(new Uri(uri), actualUri, new UriComparer<Uri>());
}

[Fact]
public void BuildFilterWithInOperatorUsingCollectionEnumConstant()
{
Uri queryUri = new Uri("People?$filter=Shape in ('Yellow','Blue')", UriKind.Relative);
Uri actualUri = UriBuilder(queryUri, ODataUrlKeyDelimiter.Parentheses, settings);
string uri = "http://gobbledygook/People?$filter=ID%20in%20(1%2C2%2C3)";
Assert.Equal(new Uri(uri), actualUri, new UriComparer<Uri>());
}

[Fact]
public void BuildFilterWithInOperatorUsingBracketedCollectionConstant()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ public static void SetODataUriParserSettingsTo(ODataUriParserSettings sourceSett
}
}

public static Uri UriBuilder(Uri queryUri, ODataUrlKeyDelimiter urlKeyDelimiter, ODataUriParserSettings settings)
public static Uri UriBuilder(Uri queryUri, ODataUrlKeyDelimiter urlKeyDelimiter, ODataUriParserSettings settings, ODataUriResolver resolver = null)
{
ODataUriParser odataUriParser = new ODataUriParser(HardCodedTestModel.TestModel, ServiceRoot, queryUri);
if (resolver != null)
{
odataUriParser.Resolver = resolver;
}

SetODataUriParserSettingsTo(settings, odataUriParser.Settings);
odataUriParser.UrlKeyDelimiter = urlKeyDelimiter;
ODataUri odataUri = odataUriParser.ParseUri();

return odataUri.BuildUri(urlKeyDelimiter);
}
#endregion
#endregion
}
}

0 comments on commit a50dd31

Please sign in to comment.