Skip to content

Commit

Permalink
Merge pull request #2 from andrej-dyck/feature/make-expectation-messa…
Browse files Browse the repository at this point in the history
…ge-optional

Make expectation message optional
  • Loading branch information
andrej-dyck authored Aug 11, 2022
2 parents 0b8ce55 + 391551c commit e8e5f87
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 18 deletions.
6 changes: 6 additions & 0 deletions DotNet.Extensions.Require.Test/ChecksTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ public void Check_ThrowsCheckFailed_WhenRequirementIsNotSatisfied() =>
() => 1.Check(v => v != 1, expectation: "message")
)?.WithMessage("message");

[Test]
public void Check_UsesArgumentExpressionAsExpectation_WhenNoMessageIsGiven() =>
Assert.Throws<CheckFailed>(
() => 1.Check(v => v != 1)
)?.WithMessage("expected: v => v != 1");

[Test]
public void Check_DoesNotThrow_WhenRequirementIsSatisfied() =>
Assert.DoesNotThrow(
Expand Down
16 changes: 15 additions & 1 deletion DotNet.Extensions.Require.Test/PreconditionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public sealed class BasicRequire : PreconditionsTests
{
protected override T Require<T>(T subject, bool condition, string expectation = "") =>
subject.Require(condition, expectation);

[Test]
public void Require_UsesArgumentExpressionAsExpectation_WhenNoMessageIsGiven() =>
Assert.Throws<ArgumentException>(
() => 1.Require(condition: 1 < 0)
)?.WithMessage("expected: 1 < 0");
}

public sealed class RequireWithLazyMessage : PreconditionsTests
Expand Down Expand Up @@ -83,6 +89,12 @@ public sealed class RequireWithPredicateAndConstantMessage : RequireWithPredicat
{
protected override T Require<T>(T subject, Predicate<T> requirement, string expectation = "") =>
subject.Require(requirement, expectation);

[Test]
public void Require_UsesArgumentExpression_WhenNoExpectationMessageIsGiven() =>
Assert.Throws<ArgumentException>(
() => 1.Require(requirement: n => n < 0)
)?.WithMessage("expected: n => n < 0");
}

public sealed class RequireWithPredicateAndLazyMessage : RequireWithPredicate
Expand Down Expand Up @@ -111,6 +123,8 @@ public void Require_CanUseTheValue_ToConstructTheExpectation() =>
[Test]
public void Require_DoesNotBuildExpectationMessage_WhenRequirementIsSatisfied() =>
Assert.DoesNotThrow(
() => 1.Require(requirement: _ => true, value => throw new NUnitException($"must not be called for {value}"))
() => 1.Require(
requirement: _ => true,
value => throw new NUnitException($"must not be called for {value}"))
);
}
14 changes: 11 additions & 3 deletions DotNet.Extensions.Require/Checks.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
namespace DotNet.Extensions.Require;
using System.Runtime.CompilerServices;

namespace DotNet.Extensions.Require;

