Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Commit

Permalink
SendPrimitiveAsync throws InvalidOperationException for enums
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinM85 committed Jun 25, 2024
1 parent 2685773 commit 6ef8a32
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 14 deletions.
139 changes: 139 additions & 0 deletions Microsoft.Kiota.Http.HttpClientLibrary.Tests/RequestAdapterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,134 @@ public async Task SendPrimitiveReturnsNullIfValueCannotBeParsedToEnum()

Assert.Null(response);
}

[Fact]
public async Task SendPrimitiveHandleEnumFlagsIfValuesAreStrings()
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Value1,Value3")
});

var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetStringValue())
.Returns("Value1,Value3");

var mockParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockParseNode.Object));
var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client);
var requestInfo = new RequestInformation
{
HttpMethod = Method.GET,
URI = new Uri("https://example.com")
};

var response = await adapter.SendPrimitiveAsync<TestEnumWithFlags?>(requestInfo);

Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value3, response);
}

[Fact]
public async Task SendPrimitiveHandleEnumFlagsIfValuesAreIntegers()
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("1,2")
});

var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetStringValue())
.Returns("1,2");

var mockParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockParseNode.Object));
var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client);
var requestInfo = new RequestInformation
{
HttpMethod = Method.GET,
URI = new Uri("https://example.com")
};

var response = await adapter.SendPrimitiveAsync<TestEnumWithFlags?>(requestInfo);

Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value2, response);
}

[Fact]
public async Task SendPrimitiveHandleEnumFlagsIfValuesAreFromEnumMember()
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Value__3,Value__2")
});

var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetStringValue())
.Returns("Value__3,Value__2");

var mockParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockParseNode.Object));
var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client);
var requestInfo = new RequestInformation
{
HttpMethod = Method.GET,
URI = new Uri("https://example.com")
};

var response = await adapter.SendPrimitiveAsync<TestEnumWithFlags?>(requestInfo);

Assert.Equal(TestEnumWithFlags.Value2 | TestEnumWithFlags.Value3, response);
}

[Fact]
public async Task SendPrimitiveReturnsNullIfFlagValueCannotBeParsedToEnum()
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Value0")
});

var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetStringValue())
.Returns("Value0");

var mockParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockParseNode.Object));
var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client);
var requestInfo = new RequestInformation
{
HttpMethod = Method.GET,
URI = new Uri("https://example.com")
};

var response = await adapter.SendPrimitiveAsync<TestEnumWithFlags?>(requestInfo);

Assert.Null(response);
}
}

public enum TestEnum
Expand All @@ -675,4 +803,15 @@ public enum TestEnum
[EnumMember(Value = "Value__3")]
Value3
}

[Flags]
public enum TestEnumWithFlags
{
[EnumMember(Value = "Value__1")]
Value1 = 0x01,
[EnumMember(Value = "Value__2")]
Value2 = 0x02,
[EnumMember(Value = "Value__3")]
Value3 = 0x04
}
}
62 changes: 48 additions & 14 deletions src/HttpClientRequestAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,27 +299,61 @@ public string? BaseUrl
Nullable.GetUnderlyingType(modelType) is { IsEnum: true } underlyingType &&
rootNode.GetStringValue() is { Length: > 0 } rawValue)
{
foreach(var field in underlyingType.GetFields())
if(underlyingType.IsDefined(typeof(FlagsAttribute)))
{
if(field.GetCustomAttribute<EnumMemberAttribute>() is { } attr && rawValue.Equals(attr.Value, StringComparison.Ordinal))
int intValue = 0;
while(rawValue.Length > 0)
{
rawValue = field.Name;
break;
}
}
int commaIndex = rawValue.IndexOf(',');
var valueName = commaIndex < 0 ? rawValue : rawValue.Substring(0, commaIndex);
foreach(var field in underlyingType.GetFields())
{
if(field.GetCustomAttribute<EnumMemberAttribute>() is { } attr && valueName.Equals(attr.Value, StringComparison.Ordinal))
{
valueName = field.Name;
break;
}
}
#if NET5_0_OR_GREATER
Enum.TryParse(underlyingType, rawValue, true, out object? enumResult);
result = enumResult;
if(Enum.TryParse(underlyingType, valueName, true, out var enumPartResult))
intValue |= (int)enumPartResult!;
#else
try
{
result = Enum.Parse(underlyingType, rawValue, true);
try
{
intValue |= (int)Enum.Parse(underlyingType, valueName, true);
}
catch { }
#endif

rawValue = commaIndex < 0 ? string.Empty : rawValue.Substring(commaIndex + 1);
}
result = intValue > 0 ? Enum.Parse(underlyingType, intValue.ToString(), true) : null;
}
catch
else
{
result = null;
}
foreach(var field in underlyingType.GetFields())
{
if(field.GetCustomAttribute<EnumMemberAttribute>() is { } attr && rawValue.Equals(attr.Value, StringComparison.Ordinal))
{
rawValue = field.Name;
break;
}
}

#if NET5_0_OR_GREATER
Enum.TryParse(underlyingType, rawValue, true, out object? enumResult);
result = enumResult;
#else
try
{
result = Enum.Parse(underlyingType, rawValue, true);
}
catch
{
result = null;
}
#endif
}
}
else
{
Expand Down

0 comments on commit 6ef8a32

Please sign in to comment.