-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from bmazzarol/action_result
feat(action-result): added builder support for ActionResult [ci]
- Loading branch information
Showing
16 changed files
with
441 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using System.Net; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Mvc.Abstractions; | ||
using Microsoft.AspNetCore.Mvc.Infrastructure; | ||
using Microsoft.AspNetCore.Mvc.ModelBinding; | ||
using Microsoft.AspNetCore.Routing; | ||
|
||
namespace HttpBuildR.ActionResult.Tests; | ||
|
||
public static class ActionResultTests | ||
{ | ||
private static Scenario.Acted<ActionResult<T>, HttpResponse> AsResponse<T>( | ||
this ActionResult<T> actionResult | ||
) => | ||
actionResult | ||
.ArrangeData() | ||
.Act(async ar => | ||
{ | ||
var actionContext = new ActionContext( | ||
new DefaultHttpContext(), | ||
new RouteData(), | ||
new ActionDescriptor(), | ||
new ModelStateDictionary() | ||
); | ||
await ((IConvertToActionResult)ar).Convert().ExecuteResultAsync(actionContext); | ||
return actionContext.HttpContext.Response; | ||
}); | ||
|
||
[Fact(DisplayName = "A T can be converted to an OK response")] | ||
public static void Case1() => "this is a test".AsOk().Should().NotBeNull(); | ||
|
||
[Fact(DisplayName = "A T can be converted to an OK response and cookie")] | ||
public static async Task Case2() => | ||
await "this is a test" | ||
.AsOk(Cookie.New("a", "c")) | ||
.AsResponse() | ||
.Assert(r => r.StatusCode.Should().Be((int)HttpStatusCode.OK)) | ||
.And(r => r.Headers.Should().ContainKey("Set-Cookie").And.ContainValue("a=c; path=/")) | ||
.And(r => new StreamReader(r.Body).ReadToEnd().Should().Be("\"this is a test\"")); | ||
|
||
[Fact(DisplayName = "A response can be converted to an action response")] | ||
public static async Task Case3() => | ||
await Resp.BadRequest | ||
.Result() | ||
.WithProblemDetails("a", "a", "a", "A") | ||
.AsAction<string>(Cookie.New("a", "b")) | ||
.AsResponse() | ||
.Assert(r => r.StatusCode.Should().Be((int)HttpStatusCode.BadRequest)) | ||
.And(r => r.Headers.Should().ContainKey("Set-Cookie").And.ContainValue("a=b; path=/")) | ||
.And( | ||
r => | ||
new StreamReader(r.Body) | ||
.ReadToEnd() | ||
.Should() | ||
.Be( | ||
"{\"type\":\"a\",\"title\":\"a\",\"status\":400,\"detail\":\"a\",\"instance\":\"A\"}" | ||
) | ||
); | ||
|
||
[Fact(DisplayName = "A response can be converted to an action response with no content")] | ||
public static async Task Case4() => | ||
await Resp.NotAcceptable | ||
.Result() | ||
.WithHeader("a", "b") | ||
.AsAction<string>() | ||
.AsResponse() | ||
.Assert(r => r.StatusCode.Should().Be((int)HttpStatusCode.NotAcceptable)) | ||
.And(r => r.Headers.Should().ContainKey("a").And.ContainValue("b")) | ||
.And(r => r.ContentType.Should().BeNullOrEmpty()) | ||
.And(r => r.ContentLength.Should().Be(0L)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
global using BunsenBurner; | ||
global using FluentAssertions; | ||
global using Xunit; | ||
global using Resp = System.Net.HttpStatusCode; | ||
global using Scenario = BunsenBurner.Scenario<BunsenBurner.Syntax.Aaa>; |
35 changes: 35 additions & 0 deletions
35
HttpBuildR.ActionResult.Tests/HttpBuildR.ActionResult.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="BunsenBurner" Version="6.2.0" /> | ||
<PackageReference Include="FluentAssertions" Version="6.9.0" /> | ||
<PackageReference Include="Meziantou.Xunit.ParallelTestFramework" Version="2.1.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" /> | ||
<PackageReference Include="xunit" Version="2.4.2" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="3.2.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\HttpBuildR.ActionResult\HttpBuildR.ActionResult.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<AssemblyAttribute Include="System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
using System.Diagnostics.Contracts; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
// ReSharper disable once CheckNamespace | ||
namespace HttpBuildR; | ||
|
||
using Resp = System.Net.HttpStatusCode; | ||
|
||
/// <summary> | ||
/// ActionResult builders | ||
/// </summary> | ||
public static class ActionResult | ||
{ | ||
private sealed class HttpResponseMessageAction : Microsoft.AspNetCore.Mvc.ActionResult | ||
{ | ||
private readonly HttpResponseMessage _response; | ||
private readonly IEnumerable<Cookie> _cookies; | ||
|
||
public HttpResponseMessageAction(HttpResponseMessage response, IEnumerable<Cookie> cookies) | ||
{ | ||
_response = response; | ||
_cookies = cookies; | ||
} | ||
|
||
public override async Task ExecuteResultAsync(ActionContext context) | ||
{ | ||
var resp = context.HttpContext.Response; | ||
resp.StatusCode = (int)_response.StatusCode; | ||
foreach (var kvp in _response.Headers.Concat(_response.Content.Headers)) | ||
resp.Headers.Add(kvp.Key, new StringValues(kvp.Value.ToArray())); | ||
resp.Body = await _response.Content.ReadAsStreamAsync( | ||
context.HttpContext.RequestAborted | ||
); | ||
resp.ContentType = _response.Content.Headers.ContentType?.ToString() ?? string.Empty; | ||
resp.ContentLength = _response.Content.Headers.ContentLength; | ||
foreach (var cookie in _cookies) | ||
resp.Cookies.Append(cookie.Key, cookie.Value, cookie.Options); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Converts T to an ActionResult of T | ||
/// </summary> | ||
/// <param name="result">result</param> | ||
/// <param name="cookies">optional cookies</param> | ||
/// <typeparam name="T">some T</typeparam> | ||
/// <returns>action result of T</returns> | ||
[Pure] | ||
public static ActionResult<T> AsOk<T>(this T result, params Cookie[] cookies) | ||
where T : notnull => | ||
cookies.Length == 0 | ||
? new(result) | ||
: Resp.OK.Result().WithJsonContent(result).AsAction<T>(cookies); | ||
|
||
/// <summary> | ||
/// Converts a HttpResponseMessage to an ActionResult of T | ||
/// </summary> | ||
/// <param name="response">response</param> | ||
/// <param name="cookies">optional cookies</param> | ||
/// <typeparam name="T">some T</typeparam> | ||
/// <returns>action result of T</returns> | ||
[Pure] | ||
public static ActionResult<T> AsAction<T>( | ||
this HttpResponseMessage response, | ||
params Cookie[] cookies | ||
) => new HttpResponseMessageAction(response, cookies); | ||
|
||
/// <summary> | ||
/// Adds problem details as a json response body | ||
/// </summary> | ||
/// <param name="response">response</param> | ||
/// <param name="details">problem details</param> | ||
/// <returns>response</returns> | ||
[Pure] | ||
public static HttpResponseMessage WithProblemDetails( | ||
this HttpResponseMessage response, | ||
ProblemDetails details | ||
) => response.WithJsonContent(details); | ||
|
||
/// <summary> | ||
/// Adds problem details as a json response body | ||
/// </summary> | ||
/// <param name="response">response</param> | ||
/// <param name="type">A URI reference [RFC3986] that identifies the problem type</param> | ||
/// <param name="title">A short, human-readable summary of the problem type</param> | ||
/// <param name="detail">A human-readable explanation specific to this occurrence of the problem</param> | ||
/// <param name="instance">A URI reference that identifies the specific occurrence of the problem</param> | ||
/// <returns>response</returns> | ||
[Pure] | ||
public static HttpResponseMessage WithProblemDetails( | ||
this HttpResponseMessage response, | ||
string? type, | ||
string? title, | ||
string? detail, | ||
string? instance | ||
) => | ||
response.WithProblemDetails( | ||
new ProblemDetails | ||
{ | ||
Type = type, | ||
Title = title, | ||
Detail = detail, | ||
Status = (int)response.StatusCode, | ||
Instance = instance | ||
} | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using Microsoft.AspNetCore.Http; | ||
|
||
// ReSharper disable once CheckNamespace | ||
namespace HttpBuildR; | ||
|
||
/// <summary> | ||
/// Defines a response cookie | ||
/// </summary> | ||
public readonly struct Cookie | ||
{ | ||
/// <summary> | ||
/// Key | ||
/// </summary> | ||
public string Key { get; } | ||
|
||
/// <summary> | ||
/// Value | ||
/// </summary> | ||
public string Value { get; } | ||
|
||
/// <summary> | ||
/// Cookie options | ||
/// </summary> | ||
public CookieOptions Options { get; } | ||
|
||
private Cookie(string key, string value, CookieOptions options) | ||
{ | ||
Key = key; | ||
Value = value; | ||
Options = options; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new cookie | ||
/// </summary> | ||
/// <param name="key">key</param> | ||
/// <param name="value">value</param> | ||
/// <param name="options">options</param> | ||
/// <returns>cookie</returns> | ||
public static Cookie New(string key, string value, CookieOptions? options = null) => | ||
new(key, value, options ?? new CookieOptions()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Authors>Ben Mazzarol</Authors> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup> | ||
<PackageId>HttpBuildR.ActionResult</PackageId> | ||
<Title>Http BuildR ActionResult</Title> | ||
<Description>Simple respones builder functions on top of ActionResult!</Description> | ||
<PackageTags>C#, Functional, Fluent, Builder, Response, ActionResult</PackageTags> | ||
<PackageProjectUrl>https://github.com/bmazzarol/Http-BuildR</PackageProjectUrl> | ||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | ||
<Copyright>Copyright (c) Ben Mazzarol. All rights reserved.</Copyright> | ||
<ProjectGuid>d4e22a46-f7cd-400e-85bb-f849038bb67b</ProjectGuid> | ||
<IncludeSymbols>true</IncludeSymbols> | ||
<SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||
<PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
<EmbedUntrackedSources>true</EmbedUntrackedSources> | ||
<IsPackable>True</IsPackable> | ||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile> | ||
<PackageReadmeFile>README.md</PackageReadmeFile> | ||
<PackageIcon>construction-icon.png</PackageIcon> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
<AnalysisMode>Recommended</AnalysisMode> | ||
<OutputType>library</OutputType> | ||
<LangVersion>latest</LangVersion> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<None Include="README.md" Pack="true" PackagePath="\" /> | ||
<None Include="..\construction-icon.png" Pack="true" PackagePath="\" /> | ||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="AsyncFixer" Version="1.6.0"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.14"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Roslynator.Analyzers" Version="4.2.0"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.51.0.59060"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\HttpBuildR.Response\HttpBuildR.Response.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.