Skip to content

Commit

Permalink
Merge pull request #414 from YudApps/feature/serialization_unitresult
Browse files Browse the repository at this point in the history
Add UnitResult support to CSharpFunctionalExtensions.Json.Serialization
  • Loading branch information
vkhorikov authored Jun 4, 2022
2 parents f4e6a08 + 980a2fb commit f812001
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ public async Task ReadResultAsyncOfT_NullHttpResponseMessage_Failure()
result.IsSuccess.Should().BeFalse();
}

[Fact]
public async Task ReadUnitResultAsyncOfT_NullHttpResponseMessage_Failure()
{
// Assign
HttpResponseMessage httpResponseMessage = null;

// Act
var result = await httpResponseMessage.ReadUnitResultAsync<int>();

// Assert
result.IsSuccess.Should().BeFalse();
}

[Fact]
public async Task ReadResultAsync_NullHttpResponseMessageContent_Failure()
{
Expand Down Expand Up @@ -67,6 +80,19 @@ public async Task ReadResultAsyncOfT_NullHttpResponseMessageContent_Failure()
result.Error.Should().Be(DtoMessages.ContentJsonNotResult);
}

[Fact]
public async Task ReadUnitResultAsyncOfT_NullHttpResponseMessageContent_Failure()
{
// Assign
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);

// Act
var result = await httpResponseMessage.ReadUnitResultAsync<UnitResultError>();

// Assert
result.IsSuccess.Should().BeFalse();
}

[Fact]
public async Task ReadResultAsync_EmptyResponseMessageContent_Failure()
{
Expand Down Expand Up @@ -97,6 +123,20 @@ public async Task ReadResultAsyncOfT_EmptyResponseMessageContent_Failure()
result.Error.Should().Be(DtoMessages.ContentJsonNotResult);
}

[Fact]
public async Task ReadUnitResultAsyncOfT_EmptyResponseMessageContent_Failure()
{
// Assign
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
httpResponseMessage.Content = new StringContent(string.Empty);

// Act
var result = await httpResponseMessage.ReadUnitResultAsync<UnitResultError>();

// Assert
result.IsSuccess.Should().BeFalse();
}

[Fact]
public async Task ReadResultAsync_JsonNull_Failure()
{
Expand Down Expand Up @@ -129,6 +169,22 @@ public async Task ReadResultAsyncOfT_JsonNull_Failure()
result.Error.Should().Be(DtoMessages.ContentJsonNotResult);
}

[Fact]
public async Task ReadUnitResultAsyncOfT_JsonNull_Failure()
{
// Assign
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
httpResponseMessage.Content = JsonContent.Create<UnitResultError>(null);


// Act
var result = await httpResponseMessage.ReadResultAsync<UnitResultError>();

// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Be(DtoMessages.ContentJsonNotResult);
}

[Fact]
public async Task ReadResultAsync_JsonInt_Failure()
{
Expand Down Expand Up @@ -159,7 +215,7 @@ public async Task ReadResultAsyncOfT_JsonInt_Failure()
result.Error.Should().Be(DtoMessages.ContentJsonNotResult);
}

[Fact(Skip = "Fails when building on lunux")]
[Fact(Skip = "Fails when building on Linux")]
public async Task ReadResultAsync_JsonObject_Failure()
{
// Assign
Expand All @@ -174,7 +230,7 @@ public async Task ReadResultAsync_JsonObject_Failure()
result.Error.Should().Be(DtoMessages.ContentJsonNotResult);
}

[Fact(Skip = "Fails when building on lunux")]
[Fact(Skip = "Fails when building on Linux")]
public async Task ReadResultAsyncOfT_JsonObject_Failure()
{
// Assign
Expand Down Expand Up @@ -219,6 +275,20 @@ public async Task ReadResultAsyncOfT_JsonResultDtoOfSuccess_Success()
result.Value.Should().Be(value);
}

