Skip to content

Throw for invalid TryParse and BindAsync #36628

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

Merged
merged 4 commits into from
Sep 17, 2021
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
251 changes: 247 additions & 4 deletions src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Http.Extensions.Tests
{
Expand Down Expand Up @@ -274,6 +275,63 @@ public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNul
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
}

[Theory]
[InlineData(typeof(InvalidVoidReturnTryParseStruct))]
[InlineData(typeof(InvalidVoidReturnTryParseClass))]
[InlineData(typeof(InvalidWrongTypeTryParseStruct))]
[InlineData(typeof(InvalidWrongTypeTryParseClass))]
[InlineData(typeof(InvalidTryParseNullableStruct))]
[InlineData(typeof(InvalidTooFewArgsTryParseStruct))]
[InlineData(typeof(InvalidTooFewArgsTryParseClass))]
[InlineData(typeof(InvalidNonStaticTryParseStruct))]
[InlineData(typeof(InvalidNonStaticTryParseClass))]
public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
{
var ex = Assert.Throws<InvalidOperationException>(
() => new ParameterBindingMethodCache().FindTryParseMethod(type));
Assert.StartsWith($"TryParse method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
Assert.Contains($"bool TryParse(string, IFormatProvider, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
Assert.Contains($"bool TryParse(string, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
}

[Theory]
[InlineData(typeof(TryParseClassWithGoodAndBad))]
[InlineData(typeof(TryParseStructWithGoodAndBad))]
public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
{
var method = new ParameterBindingMethodCache().FindTryParseMethod(type);
Assert.NotNull(method);
}

[Theory]
[InlineData(typeof(InvalidWrongReturnBindAsyncStruct))]
[InlineData(typeof(InvalidWrongReturnBindAsyncClass))]
[InlineData(typeof(InvalidWrongParamBindAsyncStruct))]
[InlineData(typeof(InvalidWrongParamBindAsyncClass))]
public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
{
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(type, "anything");
var ex = Assert.Throws<InvalidOperationException>(
() => cache.FindBindAsyncMethod(parameter));
Assert.StartsWith($"BindAsync method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context)", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context)", ex.Message);
}

[Theory]
[InlineData(typeof(BindAsyncStructWithGoodAndBad))]
[InlineData(typeof(BindAsyncClassWithGoodAndBad))]
public void FindBindAsyncMethod_IgnoresInvalidBindAsyncIfGoodOneFound(Type type)
{
var cache = new ParameterBindingMethodCache();
var parameter = new MockParameterInfo(type, "anything");
var (expression, _) = cache.FindBindAsyncMethod(parameter);
Assert.NotNull(expression);
}

enum Choice
{
One,
Expand All @@ -292,8 +350,6 @@ private static void NullableReturningBindAsyncStructMethod(NullableReturningBind

private static void BindAsyncSingleArgRecordMethod(BindAsyncSingleArgRecord arg) { }
private static void BindAsyncSingleArgStructMethod(BindAsyncSingleArgStruct arg) { }
private static void BindAsyncNullableSingleArgStructMethod(BindAsyncSingleArgStruct? arg) { }
private static void NullableReturningBindAsyncSingleArgStructMethod(NullableReturningBindAsyncSingleArgStruct arg) { }

private static ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
{
Expand Down Expand Up @@ -331,6 +387,157 @@ public static bool TryParse(string? value, IFormatProvider formatProvider, out T
}
}

private record struct InvalidVoidReturnTryParseStruct(int Value)
{
public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return;
}

result = new InvalidVoidReturnTryParseStruct(val);
return;
}
}

private record struct InvalidWrongTypeTryParseStruct(int Value)
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}

result = new InvalidVoidReturnTryParseStruct(val);
return true;
}
}

private record struct InvalidTryParseNullableStruct(int Value)
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidTryParseNullableStruct? result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}

result = new InvalidTryParseNullableStruct(val);
return true;
}
}

private record struct InvalidTooFewArgsTryParseStruct(int Value)
{
public static bool TryParse(out InvalidTooFewArgsTryParseStruct result)
{
result = default;
return false;
}
}

private struct TryParseStructWithGoodAndBad
{
public static bool TryParse(string? value, out TryParseStructWithGoodAndBad result)
{
result = new();
return false;
}

public static void TryParse(out TryParseStructWithGoodAndBad result)
{
result = new();
}
}

private record struct InvalidNonStaticTryParseStruct(int Value)
{
public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}

result = new InvalidVoidReturnTryParseStruct(val);
return true;
}
}

private class InvalidVoidReturnTryParseClass
{
public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
return;
}

result = new();
}
}

private class InvalidWrongTypeTryParseClass
{
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
return false;
}

result = new();
return true;
}
}

private class InvalidTooFewArgsTryParseClass
{
public static bool TryParse(out InvalidTooFewArgsTryParseClass result)
{
result = new();
return false;
}
}

private class TryParseClassWithGoodAndBad
{
public static bool TryParse(string? value, out TryParseClassWithGoodAndBad result)
{
result = new();
return false;
}

public static bool TryParse(out TryParseClassWithGoodAndBad result)
{
result = new();
return false;
}
}

private class InvalidNonStaticTryParseClass
{
public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidNonStaticTryParseClass result)
{
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
return false;
}

result = new();
return true;
}
}

private record BindAsyncRecord(int Value)
{
public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
Expand Down Expand Up @@ -395,9 +602,45 @@ public static ValueTask<BindAsyncSingleArgStruct> BindAsync(HttpContext context)
}
}

private record struct NullableReturningBindAsyncSingleArgStruct(int Value)
private record struct InvalidWrongReturnBindAsyncStruct(int Value)
{
public static ValueTask<NullableReturningBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter) =>
public static Task<InvalidWrongReturnBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}

private class InvalidWrongReturnBindAsyncClass
{
public static Task<InvalidWrongReturnBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}

private record struct InvalidWrongParamBindAsyncStruct(int Value)
{
public static ValueTask<InvalidWrongParamBindAsyncStruct> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}

private class InvalidWrongParamBindAsyncClass
{
public static Task<InvalidWrongParamBindAsyncClass> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}

private record struct BindAsyncStructWithGoodAndBad(int Value)
{
public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();

public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}

private class BindAsyncClassWithGoodAndBad
{
public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();

public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
throw new NotImplementedException();
}

Expand Down
45 changes: 45 additions & 0 deletions src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,51 @@ void TestBothInvalidAction(Todo value1, [FromBody] int value2) { }
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBothInvalidAction));
}

[Fact]
public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidTryParse()
{
void TestTryParseStruct(BadTryParseStruct value1) { }
void TestTryParseClass(BadTryParseClass value1) { }

Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseStruct));
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseClass));
}

private struct BadTryParseStruct
{
public static void TryParse(string? value, out BadTryParseStruct result) { }
}

private class BadTryParseClass
{
public static void TryParse(string? value, out BadTryParseClass result)
{
result = new();
}
}

[Fact]
public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidBindAsync()
{
void TestBindAsyncStruct(BadBindAsyncStruct value1) { }
void TestBindAsyncClass(BadBindAsyncClass value1) { }

var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncStruct));
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncClass));
}

private struct BadBindAsyncStruct
{
public static Task<BadBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}

private class BadBindAsyncClass
{
public static Task<BadBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
throw new NotImplementedException();
}

public static object[][] ExplicitFromServiceActions
{
get
Expand Down
Loading