diff --git a/specs/Qowaiv.Validation.Specs/Abstractions/Result_Act_Debugger_specs.cs b/specs/Qowaiv.Validation.Specs/Abstractions/Result_Act_Debugger_specs.cs new file mode 100644 index 0000000..ce7babb --- /dev/null +++ b/specs/Qowaiv.Validation.Specs/Abstractions/Result_Act_Debugger_specs.cs @@ -0,0 +1,120 @@ +using Qowaiv.Validation.Abstractions; +using System.Diagnostics; + +namespace Abstractions.Result_Act_Debugger_specs; + +[NonParallelizable] +public class When_debugger_is_attached +{ + [SetUp] + public void SetUp() + { + SetDebugger.IsAttached(true); + SetDebugger.Break(true); + } + + [TearDown] + public void TearDown() + { + SetDebugger.IsAttached(null); + SetDebugger.Break(false); + } + + [Test] + public void Break_on_Act_when_invalid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.WithMessages(ValidationMessage.Error("Break")))) + .Should().Throw(); + } + [Test] + public void Break_on_Act_T_when_invalid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.WithMessages(ValidationMessage.Error("Break")))) + .Should().Throw(); + } + + [Test] + public void Continue_on_Act_when_valid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.OK)) + .Should().NotThrow(); + } + + [Test] + public void Continue_on_Act_of_T_when_valid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.For(17))) + .Should().NotThrow(); + } +} + +[NonParallelizable] +public class When_debugger_is_not_attached +{ + [SetUp] + public void SetUp() => SetDebugger.IsAttached(false); + + [TearDown] + public void TearDown() => SetDebugger.IsAttached(null); + + + [Test] + public void Continue_on_Act_when_invalid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.WithMessages(ValidationMessage.Error("Break")))) + .Should().NotThrow(); + } + [Test] + public void Continue_on_Act_T_when_invalid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.WithMessages(ValidationMessage.Error("Break")))) + .Should().NotThrow(); + } + + [Test] + public void Continue_on_Act_when_valid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.OK)) + .Should().NotThrow(); + } + + [Test] + public void Continue_on_Act_of_T_when_valid() + { + var result = Result.For(42); + result.Invoking(res => res.Act(r => Result.For(17))) + .Should().NotThrow(); + } +} + +static class SetDebugger +{ + public static void IsAttached(bool? isAttached) + { + Func action = isAttached.HasValue + ? () => isAttached.Value + : () => Debugger.IsAttached; + + DebuggerWrapper.GetProperty(nameof(IsAttached)).SetValue(null, action); + } + + public static void Break(bool @throw) + { + Action action = @throw + ? () => throw new DebuggerBreaks() + : Debugger.Break; + + DebuggerWrapper.GetProperty(nameof(Break)).SetValue(null, action); + } + + private static Type DebuggerWrapper = typeof(Result).Assembly.DefinedTypes.Single(t => t.Name == nameof(DebuggerWrapper)); +} + +public sealed class DebuggerBreaks : Exception { } diff --git a/src/Qowaiv.Validation.Abstractions/Diagnostics/DebuggerWrapper.cs b/src/Qowaiv.Validation.Abstractions/Diagnostics/DebuggerWrapper.cs new file mode 100644 index 0000000..b7dfd05 --- /dev/null +++ b/src/Qowaiv.Validation.Abstractions/Diagnostics/DebuggerWrapper.cs @@ -0,0 +1,22 @@ +namespace Qowaiv.Validation.Abstractions.Diagnostics; + +/// +/// The class is a part of the System.Diagnostics package +/// and is used for communicating with a debugger. +/// +/// +/// For testability reasons, this internal wrapper is added, so that under test +/// the behavior can be adjusted. +/// +internal static class DebuggerWrapper +{ + /// Returns whether or not a debugger is attached to the process. + public static Func IsAttached { get; set; } = () => Debugger.IsAttached; + + /// + /// Break causes a breakpoint to be signalled to an attached debugger.If no debugger + /// is attached, the user is asked if they want to attach a debugger. If yes, then the + /// debugger is launched. + /// + public static Action Break { get; set; } = Debugger.Break; +} diff --git a/src/Qowaiv.Validation.Abstractions/Result.cs b/src/Qowaiv.Validation.Abstractions/Result.cs index 5aa7038..f586e8c 100644 --- a/src/Qowaiv.Validation.Abstractions/Result.cs +++ b/src/Qowaiv.Validation.Abstractions/Result.cs @@ -32,6 +32,15 @@ internal Result(FixedMessages messages) [Pure] public IEnumerable Infos => Messages.GetInfos(); + /// Applies when not valid and with . + internal void BreakIfInvalid() + { + if (!IsValid && DebuggerWrapper.IsAttached()) + { + DebuggerWrapper.Break(); + } + } + /// Represents an OK . public static readonly Result OK = new(FixedMessages.Empty); diff --git a/src/Qowaiv.Validation.Abstractions/Result_TModel.cs b/src/Qowaiv.Validation.Abstractions/Result_TModel.cs index 81827a7..64be11d 100644 --- a/src/Qowaiv.Validation.Abstractions/Result_TModel.cs +++ b/src/Qowaiv.Validation.Abstractions/Result_TModel.cs @@ -82,6 +82,7 @@ public Result Act(Func> action) if (IsValid) { var outcome = action(Value); + outcome.BreakIfInvalid(); return new(outcome.IsValid ? outcome.Value : default, @@ -105,6 +106,7 @@ public Result Act(Func action) if (IsValid) { var outcome = action(Value); + outcome.BreakIfInvalid(); return For(Value, ((FixedMessages)Messages).AddRange(outcome.Messages)); } else return WithMessages(Messages);