Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions FirstClassErrors.RequestBinder.UnitTests/BindingContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Linq.Expressions;
using System.Reflection;

using FirstClassErrors.Testing;

using NFluent;

#endregion
Expand Down Expand Up @@ -30,8 +32,8 @@ public void ConverterMayFailWithAPrimaryPortError() {
var bind = Bind.PropertiesOf(Request()).FailWith(BookingEnvelopeError.CommandInvalid);

bind.SimpleProperty(r => r.GuestEmail).AsRequired(_ => Outcome<EmailAddress>.Failure(
PrimaryPortError.Create(ErrorCode.Create("TEST_PORT_LEVEL_REJECTION"), "Rejected at the port.", Transience.NonTransient)
.WithPublicMessage("Rejected.")));
PrimaryPortError.Create(ErrorCode.Create("TEST_PORT_LEVEL_REJECTION"), Any.DiagnosticMessage(), Any.Transience())
.WithPublicMessage(Any.ShortMessage())));

Outcome<string> outcome = bind.New(_ => "never");
Error invalid = outcome.Error!.InnerErrors.Single();
Expand All @@ -44,9 +46,9 @@ public void ConverterFailingWithAnotherFamilyIsABug() {
var bind = Bind.PropertiesOf(Request()).FailWith(BookingEnvelopeError.CommandInvalid);

InfrastructureError foreignFamily =
InfrastructureError.Create(ErrorCode.Create("TEST_FOREIGN_FAMILY"), "A family the port envelope cannot hold.",
InteractionDirection.Outgoing, Transience.Unknown)
.WithPublicMessage("Foreign.");
InfrastructureError.Create(Any.ErrorCode(), Any.DiagnosticMessage(),
Any.InteractionDirection(), Transience.Unknown)
.WithPublicMessage(Any.ShortMessage());

InvalidOperationException exception = Assert.Throws<InvalidOperationException>(
() => { bind.SimpleProperty(r => r.GuestEmail).AsRequired(_ => Outcome<EmailAddress>.Failure(foreignFamily)); });
Expand Down Expand Up @@ -87,9 +89,9 @@ public void BareNestedElementFailureIsWrapped() {
}

private static PrimaryPortError LeafPortError() {
return PrimaryPortError.Create(ErrorCode.Create("TEST_NESTED_PORT_LEAF"), "A bare port-level leaf, not a build-terminal envelope.",
Transience.NonTransient)
.WithPublicMessage("Rejected.");
return PrimaryPortError.Create(ErrorCode.Create("TEST_NESTED_PORT_LEAF"), Any.DiagnosticMessage(),
Any.Transience())
.WithPublicMessage(Any.ShortMessage());
}

[Fact(DisplayName = "A required nested binding that fails with a bare PrimaryPortError leaf (not its build-terminal envelope) is wrapped, so the path survives.")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using System.Reflection;

using FirstClassErrors.Testing;

using NFluent;

#endregion
Expand All @@ -24,8 +26,8 @@ private static Outcome<Stay> BindStay(RequestBinder<StayDto> stay) {
private static Outcome<Stay> AssembleStay(BookingDate checkIn, BookingDate checkOut) {
return checkOut.Value > checkIn.Value
? Outcome<Stay>.Success(new Stay(checkIn, checkOut))
: Outcome<Stay>.Failure(DomainError.Create(CheckOutNotAfterCheckIn, "Check-out must be after check-in.")
.WithPublicMessage("The stay dates are inconsistent."));
: Outcome<Stay>.Failure(DomainError.Create(CheckOutNotAfterCheckIn, Any.DiagnosticMessage())
.WithPublicMessage(Any.ShortMessage()));
}

[Fact(DisplayName = "New assembles the command exactly once when every property bound.")]
Expand Down
15 changes: 0 additions & 15 deletions FirstClassErrors.UnitTests/ErrorCodeFactory.cs

This file was deleted.

4 changes: 3 additions & 1 deletion FirstClassErrors.UnitTests/ErrorCodeTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#region Usings declarations

using FirstClassErrors.Testing;

using JetBrains.Annotations;

using NFluent;
Expand Down Expand Up @@ -72,7 +74,7 @@ public void ErrorCodesWithDifferentCodesAreNotEqual() {
[Fact(DisplayName = "An error code compared to null is not equal.")]
public void ErrorCodeComparedToNullIsNotEqual() {
// Setup
ErrorCode? errorCode = ErrorCode.Create("NULL_TEST");
ErrorCode? errorCode = Any.ErrorCode();

// Exercise
bool result = errorCode.Equals(null);
Expand Down
10 changes: 6 additions & 4 deletions FirstClassErrors.UnitTests/ErrorContextImmutabilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using System.Diagnostics.CodeAnalysis;

using FirstClassErrors.Testing;

using JetBrains.Annotations;

using NFluent;
Expand Down Expand Up @@ -31,8 +33,8 @@ public void Dispose() {
public void TheContextValuesAreReadOnlyAndCannotBeMutated() {
// Setup
ErrorContextKey<string> key = ErrorContextKey.Create<string>("K");
ErrorContext context = DomainError.Create(ErrorCode.Unspecified, "m",
ctx => ctx.Add(key, "value")).WithPublicMessage("m").Context;
ErrorContext context = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(),
ctx => ctx.Add(key, Any.String())).WithPublicMessage(Any.ShortMessage()).Context;

// Exercise & verify
Check.ThatCode(() => ((IDictionary<ErrorContextKey, object?>)context.Values).Clear())
Expand All @@ -43,8 +45,8 @@ public void TheContextValuesAreReadOnlyAndCannotBeMutated() {
public void AnEntryWhoseStoredValueIsNullIsPresentButReportedAsNotFound() {
// Setup
ErrorContextKey<string> key = ErrorContextKey.Create<string>("K");
ErrorContext context = DomainError.Create(ErrorCode.Unspecified, "m",
ctx => ctx.Add(key, null)).WithPublicMessage("m").Context;
ErrorContext context = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(),
ctx => ctx.Add(key, null)).WithPublicMessage(Any.ShortMessage()).Context;

// Exercise
bool found = context.TryGet(key, out _);
Expand Down
44 changes: 23 additions & 21 deletions FirstClassErrors.UnitTests/ErrorDefensiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using System.Diagnostics.CodeAnalysis;

using FirstClassErrors.Testing;

using JetBrains.Annotations;

using NFluent;
Expand Down Expand Up @@ -30,7 +32,7 @@ public void Dispose() {
[Fact(DisplayName = "A null error code is replaced by the unspecified error code.")]
public void ANullErrorCodeIsReplacedByTheUnspecifiedErrorCode() {
// Exercise
DomainError error = DomainError.Create(null!, "diagnostic").WithPublicMessage("short");
DomainError error = DomainError.Create(null!, Any.DiagnosticMessage()).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.Code).IsSameReferenceAs(ErrorCode.Unspecified);
Expand All @@ -39,7 +41,7 @@ public void ANullErrorCodeIsReplacedByTheUnspecifiedErrorCode() {
[Fact(DisplayName = "A null diagnostic message is replaced by a fallback sentinel.")]
public void ANullDiagnosticMessageIsReplacedByAFallbackSentinel() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, null!).WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), null!).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.DiagnosticMessage).IsEqualTo(Error.MissingDiagnosticMessage);
Expand All @@ -51,7 +53,7 @@ public void ANullDiagnosticMessageIsReplacedByAFallbackSentinel() {
[InlineData(" ")]
public void AnEmptyOrWhitespaceDiagnosticMessageIsReplacedByAFallbackSentinel(string value) {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, value).WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), value).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.DiagnosticMessage).IsEqualTo(Error.MissingDiagnosticMessage);
Expand All @@ -60,7 +62,7 @@ public void AnEmptyOrWhitespaceDiagnosticMessageIsReplacedByAFallbackSentinel(st
[Fact(DisplayName = "The diagnostic message is trimmed.")]
public void TheDiagnosticMessageIsTrimmed() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, " hello ").WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), " hello ").WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.DiagnosticMessage).IsEqualTo("hello");
Expand All @@ -69,7 +71,7 @@ public void TheDiagnosticMessageIsTrimmed() {
[Fact(DisplayName = "A null short message is replaced by a fallback sentinel.")]
public void ANullShortMessageIsReplacedByAFallbackSentinel() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage(null!);
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(null!);

// Verify
Check.That(error.ShortMessage).IsEqualTo(Error.MissingShortMessage);
Expand All @@ -81,7 +83,7 @@ public void ANullShortMessageIsReplacedByAFallbackSentinel() {
[InlineData(" ")]
public void AnEmptyOrWhitespaceShortMessageIsReplacedByAFallbackSentinel(string value) {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage(value);
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(value);

// Verify
Check.That(error.ShortMessage).IsEqualTo(Error.MissingShortMessage);
Expand All @@ -90,7 +92,7 @@ public void AnEmptyOrWhitespaceShortMessageIsReplacedByAFallbackSentinel(string
[Fact(DisplayName = "A missing diagnostic message is recorded in the context under #MISSING_REQUIRED_MESSAGE.")]
public void AMissingDiagnosticMessageIsRecordedInTheContext() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, null!).WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), null!).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.Context.Values.ContainsKey(ErrorContextKey.MissingRequiredMessages)).IsTrue();
Expand All @@ -102,7 +104,7 @@ public void AMissingDiagnosticMessageIsRecordedInTheContext() {
[Fact(DisplayName = "Missing diagnostic and short messages are both recorded in the context.")]
public void MissingDiagnosticAndShortMessagesAreBothRecordedInTheContext() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, null!).WithPublicMessage(null!);
DomainError error = DomainError.Create(Any.ErrorCode(), null!).WithPublicMessage(null!);

