Skip to content
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

Fix 179 #180

Merged
merged 5 commits into from
May 15, 2024
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
27 changes: 13 additions & 14 deletions sample/Ardalis.Result.Sample.Core/Ardalis.Result.Sample.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>Ardalis.Result.Sample.Core</AssemblyName>
<RootNamespace>Ardalis.Result.Sample.Core</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<AssemblyName>Ardalis.Result.Sample.Core</AssemblyName>
<RootNamespace>Ardalis.Result.Sample.Core</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" />
<PackageReference Include="System.ComponentModel.Annotations" />
<PackageReference Include="System.Runtime" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Ardalis.Result.FluentValidation\Ardalis.Result.FluentValidation.csproj" />
<ProjectReference Include="..\..\src\Ardalis.Result\Ardalis.Result.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" />
<PackageReference Include="System.ComponentModel.Annotations" />
<PackageReference Include="System.Runtime" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Ardalis.Result.FluentValidation\Ardalis.Result.FluentValidation.csproj" />
<ProjectReference Include="..\..\src\Ardalis.Result\Ardalis.Result.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace Ardalis.Result.Sample.Core.DTOs;
using System;

namespace Ardalis.Result.Sample.Core.DTOs;

public class CreatePersonRequestDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; } = String.Empty;
public string LastName { get; set; } = String.Empty;
}
3 changes: 2 additions & 1 deletion sample/Ardalis.Result.Sample.Core/DTOs/ForecastRequestDto.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@

using System;
using System.ComponentModel.DataAnnotations;