public static class Checks
{
public static T Check<T>(this T @this, Predicate<T> requirement, string expectation) =>
requirement(@this) ? @this : throw new CheckFailed(expectation);
public static T Check<T>(
this T @this,
Predicate<T> requirement,
string? expectation = null,
[CallerArgumentExpression("requirement")]
string argExpr = "?"
) =>
requirement(@this) ? @this : throw new CheckFailed(expectation ?? $"expected: {argExpr}");

public static T Check<T>(this T @this, Predicate<T> requirement, Func<string> expectation) =>
requirement(@this) ? @this : throw new CheckFailed(expectation());
Expand Down
24 changes: 19 additions & 5 deletions DotNet.Extensions.Require/Preconditions.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
namespace DotNet.Extensions.Require;
using System.Runtime.CompilerServices;

namespace DotNet.Extensions.Require;

public static class Preconditions
{
public static T Require<T>(this T @this, bool condition, string expectation) =>
condition ? @this : throw new ArgumentException(expectation);
public static T Require<T>(
this T @this,
bool condition,
string? expectation = null,
[CallerArgumentExpression("condition")]
string argExpr = "?"
) =>
condition ? @this : throw new ArgumentException(expectation ?? $"expected: {argExpr}");

public static T Require<T>(this T @this, bool condition, Func<string> expectation) =>
condition ? @this : throw new ArgumentException(expectation());

public static T Require<T>(this T @this, bool condition, Func<T, string> expectation) =>
condition ? @this : throw new ArgumentException(expectation(@this));

public static T Require<T>(this T @this, Predicate<T> requirement, string expectation) =>
requirement(@this) ? @this : throw new ArgumentException(expectation);
public static T Require<T>(
this T @this,
Predicate<T> requirement,
string? expectation = null,
[CallerArgumentExpression("requirement")]
string argExpr = "?"
) =>
requirement(@this) ? @this : throw new ArgumentException(expectation ?? $"expected: {argExpr}");

public static T Require<T>(this T @this, Predicate<T> requirement, Func<string> expectation) =>
requirement(@this) ? @this : throw new ArgumentException(expectation());
Expand Down
35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![codecov](https://codecov.io/gh/andrej-dyck/dotnet-extensions-require/branch/main/graph/badge.svg?token=9IL6K5CX37)](https://codecov.io/gh/andrej-dyck/dotnet-extensions-require)
[![NuGet](https://badgen.net/nuget/v/Require-Expressions)](https://www.nuget.org/packages/Require-Expressions/)

A small library that provides _pre-condition_ checks as an extension method on types.
A small library that provides _pre-condition_ checks as an extension method on types.
This allows the client code to check a condition on arguments and use the value directly if no exception is thrown.

```csharp
Expand All @@ -24,18 +24,23 @@ dotnet add package Require-Expressions
## Examples for `Require`

Basic `Require` function

```csharp
var requestedSeats = seats.Require(condition: seats > 0);
// throws ArgumentException with "expected: seats > 0" as message when condition is not met
var requestedSeats = seats.Require(
condition: seats > 0,
expectation: "expected: seats > 0"
); // throws ArgumentException with expectation as message when condition is not met
expectation: "Expected positive number of seats" // or with custom expectation message
);
```

`Require` functions with lazily constructed expectation messages

```csharp
var requestedSeats = seats.Require(
condition: seats > 0,
expectation: () => "expected: seats > 0"
expectation: () => "Expected positive number of seats"
);

var requestedDate = reservationDate.Require(
Expand All @@ -45,10 +50,14 @@ var requestedDate = reservationDate.Require(
```

`Require` functions with predicate for convenience

```csharp
var requestedSeats = seats.Require(condition: s => s > 0);
// throws ArgumentException with "expected: s => s > 0" as message when condition is not met
var requestedSeats = seats.Require(
condition: s => s > 0,
expectation: "expected: seats > 0"
expectation: "Expected positive number of seats" // or with custom expectation message
);

var requestedDate = reservationDate.Require(
Expand All @@ -60,14 +69,16 @@ var requestedDate = reservationDate.Require(
## Examples for `Check`

For checks other than on arguments, the `Check` function can be used

```csharp
var reservationRequest = JsonSerializer.Deserialize<Reservation>(request)
.Check(requirement: r => r.Seats > 0, expectation: "expected: seats > 0")
.Check(requirement: r => r.Seats > 0) // expectation message will be "expected r => r.Seats > 0"
.Check(r => r.Date > now, r => $"Reservation date {r.Date} must be in the future")
// throws CheckFailed with expectation as message when requirement is not satisfied
```

With `Check`, custom exceptions can be constructed

```csharp
var reservationRequest = JsonSerializer.Deserialize<Reservation>(request)
.Check(r => r.Seats > 0, exception: () => new SeatsMustBePositive())
Expand All @@ -78,28 +89,34 @@ var reservationRequest = JsonSerializer.Deserialize<Reservation>(request)

**Why yet another preconditions library?**

Most libraries I found use precondition _statements_, while I found preconditions as _expressions_ more useful. For example, the latter allows for [_expression-bodied_ functions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members).
Most libraries I found use precondition _statements_, while I found preconditions as _expressions_ more useful.
For example, the latter allows for [_expression-bodied_ functions](
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members
).

**I don't like extension methods**

No problem, there are many other great precondition libraries. Have a look at [.Net's contracts](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.contracts?view=net-6.0).
No problem, there are many other great precondition libraries. Have a look
at [.Net's contracts](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.contracts?view=net-6.0).

**I don't want yet another dependency**

No problem, just copy&paste the code you need into your project. Or use the build-in .Net contracts.

**Can you add this function too?**

Probably not as I want to keep this library small and focused. But you are very welcome to post a PR.
Probably not as I want to keep this library small and focused. But you are very welcome to post a PR.

## Build & Test

**Build project**

```shell
dotnet build
```

**Run unit tests**

```shell
dotnet test
```

0 comments on commit e8e5f87

Please sign in to comment.