Validation library inspired by the concepts of Secure by Design, by Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano (MEAP 2019 Manning Publications). (Preconditions, Postconditions, Invariants & Proto-Primitives at https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts)
Remember that arguments are actual values to methods declared parameters. The utility class used to validate arguments is Triplex.Validations.Arguments
. If an argument is invalid this utility will throw an System.ArgumentException
or one of its derivatives.
Sometimes we need to check internal state before or after certain operation to ensure preconditions and invariants respectively. To check for those we use Triplex.Validation.State
utility. This one throws System.InvalidOperationException
or one of its derivatives.
- Do reject bad input as soon as possible specially
null
s. - Control data coming from public interfaces.
- Help API creators to enforce code contracts.
- Remove compiler warnings around
null
checks.
For arguments, all checks imply a null
check first. So if you see something like _value = Arguments.OrException(value);
means "return the exact same value or throw an exception if it is null
". By the same logic, Arguments.LessThan(value, other);
means "return the exact same value if is less than other
otherwise throw an exception, but if some argument value is
null
throw ArgumentNullException
.
- Simle
Email
wrapper class with Warning
(Fig. 01 - Email
class showing warning)
(Fig. 02 - Email
class showing expanded warning)
- Same issue even when we declare argument as "non-nullable"
string
instead ofstring?
(Fig. 03 - Email
class showing even with non-null reference type)
- Removing warning using
Arguments.OrException(value)
(Fig. 04 - Issue solved using block style)
(Fig. 05 - Issue solved using expression-method style)
using Triplex.Validations;
public sealed class Email
{
private readonly string _value;
public Email(string value)
{
//a) Simple form (param name for ArgumentException is automatically taken from 1st parameter)
Arguments.OrException(value);
//b) With custom message (param name for ArgumentException is automatically taken from 1st parameter)
Arguments.OrExceptionWithMessage(value, "Invalid email");
// Use value ensuring that it is not null now
_value = value.ToLowerInvariant();
// or use returned value from argument check
_value = Arguments.OrException(value); //return same input (literally same reference)
}
// Collapse Check-Then-Assign pattern using returned value from checks.
public Email(string username, string domain)
=> _value = $"{Arguments.OrException(username)}@{Arguments.OrException(domain)}";
}
All checks has two forms:
- Data and inferred argument name. Like
Arguments.OrException(someArg);
- Data, custom message, and inferred argument name. Like
Arguments.OrExceptionWithMessage(someArg, "Your custom exception message goes here");
If you need to use a different argument name, both forms support a last optional parameter:
Arguments.OrException(someArg, "manualArgumentName");
Arguments.OrExceptionWithMessage(someArg, "Custom ex. message", "manualArgumentName");
It is not required to use nameof(someArg)
the following forms are equivalent
Arguments.OrException(someArg);
≡Arguments.OrException(someArg, nameof(someArg));
Arguments.OrExceptionWithMessage(someArg, "Custom ex. message");
≡Arguments.OrExceptionWithMessage(someArg, "Custom ex. message", nameof(someArg));
using Triplex.Validations;
public sealed class MyList<T>
{
public T Remove(int index) {
//0. Pre-condition
State.IsTrue(_size > 0, "Unable to remove elements when empty.");
//1. Contract
Arguments.Between(index, 0, _size - 1, "Index out of bounds.");
//2. Do your stuff
T removed = DoRemoveElementAt(index);
//3. After removing size must be zero or greater
State.StillHolds(_size >= 0, "Internal error, size must not be negative.");
return removed;
}
}
State checks semantics:
- Preconditions:
Is*(expression)
whereexpression
depends on the actual check. Could beboolean
or just a value (like inState.IsNotNull(value)
). - Postconditions:
Still*(expression)
where expression depends on the actual check. Could beboolean
or just a value.
There is no argument name, because State
is used to validate objects internal state, as a hole, or invariants.
We like to get help from everybody, if you want to contribute to this tool, found some issue or just want a feature request please read, first, the Contributing guide.
- Fork + Clone this repo
- Build and Run Tests locally
- Build only
dotnet build
- Full clean, build and test
dotnet clean && dotnet test /p:ExcludeByAttribute=GeneratedCode /p:CollectCoverage=true
- Build only
- Incremental build and test (regular dev workflow)
dotnet test /p:CollectCoverage=true
At the end of a test run you should see something like
+---------------------+------+--------+--------+
| Module | Line | Branch | Method |
+---------------------+------+--------+--------+
| Triplex.Validations | 100% | 100% | 100% |
+---------------------+------+--------+--------+
+---------+------+--------+--------+
| | Line | Branch | Method |
+---------+------+--------+--------+
| Total | 100% | 100% | 100% |
+---------+------+--------+--------+
| Average | 100% | 100% | 100% |
+---------+------+--------+--------+
You should maintain a 100% of test coverage, please do not submit Pull Requests if you are unable to do that. If you need help, or does not know how to achieve the expected coverage percentage please contact maintainers for advice.