diff --git a/src/Http/Http.Results/src/HttpResultsHelper.cs b/src/Http/Http.Results/src/HttpResultsHelper.cs index b899467b1d80a2e418ee885236d9ae8b53c75581..6cdfb07923b90b18b3d255912aaaf31fc7cd0fb2 100644 --- a/src/Http/Http.Results/src/HttpResultsHelper.cs +++ b/src/Http/Http.Results/src/HttpResultsHelper.cs @@ -28,9 +28,21 @@ internal static partial class HttpResultsHelper return Task.CompletedTask; } - Log.WritingResultAsJson(logger, typeof(T).Name); + var declaredType = typeof(T); - return httpContext.Response.WriteAsJsonAsync( + Log.WritingResultAsJson(logger, declaredType.Name); + + if (declaredType.IsValueType) + { + // In this case the polymorphism is not + // relevant and we don't need to box. + return httpContext.Response.WriteAsJsonAsync( + value, + options: jsonSerializerOptions, + contentType: contentType); + } + + return httpContext.Response.WriteAsJsonAsync<object?>( value, options: jsonSerializerOptions, contentType: contentType); diff --git a/src/Http/Http.Results/src/ProblemHttpResult.cs b/src/Http/Http.Results/src/ProblemHttpResult.cs index 9fdf1f2c1d10f8c3101ec7cbac167c8cee10626a..bd7e72ff87aa9bd15e751e558d0f704f50e64199 100644 --- a/src/Http/Http.Results/src/ProblemHttpResult.cs +++ b/src/Http/Http.Results/src/ProblemHttpResult.cs @@ -53,7 +53,7 @@ public sealed class ProblemHttpResult : IResult httpContext.Response.StatusCode = code; } - return HttpResultsHelper.WriteResultAsJsonAsync<object?>( + return HttpResultsHelper.WriteResultAsJsonAsync( httpContext, logger, value: ProblemDetails, diff --git a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt index 8b53b51a0631d987b570c14c4aa4edb0f47c5846..0815b2b6dee11d64c1758d9b8a4ae7a435768885 100644 --- a/src/Http/Http.Results/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Results/src/PublicAPI.Unshipped.txt @@ -146,12 +146,23 @@ Microsoft.AspNetCore.Http.HttpResults.RedirectToRouteHttpResult.RouteName.get -> Microsoft.AspNetCore.Http.HttpResults.RedirectToRouteHttpResult.RouteValues.get -> Microsoft.AspNetCore.Routing.RouteValueDictionary? *REMOVED*static Microsoft.AspNetCore.Http.Results.Content(string! content, string? contentType = null, System.Text.Encoding? contentEncoding = null) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Content(string! content, Microsoft.Net.Http.Headers.MediaTypeHeaderValue! contentType) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Accepted<TValue>(string? uri = null, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.AcceptedAtRoute<TValue>(string? routeName = null, object? routeValues = null, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.BadRequest<TValue>(TValue? error) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Conflict<TValue>(TValue? error) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Content(string? content, Microsoft.Net.Http.Headers.MediaTypeHeaderValue! contentType) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Content(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Content(string? content, string? contentType, System.Text.Encoding? contentEncoding) -> Microsoft.AspNetCore.Http.IResult! *REMOVED*static Microsoft.AspNetCore.Http.Results.Text(string! content, string? contentType = null, System.Text.Encoding? contentEncoding = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Created<TValue>(System.Uri! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Created<TValue>(string! uri, TValue? value) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.CreatedAtRoute<TValue>(string? routeName = null, object? routeValues = null, TValue? value = default(TValue?)) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Json<TValue>(TValue? data, System.Text.Json.JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.NotFound<TValue>(TValue? value) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.Ok<TValue>(TValue? value) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Text(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.Results.Text(string? content, string? contentType, System.Text.Encoding? contentEncoding) -> Microsoft.AspNetCore.Http.IResult! +static Microsoft.AspNetCore.Http.Results.UnprocessableEntity<TValue>(TValue? error) -> Microsoft.AspNetCore.Http.IResult! static Microsoft.AspNetCore.Http.TypedResults.Content(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Content(string? content, string? contentType, System.Text.Encoding? contentEncoding) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! static Microsoft.AspNetCore.Http.TypedResults.Text(string? content, string? contentType = null, System.Text.Encoding? contentEncoding = null, int? statusCode = null) -> Microsoft.AspNetCore.Http.HttpResults.ContentHttpResult! diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs index 80cd45842ba300ce0f30819892f5553ab1082b65..29336a00dae5e98fe2e1bfae56caca04a3af1594 100644 --- a/src/Http/Http.Results/src/Results.cs +++ b/src/Http/Http.Results/src/Results.cs @@ -166,6 +166,22 @@ public static partial class Results /// <remarks>Callers should cache an instance of serializer settings to avoid /// recreating cached data with each call.</remarks> public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) + => Json<object>(data, options, contentType, statusCode); + + /// <summary> + /// Creates a <see cref="IResult"/> that serializes the specified <paramref name="data"/> object to JSON. + /// </summary> + /// <param name="data">The object to write as JSON.</param> + /// <param name="options">The serializer options to use when serializing the value.</param> + /// <param name="contentType">The content-type to set on the response.</param> + /// <param name="statusCode">The status code to set on the response.</param> + /// <returns>The created <see cref="JsonHttpResult{TValue}"/> that serializes the specified <paramref name="data"/> + /// as JSON format for the response.</returns> + /// <remarks>Callers should cache an instance of serializer settings to avoid + /// recreating cached data with each call.</remarks> +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Json<TValue>(TValue? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => TypedResults.Json(data, options, contentType, statusCode); /// <summary> @@ -454,7 +470,17 @@ public static partial class Results /// </summary> /// <param name="value">The value to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult NotFound(object? value = null) +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + => NotFound<object>(value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status404NotFound"/> response. + /// </summary> + /// <param name="value">The value to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult NotFound<TValue>(TValue? value) => value is null ? TypedResults.NotFound() : TypedResults.NotFound(value); /// <summary> @@ -469,7 +495,17 @@ public static partial class Results /// </summary> /// <param name="error">An error object to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult BadRequest(object? error = null) +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + => BadRequest<object>(error); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response. + /// </summary> + /// <param name="error">An error object to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult BadRequest<TValue>(TValue? error) => error is null ? TypedResults.BadRequest() : TypedResults.BadRequest(error); /// <summary> @@ -477,7 +513,17 @@ public static partial class Results /// </summary> /// <param name="error">An error object to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult Conflict(object? error = null) +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + => Conflict<object>(error); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status409Conflict"/> response. + /// </summary> + /// <param name="error">An error object to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult Conflict<TValue>(TValue? error) => error is null ? TypedResults.Conflict() : TypedResults.Conflict(error); /// <summary> @@ -492,7 +538,17 @@ public static partial class Results /// </summary> /// <param name="value">The value to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult Ok(object? value = null) +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + => Ok<object>(value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status200OK"/> response. + /// </summary> + /// <param name="value">The value to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult Ok<TValue>(TValue? value) => value is null ? TypedResults.Ok() : TypedResults.Ok(value); /// <summary> @@ -500,7 +556,17 @@ public static partial class Results /// </summary> /// <param name="error">An error object to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult UnprocessableEntity(object? error = null) +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + => UnprocessableEntity<object>(error); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response. + /// </summary> + /// <param name="error">An error object to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult UnprocessableEntity<TValue>(TValue? error) => error is null ? TypedResults.UnprocessableEntity() : TypedResults.UnprocessableEntity(error); /// <summary> @@ -580,6 +646,15 @@ public static partial class Results /// <param name="value">The value to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> public static IResult Created(string uri, object? value) + => Created<object>(uri, value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status201Created"/> response. + /// </summary> + /// <param name="uri">The URI at which the content has been created.</param> + /// <param name="value">The value to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult Created<TValue>(string uri, TValue? value) => value is null ? TypedResults.Created(uri) : TypedResults.Created(uri, value); /// <summary> @@ -589,6 +664,15 @@ public static partial class Results /// <param name="value">The value to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> public static IResult Created(Uri uri, object? value) + => Created<object>(uri, value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status201Created"/> response. + /// </summary> + /// <param name="uri">The URI at which the content has been created.</param> + /// <param name="value">The value to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> + public static IResult Created<TValue>(Uri uri, TValue? value) => value is null ? TypedResults.Created(uri) : TypedResults.Created(uri, value); /// <summary> @@ -599,6 +683,18 @@ public static partial class Results /// <param name="value">The value to be included in the HTTP response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null) + => CreatedAtRoute<object>(routeName, routeValues, value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status201Created"/> response. + /// </summary> + /// <param name="routeName">The name of the route to use for generating the URL.</param> + /// <param name="routeValues">The route data to use for generating the URL.</param> + /// <param name="value">The value to be included in the HTTP response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult CreatedAtRoute<TValue>(string? routeName = null, object? routeValues = null, TValue? value = default) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => value is null ? TypedResults.CreatedAtRoute(routeName, routeValues) : TypedResults.CreatedAtRoute(value, routeName, routeValues); /// <summary> @@ -608,6 +704,17 @@ public static partial class Results /// <param name="value">The optional content value to format in the response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> public static IResult Accepted(string? uri = null, object? value = null) + => Accepted<object>(uri, value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status202Accepted"/> response. + /// </summary> + /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param> + /// <param name="value">The optional content value to format in the response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult Accepted<TValue>(string? uri = null, TValue? value = default) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => value is null ? TypedResults.Accepted(uri) : TypedResults.Accepted(uri, value); /// <summary> @@ -617,7 +724,21 @@ public static partial class Results /// <param name="routeValues">The route data to use for generating the URL.</param> /// <param name="value">The optional content value to format in the response body.</param> /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. public static IResult AcceptedAtRoute(string? routeName = null, object? routeValues = null, object? value = null) +#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads. + => AcceptedAtRoute<object>(routeName, routeValues, value); + + /// <summary> + /// Produces a <see cref="StatusCodes.Status202Accepted"/> response. + /// </summary> + /// <param name="routeName">The name of the route to use for generating the URL.</param> + /// <param name="routeValues">The route data to use for generating the URL.</param> + /// <param name="value">The optional content value to format in the response body.</param> + /// <returns>The created <see cref="IResult"/> for the response.</returns> +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static IResult AcceptedAtRoute<TValue>(string? routeName = null, object? routeValues = null, TValue? value = default) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters => value is null ? TypedResults.AcceptedAtRoute(routeName, routeValues) : TypedResults.AcceptedAtRoute(value, routeName, routeValues); /// <summary> diff --git a/src/Http/Http.Results/test/ResultsTests.cs b/src/Http/Http.Results/test/ResultsTests.cs index a652f96f5239c6ac8c77289ee003c8a0df5af6d9..e484f9dba6608abb404b9653fb854b33e0b1c378 100644 --- a/src/Http/Http.Results/test/ResultsTests.cs +++ b/src/Http/Http.Results/test/ResultsTests.cs @@ -22,7 +22,7 @@ public class ResultsTests { // Arrange var uri = "https://example.org"; - var value = new { }; + object value = new { }; // Act var result = Results.Accepted(uri, value) as Accepted<object>; @@ -33,6 +33,22 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void AcceptedOfT_WithUrlAndValue_ResultHasCorrectValues() + { + // Arrange + var uri = "https://example.org"; + var value = new Todo(1); + + // Act + var result = Results.Accepted(uri, value) as Accepted<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); + Assert.Equal(uri, result.Location); + Assert.Equal(value, result.Value); + } + [Fact] public void Accepted_WithUrl_ResultHasCorrectValues() { @@ -64,7 +80,7 @@ public class ResultsTests // Arrange var routeName = "routeName"; var routeValues = new { foo = 123 }; - var value = new { }; + object value = new { }; // Act var result = Results.AcceptedAtRoute(routeName, routeValues, value) as AcceptedAtRoute<object>; @@ -76,6 +92,24 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void AcceptedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new { foo = 123 }; + var value = new Todo(1); + + // Act + var result = Results.AcceptedAtRoute(routeName, routeValues, value) as AcceptedAtRoute<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status202Accepted, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void AcceptedAtRoute_WithRouteNameAndRouteValues_ResultHasCorrectValues() { @@ -108,7 +142,7 @@ public class ResultsTests public void BadRequest_WithValue_ResultHasCorrectValues() { // Arrange - var value = new { }; + object value = new { }; // Act var result = Results.BadRequest(value) as BadRequest<object>; @@ -118,6 +152,20 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void BadRequestOfT_WithValue_ResultHasCorrectValues() + { + // Arrange + var value = new Todo(1); + + // Act + var result = Results.BadRequest(value) as BadRequest<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + Assert.Equal(value, result.Value); + } + [Fact] public void BadRequest_WithNoArgs_ResultHasCorrectValues() { @@ -315,7 +363,7 @@ public class ResultsTests public void Conflict_WithValue_ResultHasCorrectValues() { // Arrange - var value = new { }; + object value = new { }; // Act var result = Results.Conflict(value) as Conflict<object>; @@ -325,6 +373,20 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void ConflictOfT_WithValue_ResultHasCorrectValues() + { + // Arrange + var value = new Todo(1); + + // Act + var result = Results.Conflict(value) as Conflict<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status409Conflict, result.StatusCode); + Assert.Equal(value, result.Value); + } + [Fact] public void Conflict_WithNoArgs_ResultHasCorrectValues() { @@ -391,7 +453,7 @@ public class ResultsTests { // Arrange var uri = "https://example.com/entity"; - var value = new { }; + object value = new { }; // Act var result = Results.Created(uri, value) as Created<object>; @@ -402,6 +464,22 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void CreatedOfT_WithStringUriAndValue_ResultHasCorrectValues() + { + // Arrange + var uri = "https://example.com/entity"; + var value = new Todo(1); + + // Act + var result = Results.Created(uri, value) as Created<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Equal(uri, result.Location); + Assert.Equal(value, result.Value); + } + [Fact] public void Created_WithStringUri_ResultHasCorrectValues() { @@ -421,7 +499,7 @@ public class ResultsTests { // Arrange var uri = new Uri("https://example.com/entity"); - var value = new { }; + object value = new { }; // Act var result = Results.Created(uri, value) as Created<object>; @@ -432,6 +510,22 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void CreatedOfT_WithUriAndValue_ResultHasCorrectValues() + { + // Arrange + var uri = new Uri("https://example.com/entity"); + var value = new Todo(1); + + // Act + var result = Results.Created(uri, value) as Created<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Equal(uri.ToString(), result.Location); + Assert.Equal(value, result.Value); + } + [Fact] public void Created_WithUri_ResultHasCorrectValues() { @@ -488,7 +582,7 @@ public class ResultsTests // Arrange var routeName = "routeName"; var routeValues = new { foo = 123 }; - var value = new { }; + object value = new { }; // Act var result = Results.CreatedAtRoute(routeName, routeValues, value) as CreatedAtRoute<object>; @@ -500,12 +594,30 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void CreatedAtRouteOfT_WithRouteNameAndRouteValuesAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var routeValues = new { foo = 123 }; + var value = new Todo(1); + + // Act + var result = Results.CreatedAtRoute(routeName, routeValues, value) as CreatedAtRoute<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(new RouteValueDictionary(routeValues), result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void CreatedAtRoute_WithRouteNameAndValue_ResultHasCorrectValues() { // Arrange var routeName = "routeName"; - var value = new { }; + object value = new { }; // Act var result = Results.CreatedAtRoute(routeName, null, value) as CreatedAtRoute<object>; @@ -517,6 +629,23 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void CreatedAtRouteOfT_WithRouteNameAndValue_ResultHasCorrectValues() + { + // Arrange + var routeName = "routeName"; + var value = new Todo(1); + + // Act + var result = Results.CreatedAtRoute(routeName, null, value) as CreatedAtRoute<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status201Created, result.StatusCode); + Assert.Equal(routeName, result.RouteName); + Assert.Equal(new RouteValueDictionary(), result.RouteValues); + Assert.Equal(value, result.Value); + } + [Fact] public void CreatedAtRoute_WithRouteName_ResultHasCorrectValues() { @@ -558,7 +687,7 @@ public class ResultsTests public void Json_WithAllArgs_ResultHasCorrectValues() { // Arrange - var data = new { }; + object data = new { }; var options = new JsonSerializerOptions(); var contentType = "application/custom+json"; var statusCode = StatusCodes.Status208AlreadyReported; @@ -573,6 +702,25 @@ public class ResultsTests Assert.Equal(statusCode, result.StatusCode); } + [Fact] + public void JsonOfT_WithAllArgs_ResultHasCorrectValues() + { + // Arrange + var data = new Todo(1); + var options = new JsonSerializerOptions(); + var contentType = "application/custom+json"; + var statusCode = StatusCodes.Status208AlreadyReported; + + // Act + var result = Results.Json(data, options, contentType, statusCode) as JsonHttpResult<Todo>; + + // Assert + Assert.Equal(data, result.Value); + Assert.Equal(options, result.JsonSerializerOptions); + Assert.Equal(contentType, result.ContentType); + Assert.Equal(statusCode, result.StatusCode); + } + [Fact] public void Json_WithNoArgs_ResultHasCorrectValues() { @@ -681,7 +829,7 @@ public class ResultsTests public void NotFound_WithValue_ResultHasCorrectValues() { // Arrange - var value = new { }; + object value = new { }; // Act var result = Results.NotFound(value) as NotFound<object>; @@ -691,6 +839,20 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void NotFoundOfT_WithValue_ResultHasCorrectValues() + { + // Arrange + var value = new Todo(1); + + // Act + var result = Results.NotFound(value) as NotFound<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode); + Assert.Equal(value, result.Value); + } + [Fact] public void NotFound_WithNoArgs_ResultHasCorrectValues() { @@ -705,7 +867,7 @@ public class ResultsTests public void Ok_WithValue_ResultHasCorrectValues() { // Arrange - var value = new { }; + object value = new { }; // Act var result = Results.Ok(value) as Ok<object>; @@ -715,6 +877,20 @@ public class ResultsTests Assert.Equal(value, result.Value); } + [Fact] + public void OkOfT_WithValue_ResultHasCorrectValues() + { + // Arrange + var value = new Todo(1); + + // Act + var result = Results.Ok(value) as Ok<Todo>; + + // Assert + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + Assert.Equal(value, result.Value); + } + [Fact] public void Ok_WithNoArgs_ResultHasCorrectValues() { @@ -1096,4 +1272,6 @@ public class ResultsTests }; public static IEnumerable<object[]> FactoryMethodsFromTuples() => FactoryMethodsTuples.Select(t => new object[] { t.Item1, t.Item2 }); + + private record Todo(int Id); }