// Verify
IReadOnlyList<string> missing = (IReadOnlyList<string>)error.Context.Values[ErrorContextKey.MissingRequiredMessages]!;
Expand All @@ -112,7 +114,7 @@ public void MissingDiagnosticAndShortMessagesAreBothRecordedInTheContext() {
[Fact(DisplayName = "Present mandatory messages leave no #MISSING_REQUIRED_MESSAGE entry in the context.")]
public void PresentMandatoryMessagesLeaveNoMissingEntryInTheContext() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.Context.Values.ContainsKey(ErrorContextKey.MissingRequiredMessages)).IsFalse();
Expand All @@ -121,7 +123,7 @@ public void PresentMandatoryMessagesLeaveNoMissingEntryInTheContext() {
[Fact(DisplayName = "The short message is trimmed.")]
public void TheShortMessageIsTrimmed() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage(" short ");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(" short ");

// Verify
Check.That(error.ShortMessage).IsEqualTo("short");
Expand All @@ -130,7 +132,7 @@ public void TheShortMessageIsTrimmed() {
[Fact(DisplayName = "A null detailed message leaves the detailed message null.")]
public void ANullDetailedMessageLeavesTheDetailedMessageNull() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage("short", null);
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(Any.ShortMessage(), null);

// Verify
Check.That(error.DetailedMessage).IsNull();
Expand All @@ -142,7 +144,7 @@ public void ANullDetailedMessageLeavesTheDetailedMessageNull() {
[InlineData(" ")]
public void AnEmptyOrWhitespaceDetailedMessageLeavesTheDetailedMessageNull(string value) {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage("short", value);
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(Any.ShortMessage(), value);

// Verify
Check.That(error.DetailedMessage).IsNull();
Expand All @@ -151,7 +153,7 @@ public void AnEmptyOrWhitespaceDetailedMessageLeavesTheDetailedMessageNull(strin
[Fact(DisplayName = "A detailed message is trimmed when provided.")]
public void ADetailedMessageIsTrimmedWhenProvided() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic").WithPublicMessage("short", " detailed ");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage()).WithPublicMessage(Any.ShortMessage(), " detailed ");

// Verify
Check.That(error.DetailedMessage).IsEqualTo("detailed");
Expand All @@ -160,8 +162,8 @@ public void ADetailedMessageIsTrimmedWhenProvided() {
[Fact(DisplayName = "A configure-context delegate that throws is captured into the context.")]
public void AConfigureContextDelegateThatThrowsIsCapturedIntoTheContext() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic", _ => throw new InvalidOperationException("boom"))
.WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(), _ => throw new InvalidOperationException("boom"))
.WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.Context.IsEmpty).IsFalse();
Expand All @@ -178,12 +180,12 @@ public void AConfigureContextDelegateThatThrowsPreservesTheEntriesAddedBeforeThe
ErrorContextKey<string> beforeKey = ErrorContextKey.Create<string>("BeforeFailure");

// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic", builder => {
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(), builder => {
builder.Add(beforeKey, "kept");

throw new InvalidOperationException("boom");
})
.WithPublicMessage("short");
.WithPublicMessage(Any.ShortMessage());

// Verify — the entry added before the failure survives alongside the captured exception.
bool found = error.Context.TryGet(beforeKey, out string? kept);
Expand All @@ -198,7 +200,7 @@ public void InnerErrorsAreStoredAsADefensiveCopyOfTheProvidedCollection() {
List<DomainError> innerErrors = new() {
ErrorFactory.Domain(ErrorCode.Unspecified, "first")
};
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic", innerErrors).WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(), innerErrors).WithPublicMessage(Any.ShortMessage());

// Exercise
innerErrors.Add(ErrorFactory.Domain(ErrorCode.Unspecified, "second"));
Expand All @@ -217,7 +219,7 @@ public void NullEntriesInTheProvidedInnerErrorsCollectionAreFilteredOut() {
};

// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic", innerErrors).WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(), innerErrors).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.InnerErrors).CountIs(2);
Expand All @@ -230,7 +232,7 @@ public void ACollectionMadeOnlyOfNullInnerErrorsYieldsAnEmptyInnerErrorsList() {
List<DomainError> innerErrors = new() { null!, null! };

// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "diagnostic", innerErrors).WithPublicMessage("short");
DomainError error = DomainError.Create(Any.ErrorCode(), Any.DiagnosticMessage(), innerErrors).WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.InnerErrors).IsEmpty();
Expand All @@ -239,7 +241,7 @@ public void ACollectionMadeOnlyOfNullInnerErrorsYieldsAnEmptyInnerErrorsList() {
[Fact(DisplayName = "The string representation combines the diagnostic message and the code.")]
public void TheStringRepresentationCombinesTheDiagnosticMessageAndTheCode() {
// Exercise
DomainError error = DomainError.Create(ErrorCode.Unspecified, "boom").WithPublicMessage("short");
DomainError error = DomainError.Create(ErrorCode.Unspecified, "boom").WithPublicMessage(Any.ShortMessage());

// Verify
Check.That(error.ToString()).IsEqualTo("boom (#UNSPECIFIED)");
Expand Down
Loading
Loading