IOKode.OpinionatedFramework.Generators.Ensuring 1.1.0

Prefix Reserved
dotnet add package IOKode.OpinionatedFramework.Generators.Ensuring --version 1.1.0                
NuGet\Install-Package IOKode.OpinionatedFramework.Generators.Ensuring -Version 1.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IOKode.OpinionatedFramework.Generators.Ensuring" Version="1.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add IOKode.OpinionatedFramework.Generators.Ensuring --version 1.1.0                
#r "nuget: IOKode.OpinionatedFramework.Generators.Ensuring, 1.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install IOKode.OpinionatedFramework.Generators.Ensuring as a Cake Addin
#addin nuget:?package=IOKode.OpinionatedFramework.Generators.Ensuring&version=1.1.0

// Install IOKode.OpinionatedFramework.Generators.Ensuring as a Cake Tool
#tool nuget:?package=IOKode.OpinionatedFramework.Generators.Ensuring&version=1.1.0                

Overview

OpinionatedFramework Ensure API is a comprehensive library written in C# for the validation of preconditions, postconditions, and invariants.

One of the main features of this library is the ability to customize the exception thrown during each validation. It also excels in its extensibility, making it easy to add new validations.

While the Ensure API is part of the OpinionatedFramework, it is distributed in a separately NuGet package and remains decoupled from it.

Installing

Install the NuGet package IOKode.OpinionatedFramework.Ensuring.

Getting started

The static Ensure class serves as the entry point to the library's functionality. The API is designed to offer natural usage to the developers.

The basic usage of the API follows this pattern:

Ensure.{Validation Category}.{Validation}.ElseThrows(exception);

Let's take a look at some examples:

Ensure.Stream.CanRead(dataStream).ElseThrows(new InvalidOperationException("Cannot read the stream."));
Ensure.String.IsNotNullOrEmpty(firstName).ElseThrows(new ArgumentNullException(nameof(firstName)));

Built-in validations category (ensurers)

Each "validation category" is called "ensurer".

There are seven built-in ensurers available. Take a look in the source code:

  • Boolean
  • Enumerable
  • Number
  • Object
  • Stream
  • String
  • Type

Adding custom ensurers

Extending the Ensure API with custom validations is straightforward. First, you need to reference the IOKode.OpinionatedFramework.Generators.Ensuring package:

<ItemGroup>
    <PackageReference Include="IOKode.OpinionatedFramework.Ensuring" Version="1.0.1" />
    <PackageReference Include="IOKode.OpinionatedFramework.Generators.Ensuring" Version="1.0.1" />
</ItemGroup>

Next, you should create a static class with public static methods that return a boolean value and decorate the class with the [Ensurer] attribute. The class name should end with Ensurer. A set of source-generators take care of generating the Ensure class and other needed stuff.

Here's an example of a custom ensurer for the Person class.

[Ensurer]
public static class PersonEnsurer
{
    public static bool IsInLegalAge(Person person)
    {
        return person.Age >= 18;
    }
}

That's all. Now you can use it:

Ensure.Person.IsInLegalAge(john).ElseThrows(new PersonIsUnderLegalAgeException(john));

Custom exception throwing

ElseThrows method will throw the exception if the validation not passes.

To make exception selection more convenient, the library includes a set of extension methods:

  • ElseThrowsNullArgument(string paramName) // throws ArgumentNullException
  • ElseThrowsIllegalArgument(string paramName, string message) // throws ArgumentException
  • ElseThrowsInvalidOperation(string message) // throws InvalidOperationException
  • ElseThrowsBase(string message) // throws Exception

Using these extension methods, the previous examples can be rewritten as:

Ensure.Stream.CanRead(dataStream).ElseThrowsInvalidOperation("Cannot read the stream.");
Ensure.String.IsNotNullOrEmpty(firstName).ElseThrowsNullArgument(nameof(firstName));

These methods provide a convenient way to throw custom exceptions that best fit the context of your application.

Adding your own extension methods for throwing

You're free to add your own extension methods for throwing exceptions as needed.

The responsible of throwing the exception is the Thrower class, so you can add an extension method like this:

public static class PersonThrowerExtensions
{
    public static void ElseThrowsPersonUnderLegalAge(this Thrower thrower, Person person)
    {
        var exception = new PersonIsUnderLegalAgeException(person);
        thrower.ElseThrows(exception);
    }
}

To use this extension method, simply call it like you would call the pre-existing exception extension methods:

Ensure.Person.IsInLegalAge(john).ElseThrowsPersonUnderLegalAge(john);

In the above line, we used a custom ensurer and an a custom thrower's extension method.

Behind the scenes

When utilizing the OpinionatedFramework Ensure API, you are harnessing a chain of calls that seamlessly connect your validation logic with exception handling. Let's unpack this chain to understand how it functions.

  1. Ensurer: The static Ensurer class is the entry point of the validation API. It's is a generated class that contains properties that correspond to each EnsurerThrowers – bridges between Ensurers and the Thrower.
// Generated code
public static class Ensurer
{
    // Other Ensurer-Thrower bridges
    public static PersonEnsurerThrower Person => new PersonEnsurerThrower();
    // Other Ensurer-Thrower bridges
}
  1. {EnsurerName}EnsurerThrower: For each ensurer, a corresponding {EnsurerName}EnsurerThrower class is generated. This class comprises all methods from the original ensurer, but instead of returning a boolean, it yields an instance of the Thrower class.
// Generated code
public class PersonEnsurerThrower
{
    public Thrower IsInLegalAge(Person person)
    {
        bool isValid = PersonEnsurer.IsInLegalAge(person); // Call to the Ensurer's static method.
        return new Thrower(isValid); // Returns a Thrower instance with the validation result.
    }
}

  1. Thrower: The Thrower instance returned by the {EnsurerName}EnsurerThrower carries the result of the validation check. By calling the ElseThrows(Exception) method on the Thrower instance, an exception is thrown if the validation has not passed.
Ensurer // Entry point
    .Person // Returns the Ensurer-Thrower bridge
        .IsInLegalAge(john) // Returns a Thrower instance
            .ElseThrows(new PersonIsUnderLegalAgeException(john)) // Throws the exception if the validation has not passed
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.0 227 6/21/2023
1.0.4 160 6/18/2023
1.0.3 163 6/18/2023
1.0.2 157 6/18/2023
1.0.1 189 6/3/2023
1.0.1-preview3 229 6/3/2023
1.0.1-preview1 287 6/3/2023
0.3.0-dev 101 5/28/2024
0.2.0-dev 94 5/28/2024