Skip to content

Commit

Permalink
Merge pull request #61 from MrDave1999/feat/issue-59
Browse files Browse the repository at this point in the history
feat: Add result operations to represent the file contents
  • Loading branch information
MrDave1999 authored Mar 1, 2024
2 parents 7daff39 + f7e4483 commit 2882302
Show file tree
Hide file tree
Showing 24 changed files with 613 additions and 6 deletions.
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageVersion Include="Bogus" Version="34.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.3" />
<PackageVersion Include="FluentAssertions" Version="6.10.0" />
Expand All @@ -17,4 +18,4 @@
<PackageVersion Include="NUnit.Analyzers" Version="3.5.0" />
<PackageVersion Include="coverlet.msbuild" Version="3.1.2" />
</ItemGroup>
</Project>
</Project>
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,8 @@ The following table is used as a reference to know which type of result correspo
| Result.UpdatedResource | 200 - Ok |
| Result.DeletedResource | 200 - Ok |
| Result.ObtainedResource | 200 - Ok |
| Result.ObtainedResources| 200 - Ok |
| Result.File | 200 - Ok |
| Result.Invalid | 400 - Bad Request |
| Result.NotFound | 404 - Not Found |
| Result.Unauthorized | 401 - Unauthorized |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace SimpleResults.Example.AspNetCore.Controllers;

[Tags("FileResult WebApi")]
[Route("FileResult-WebApi")]
public class FileResultController
{
private readonly FileResultService _fileResultService;

public FileResultController(FileResultService fileResultService)
{
_fileResultService = fileResultService;
}

[SwaggerResponse(type: typeof(byte[]), statusCode: StatusCodes.Status200OK, contentTypes: MediaTypeNames.Application.Pdf)]
[SwaggerResponse(type: typeof(Result), statusCode: StatusCodes.Status400BadRequest, contentTypes: MediaTypeNames.Application.Json)]
[HttpGet("byte-array")]
public Result<ByteArrayFileContent> GetByteArray([FromQuery]FileResultRequest request)
{
return _fileResultService.GetByteArray(request.FileName);
}

[SwaggerResponse(type: typeof(byte[]), statusCode: StatusCodes.Status200OK, contentTypes: MediaTypeNames.Application.Pdf)]
[SwaggerResponse(type: typeof(Result), statusCode: StatusCodes.Status400BadRequest, contentTypes: MediaTypeNames.Application.Json)]
[HttpGet("stream")]
public Result<StreamFileContent> GetStream([FromQuery]FileResultRequest request)
{
return _fileResultService.GetStream(request.FileName);
}
}
4 changes: 3 additions & 1 deletion samples/SimpleResults.Example.AspNetCore/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
global using Bogus;
global using System.Net.Mime;
global using Bogus;
global using Microsoft.AspNetCore.Mvc;
global using Swashbuckle.AspNetCore.Annotations;
global using SimpleResults;
global using SimpleResults.Example;
global using SimpleResults.Example.AspNetCore;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace SimpleResults.Example.AspNetCore.MinimalApi;

