Skip to content

Commit

Permalink
Merge pull request #290 from bfren/289-make-result-class-implement-ia…
Browse files Browse the repository at this point in the history
…ctionresult

Implementing IActionResult in Result class - #289
  • Loading branch information
bfren committed Apr 12, 2022
2 parents e56fed6 + 856c3ee commit 92a0f7b
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 95 deletions.
2 changes: 1 addition & 1 deletion Version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.0.0-beta.220410.1
8.0.0-beta.220412.1
2 changes: 1 addition & 1 deletion src/Jeebs.Data/Jeebs.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="StrongId.Dapper" Version="8.1.2" />
<PackageReference Include="StrongId.Dapper" Version="8.1.3" />
</ItemGroup>
<ItemGroup Label="Test Internals">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
Expand Down
39 changes: 39 additions & 0 deletions src/Jeebs.Mvc/IResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Jeebs Rapid Application Development
// Copyright (c) bfren - licensed under https://mit.bfren.dev/2013

using Jeebs.Mvc.Models;

namespace Jeebs.Mvc;

/// <summary>
/// Represents the result of an operation
/// </summary>
/// <typeparam name="T">Value type</typeparam>
public interface IResult<out T>
{
/// <summary>
/// Returns true if the operation was a success
/// </summary>
bool Success { get; }

/// <summary>
/// User feedback alert message - by default returns 'Success' or the Reason message,
/// but can be set to display something else
/// </summary>
Alert Message { get; }

/// <summary>
/// Returns the value if the operation succeeded, or null if not
/// </summary>
T? Value { get; }

/// <summary>
/// HTTP status code - by default returns 200 on success or 500 on failure
/// </summary>
int StatusCode { get; }

/// <summary>
/// Optional URL to redirect to on success
/// </summary>
string? RedirectTo { get; }
}
2 changes: 1 addition & 1 deletion src/Jeebs.Mvc/Jeebs.Mvc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<ProjectReference Include="..\Jeebs.Apps.Web\Jeebs.Apps.Web.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StrongId.Mvc" Version="8.1.2" />
<PackageReference Include="StrongId.Mvc" Version="8.1.3" />
</ItemGroup>
</Project>
191 changes: 105 additions & 86 deletions src/Jeebs.Mvc/Result.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,132 @@
// Jeebs Rapid Application Development
// Copyright (c) bfren - licensed under https://mit.bfren.dev/2013

using System.Net;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Jeebs.Functions;
using Jeebs.Mvc.Enums;
using Jeebs.Mvc.Models;
using MaybeF.Internals;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

namespace Jeebs.Mvc;

/// <summary>
/// Represents the result of an operation
/// </summary>
public abstract record class Result
/// <inheritdoc cref="IResult{T}"/>
public record class Result<T> : IActionResult, IResult<T>
{
/// <summary>
/// Returns true if the operation was a success
/// Create with value
/// </summary>
public abstract bool Success { get; }
/// <param name="value"></param>
internal Result(Maybe<T> value) =>
Maybe = value;

/// <summary>
/// User feedback alert message - by default returns 'Success' or the Reason message,
/// but can be set to display something else
/// Maybe result object
/// </summary>
public abstract Alert Message { get; init; }
internal Maybe<T> Maybe { get; private init; }

/// <summary>
/// Returns the value if the operation succeeded, or null if not
/// Returns true if the operation was a success - or in the special case that <typeparamref name="T"/>
/// is <see cref="bool"/> and <see cref="Maybe"/> is <see cref="Some{T}"/>, returns that value
/// </summary>
public object? Value { get; }
public bool Success =>
Maybe switch
{
Some<bool> some =>
some.Value,

/// <summary>
/// If set, tells the client to redirect to this URL
/// </summary>
Some<T> =>
true,

_ =>
false
};

/// <inheritdoc/>
public Alert Message
{
get => message switch
{
Alert message =>
message,

_ =>
Maybe.Switch(
some: _ => Alert.Success(nameof(AlertType.Success)),
none: r => Alert.Error(r.ToString() ?? r.GetType().Name)
)
};
init => message = value;
}

private Alert? message;

/// <inheritdoc/>
public T? Value =>
Maybe.IsSome(out var value) switch
{
true =>
value,

_ =>
default
};

/// <inheritdoc/>
[JsonIgnore]
public int StatusCode
{
get => statusCode switch
{
int statusCode =>
statusCode,

_ =>
Success switch
{
true =>
(int)HttpStatusCode.OK,

false =>
(int)HttpStatusCode.InternalServerError
}
};
init => statusCode = value;
}

private int? statusCode;

/// <inheritdoc/>
public string? RedirectTo { get; init; }

/// <summary>
/// Return value serialised as JSON
/// </summary>
public sealed override string ToString() =>
JsonF.Serialise(this).Unwrap(() => JsonF.Empty);
/// <inheritdoc cref="ActionResult.ExecuteResultAsync(ActionContext)"/>
public Task ExecuteResultAsync(ActionContext context)
{
// Create MVC JsonResult from this result's properties
var jsonResult = new JsonResult(this, JsonF.CopyOptions())
{
ContentType = "application/json",
StatusCode = StatusCode
};

#region Static Create
// Get result executor
var services = context.HttpContext.RequestServices;
var executor = services.GetRequiredService<IActionResultExecutor<JsonResult>>();

// Execute JsonResult
return executor.ExecuteAsync(context, jsonResult);
}
}

