Skip to content

Commit 9e496ff

Browse files
[release/6.0-rc2] Throw for invalid TryParse and BindAsync (#36662)
* Backport of #36628 to release/6.0-rc2 * Throw for invalid TryParse and BindAsync * nit * use TypeNameHelper * nit Co-authored-by: Brennan <[email protected]>
1 parent 8e2d9d1 commit 9e496ff

File tree

3 files changed

+324
-6
lines changed

3 files changed

+324
-6
lines changed

src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs

Lines changed: 247 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Globalization;
77
using System.Linq.Expressions;
88
using System.Reflection;
9+
using Microsoft.Extensions.Internal;
910

1011
namespace Microsoft.AspNetCore.Http.Extensions.Tests
1112
{
@@ -274,6 +275,63 @@ public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNul
274275
Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
275276
}
276277

278+
[Theory]
279+
[InlineData(typeof(InvalidVoidReturnTryParseStruct))]
280+
[InlineData(typeof(InvalidVoidReturnTryParseClass))]
281+
[InlineData(typeof(InvalidWrongTypeTryParseStruct))]
282+
[InlineData(typeof(InvalidWrongTypeTryParseClass))]
283+
[InlineData(typeof(InvalidTryParseNullableStruct))]
284+
[InlineData(typeof(InvalidTooFewArgsTryParseStruct))]
285+
[InlineData(typeof(InvalidTooFewArgsTryParseClass))]
286+
[InlineData(typeof(InvalidNonStaticTryParseStruct))]
287+
[InlineData(typeof(InvalidNonStaticTryParseClass))]
288+
public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
289+
{
290+
var ex = Assert.Throws<InvalidOperationException>(
291+
() => new ParameterBindingMethodCache().FindTryParseMethod(type));
292+
Assert.StartsWith($"TryParse method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
293+
Assert.Contains($"bool TryParse(string, IFormatProvider, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
294+
Assert.Contains($"bool TryParse(string, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
295+
}
296+
297+
[Theory]
298+
[InlineData(typeof(TryParseClassWithGoodAndBad))]
299+
[InlineData(typeof(TryParseStructWithGoodAndBad))]
300+
public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
301+
{
302+
var method = new ParameterBindingMethodCache().FindTryParseMethod(type);
303+
Assert.NotNull(method);
304+
}
305+
306+
[Theory]
307+
[InlineData(typeof(InvalidWrongReturnBindAsyncStruct))]
308+
[InlineData(typeof(InvalidWrongReturnBindAsyncClass))]
309+
[InlineData(typeof(InvalidWrongParamBindAsyncStruct))]
310+
[InlineData(typeof(InvalidWrongParamBindAsyncClass))]
311+
public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
312+
{
313+
var cache = new ParameterBindingMethodCache();
314+
var parameter = new MockParameterInfo(type, "anything");
315+
var ex = Assert.Throws<InvalidOperationException>(
316+
() => cache.FindBindAsyncMethod(parameter));
317+
Assert.StartsWith($"BindAsync method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
318+
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
319+
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context)", ex.Message);
320+
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
321+
Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context)", ex.Message);
322+
}
323+
324+
[Theory]
325+
[InlineData(typeof(BindAsyncStructWithGoodAndBad))]
326+
[InlineData(typeof(BindAsyncClassWithGoodAndBad))]
327+
public void FindBindAsyncMethod_IgnoresInvalidBindAsyncIfGoodOneFound(Type type)
328+
{
329+
var cache = new ParameterBindingMethodCache();
330+
var parameter = new MockParameterInfo(type, "anything");
331+
var (expression, _) = cache.FindBindAsyncMethod(parameter);
332+
Assert.NotNull(expression);
333+
}
334+
277335
enum Choice
278336
{
279337
One,
@@ -292,8 +350,6 @@ private static void NullableReturningBindAsyncStructMethod(NullableReturningBind
292350

293351
private static void BindAsyncSingleArgRecordMethod(BindAsyncSingleArgRecord arg) { }
294352
private static void BindAsyncSingleArgStructMethod(BindAsyncSingleArgStruct arg) { }
295-
private static void BindAsyncNullableSingleArgStructMethod(BindAsyncSingleArgStruct? arg) { }
296-
private static void NullableReturningBindAsyncSingleArgStructMethod(NullableReturningBindAsyncSingleArgStruct arg) { }
297353

298354
private static ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
299355
{
@@ -331,6 +387,157 @@ public static bool TryParse(string? value, IFormatProvider formatProvider, out T
331387
}
332388
}
333389

390+
private record struct InvalidVoidReturnTryParseStruct(int Value)
391+
{
392+
public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
393+
{
394+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
395+
{
396+
result = default;
397+
return;
398+
}
399+
400+
result = new InvalidVoidReturnTryParseStruct(val);
401+
return;
402+
}
403+
}
404+
405+
private record struct InvalidWrongTypeTryParseStruct(int Value)
406+
{
407+
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
408+
{
409+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
410+
{
411+
result = default;
412+
return false;
413+
}
414+
415+
result = new InvalidVoidReturnTryParseStruct(val);
416+
return true;
417+
}
418+
}
419+
420+
private record struct InvalidTryParseNullableStruct(int Value)
421+
{
422+
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidTryParseNullableStruct? result)
423+
{
424+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
425+
{
426+
result = default;
427+
return false;
428+
}
429+
430+
result = new InvalidTryParseNullableStruct(val);
431+
return true;
432+
}
433+
}
434+
435+
private record struct InvalidTooFewArgsTryParseStruct(int Value)
436+
{
437+
public static bool TryParse(out InvalidTooFewArgsTryParseStruct result)
438+
{
439+
result = default;
440+
return false;
441+
}
442+
}
443+
444+
private struct TryParseStructWithGoodAndBad
445+
{
446+
public static bool TryParse(string? value, out TryParseStructWithGoodAndBad result)
447+
{
448+
result = new();
449+
return false;
450+
}
451+
452+
public static void TryParse(out TryParseStructWithGoodAndBad result)
453+
{
454+
result = new();
455+
}
456+
}
457+
458+
private record struct InvalidNonStaticTryParseStruct(int Value)
459+
{
460+
public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
461+
{
462+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
463+
{
464+
result = default;
465+
return false;
466+
}
467+
468+
result = new InvalidVoidReturnTryParseStruct(val);
469+
return true;
470+
}
471+
}
472+
473+
private class InvalidVoidReturnTryParseClass
474+
{
475+
public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
476+
{
477+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
478+
{
479+
result = new();
480+
return;
481+
}
482+
483+
result = new();
484+
}
485+
}
486+
487+
private class InvalidWrongTypeTryParseClass
488+
{
489+
public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
490+
{
491+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
492+
{
493+
result = new();
494+
return false;
495+
}
496+
497+
result = new();
498+
return true;
499+
}
500+
}
501+
502+
private class InvalidTooFewArgsTryParseClass
503+
{
504+
public static bool TryParse(out InvalidTooFewArgsTryParseClass result)
505+
{
506+
result = new();
507+
return false;
508+
}
509+
}
510+
511+
private class TryParseClassWithGoodAndBad
512+
{
513+
public static bool TryParse(string? value, out TryParseClassWithGoodAndBad result)
514+
{
515+
result = new();
516+
return false;
517+
}
518+
519+
public static bool TryParse(out TryParseClassWithGoodAndBad result)
520+
{
521+
result = new();
522+
return false;
523+
}
524+
}
525+
526+
private class InvalidNonStaticTryParseClass
527+
{
528+
public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidNonStaticTryParseClass result)
529+
{
530+
if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
531+
{
532+
result = new();
533+
return false;
534+
}
535+
536+
result = new();
537+
return true;
538+
}
539+
}
540+
334541
private record BindAsyncRecord(int Value)
335542
{
336543
public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
@@ -395,9 +602,45 @@ public static ValueTask<BindAsyncSingleArgStruct> BindAsync(HttpContext context)
395602
}
396603
}
397604

398-
private record struct NullableReturningBindAsyncSingleArgStruct(int Value)
605+
private record struct InvalidWrongReturnBindAsyncStruct(int Value)
399606
{
400-
public static ValueTask<NullableReturningBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter) =>
607+
public static Task<InvalidWrongReturnBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
608+
throw new NotImplementedException();
609+
}
610+
611+
private class InvalidWrongReturnBindAsyncClass
612+
{
613+
public static Task<InvalidWrongReturnBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
614+
throw new NotImplementedException();
615+
}
616+
617+
private record struct InvalidWrongParamBindAsyncStruct(int Value)
618+
{
619+
public static ValueTask<InvalidWrongParamBindAsyncStruct> BindAsync(ParameterInfo parameter) =>
620+
throw new NotImplementedException();
621+
}
622+
623+
private class InvalidWrongParamBindAsyncClass
624+
{
625+
public static Task<InvalidWrongParamBindAsyncClass> BindAsync(ParameterInfo parameter) =>
626+
throw new NotImplementedException();
627+
}
628+
629+
private record struct BindAsyncStructWithGoodAndBad(int Value)
630+
{
631+
public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
632+
throw new NotImplementedException();
633+
634+
public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
635+
throw new NotImplementedException();
636+
}
637+
638+
private class BindAsyncClassWithGoodAndBad
639+
{
640+
public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
641+
throw new NotImplementedException();
642+
643+
public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
401644
throw new NotImplementedException();
402645
}
403646

src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,6 +1609,51 @@ void TestBothInvalidAction(Todo value1, [FromBody] int value2) { }
16091609
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBothInvalidAction));
16101610
}
16111611