namespace Ardalis.Result.Sample.Core.DTOs
{
public class ForecastRequestDto
{
[Required]
public string PostalCode { get; set; }
public string PostalCode { get; set; } = String.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Ardalis.Result.Sample.Core.Exceptions
{
public class ForecastRequestInvalidException : Exception
{
public Dictionary<string,string> ValidationErrors { get; set; }
public Dictionary<string,string> ValidationErrors { get; set; } = new();

public ForecastRequestInvalidException(Dictionary<string, string> validationErrors) : base("Forecast request is invalid.")
{
Expand Down
6 changes: 3 additions & 3 deletions sample/Ardalis.Result.Sample.Core/Model/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ namespace Ardalis.Result.Sample.Core.Model
public class Person
{
public int Id { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public string Surname { get; set; } = String.Empty;
public string Forename { get; set; } = String.Empty;

public List<Person> Children { get; set; }
public List<Person> Children { get; set; } = new();

public DateTime DateOfBirth { get; set; }
}
Expand Down
2 changes: 1 addition & 1 deletion sample/Ardalis.Result.Sample.Core/Model/WeatherForecast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public class WeatherForecast

public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

public string Summary { get; set; }
public string Summary { get; set; } = String.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task ReturnsOkWithValueGivenValidPostalCode()
var stringResponse = await response.Content.ReadAsStringAsync();
var forecasts = JsonConvert.DeserializeObject<List<WeatherForecast>>(stringResponse);

Assert.Equal("Freezing", forecasts.First().Summary);
Assert.Equal("Freezing", forecasts?.First()?.Summary);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@

namespace Ardalis.Result.SampleWeb.FunctionalTests;

public class WeatherForecastControllerThrows : IClassFixture<WebApplicationFactory<WebMarker>>
{
private const string CONTROLLER_THROWS_ROUTE = "/weatherforecast/throws";
private readonly HttpClient _client;

public WeatherForecastControllerThrows(WebApplicationFactory<WebMarker> factory)
{
_client = factory.CreateClient();
}

[Fact]
public async Task Returns400BadRequestNot500()
{
var response = await _client.GetAsync(CONTROLLER_THROWS_ROUTE);

Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var stringResponse = await response.Content.ReadAsStringAsync();
var problemDetails = JsonConvert.DeserializeObject<ProblemDetails>(stringResponse);

Assert.Equal("One or more validation errors occurred.", problemDetails?.Title);
Assert.Equal(400, problemDetails.Status);

Check warning on line 36 in sample/Ardalis.Result.SampleWeb.FunctionalTests/WeatherForecastControllerPost.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
}
}

public class WeatherForecastControllerPost : IClassFixture<WebApplicationFactory<WebMarker>>
{
private const string CONTROLLER_POST_ROUTE = "/weatherforecast/create";
Expand All @@ -36,7 +60,7 @@
var stringResponse = await response.Content.ReadAsStringAsync();
var forecasts = JsonConvert.DeserializeObject<List<WeatherForecast>>(stringResponse);

Assert.Equal("Freezing", forecasts.First().Summary);
Assert.Equal("Freezing", forecasts?.First()?.Summary);
}

[Theory]
Expand All @@ -52,7 +76,7 @@

var validationProblemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(stringResponse);

Assert.Contains(validationProblemDetails.Errors, d => d.Key == nameof(ForecastRequestDto.PostalCode));

Check warning on line 79 in sample/Ardalis.Result.SampleWeb.FunctionalTests/WeatherForecastControllerPost.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
Assert.Equal(400, validationProblemDetails.Status);
}

Expand All @@ -69,7 +93,7 @@

var problemDetails = JsonConvert.DeserializeObject<ProblemDetails>(stringResponse);

Assert.Equal("Resource not found.", problemDetails.Title);

Check warning on line 96 in sample/Ardalis.Result.SampleWeb.FunctionalTests/WeatherForecastControllerPost.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
Assert.Equal(404, problemDetails.Status);
}

Expand All @@ -86,7 +110,7 @@

var validationProblemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(stringResponse);

Assert.Contains(validationProblemDetails.Errors, d => d.Key == nameof(ForecastRequestDto.PostalCode));

Check warning on line 113 in sample/Ardalis.Result.SampleWeb.FunctionalTests/WeatherForecastControllerPost.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
Assert.Contains(validationProblemDetails.Errors[nameof(ForecastRequestDto.PostalCode)], e => e.Equals("PostalCode cannot exceed 10 characters.", System.StringComparison.OrdinalIgnoreCase));
Assert.Equal(400, validationProblemDetails.Status);
}
Expand All @@ -104,7 +128,7 @@

var problemDetails = JsonConvert.DeserializeObject<ProblemDetails>(stringResponse);

Assert.Equal("There was a conflict.", problemDetails.Title);

Check warning on line 131 in sample/Ardalis.Result.SampleWeb.FunctionalTests/WeatherForecastControllerPost.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
Assert.Equal(409, problemDetails.Status);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public Task<Result<IEnumerable<WeatherForecast>>> CreateForecast([FromBody] NewF
public class NewForecastCommand : IRequest<Result<IEnumerable<WeatherForecast>>>
{
[Required]
public string PostalCode { get; set; }
public string PostalCode { get; set; } = String.Empty;
}

public class NewForecastHandler : IRequestHandler<NewForecastCommand, Result<IEnumerable<WeatherForecast>>>
Expand Down
2 changes: 1 addition & 1 deletion sample/Ardalis.Result.SampleWeb/Pages/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public IndexModel(IStringLocalizer<IndexModel> stringLocalizer)
_stringLocalizer = stringLocalizer;
}

public string Message { get; set; }
public string Message { get; set; } = String.Empty;
public void OnGet()
{
Message = _stringLocalizer["message"].Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,15 @@ public ActionResult<WeatherForecastSummaryDto> CreateSummaryForecast([FromBody]
// .Map(WeatherForecastSummaryDto.MapFrom)
.ToActionResult(this);
}

/// <summary>
/// Issue #179
/// </summary>
/// <returns></returns>
[HttpGet("throws")]
[TranslateResultToActionResult]
public Result<int> Throws()
{
return Result<int>.Invalid(new ValidationError("foo"));
}
}
2 changes: 1 addition & 1 deletion sample/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<NetCoreFrameworks>net6.0;net7.0;net8.0</NetCoreFrameworks>
<TargetFrameworks>net4.7.1;$(NetCoreFrameworks)</TargetFrameworks>
<TargetFrameworks>net48;$(NetCoreFrameworks)</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
Expand Down
6 changes: 5 additions & 1 deletion src/Ardalis.Result.AspNetCore/ResultStatusMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ private static ValidationProblemDetails BadRequest(ControllerBase controller, IR
{
foreach (var error in result.ValidationErrors)
{
controller.ModelState.AddModelError(error.Identifier, error.ErrorMessage);
// TODO: mark ValidationError.Identifier as required and limit setting (see #179)
string identifier = error.Identifier ?? "(identifier)";
// TODO: mark ValidationError.Identifier as required and limit setting (see #179)
string errorMessage = error.ErrorMessage ?? "(error message)";
controller.ModelState.AddModelError(identifier, errorMessage);
}

return new ValidationProblemDetails(controller.ModelState);
Expand Down
1 change: 1 addition & 0 deletions src/Ardalis.Result/ValidationError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public ValidationError(string identifier, string errorMessage, string errorCode,
}

public string Identifier { get; set; }
// TODO: Mark required and limit setting (see #179)
public string ErrorMessage { get; set; }
public string ErrorCode { get; set; }
public ValidationSeverity Severity { get; set; } = ValidationSeverity.Error;
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<NetCoreFrameworks>net6.0;net7.0;net8.0</NetCoreFrameworks>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;$(NetCoreFrameworks)</TargetFrameworks>
<LangVersion>latest</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Steve Smith (@ardalis); Shady Nagy (@ShadyNagy)</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Reflection;

namespace Ardalis.Result.AspNetCore.UnitTests;

public class BaseResultConventionTest
{
protected bool ProducesResponseTypeAttribute(IFilterMetadata filterMetadata, int statusCode, Type type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ public void ResultWithValue(string actionName, Type expectedType)

convention.Apply(actionModel);

Assert.Equal(9, actionModel.Filters.Where(f => f is ProducesResponseTypeAttribute).Count());
Assert.Equal(10, actionModel.Filters.Where(f => f is ProducesResponseTypeAttribute).Count());

Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 200, expectedType));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 204, typeof(void)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 404, typeof(ProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 400, typeof(ValidationProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 401, typeof(void)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,8 @@ public void ChangeResultStatus()
[InlineData("Index", typeof(void), typeof(HttpPostAttribute), 204)]
[InlineData("Index", typeof(void), typeof(HttpDeleteAttribute), 204)]
[InlineData("Index", typeof(void), typeof(HttpGetAttribute), 204)]
[InlineData(nameof(TestController.ResultString), typeof(string), typeof(HttpPostAttribute), 201)]
[InlineData(nameof(TestController.ResultString), typeof(string), typeof(HttpDeleteAttribute), 204)]
[InlineData(nameof(TestController.ResultString), typeof(string), typeof(HttpGetAttribute), 200)]
[InlineData(nameof(TestController.ResultEnumerableString), typeof(IEnumerable<string>), typeof(HttpPostAttribute), 201)]
[InlineData(nameof(TestController.ResultEnumerableString), typeof(IEnumerable<string>), typeof(HttpDeleteAttribute), 204)]
[InlineData(nameof(TestController.ResultEnumerableString), typeof(IEnumerable<string>), typeof(HttpGetAttribute), 200)]
public void ChangeResultStatus_ForSpecificMethods(string actionName, Type expectedType, Type attributeType, int expectedStatusCode)
{
var convention = new ResultConvention(new ResultStatusMap()
Expand Down Expand Up @@ -96,4 +92,39 @@ public void ChangeResultStatus_ForSpecificMethods(string actionName, Type expect
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 500, typeof(ProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 503, typeof(ProblemDetails)));
}

[Theory]
[InlineData(nameof(TestController.ResultString), typeof(string), typeof(HttpPostAttribute), 201)]
[InlineData(nameof(TestController.ResultString), typeof(string), typeof(HttpGetAttribute), 200)]
[InlineData(nameof(TestController.ResultEnumerableString), typeof(IEnumerable<string>), typeof(HttpPostAttribute), 201)]
[InlineData(nameof(TestController.ResultEnumerableString), typeof(IEnumerable<string>), typeof(HttpGetAttribute), 200)]
public void ChangeResultStatus_ForDeleteMethods(string actionName, Type expectedType, Type attributeType, int expectedStatusCode)
{
var convention = new ResultConvention(new ResultStatusMap()
.AddDefaultMap()
.For(ResultStatus.Ok, HttpStatusCode.OK, opts => opts
.For("POST", HttpStatusCode.Created)
.For("DELETE", HttpStatusCode.NoContent)));

var actionModelBuilder = new ActionModelBuilder()
.AddActionFilter(new TranslateResultToActionResultAttribute())
.AddActionAttribute((Attribute)Activator.CreateInstance(attributeType)!);

var actionModel = actionModelBuilder.GetActionModel(actionName);

convention.Apply(actionModel);

Assert.Equal(10, actionModel.Filters.Where(f => f is ProducesResponseTypeAttribute).Count());

Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, expectedStatusCode, expectedType));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 204, typeof(void)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 404, typeof(ProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 400, typeof(ValidationProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 401, typeof(void)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 403, typeof(void)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 409, typeof(ProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 422, typeof(ProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 500, typeof(ProblemDetails)));
Assert.Contains(actionModel.Filters, f => ProducesResponseTypeAttribute(f, 503, typeof(ProblemDetails)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Mvc;
using Xunit;

namespace Ardalis.Result.AspNetCore.UnitTests;

public class ResultStatusMapAddDefaultMap
{
[Fact]
public void ReturnsBadRequestGivenInvalid()
{
var controller = new TestController();

var result = Result<int>.Invalid(new ValidationError("test"));

// TODO: require identifier
Assert.True(true);
}

public class TestController : ControllerBase
{
public Result Index()
{
throw new NotImplementedException();
}

public Result<string> ResultString()
{
throw new NotImplementedException();
}

public Result<IEnumerable<string>> ResultEnumerableString()
{
throw new NotImplementedException();
}
}

}
4 changes: 2 additions & 2 deletions tests/Ardalis.Result.UnitTests/SystemTextJsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class SystemTextJsonSerializer
public void ShouldSerializeResultOfValueType()
{
var result = Result.Success(5);
string expected = "{\"Value\":5,\"Status\":0,\"IsSuccess\":true,\"SuccessMessage\":\"\",\"CorrelationId\":\"\",\"Errors\":[],\"ValidationErrors\":[]}";
string expected = "{\"Value\":5,\"Status\":0,\"IsSuccess\":true,\"SuccessMessage\":\"\",\"CorrelationId\":\"\",\"Location\":\"\",\"Errors\":[],\"ValidationErrors\":[]}";

var json = JsonSerializer.Serialize(result);

Expand All @@ -20,7 +20,7 @@ public void ShouldSerializeResultOfValueType()
public void ShouldSerializeResultOfReferenceType()
{
var result = Result.Success(new Foo { Bar = "Result!" });
string expected = "{\"Value\":{\"Bar\":\"Result!\"},\"Status\":0,\"IsSuccess\":true,\"SuccessMessage\":\"\",\"CorrelationId\":\"\",\"Errors\":[],\"ValidationErrors\":[]}";
string expected = "{\"Value\":{\"Bar\":\"Result!\"},\"Status\":0,\"IsSuccess\":true,\"SuccessMessage\":\"\",\"CorrelationId\":\"\",\"Location\":\"\",\"Errors\":[],\"ValidationErrors\":[]}";

var json = JsonSerializer.Serialize(result);

Expand Down
2 changes: 1 addition & 1 deletion tests/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<NetCoreFrameworks>net6.0;net7.0;net8.0</NetCoreFrameworks>
<TargetFrameworks>net4.7.1;$(NetCoreFrameworks)</TargetFrameworks>
<TargetFrameworks>net48;$(NetCoreFrameworks)</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
Expand Down
Loading