/// <summary>
/// Easily create <see cref="Result{T}"/> objects
/// </summary>
public static class Result
{
/// <summary>
/// Create with value
/// </summary>
Expand Down Expand Up @@ -75,70 +160,4 @@ public abstract record class Result
/// <param name="message"></param>
public static Result<T> Create<T>(Maybe<T> value, Alert message) =>
new(value) { Message = message };

#endregion Static Create
}

/// <inheritdoc cref="Result"/>
/// <typeparam name="T">Value type</typeparam>
public sealed record class Result<T> : Result
{
/// <inheritdoc/>
public override Alert Message
{
get => message switch
{
Alert message =>
message,

_ =>
Maybe.Switch(
some: _ => Alert.Success(nameof(AlertType.Success)),
none: r => Alert.Error(r.ToString() ?? r.GetType().Name)
)
};
init => message = value;
}

private Alert? message;

/// <summary>
/// Maybe result object
/// </summary>
internal Maybe<T> Maybe { get; private init; }

/// <summary>
/// Returns true if the operation was a success - or in the special case that <typeparamref name="T"/>
/// is <see cref="bool"/> and <see cref="Maybe"/> is <see cref="Some{T}"/>, returns that value
/// </summary>
public override bool Success =>
Maybe switch
{
Some<bool> some =>
some.Value,

Some<T> =>
true,

_ =>
false
};

/// <inheritdoc cref="Result.Value"/>
public new T? Value =>
Maybe.IsSome(out var value) switch
{
true =>
value,

_ =>
default
};

/// <summary>
/// Create with value
/// </summary>
/// <param name="value"></param>
internal Result(Maybe<T> value) =>
Maybe = value;
}
2 changes: 1 addition & 1 deletion src/Jeebs/Jeebs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageReference Include="System.Text.Json" Version="7.0.0-preview.2.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StrongId" Version="8.1.2" />
<PackageReference Include="StrongId" Version="8.1.3" />
</ItemGroup>
<ItemGroup Label="Embed Version">
<EmbeddedResource Include="$(VersionPath)" />
Expand Down
2 changes: 1 addition & 1 deletion tests/Tests.Jeebs.Data.Query/Tests.Jeebs.Data.Query.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<RootNamespace>Jeebs.Data.Querying</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StrongId.Testing" Version="8.1.2" />
<PackageReference Include="StrongId.Testing" Version="8.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Jeebs.Data.Query\Jeebs.Data.Query.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion tests/Tests.Jeebs.Data/Tests.Jeebs.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<RootNamespace>Jeebs.Data</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StrongId.Testing" Version="8.1.2" />
<PackageReference Include="StrongId.Testing" Version="8.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Jeebs.Data\Jeebs.Data.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion tests/Tests.Jeebs.Mvc.Auth/Tests.Jeebs.Mvc.Auth.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<RootNamespace>Jeebs.Mvc.Auth</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StrongId.Testing" Version="8.1.2" />
<PackageReference Include="StrongId.Testing" Version="8.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Jeebs.Mvc.Auth\Jeebs.Mvc.Auth.csproj" />
Expand Down
43 changes: 43 additions & 0 deletions tests/Tests.Jeebs.Mvc/_/Result/Create_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Jeebs Unit Tests
// Copyright (c) bfren - licensed under https://mit.bfren.dev/2013

using Jeebs.Mvc.Models;
using MaybeF;

namespace Jeebs.Mvc.Result_Tests;
public class Create_Tests
{
[Fact]
public void With_Value__Sets_Value()
{
// Arrange
var value = Rnd.Guid;

// Act
var r0 = Result.Create(value);
var r1 = Result.Create(F.Some(value));

// Assert
Assert.Equal(value, r0.Value);
Assert.Equal(value, r1.Value);
}

[Fact]
public void With_Value_And_Message__Sets_Value_And_Message()
{
// Arrange
var value = Rnd.Guid;
var message = Alert.Warning(Rnd.Str);

// Act
var r0 = Result.Create(value, message);
var r1 = Result.Create(F.Some(value), message);

// Assert
Assert.Equal(value, r0.Value);
Assert.Equal(message, r0.Message);
Assert.Equal(value, r1.Value);
Assert.Equal(value, r1.Value);
Assert.Equal(message, r1.Message);
}
}
Loading

0 comments on commit 92a0f7b

Please sign in to comment.