Skip to content

Commit

Permalink
feat: get rid of some legacy code related to ExceptionInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
alexyakunin committed Sep 18, 2023
1 parent b80168b commit ab20edf
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 131 deletions.
12 changes: 0 additions & 12 deletions src/Stl.Fusion/ExceptionExt.cs

This file was deleted.

1 change: 0 additions & 1 deletion src/Stl.Testing/SerializationTestExt.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text;
using Cysharp.Text;
using FluentAssertions;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Newtonsoft.Json;
Expand Down
94 changes: 94 additions & 0 deletions src/Stl.Testing/TypeEvolutionTester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Text;
using FluentAssertions;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Newtonsoft.Json;
using Xunit.Abstractions;

namespace Stl.Testing;

public class TypeEvolutionTester<TOld, TNew>
{
public Action<TOld, TNew> AssertEqual { get; }
public JsonSerializerOptions SystemJsonOptions { get; set; }
public JsonSerializerSettings NewtonsoftJsonSettings { get; set; }
public ITestOutputHelper? Output { get; }

public TypeEvolutionTester(Action<TOld, TNew> assertEqual, ITestOutputHelper? output = null)
{
AssertEqual = assertEqual;
Output = output;
SystemJsonOptions = MemberwiseCloner.Invoke(SystemJsonSerializer.DefaultOptions);
NewtonsoftJsonSettings = MemberwiseCloner.Invoke(NewtonsoftJsonSerializer.DefaultSettings);
NewtonsoftJsonSettings.Formatting = Formatting.Indented;
}

public void CheckAllSerializers(TOld value)
{
CheckSystemJsonSerializer(value);
CheckNewtonsoftJsonSerializer(value);
CheckMessagePackByteSerializer(value);
CheckMemoryPackByteSerializer(value);
}

// System.Text.Json serializer

public void CheckSystemJsonSerializer(TOld value)
{
var s = new SystemJsonSerializer(SystemJsonOptions);
var json = s.Write(value);
Output?.WriteLine($"SystemJsonSerializer: {json}");
var v0 = s.Read<TNew>(json);
AssertEqual(value, v0);

using var buffer = new ArrayPoolBufferWriter<byte>();
s.Write(buffer, value);
var bytes = buffer.WrittenMemory;
var json2 = Encoding.UTF8.GetDecoder().Convert(bytes.Span);
json2.Should().Be(json);
var v1 = s.Read<TNew>(bytes);
AssertEqual(value, v1);
}

// Newtonsoft.Json serializer

public void CheckNewtonsoftJsonSerializer(TOld value, ITestOutputHelper? output = null)
{
var s = new NewtonsoftJsonSerializer(NewtonsoftJsonSettings);
var json = s.Write(value);
Output?.WriteLine($"NewtonsoftJsonSerializer: {json}");
var v0 = s.Read<TNew>(json);
AssertEqual(value, v0);

using var buffer = new ArrayPoolBufferWriter<byte>();
s.Write(buffer, value);
var bytes = buffer.WrittenMemory;
var json2 = Encoding.UTF8.GetDecoder().Convert(bytes.Span);
json2.Should().Be(json);
var v1 = s.Read<TNew>(bytes);
AssertEqual(value, v1);
}

// MessagePack serializer

public void CheckMessagePackByteSerializer(TOld value, ITestOutputHelper? output = null)
{
var s = new MessagePackByteSerializer();
using var buffer = s.Write(value);
var b0 = buffer.WrittenMemory.ToArray();
Output?.WriteLine($"MessagePackByteSerializer: {JsonFormatter.Format(b0)}");
var v0 = s.Read<TNew>(b0);
AssertEqual(value, v0);
}

// MemoryPack serializer

public void CheckMemoryPackByteSerializer(TOld value, ITestOutputHelper? output = null)
{
var s = new MemoryPackByteSerializer();
using var buffer = s.Write(value);
var b0 = buffer.WrittenMemory.ToArray();
Output?.WriteLine($"MemoryPackByteSerializer: {JsonFormatter.Format(b0)}");
var v0 = s.Read<TNew>(b0);
AssertEqual(value, v0);
}
}
1 change: 1 addition & 0 deletions src/Stl/RequirementExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static Requirement<T> WithServiceException<T>(this Requirement<T> require
{
if (requirement is ServiceExceptionWrapper<T>)
return requirement;

return ReferenceEquals(requirement, MustExistRequirement<T>.Default)
? ServiceExceptionWrapper<T>.Default
: new ServiceExceptionWrapper<T>(requirement);
Expand Down
106 changes: 34 additions & 72 deletions src/Stl/Serialization/ExceptionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,111 +9,88 @@ namespace Stl.Serialization;
private static readonly Type[] ExceptionCtorArgumentTypes2 = { typeof(string) };

public static ExceptionInfo None { get; } = default;
public static Func<ExceptionInfo, Exception?> ToExceptionConverter { get; set; } = DefaultToExceptionConverter;
public static Func<TypeRef, Type>? UnknownExceptionTypeResolver { get; set; } = null;

private readonly string _message;

[DataMember(Order = 0), MemoryPackOrder(0)]
public TypeRef TypeRef { get; }
[DataMember(Order = 1), MemoryPackOrder(1)]
public string Message => _message ?? "";
[DataMember(Order = 2), MemoryPackOrder(2)]
public TypeRef WrappedTypeRef { get; }
[IgnoreDataMember, MemoryPackIgnore]
public bool IsNone => TypeRef.AssemblyQualifiedName.IsEmpty;
[IgnoreDataMember, MemoryPackIgnore]
public bool HasWrappedTypeRef => !WrappedTypeRef.AssemblyQualifiedName.IsEmpty;

[JsonConstructor, Newtonsoft.Json.JsonConstructor, MemoryPackConstructor]
public ExceptionInfo(TypeRef typeRef, string? message, TypeRef wrappedTypeRef = default)
public ExceptionInfo(TypeRef typeRef, string? message)
{
TypeRef = typeRef;
_message = message ?? "";
WrappedTypeRef = wrappedTypeRef;
}

public ExceptionInfo(Exception? exception)
{
if (exception == null) {
TypeRef = default;
_message = "";
WrappedTypeRef = default;
} else if (exception is IWrappedException re) {
var wrappedException = re.Unwrap();
TypeRef = exception.GetType();
_message = wrappedException.Message;
WrappedTypeRef = wrappedException.GetType();
} else {
TypeRef = exception.GetType();
TypeRef = new TypeRef(exception.GetType()).WithoutAssemblyVersions();
_message = exception.Message;
WrappedTypeRef = default;
}
}

public override string ToString()
{
if (IsNone)
return $"{GetType().Name}()";
if (HasWrappedTypeRef)
return $"{GetType().Name}({TypeRef} -> {WrappedTypeRef}, {JsonFormatter.Format(Message)})";
return $"{GetType().Name}({TypeRef}, {JsonFormatter.Format(Message)})";
}
=> IsNone
? $"{GetType().Name}()"
: $"{GetType().Name}({TypeRef}, {JsonFormatter.Format(Message)})";

public Exception? ToException()
=> ToExceptionConverter.Invoke(this);

public ExceptionInfo Unwrap()
=> HasWrappedTypeRef ? new ExceptionInfo(WrappedTypeRef, Message) : this;

// Conversion

public static implicit operator ExceptionInfo(Exception exception)
=> new(exception);

public static Exception? DefaultToExceptionConverter(ExceptionInfo exceptionInfo)
{
if (exceptionInfo.IsNone)
if (IsNone)
return null;

try {
return CreateException(exceptionInfo) ?? Errors.RemoteException(exceptionInfo);
return TryCreateException(this) ?? Errors.RemoteException(this);
}
catch (Exception) {
return Errors.RemoteException(exceptionInfo);
return Errors.RemoteException(this);
}
}

private static Exception? CreateException(ExceptionInfo exceptionInfo)
{
var type = exceptionInfo.TypeRef.Resolve();
if (!exceptionInfo.HasWrappedTypeRef)
return CreateStandardException(type, exceptionInfo.Message);
// Conversion

if (!typeof(IWrappedException).IsAssignableFrom(type))
return null;
if (!typeof(Exception).IsAssignableFrom(type))
return null;
public static implicit operator ExceptionInfo(Exception exception)
=> new(exception);

var wrappedType = exceptionInfo.WrappedTypeRef.Resolve();
var wrappedException = CreateStandardException(wrappedType, exceptionInfo.Message);
if (wrappedException == null)
return null;
// Equality

var ctor = type.GetConstructor(ExceptionCtorArgumentTypes1);
if (ctor == null)
return null;
return (Exception) type.CreateInstance(exceptionInfo.Message, wrappedException);
}
public bool Equals(ExceptionInfo other)
=> TypeRef.Equals(other.TypeRef)
&& StringComparer.Ordinal.Equals(Message, other.Message);
public override bool Equals(object? obj)
=> obj is ExceptionInfo other && Equals(other);
public override int GetHashCode()
=> HashCode.Combine(TypeRef, StringComparer.Ordinal.GetHashCode(Message));
public static bool operator ==(ExceptionInfo left, ExceptionInfo right)
=> left.Equals(right);
public static bool operator !=(ExceptionInfo left, ExceptionInfo right)
=> !left.Equals(right);

// Private methods

private static Exception? CreateStandardException(Type type, string message)
private static Exception? TryCreateException(ExceptionInfo exceptionInfo)
{
if (exceptionInfo.IsNone)
return null;

var (type, message) = (exceptionInfo.TypeRef.TryResolve(), exceptionInfo.Message);
type ??= UnknownExceptionTypeResolver?.Invoke(exceptionInfo.TypeRef);
if (!typeof(Exception).IsAssignableFrom(type))
return null;

var ctor = type.GetConstructor(ExceptionCtorArgumentTypes1);
if (ctor != null) {
try {
return (Exception) type.CreateInstance(message, (Exception?) null);
return (Exception)type.CreateInstance(message, (Exception?) null);
}
catch {
// Intended
Expand All @@ -128,21 +105,6 @@ public static implicit operator ExceptionInfo(Exception exception)
if (!StringComparer.Ordinal.Equals("message", parameter?.Name ?? ""))
return null;

return (Exception) type.CreateInstance(message);
return (Exception)type.CreateInstance(message);
}

// Equality

public bool Equals(ExceptionInfo other)
=> TypeRef.Equals(other.TypeRef)
&& WrappedTypeRef.Equals(other.WrappedTypeRef)
&& StringComparer.Ordinal.Equals(Message, other.Message);
public override bool Equals(object? obj)
=> obj is ExceptionInfo other && Equals(other);
public override int GetHashCode()
=> HashCode.Combine(TypeRef, WrappedTypeRef, StringComparer.Ordinal.GetHashCode(Message));
public static bool operator ==(ExceptionInfo left, ExceptionInfo right)
=> left.Equals(right);
public static bool operator !=(ExceptionInfo left, ExceptionInfo right)
=> !left.Equals(right);
}
2 changes: 0 additions & 2 deletions src/Stl/Serialization/ITextSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Buffers;

namespace Stl.Serialization;

public interface ITextSerializer : IByteSerializer
Expand Down
6 changes: 0 additions & 6 deletions src/Stl/Serialization/IWrappedException.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/Stl/Serialization/Internal/Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public static Exception SerializedTypeMismatch(Type supportedType, Type requeste
$"The serializer implements '{supportedType}' serialization, but '{requestedType}' was requested to (de)serialize.");

public static Exception RemoteException(ExceptionInfo exceptionInfo)
=> new RemoteException(exceptionInfo, $"Remote exception: {exceptionInfo.ToString()}");
=> new RemoteException(exceptionInfo, exceptionInfo.Message.NullIfEmpty() ?? "Unknown error.");
}
5 changes: 1 addition & 4 deletions src/Stl/ServiceException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ namespace Stl;

#pragma warning disable SYSLIB0051

public class ServiceException : Exception, IWrappedException
public class ServiceException : Exception
{
public ServiceException() { }
public ServiceException(string? message) : base(message) { }
public ServiceException(string? message, Exception? innerException) : base(message, innerException) { }
protected ServiceException(SerializationInfo info, StreamingContext context) : base(info, context) { }

public Exception Unwrap()
=> InnerException ?? this;
}
9 changes: 4 additions & 5 deletions tests/Stl.Fusion.Tests/Services/SimplestProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface ISimplestProvider
[ComputeMethod(MinCacheDuration = 0.5, TransientErrorInvalidationDelay = 0.5)]
Task<int> GetCharCount();
[ComputeMethod(TransientErrorInvalidationDelay = 0.5)]
Task<int> Fail(Type exceptionType, bool wrapToResultException);
Task<int> Fail(Type exceptionType);

[CommandHandler]
Task SetValue(SetValueCommand command, CancellationToken cancellationToken = default);
Expand Down Expand Up @@ -61,11 +61,10 @@ public virtual async Task<int> GetCharCount()
}
}