[Fact]
public async Task ReadUnitResultAsyncOfT_JsonResultDtoOfSuccess_Success()
{
// Assign
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
httpResponseMessage.Content = JsonContent.Create(UnitResultDto.Success<UnitResultError>());

// Act
var result = await httpResponseMessage.ReadUnitResultAsync<UnitResultError>();

// Assert
result.IsSuccess.Should().BeTrue();
}

[Fact]
public async Task ReadResultAsync_JsonResultDtoOfFailure_Failure()
{
Expand Down Expand Up @@ -250,6 +320,22 @@ public async Task ReadResultAsyncOfT_JsonResultDtoOfFailure_Failure()
result.Error.Should().Be(error);
}

[Fact]
public async Task ReadUnitResultAsyncOfT_JsonResultDtoOfFailure_Failure()
{
// Assign
var error = new UnitResultError() { ErrorMessage = "Failure" };
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
httpResponseMessage.Content = JsonContent.Create(UnitResultDto.Failure(error));

// Act
var result = await httpResponseMessage.ReadUnitResultAsync<UnitResultError>();

// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().BeEquivalentTo(error);
}

[Fact]
public async Task ReadResultAsync_StringResultDtoOfSuccess_Success()
{
Expand Down Expand Up @@ -279,5 +365,27 @@ public async Task ReadResultAsyncOfT_StringResultDtoOfSuccess_Success()
result.IsSuccess.Should().BeTrue();
result.Value.Should().Be(value);
}

[Fact]
public async Task ReadUnitResultAsyncOfT_UnitResultErrorResultDtoOfSuccess_Success()
{
// Assign
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
httpResponseMessage.Content = new StringContent($"{{ \"Error\": null }}");

// Act
var result = await httpResponseMessage.ReadUnitResultAsync<UnitResultError>();

// Assert
result.IsSuccess.Should().BeTrue();
}

