From b485fda90b006cbbc642fce4a6c04888976aa627 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 Jun 2020 19:15:40 +0200 Subject: [PATCH] Exception filter. --- .../DomainObjectVersionException.cs | 25 ++----- .../WrongEventVersionException.cs | 25 ++----- .../Orleans/ExceptionWrapperFilter.cs | 37 +++++++++ .../Orleans/OrleansWrapperException.cs | 50 +++++++++++++ .../States/InconsistentStateException.cs | 25 ++----- .../Squidex/Config/Orleans/OrleansServices.cs | 1 + .../Orleans/ExceptionWrapperFilterTests.cs | 75 +++++++++++++++++++ 7 files changed, 187 insertions(+), 51 deletions(-) create mode 100644 backend/src/Squidex.Infrastructure/Orleans/ExceptionWrapperFilter.cs create mode 100644 backend/src/Squidex.Infrastructure/Orleans/OrleansWrapperException.cs create mode 100644 backend/tests/Squidex.Infrastructure.Tests/Orleans/ExceptionWrapperFilterTests.cs diff --git a/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs b/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs index 22192c423..3b6d50aee 100644 --- a/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs +++ b/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs @@ -13,39 +13,30 @@ namespace Squidex.Infrastructure [Serializable] public class DomainObjectVersionException : DomainObjectException { - private readonly long currentVersion; - private readonly long expectedVersion; + public long CurrentVersion { get; } - public long CurrentVersion - { - get { return currentVersion; } - } - - public long ExpectedVersion - { - get { return expectedVersion; } - } + public long ExpectedVersion { get; } public DomainObjectVersionException(string id, Type type, long currentVersion, long expectedVersion) : base(FormatMessage(id, type, currentVersion, expectedVersion), id, type) { - this.currentVersion = currentVersion; + CurrentVersion = currentVersion; - this.expectedVersion = expectedVersion; + ExpectedVersion = expectedVersion; } protected DomainObjectVersionException(SerializationInfo info, StreamingContext context) : base(info, context) { - currentVersion = info.GetInt64(nameof(currentVersion)); + CurrentVersion = info.GetInt64(nameof(CurrentVersion)); - expectedVersion = info.GetInt64(nameof(expectedVersion)); + ExpectedVersion = info.GetInt64(nameof(ExpectedVersion)); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { - info.AddValue(nameof(currentVersion), currentVersion); - info.AddValue(nameof(expectedVersion), expectedVersion); + info.AddValue(nameof(CurrentVersion), CurrentVersion); + info.AddValue(nameof(ExpectedVersion), ExpectedVersion); base.GetObjectData(info, context); } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs b/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs index 0651467b8..f526caee8 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs @@ -13,39 +13,30 @@ namespace Squidex.Infrastructure.EventSourcing [Serializable] public class WrongEventVersionException : Exception { - private readonly long currentVersion; - private readonly long expectedVersion; + public long CurrentVersion { get; } - public long CurrentVersion - { - get { return currentVersion; } - } - - public long ExpectedVersion - { - get { return expectedVersion; } - } + public long ExpectedVersion { get; } public WrongEventVersionException(long currentVersion, long expectedVersion) : base(FormatMessage(currentVersion, expectedVersion)) { - this.currentVersion = currentVersion; + CurrentVersion = currentVersion; - this.expectedVersion = expectedVersion; + ExpectedVersion = expectedVersion; } protected WrongEventVersionException(SerializationInfo info, StreamingContext context) : base(info, context) { - currentVersion = info.GetInt64(nameof(currentVersion)); + CurrentVersion = info.GetInt64(nameof(CurrentVersion)); - expectedVersion = info.GetInt64(nameof(expectedVersion)); + ExpectedVersion = info.GetInt64(nameof(ExpectedVersion)); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { - info.AddValue(nameof(currentVersion), currentVersion); - info.AddValue(nameof(expectedVersion), expectedVersion); + info.AddValue(nameof(CurrentVersion), CurrentVersion); + info.AddValue(nameof(ExpectedVersion), ExpectedVersion); base.GetObjectData(info, context); } diff --git a/backend/src/Squidex.Infrastructure/Orleans/ExceptionWrapperFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/ExceptionWrapperFilter.cs new file mode 100644 index 000000000..be53ab4ce --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Orleans/ExceptionWrapperFilter.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Orleans; + +namespace Squidex.Infrastructure.Orleans +{ + public sealed class ExceptionWrapperFilter : IIncomingGrainCallFilter + { + public async Task Invoke(IIncomingGrainCallContext context) + { + try + { + await context.Invoke(); + } + catch (Exception ex) + { + var type = ex.GetType(); + + if (!type.IsSerializable) + { + throw new OrleansWrapperException(ex, type); + } + else + { + throw; + } + } + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Orleans/OrleansWrapperException.cs b/backend/src/Squidex.Infrastructure/Orleans/OrleansWrapperException.cs new file mode 100644 index 000000000..a0f7612bd --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Orleans/OrleansWrapperException.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Runtime.Serialization; +using System.Text; + +namespace Squidex.Infrastructure.Orleans +{ + [Serializable] + public class OrleansWrapperException : Exception + { + public Type ExceptionType { get; } + + public OrleansWrapperException(Exception wrapped, Type exceptionType) + : base(FormatMessage(wrapped, exceptionType)) + { + ExceptionType = exceptionType; + } + + protected OrleansWrapperException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + ExceptionType = Type.GetType(info.GetString(nameof(ExceptionType))!)!; + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameof(ExceptionType), ExceptionType.AssemblyQualifiedName); + + base.GetObjectData(info, context); + } + + private static string FormatMessage(Exception wrapped, Type exceptionType) + { + var sb = new StringBuilder(); + + sb.AppendLine($"Wrapping exception of type {exceptionType}, because original exception is not serialized."); + sb.AppendLine(); + sb.AppendLine("Original exception:"); + sb.AppendLine(wrapped.ToString()); + + return sb.ToString(); + } + } +} diff --git a/backend/src/Squidex.Infrastructure/States/InconsistentStateException.cs b/backend/src/Squidex.Infrastructure/States/InconsistentStateException.cs index 96ea73b9f..e66f1a2cf 100644 --- a/backend/src/Squidex.Infrastructure/States/InconsistentStateException.cs +++ b/backend/src/Squidex.Infrastructure/States/InconsistentStateException.cs @@ -13,39 +13,30 @@ namespace Squidex.Infrastructure.States [Serializable] public class InconsistentStateException : Exception { - private readonly long currentVersion; - private readonly long expectedVersion; + public long CurrentVersion { get; } - public long CurrentVersion - { - get { return currentVersion; } - } - - public long ExpectedVersion - { - get { return expectedVersion; } - } + public long ExpectedVersion { get; } public InconsistentStateException(long currentVersion, long expectedVersion, Exception? inner = null) : base(FormatMessage(currentVersion, expectedVersion), inner) { - this.currentVersion = currentVersion; + CurrentVersion = currentVersion; - this.expectedVersion = expectedVersion; + ExpectedVersion = expectedVersion; } protected InconsistentStateException(SerializationInfo info, StreamingContext context) : base(info, context) { - currentVersion = info.GetInt64(nameof(currentVersion)); + CurrentVersion = info.GetInt64(nameof(CurrentVersion)); - expectedVersion = info.GetInt64(nameof(expectedVersion)); + ExpectedVersion = info.GetInt64(nameof(ExpectedVersion)); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { - info.AddValue(nameof(currentVersion), currentVersion); - info.AddValue(nameof(expectedVersion), expectedVersion); + info.AddValue(nameof(CurrentVersion), CurrentVersion); + info.AddValue(nameof(ExpectedVersion), ExpectedVersion); base.GetObjectData(info, context); } diff --git a/backend/src/Squidex/Config/Orleans/OrleansServices.cs b/backend/src/Squidex/Config/Orleans/OrleansServices.cs index 8c56dfba6..080301dd3 100644 --- a/backend/src/Squidex/Config/Orleans/OrleansServices.cs +++ b/backend/src/Squidex/Config/Orleans/OrleansServices.cs @@ -59,6 +59,7 @@ namespace Squidex.Config.Orleans options.HostSelf = false; }); + builder.AddIncomingGrainCallFilter(); builder.AddIncomingGrainCallFilter(); builder.AddIncomingGrainCallFilter(); builder.AddIncomingGrainCallFilter(); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Orleans/ExceptionWrapperFilterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Orleans/ExceptionWrapperFilterTests.cs new file mode 100644 index 000000000..ff1d44034 --- /dev/null +++ b/backend/tests/Squidex.Infrastructure.Tests/Orleans/ExceptionWrapperFilterTests.cs @@ -0,0 +1,75 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Orleans; +using Squidex.Infrastructure.TestHelpers; +using Xunit; + +namespace Squidex.Infrastructure.Orleans +{ + public class ExceptionWrapperFilterTests + { + private readonly IIncomingGrainCallContext context = A.Fake(); + private readonly ExceptionWrapperFilter sut; + + private sealed class InvalidException : Exception + { + public InvalidException(string message) + : base(message) + { + } + } + + public ExceptionWrapperFilterTests() + { + sut = new ExceptionWrapperFilter(); + } + + [Fact] + public async Task Should_just_forward_serializable_exception() + { + var original = new InvalidOperationException(); + + A.CallTo(() => context.Invoke()) + .Throws(original); + + var ex = await Assert.ThrowsAnyAsync(() => sut.Invoke(context)); + + Assert.Same(ex, original); + } + + [Fact] + public async Task Should_wrap_non_serializable_exception() + { + var original = new InvalidException("My Message"); + + A.CallTo(() => context.Invoke()) + .Throws(original); + + var ex = await Assert.ThrowsAnyAsync(() => sut.Invoke(context)); + + Assert.Equal(original.GetType(), ex.ExceptionType); + Assert.Contains(original.Message, ex.Message); + } + + [Fact] + public void Should_serialize_and_deserialize() + { + var original = new InvalidException("My Message"); + + var source = new OrleansWrapperException(original, original.GetType()); + var result = source.SerializeAndDeserializeBinary(); + + Assert.Equal(result.ExceptionType, source.ExceptionType); + + Assert.Equal(result.Message, source.Message); + } + } +}