public static class FileResultEndpoint
{
public static void AddFileResultRoutes(this WebApplication app)
{
var fileGroup = app
.MapGroup("/FileResult-MinimalApi")
.WithTags("FileResult MinimalApi")
.AddEndpointFilter<TranslateResultToHttpResultFilter>();

fileGroup.MapGet("/byte-array", ([AsParameters]FileResultRequest request, FileResultService service) =>
{
return service.GetByteArray(request.FileName);
})
.Produces<byte[]>(StatusCodes.Status200OK, MediaTypeNames.Application.Pdf)
.Produces<Result>(StatusCodes.Status400BadRequest);

fileGroup.MapGet("/stream", ([AsParameters]FileResultRequest request, FileResultService service) =>
{
return service.GetStream(request.FileName);
})
.Produces<byte[]>(StatusCodes.Status200OK, MediaTypeNames.Application.Pdf)
.Produces<Result>(StatusCodes.Status400BadRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SimpleResults.Example.AspNetCore.Models;

public class FileResultRequest
{
public string FileName { get; init; }
}
10 changes: 8 additions & 2 deletions samples/SimpleResults.Example.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
.AddSingleton<List<Order>>()
.AddSingleton<UserService>()
.AddSingleton<PersonService>()
.AddSingleton<OrderService>();
.AddSingleton<OrderService>()
.AddSingleton<FileResultService>();

builder.Services.AddControllers(options =>
{
Expand All @@ -20,7 +21,10 @@
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(options =>
{
options.EnableAnnotations();
});

var app = builder.Build();

Expand All @@ -44,6 +48,8 @@

app.AddMessageRoutes();

app.AddFileResultRoutes();

app.Run();

// This class used in the integration test project.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
<PackageReference Include="Bogus" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace SimpleResults.Example.Web.Tests.Features;

public class GetFileResultTests
{
[TestCase(Routes.File.ByteArrayController)]
[TestCase(Routes.File.StreamController)]
[TestCase(Routes.File.ByteArrayMinimalApi)]
[TestCase(Routes.File.StreamMinimalApi)]
public async Task Get_WhenBytesAreObtained_ShouldReturnsHttpStatusCodeOk(string route)
{
// Arrange
using var factory = new WebApplicationFactory<Program>();
var client = factory.CreateClient();
byte[] expected = [1, 1, 0, 0];
var fileName = "Report.pdf";
var requestUri = $"{route}?fileName={fileName}";

// Act
var httpResponse = await client.GetAsync(requestUri);
byte[] result = await httpResponse
.Content
.ReadAsByteArrayAsync();

// Asserts
httpResponse.StatusCode.Should().Be(HttpStatusCode.OK);
result.Should().BeEquivalentTo(expected);
}

[TestCase(Routes.File.ByteArrayController)]
[TestCase(Routes.File.StreamController)]
[TestCase(Routes.File.ByteArrayMinimalApi)]
[TestCase(Routes.File.StreamMinimalApi)]
public async Task Get_WhenFileNameIsEmpty_ShouldReturnsHttpStatusCodeBadRequest(string requestUri)
{
// Arrange
using var factory = new WebApplicationFactory<Program>();
var client = factory.CreateClient();

// Act
var httpResponse = await client.GetAsync(requestUri);
var result = await httpResponse
.Content
.ReadFromJsonAsync<Result>();

// Asserts
httpResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest);
result.IsSuccess.Should().BeFalse();
result.Message.Should().NotBeNullOrEmpty();
result.Errors.Should().BeEmpty();
}
}
8 changes: 8 additions & 0 deletions samples/SimpleResults.Example.Web.Tests/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ public class Message
public const string MinimalApi = "/Message-MinimalApi";
}

public class File
{
public const string ByteArrayController = "/FileResult-WebApi/byte-array";
public const string StreamController = "/FileResult-WebApi/stream";
public const string ByteArrayMinimalApi = "/FileResult-MinimalApi/byte-array";
public const string StreamMinimalApi = "/FileResult-MinimalApi/stream";
}

public class Order
{
public const string ManualValidation = "/Order-ManualValidation";
Expand Down
37 changes: 37 additions & 0 deletions samples/SimpleResults.Example/FileResultService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace SimpleResults.Example;

public class FileResultService
{
public Result<ByteArrayFileContent> GetByteArray(string fileName)
{
if(string.IsNullOrWhiteSpace(fileName))
{
return Result.Invalid("FileName is required");
}

byte[] content = [1, 1, 0, 0];
var byteArrayFileContent = new ByteArrayFileContent(content)
{
ContentType = MediaTypeNames.Application.Pdf,
FileName = fileName
};
return Result.File(byteArrayFileContent);
}

public Result<StreamFileContent> GetStream(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return Result.Invalid("FileName is required");
}

byte[] buffer = [1, 1, 0, 0];
Stream content = new MemoryStream(buffer);
var streamFileContent = new StreamFileContent(content)
{
ContentType = MediaTypeNames.Application.Pdf,
FileName = fileName
};
return Result.File(streamFileContent);
}
}
3 changes: 2 additions & 1 deletion samples/SimpleResults.Example/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
global using FluentValidation;
global using FluentValidation.Results;
global using System.Text.Json.Serialization;
global using System.Text.Json.Serialization;
global using System.Net.Mime;
76 changes: 76 additions & 0 deletions src/AspNetCore/FileResultConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.AspNetCore.Mvc;

namespace SimpleResults;

internal class FileResultConverter
{
public static FileContentResult ConvertToFileContentResult(ResultBase resultBase)
{
var result = resultBase as Result<ByteArrayFileContent>;
if (result is null)
{
var typeName = typeof(FileContentResult).FullName;
throw new InvalidOperationException(new FailedConversionError(typeName).Message);
}

var byteArrayFile = result.Data;
var fileContent = new FileContentResult(byteArrayFile.Content, byteArrayFile.ContentType)
{
FileDownloadName = byteArrayFile.FileName
};
return fileContent;
}

public static FileStreamResult ConvertToFileStreamResult(ResultBase resultBase)
{
var result = resultBase as Result<StreamFileContent>;
if (result is null)
{
var typeName = typeof(FileStreamResult).FullName;
throw new InvalidOperationException(new FailedConversionError(typeName).Message);
}

var streamFile = result.Data;
var fileContent = new FileStreamResult(streamFile.Content, streamFile.ContentType)
{
FileDownloadName = streamFile.FileName
};
return fileContent;
}

public static IResult ConvertToFileContentHttpResult(ResultBase resultBase)
{
var result = resultBase as Result<ByteArrayFileContent>;
if (result is null)
{
var typeName = typeof(IResult).FullName;
throw new InvalidOperationException(new FailedConversionError(typeName).Message);
}

var byteArrayFile = result.Data;
var fileContent = Results.File(
byteArrayFile.Content,
byteArrayFile.ContentType,
byteArrayFile.FileName);

return fileContent;
}

public static IResult ConvertToFileStreamHttpResult(ResultBase resultBase)
{
var result = resultBase as Result<StreamFileContent>;
if (result is null)
{
var typeName = typeof(IResult).FullName;
throw new InvalidOperationException(new FailedConversionError(typeName).Message);
}

var streamFile = result.Data;
var fileContent = Results.File(
streamFile.Content,
streamFile.ContentType,
streamFile.FileName);

return fileContent;
}
}
10 changes: 10 additions & 0 deletions src/AspNetCore/Reasons/FailedConversionError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SimpleResults.Resources;

namespace SimpleResults;

internal readonly ref struct FailedConversionError
{
public string Message { get; }
public FailedConversionError(string typeName)
=> Message = string.Format(ResponseMessages.FailedConversion, typeName ?? string.Empty);
}
4 changes: 4 additions & 0 deletions src/AspNetCore/ResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public static ActionResult<Result> ToActionResult(this Result result)
ResultStatus.Failure => new UnprocessableContentResult(result),
ResultStatus.CriticalError => new InternalServerErrorResult(result),
ResultStatus.Forbidden => new ForbiddenResult(result),
ResultStatus.ByteArrayFile => FileResultConverter.ConvertToFileContentResult(result),
ResultStatus.StreamFile => FileResultConverter.ConvertToFileStreamResult(result),
_ => throw new NotSupportedException(string.Format(ResponseMessages.UnsupportedStatus, result.Status))
};

Expand Down Expand Up @@ -151,6 +153,8 @@ public static IResult ToHttpResult(this Result result)
ResultStatus.Failure => Results.UnprocessableEntity(result),
ResultStatus.CriticalError => new InternalServerErrorHttpResult(result),
ResultStatus.Forbidden => new ForbiddenHttpResult(result),
ResultStatus.ByteArrayFile => FileResultConverter.ConvertToFileContentHttpResult(result),
ResultStatus.StreamFile => FileResultConverter.ConvertToFileStreamHttpResult(result),
_ => throw new NotSupportedException(string.Format(ResponseMessages.UnsupportedStatus, result.Status))
};
}
Loading

0 comments on commit 2882302

Please sign in to comment.