private class UnitResultError
{
public UnitResultError() { }

public string ErrorMessage { get; set; }
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace CSharpFunctionalExtensions.Json.Serialization
{
internal static class CSharpFunctionalExtensionsJsonSerializerOptions
public static class CSharpFunctionalExtensionsJsonSerializerOptions
{
private static readonly Lazy<JsonSerializerOptions> LazyOptions = new(() =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
namespace CSharpFunctionalExtensions.Json.Serialization
using System.Net;

namespace CSharpFunctionalExtensions.Json.Serialization
{
internal static class DtoMessages
{
public static readonly string HttpResponseMessageIsNull = "HttpResponseMessage is null";

public static readonly string ContentJsonNotResult = "The response content in not a Result";

public static string NotSuccsessStatusCodeFormat(HttpStatusCode statusCode, string content) => $"HttpStatus code is {statusCode}, Content {content}";
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,69 @@
#nullable enable
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions.Json.Serialization
{
public static class HttpResponseMessageJsonExtensions
{
public static Task<Result> ReadResultAsync(this HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
public static async Task<Result> ReadResultAsync(this HttpResponseMessage response, bool ensureSuccessStatusCode = true, CancellationToken cancellationToken = default(CancellationToken))
{
if (response is null)
{
return Task.FromResult(Result.Failure(DtoMessages.HttpResponseMessageIsNull));
return Result.Failure(DtoMessages.HttpResponseMessageIsNull);
}

return
if (ensureSuccessStatusCode && !response.IsSuccessStatusCode)
{
return Result.Failure(DtoMessages.NotSuccsessStatusCodeFormat(response.StatusCode, await response.Content.ReadAsStringAsync()));
}

return await
Result.Try(() => response.Content.ReadFromJsonAsync<Result>(CSharpFunctionalExtensionsJsonSerializerOptions.Options, cancellationToken), ex => DtoMessages.ContentJsonNotResult)
.Bind(result => result);
}

public static Task<Result<T>> ReadResultAsync<T>(this HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
public static async Task<Result<T>> ReadResultAsync<T>(this HttpResponseMessage response, bool ensureSuccessStatusCode = true, CancellationToken cancellationToken = default(CancellationToken))
{
if (response is null)
{
return Task.FromResult(Result.Failure<T>(DtoMessages.HttpResponseMessageIsNull));
return Result.Failure<T>(DtoMessages.HttpResponseMessageIsNull);
}

if (ensureSuccessStatusCode && !response.IsSuccessStatusCode)
{
return Result.Failure<T>(DtoMessages.NotSuccsessStatusCodeFormat(response.StatusCode, await response.Content.ReadAsStringAsync()));
}

return
return await
Result.Try(() => response.Content.ReadFromJsonAsync<Result<T>>(CSharpFunctionalExtensionsJsonSerializerOptions.Options, cancellationToken), ex => DtoMessages.ContentJsonNotResult)
.Bind(result => result);
}

public static async Task<UnitResult<E>> ReadUnitResultAsync<E>(this HttpResponseMessage? response, bool ensureSuccessStatusCode = true, CancellationToken cancellationToken = default(CancellationToken))
where E : new()
{
if (response is null)
{
return UnitResult.Failure<E>(new());
}

if (ensureSuccessStatusCode && !response.IsSuccessStatusCode)
{
return UnitResult.Failure<E>(new());
}

try
{
return await response.Content.ReadFromJsonAsync<UnitResult<E>>(CSharpFunctionalExtensionsJsonSerializerOptions.Options, cancellationToken);
}
catch (JsonException)
{
return UnitResult.Failure<E>(new());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static JsonSerializerOptions AddCSharpFunctionalExtensionsConverters(this
{
options.Converters.Add(new ResultJsonConverter());
options.Converters.Add(new ResultJsonConverterFactory());
options.Converters.Add(new UnitResultJsonConverterFactory());
return options;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

namespace CSharpFunctionalExtensions.Json.Serialization
{
/// <summary>
/// Alternative entry-point for <see cref="UnitResultDto{E}" /> to avoid ambiguous calls
/// </summary>
internal static partial class UnitResultDto
{
/// <summary>
/// Creates a failure result with the given error.
/// </summary>
public static UnitResultDto<E> Failure<E>(E error)
{
return new UnitResultDto<E>(error);
}

/// <summary>
/// Creates a success <see cref="UnitResultDto{E}" />.
/// </summary>
public static UnitResultDto<E> Success<E>()
{
return new UnitResultDto<E>();
}
}

internal record UnitResultDto<E>([property:Required] E? Error = default)
{
[JsonIgnore]
public bool IsSuccess => Error is null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#nullable enable
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace CSharpFunctionalExtensions.Json.Serialization
{
internal class UnitResultJsonConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType) return false;

return typeToConvert.GetGenericTypeDefinition() == typeof(UnitResult<>);
}

public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type wrappedType = typeToConvert.GetGenericArguments()[0];

var genericResultType = typeof(UnitResultJsonConverterOfT<>).MakeGenericType(wrappedType);
JsonConverter? converter = Activator.CreateInstance(genericResultType) as JsonConverter;

return converter!;
}
}

internal class UnitResultJsonConverterOfT<E> : JsonConverter<UnitResult<E>>
{
public override UnitResult<E> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
try
{
return ToUnitResult(JsonSerializer.Deserialize<UnitResultDto<E>>(ref reader, options));
}
catch (JsonException)
{
return UnitResult.Failure<E>(default!);
}
}

public override void Write(Utf8JsonWriter writer, UnitResult<E> value, JsonSerializerOptions options)
=> JsonSerializer.Serialize(writer, ToUnitResultDto(value), options);

private static UnitResult<E> ToUnitResult(UnitResultDto<E>? resultDto)
{
if (resultDto is not null)
{
if (resultDto.IsSuccess)
{
return UnitResult.Success<E>();
}

if (resultDto.Error is not null)
{
return UnitResult.Failure<E>(resultDto.Error);
}
}

return UnitResult.Failure<E>(default!);
}

private static UnitResultDto<E> ToUnitResultDto(UnitResult<E> unitResult)
=> unitResult.IsSuccess
? UnitResultDto.Success<E>()
: UnitResultDto.Failure(unitResult.Error);
}
}

0 comments on commit f812001

Please sign in to comment.