1612+
[Fact]
1613+
public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidTryParse()
1614+
{
1615+
void TestTryParseStruct(BadTryParseStruct value1) { }
1616+
void TestTryParseClass(BadTryParseClass value1) { }
1617+
1618+
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseStruct));
1619+
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseClass));
1620+
}
1621+
1622+
private struct BadTryParseStruct
1623+
{
1624+
public static void TryParse(string? value, out BadTryParseStruct result) { }
1625+
}
1626+
1627+
private class BadTryParseClass
1628+
{
1629+
public static void TryParse(string? value, out BadTryParseClass result)
1630+
{
1631+
result = new();
1632+
}
1633+
}
1634+
1635+
[Fact]
1636+
public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidBindAsync()
1637+
{
1638+
void TestBindAsyncStruct(BadBindAsyncStruct value1) { }
1639+
void TestBindAsyncClass(BadBindAsyncClass value1) { }
1640+
1641+
var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncStruct));
1642+
Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncClass));
1643+
}
1644+
1645+
private struct BadBindAsyncStruct
1646+
{
1647+
public static Task<BadBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
1648+
throw new NotImplementedException();
1649+
}
1650+
1651+
private class BadBindAsyncClass
1652+
{
1653+
public static Task<BadBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
1654+
throw new NotImplementedException();
1655+
}
1656+
16121657
public static object[][] ExplicitFromServiceActions
16131658
{
16141659
get

0 commit comments

Comments
 (0)