public virtual Task<int> Fail(Type exceptionType, bool wrapToResultException)
public virtual Task<int> Fail(Type exceptionType)
{
var e = ExceptionInfo.ToExceptionConverter.Invoke(
new ExceptionInfo(exceptionType, "Fail!"))!;
throw e.MaybeToResultException(wrapToResultException);
var e = new ExceptionInfo(exceptionType, "Fail!");
throw e.ToException()!;
}

public virtual Task SetValue(SetValueCommand command, CancellationToken cancellationToken = default)
Expand Down
7 changes: 3 additions & 4 deletions tests/Stl.Fusion.Tests/SimplestProviderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,12 @@ public async Task FailTest()
var p = Services.GetRequiredService<ISimplestProvider>();
p.SetValue("");

var c1 = await Computed.Capture(() => p.Fail(typeof(TransientException), false));
var c1 = await Computed.Capture(() => p.Fail(typeof(TransientException)));
c1.Error.Should().BeOfType<TransientException>();
await c1.WhenInvalidated().WaitAsync(TimeSpan.FromSeconds(1));

c1 = await Computed.Capture(() => p.Fail(typeof(TransientException), true));
c1.Error.Should().BeOfType<ServiceException>()
.Which.InnerException.Should().BeOfType<TransientException>();
c1 = await Computed.Capture(() => p.Fail(typeof(ServiceException)));
c1.Error.Should().BeOfType<ServiceException>();
await Delay(1);
var c2 = await c1.Update();
c2.Should().BeSameAs(c1);
Expand Down
Loading

0 comments on commit ab20edf

Please sign in to comment.