Giter Club home page Giter Club logo

throw's Introduction

drawing

NuGet

Build publish Throw to NuGet codecov

GitHub contributors GitHub Stars GitHub license


A simple, fluent, extensible, and fully customizable library for throwing exceptions using .NET 6+

dotnet add package throw

Getting started png


Give it a star ⭐!

Loving it? Show your support by giving this project a star!

Nullable vs non-nullable types

This library is designed to work best with nullable reference types feature enabled.

The Throw() method is the entry method for all non-nullable types:

string name = "hello";
name.Throw().IfLongerThan(10);

And ThrowIfNull() for any nullable type:

string? name = "hello";
name.ThrowIfNull();

Trying to use Throw() on a nullable type will give a warning

string? name = null;
name.Throw() // warning CS8714: The type 'string?' cannot be used as type parameter 'TValue' in the generic type or method 'ValidatableCreationExtensions.Throw<TValue>(TValue, ExceptionCustomizations?, string?)'. Nullability of type argument 'string?' doesn't match 'notnull' constraint.
    .IfEmpty();

After validating that the nullable type isn't null, all the regular non-nullable rules can be used

name.ThrowIfNull()
    .IfEmpty()
    .IfLongerThan(3);

The expression can be implicitly cast to the non-nullable type of the original nullable type

string? name = "Amichai";
string nonNullableName = name.ThrowIfNull()
    .IfEmpty()
    .IfLongerThan(10);

or

int? a = 5;
int b = a.ThrowIfNull();

Customize everything

How customizing the exception affects the chained rules

If you have customized the exception, any rule that throws an exception will use the customization. For example:

// Default behavior:
name.Throw()
    .IfEmpty() // System.ArgumentException: String should not be empty. (Parameter 'name')
    .IfWhiteSpace() // System.ArgumentException: String should not be white space only. (Parameter 'name')
    .IfLongerThan(3) // System.ArgumentException: String should not be longer than 3 characters. (Parameter 'name')
    .IfShorterThan(10); // System.ArgumentException: String should not be shorter than 10 characters. (Parameter 'name')

// Customized behavior:
name.Throw(paramName => throw new MyCustomException($"Param name: {paramName}."))
    .IfEmpty() // MyCustomException: Param name: name.
    .IfWhiteSpace() // MyCustomException: Param name: name.
    .IfLongerThan(3) // MyCustomException: Param name: name.
    .IfShorterThan(10); // MyCustomException: Param name: name.

At any point, you can change the exception customization, and it will apply for all the rules that follow. For example:

name.Throw("String should not be empty or white space only.")
        .IfEmpty() // System.ArgumentException: String should not be empty or white space only. (Parameter 'name')
        .IfWhiteSpace() // System.ArgumentException: String should not be empty or white space only. (Parameter 'name')
    .Throw("String should not be between 3 and 10 characters long.")
        .IfLongerThan(3) // System.ArgumentException: String should not be between 3 and 10 characters long. (Parameter 'name')
        .IfShorterThan(10); // System.ArgumentException: String should not be between 3 and 10 characters long. (Parameter 'name')

To go back to the default exception, simply use the Throw() method. For example:

name.Throw("String should not be empty or white space only.")
        .IfEmpty() // System.ArgumentException: String should not be empty or white space only. (Parameter 'name')
        .IfWhiteSpace() // System.ArgumentException: String should not be empty or white space only. (Parameter 'name')
    .Throw()
        .IfLongerThan(3) // System.ArgumentException: String should not be longer than 3 characters. (Parameter 'name')
        .IfShorterThan(10); // System.ArgumentException: String should not be shorter than 10 characters. (Parameter 'name')

Exception customizations

1. Throw()

Each rule has a default behavior. If you don't customize the exception, the default behavior will be used.

Use the Throw() or ThrowIfNull() method to throw the default exception

// ArgumentNullException: Value cannot be null. (Parameter 'nullableValue')
nullableValue.ThrowIfNull();

// System.ArgumentOutOfRangeException: Value should not be less than 2/28/2042 4:41:46 PM. (Parameter 'dateTime')\n Actual value was 2/28/2022 4:41:46 PM.
dateTime.Throw().IfLessThan(DateTime.Now.AddYears(20));

// ArgumentException: Value should not be true (Parameter 'isGood')
isGood.Throw().IfTrue();

// System.ArgumentException: String should not be empty. (Parameter 'name')
name.Throw().IfEmpty();

// System.ArgumentOutOfRangeException: Value should not be greater than 0. (Parameter 'number')\n Actual value was 5.
number.Throw().IfPositive();

2. Throw("My custom message")

Pass a custom exception message to the Throw() or ThrowIfNull() method

// System.ArgumentNullException: My custom message (Parameter 'nullableValue')
nullableValue.ThrowIfNull("My custom message");

// System.ArgumentOutOfRangeException: My custom message (Parameter 'dateTime')\n Actual value was 3/1/2022 10:47:15 AM.
dateTime.Throw("My custom message").IfLessThan(DateTime.Now.AddYears(20));

// System.ArgumentException: My custom message (Parameter 'isGood')
isGood.Throw("My custom message").IfTrue();

// System.ArgumentException: My custom message (Parameter 'name')
name.Throw("My custom message").IfEmpty();

// System.ArgumentOutOfRangeException: My custom message (Parameter 'number')\n Actual value was 5.
number.Throw("My custom message").IfPositive();

3. Throw(() => new MyException())

Pass a custom exception thrower to the Throw() or ThrowIfNull() method

// MyCustomException: Exception of type 'MyCustomException' was thrown.
nullableValue.ThrowIfNull(() => throw new MyCustomException());

// MyCustomException: Exception of type 'MyCustomException' was thrown.
dateTime.Throw(() => throw new MyCustomException()).IfLessThan(DateTime.Now.AddYears(20));

// MyCustomException: Exception of type 'MyCustomException' was thrown.
isGood.Throw(() => throw new MyCustomException()).IfTrue();

// MyCustomException: Exception of type 'MyCustomException' was thrown.
name.Throw(() => throw new MyCustomException()).IfEmpty();

// MyCustomException: Exception of type 'MyCustomException' was thrown.
number.Throw(() => throw new MyCustomException()).IfPositive();

4. Throw(paramName => new MyException($"Param: {paramName}")

Pass a custom exception thrower to the Throw() or ThrowIfNull() method, that takes the parameter name as a parameter

This comes in handy in scenarios like this:

void SendEmail(User user)
{
    user.Throw(paramName => new UserException(message: "Cannot send email since user details are invalid.", paramName: paramName))
        .IfWhiteSpace(user => user.FirstName) // UserException: Cannot send email since user details are invalid. (Parameter 'user: user => user.FirstName')
        .IfWhiteSpace(user => user.LastName) // UserException: Cannot send email since user details are invalid. (Parameter 'user: user => user.LastName')
        .IfNull(user => user.Email) // UserException: Cannot send email since user details are invalid. (Parameter 'user: user => user.Email')
        .IfLongerThan(user => user.Email!, 100); // UserException: Cannot send email since user details are invalid. (Parameter 'user: user => user.Email!')

    emailService.TrySendEmail(user)
        .Throw(() => new EmailException("Email could not be sent."))
        .IfFalse();
}
// MyCustomException: Param name: nullableValue.
nullableValue.ThrowIfNull(paramName => throw new MyCustomException($"Param name: {paramName}."));

// MyCustomException: Param name: dateTime.
dateTime.Throw(paramName => throw new MyCustomException($"Param name: {paramName}.")).IfLessThan(DateTime.Now.AddYears(20));

// MyCustomException: Param name: isGood.
isGood.Throw(paramName => throw new MyCustomException($"Param name: {paramName}.")).IfTrue();

// MyCustomException: Param name: name.
name.Throw(paramName => throw new MyCustomException($"Param name: {paramName}.")).IfEmpty();

// MyCustomException: Param name: number.
number.Throw(paramName => throw new MyCustomException($"Param name: {paramName}.")).IfPositive();

Usage

Common types

Booleans

value.Throw().IfTrue(); // ArgumentException: Value should not be true (Parameter 'value')
value.Throw().IfFalse(); // ArgumentException: Value should be true (Parameter 'value')

// Any method which returns bool can inline it's exception throwing logic.
Enum.TryParse("Unexpected value", out EmployeeType value)
    .Throw()
    .IfFalse(); // System.ArgumentException: Value should be true. (Parameter 'Enum.TryParse("Unexpected value", out EmployeeType value)')

Nullable value types (bool?, int?, double?, DateTime? etc.)

bool? value = null;

value.ThrowIfNull(); // ArgumentNullException: Value cannot be null. (Parameter 'value')

// After validating `ThrowIfNull`, any of the regular value type extensions can be used.
value.ThrowIfNull() // ArgumentNullException: Value cannot be null. (Parameter 'value')
    .IfTrue(); // ArgumentException: Value should not be true (Parameter 'value')

// The returned value from `ThrowIfNull` can be implicitly cast to the original non-nullable type.
bool nonNullableValue = value.ThrowIfNull(); // ArgumentNullException: Value cannot be null. (Parameter 'value')

Strings

name.Throw().IfEmpty(); // System.ArgumentException: String should not be empty. (Parameter 'name')
name.Throw().IfWhiteSpace(); // System.ArgumentException: String should not be white space only. (Parameter 'name')
name.Throw().IfLengthEquals(7); // System.ArgumentException: String length should not be equal to 7. (Parameter 'name')
name.Throw().IfLengthNotEquals(10); // System.ArgumentException: String length should be equal to 10. (Parameter 'name')
name.Throw().IfShorterThan(10); // System.ArgumentException: String should not be shorter than 10 characters. (Parameter 'name')
name.Throw().IfLongerThan(3); // System.ArgumentException: String should not be longer than 3 characters. (Parameter 'name')
name.Throw().IfEquals("Amichai"); // System.ArgumentException: String should not be equal to 'Amichai' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfEquals("Amichai", StringComparison.InvariantCulture); // System.ArgumentException: String should not be equal to 'Amichai' (comparison type: 'InvariantCulture'). (Parameter 'name')
name.Throw().IfEqualsIgnoreCase("AMICHAI"); // System.ArgumentException: String should not be equal to 'AMICHAI' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'name')
name.Throw().IfNotEquals("Dan"); // System.ArgumentException: String should be equal to 'Dan' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfNotEquals("Dan", StringComparison.InvariantCultureIgnoreCase); // System.ArgumentException: String should be equal to 'Dan' (comparison type: 'InvariantCultureIgnoreCase'). (Parameter 'name')
name.Throw().IfNotEqualsIgnoreCase("Dan"); // System.ArgumentException: String should be equal to 'Dan' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'name')
name.Throw().IfContains("substring"); // System.ArgumentException: String should not contain 'substring' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfContains("substring", ComparisonType.InvariantCulture); // System.ArgumentException: String should contain 'substring' (comparison type: 'InvariantCulture'). (Parameter 'name')
name.Throw().IfNotContains("substring"); // System.ArgumentException: String should contain 'substring' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfNotContains("substring", ComparisonType.InvariantCultureIgnoreCase); // System.ArgumentException: String should contain 'substring' (comparison type: 'InvariantCultureIgnoreCase'). (Parameter 'name')
name.Throw().IfStartsWith("Jer"); // System.ArgumentException: String should not start with 'Jer' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfStartsWith("JER", StringComparison.OrdinalIgnoreCase); // System.ArgumentException: String should not start with 'JER' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'name')
name.Throw().IfNotStartsWith("dan"); // System.ArgumentException: String should start with 'dan' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfNotStartsWith("dan", StringComparison.InvariantCultureIgnoreCase); // System.ArgumentException: String should start with 'dan' (comparison type: 'InvariantCultureIgnoreCase'). (Parameter 'name')
name.Throw().IfEndsWith("emy"); // System.ArgumentException: String should not end with 'emy' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfEndsWith("EMY", StringComparison.OrdinalIgnoreCase); // System.ArgumentException: String should not end with 'EMY' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'name')
name.Throw().IfNotEndsWith("dan"); // System.ArgumentException: String should end with 'dan' (comparison type: 'Ordinal'). (Parameter 'name')
name.Throw().IfNotEndsWith("dan", StringComparison.OrdinalIgnoreCase); // System.ArgumentException: String should end with 'dan' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'name')
name.Throw().IfMatches("J.*y"); // System.ArgumentException: String should not match RegEx pattern 'J.*y' (Parameter 'name')
name.Throw().IfMatches("[a-z]{0,10}", RegexOptions.IgnoreCase); // System.ArgumentException: String should not match RegEx pattern '[a-z]{0,10}' (Parameter 'name')
name.Throw().IfNotMatches("^[0-9]+$"); // System.ArgumentException: String should match RegEx pattern '^[0-9]+$' (Parameter 'name')
name.Throw().IfNotMatches("abc ", RegexOptions.IgnorePatternWhitespace); // System.ArgumentException: String should match RegEx pattern '^[0-9]+$' (Parameter 'name')

Collections (IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, etc.)

Important note: if the collection is a non-evaluated expression, the expression will be evaluated.

collection.Throw().IfHasNullElements(); // System.ArgumentException: Collection should not have null elements. (Parameter 'collection')
collection.Throw().IfEmpty(); // System.ArgumentException: Collection should not be empty. (Parameter 'collection')
collection.Throw().IfNotEmpty(); // System.ArgumentException: Collection should be empty. (Parameter 'collection')
collection.Throw().IfCountLessThan(5); // System.ArgumentException: Collection count should not be less than 5. (Parameter 'collection')
collection.Throw().IfCountGreaterThan(1); // System.ArgumentException: Collection count should not be greater than 1. (Parameter 'collection')
collection.Throw().IfCountEquals(0); // System.ArgumentException: Collection count should not be equal to 0. (Parameter 'collection')
collection.Throw().IfCountNotEquals(0); // System.ArgumentException: Collection count should be equal to 0. (Parameter 'collection')
collection.Throw().IfContains("value"); // System.ArgumentException: Collection should not contain element. (Parameter 'person: p => p.Friends')
collection.Throw().IfNotContains("value"); // System.ArgumentException: Collection should contain element. (Parameter 'person: p => p.Friends')

DateTime

dateTime.Throw().IfUtc(); // System.ArgumentException: Value should not be Utc. (Parameter 'dateTime')
dateTime.Throw().IfNotUtc(); // System.ArgumentException: Value should be Utc. (Parameter 'dateTime')
dateTime.Throw().IfDateTimeKind(DateTimeKind.Unspecified); // System.ArgumentException: Value should not be Unspecified. (Parameter 'dateTime')
dateTime.Throw().IfDateTimeKindNot(DateTimeKind.Local); // System.ArgumentException: Value should be Local. (Parameter 'dateTime')
dateTime.Throw().IfGreaterThan(DateTime.Now.AddYears(-20)); // System.ArgumentOutOfRangeException: Value should not be greater than 2/28/2002 4:41:19 PM. (Parameter 'dateTime')
dateTime.Throw().IfLessThan(DateTime.Now.AddYears(20)); // System.ArgumentOutOfRangeException: Value should not be less than 2/28/2042 4:41:46 PM. (Parameter 'dateTime')
dateTime.Throw().IfEquals(other); // System.ArgumentException: Value should not be equal to 2/28/2022 4:44:39 PM. (Parameter 'dateTime')

Enums

employeeType.Throw().IfOutOfRange(); // System.ArgumentOutOfRangeException: Value should be defined in enum. (Parameter 'employeeType')
employeeType.Throw().IfEquals(EmployeeType.FullTime); // System.ArgumentException: Value should not be equal to FullTime. (Parameter 'employeeType')

Equalities (non-nullables)

dateTime.Throw().IfDefault(); // System.ArgumentException: Value should not be default. (Parameter 'dateTime')
dateTime.Throw().IfNotDefault(); // System.ArgumentException: Value should be default. (Parameter 'dateTime')
number.Throw().IfEquals(5); // System.ArgumentException: Value should not be not be equal to 5. (Parameter 'number')
number.Throw().IfNotEquals(3); // System.ArgumentException: Value should be equal to 3. (Parameter 'number')

Uris

uri.Throw().IfHttps(); // System.ArgumentException: Uri scheme should not be https. (Parameter 'uri')
uri.Throw().IfNotHttps(); // System.ArgumentException: Uri scheme should be https. (Parameter 'uri')
uri.Throw().IfHttp(); // System.ArgumentException: Uri scheme should not be http. (Parameter 'uri')
uri.Throw().IfNotHttp(); // System.ArgumentException: Uri scheme should be http. (Parameter 'uri')
uri.Throw().IfScheme(Uri.UriSchemeHttp); // System.ArgumentException: Uri scheme should not be http. (Parameter 'uri')
uri.Throw().IfSchemeNot(Uri.UriSchemeFtp); // System.ArgumentException: Uri scheme should be ftp. (Parameter 'uri')
uri.Throw().IfPort(800); // System.ArgumentException: Uri port should not be 80. (Parameter 'uri')
uri.Throw().IfPortNot(8080); // System.ArgumentException: Uri port should be 8080. (Parameter 'uri')
uri.Throw().IfAbsolute(); // System.ArgumentException: Uri should be relative. (Parameter 'uri')
uri.Throw().IfRelative(); // System.ArgumentException: Uri should be absolute. (Parameter 'uri')
uri.Throw().IfNotAbsolute(); // System.ArgumentException: Uri should be absolute. (Parameter 'uri')
uri.Throw().IfNotRelative(); // System.ArgumentException: Uri should be relative. (Parameter 'uri')
uri.Throw().IfHost("www.google.com"); // System.ArgumentException: Uri host should not be www.google.com. (Parameter 'uri')
uri.Throw().IfHostNot("www.google.com"); // System.ArgumentException: Uri host should be www.google.com. (Parameter 'uri')

Comparable (int, double, decimal, long, float, short, DateTime, DateOnly, TimeOnly etc.)

number.Throw().IfPositive(); // System.ArgumentOutOfRangeException: Value should not be greater than 0. (Parameter 'number')\n Actual value was 5.
number.Throw().IfNegative(); // System.ArgumentOutOfRangeException: Value should not be less than 0. (Parameter 'number')\n Actual value was -5.
number.Throw().IfLessThan(10); // System.ArgumentOutOfRangeException: Value should not be less than 10. (Parameter 'number')\n Actual value was 5.
number.Throw().IfGreaterThan(3); // System.ArgumentOutOfRangeException: Value should not be greater than 3. (Parameter 'number')\n Actual value was 5.
number.Throw().IfGreaterThanOrEqualTo(5); // System.ArgumentOutOfRangeException: Value should not be greater than or equal to 5. (Parameter 'number')\n Actual value was 6.
number.Throw().IfLessThanOrEqualTo(5); // System.ArgumentOutOfRangeException: Value should not be less than or equal to 5. (Parameter 'number')\n Actual value was 4.
number.Throw().IfPositiveOrZero(); // System.ArgumentOutOfRangeException: Value should not be greater than or equal to 0. (Parameter 'number')\n Actual value was 4.
number.Throw().IfNegativeOrZero(); // System.ArgumentOutOfRangeException: Value should not be less than or equal to 0. (Parameter 'number')\n Actual value was -1.
number.Throw().IfOutOfRange(0, 5); // System.ArgumentOutOfRangeException: Value should be between 0 and 5. (Parameter 'number')\n Actual value was -5.
number.Throw().IfInRange(0, 5); // System.ArgumentOutOfRangeException: Value should not be between 0 and 5. (Parameter 'number')\n Actual value was 4.

Types

myObject.Throw().IfType<string>(); // System.ArgumentException: Parameter should not be of type 'String'. (Parameter 'myObject').
myObject.Throw().IfNotType<string>(); // System.ArgumentException: Parameter should be of type 'String'. (Parameter 'myObject').

Nested properties

Boolean properties

person.Throw().IfTrue(p => p.IsFunny); // System.ArgumentException: Value should not meet condition (condition: 'person => person.IsFunny'). (Parameter 'person')
person.Throw().IfFalse(p => p.IsFunny); // System.ArgumentException: Value should meet condition (condition: 'person => person.IsFunny'). (Parameter 'person')

// We can inline the exception throwing logic with the method call.
Person person = GetPerson().Throw().IfTrue(person => person.Age < 18); // System.ArgumentException: Value should not meet condition (condition: 'person => person.Age < 18'). (Parameter 'GetPerson()')

String properties

person.Throw().IfEmpty(p => p.Name); // System.ArgumentException: String should not be empty. (Parameter 'person: p => p.Name')
person.Throw().IfWhiteSpace(p => p.Name); // System.ArgumentException: String should not be white space only. (Parameter 'person: p => p.Name')
person.Throw().IfNullOrWhiteSpace(p => p.Name); // System.ArgumentException: String should not be null or whitespace. (Parameter 'person: p => p.Name')
person.Throw().IfNullOrEmpty(p => p.Name); // System.ArgumentException: String should not be null or empty. (Parameter 'person: p => p.Name')
person.Throw().IfLengthEquals(p => p.Name, 7); // System.ArgumentException: String length should not be equal to 7. (Parameter 'person: p => p.Name')
person.Throw().IfLengthNotEquals(p => p.Name, 10); // System.ArgumentException: String length should be equal to 10. (Parameter 'person: p => p.Name')
person.Throw().IfShorterThan(p => p.Name, 10); // System.ArgumentException: String should not be shorter than 10 characters. (Parameter 'person: p => p.Name')
person.Throw().IfLongerThan(p => p.Name, 3); // System.ArgumentException: String should not be longer than 3 characters. (Parameter 'person: p => p.Name')
person.Throw().IfEquals(p => p.Name, "Amichai"); // System.ArgumentException: String should not be equal to 'Amichai' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfEquals(p => p.Name, "Amichai", StringComparison.InvariantCulture); // System.ArgumentException: String should not be equal to 'Amichai' (comparison type: 'InvariantCulture'). (Parameter 'person: p => p.Name')
person.Throw().IfEqualsIgnoreCase(p => p.Name, "AMICHAI"); // System.ArgumentException: String should not be equal to 'AMICHAI' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfNotEquals(p => p.Name, "Dan"); // System.ArgumentException: String should be equal to 'Dan' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfNotEquals(p => p.Name, "Dan", StringComparison.InvariantCultureIgnoreCase); // System.ArgumentException: String should be equal to 'Dan' (comparison type: 'InvariantCultureIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfNotEqualsIgnoreCase(p => p.Name, "Dan"); // System.ArgumentException: String should be equal to 'Dan' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfContains(p => p.Name, "substring"); // System.ArgumentException: String should not contain 'substring' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfContains(p => p.Name, "substring", ComparisonType.InvariantCulture); // System.ArgumentException: String should contain 'substring' (comparison type: 'InvariantCulture'). (Parameter 'person: p => p.Name')
person.Throw().IfNotContains(p => p.Name, "substring"); // System.ArgumentException: String should contain 'substring' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfNotContains(p => p.Name, "substring", ComparisonType.InvariantCultureIgnoreCase); // System.ArgumentException: String should contain 'substring' (comparison type: 'InvariantCultureIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfStartsWith(p => p.Name, "Jer"); // System.ArgumentException: String should not start with 'Jer' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfStartsWith(p => p.Name, "JER", StringComparison.OrdinalIgnoreCase); // System.ArgumentException: String should not start with 'JER' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfNotStartsWith(p => p.Name, "dan"); // System.ArgumentException: String should start with 'dan' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfNotStartsWith(p => p.Name, "dan", StringComparison.InvariantCultureIgnoreCase); // System.ArgumentException: String should start with 'dan' (comparison type: 'InvariantCultureIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfEndsWith(p => p.Name, "emy"); // System.ArgumentException: String should not end with 'emy' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfEndsWith(p => p.Name, "EMY", StringComparison.OrdinalIgnoreCase); // System.ArgumentException: String should not end with 'EMY' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfNotEndsWith(p => p.Name, "dan"); // System.ArgumentException: String should end with 'dan' (comparison type: 'Ordinal'). (Parameter 'person: p => p.Name')
person.Throw().IfNotEndsWith(p => p.Name, "dan", StringComparison.OrdinalIgnoreCase); // System.ArgumentException: String should end with 'dan' (comparison type: 'OrdinalIgnoreCase'). (Parameter 'person: p => p.Name')
person.Throw().IfMatches(p => p.Name, "J.*y"); // System.ArgumentException: String should not match RegEx pattern 'J.*y' (Parameter 'person: p => p.Name')
person.Throw().IfMatches(p => p.Name, "[a-z]{0,10}", RegexOptions.IgnoreCase); // System.ArgumentException: String should not match RegEx pattern '[a-z]{0,10}' (Parameter 'person: p => p.Name')
person.Throw().IfNotMatches(p => p.Name, "^[0-9]+$"); // System.ArgumentException: String should match RegEx pattern '^[0-9]+$' (Parameter 'person: p => p.Name')
person.Throw().IfNotMatches(p => p.Name, "abc ", RegexOptions.IgnorePatternWhitespace); // System.ArgumentException: String should match RegEx pattern '^[0-9]+$' (Parameter 'person: p => p.Name')

Collection properties

person.Throw().IfHasNullElements(p => p.Friends); // System.ArgumentException: Collection should not have null elements. (Parameter 'person: p => p.Friends')
person.Throw().IfEmpty(p => p.Friends); // System.ArgumentException: Collection should not be empty. (Parameter 'person: p => p.Friends')
person.Throw().IfNotEmpty(p => p.Friends); // System.ArgumentException: Collection should be empty. (Parameter 'person: p => p.Friends')
person.Throw().IfCountLessThan(p => p.Friends, 5); // System.ArgumentException: Collection count should not be less than 5. (Parameter 'person: p => p.Friends')
person.Throw().IfCountGreaterThan(p => p.Friends, 1); // System.ArgumentException: Collection count should not be greater than 1. (Parameter 'person: p => p.Friends')
person.Throw().IfCountEquals(p => p.Friends, 0); // System.ArgumentException: Collection count should not be equal to 0. (Parameter 'person: p => p.Friends')
person.Throw().IfCountNotEquals(p => p.Friends, 0); // System.ArgumentException: Collection count should be equal to 0. (Parameter 'person: p => p.Friends')
person.Throw().IfContains(p => p.Friends, "Amichai"); // System.ArgumentException: Collection should not contain element. (Parameter 'person: p => p.Friends')
person.Throw().IfNotContains(p => p.Friends, "Amichai"); // System.ArgumentException: Collection should contain element. (Parameter 'person: p => p.Friends')

DateTime properties

person.Throw().IfUtc(p => p.DateOfBirth); // System.ArgumentException: Value should not be Utc. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfNotUtc(p => p.DateOfBirth); // System.ArgumentException: Value should be Utc. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfDateTimeKind(p => p.DateOfBirth, DateTimeKind.Unspecified); // System.ArgumentException: Value should not be Unspecified. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfDateTimeKindNot(p => p.DateOfBirth, DateTimeKind.Local); // System.ArgumentException: Value should be Local. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfGreaterThan(p => p.DateOfBirth, DateTime.Now.AddYears(-20)); // System.ArgumentOutOfRangeException: Value should not be greater than 2/28/2002 4:41:19 PM. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfLessThan(p => p.DateOfBirth, DateTime.Now.AddYears(20)); // System.ArgumentOutOfRangeException: Value should not be less than 2/28/2042 4:41:46 PM. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfEquals(p => p.DateOfBirth, other); // System.ArgumentException: Value should not be equal to 2/28/2022 4:45:12 PM. (Parameter 'person: p => p.DateOfBirth')

Enum properties

person.Throw().IfOutOfRange(p => p.EmployeeType); // System.ArgumentOutOfRangeException: Value should be defined in enum. (Parameter 'person: p => p.EmployeeType')
person.Throw().IfEquals(p => p.EmployeeType, EmployeeType.FullTime); // System.ArgumentException: Value should not be equal to FullTime. (Parameter 'person: p => p.EmployeeType')

property equalities

person.Throw().IfDefault(p => p.DateOfBirth); // System.ArgumentException: Value should not be default. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfNotDefault(p => p.DateOfBirth); // System.ArgumentException: Value should be default. (Parameter 'person: p => p.DateOfBirth')
person.Throw().IfNull(p => p.MiddleName); // System.ArgumentNullException: Value cannot be null. (Parameter 'person: p => p.MiddleName')
person.Throw().IfNotNull(p => p.MiddleName); // System.ArgumentException: Value should be null. (Parameter 'person: p => p.MiddleName')
person.Throw().IfEquals(p => p.Age, 5); // System.ArgumentException: Value should not be not be equal to 5. (Parameter 'person: p => p.Age')
person.Throw().IfNotEquals(p => p.Age, 3); // System.ArgumentException: Value should be equal to 3. (Parameter 'person: p => p.Age')

Uri properties

person.Throw().IfHttps(p => p.Website); // System.ArgumentException: Uri scheme should not be https. (Parameter 'person: p => p.Website')
person.Throw().IfNotHttps(p => p.Website); // System.ArgumentException: Uri scheme should be https. (Parameter 'person: p => p.Website')
person.Throw().IfHttp(p => p.Website); // System.ArgumentException: Uri scheme should not be http. (Parameter 'person: p => p.Website')
person.Throw().IfNotHttp(p => p.Website); // System.ArgumentException: Uri scheme should be http. (Parameter 'person: p => p.Website')
person.Throw().IfScheme(p => p.Website, Uri.UriSchemeHttp); // System.ArgumentException: Uri scheme should not be http. (Parameter 'person: p => p.Website')
person.Throw().IfSchemeNot(p => p.Website, Uri.UriSchemeFtp); // System.ArgumentException: Uri scheme should be ftp. (Parameter 'person: p => p.Website')
person.Throw().IfPort(p => p.Website, 800); // System.ArgumentException: Uri port should not be 80. (Parameter 'person: p => p.Website')
person.Throw().IfPortNot(p => p.Website, 8080); // System.ArgumentException: Uri port should be 8080. (Parameter 'person: p => p.Website')
person.Throw().IfAbsolute(p => p.Website); // System.ArgumentException: Uri should be relative. (Parameter 'person: p => p.Website')
person.Throw().IfRelative(p => p.Website); // System.ArgumentException: Uri should be absolute. (Parameter 'person: p => p.Website')
person.Throw().IfNotAbsolute(p => p.Website); // System.ArgumentException: Uri should be absolute. (Parameter 'person: p => p.Website')
person.Throw().IfNotRelative(p => p.Website); // System.ArgumentException: Uri should be relative. (Parameter 'person: p => p.Website')
person.Throw().IfHost(p => p.Website, "www.google.com"); // System.ArgumentException: Uri host should not be www.google.com. (Parameter 'person: p => p.Website')
person.Throw().IfHostNot(p => p.Website, "www.google.com"); // System.ArgumentException: Uri host should be www.google.com. (Parameter 'person: p => p.Website')

Comparable properties

person.Throw().IfPositive(p => p.Age); // System.ArgumentOutOfRangeException: Value should not be greater than 0. (Parameter 'person: p => p.Age')\n Actual value was 5.
person.Throw().IfNegative(p => p.Age); // System.ArgumentOutOfRangeException: Value should not be less than 0. (Parameter 'person: p => p.Age')\n Actual value was -5.
person.Throw().IfLessThan(p => p.Age, 10); // System.ArgumentOutOfRangeException: Value should not be less than 10. (Parameter 'person: p => p.Age')\n Actual value was 5.
person.Throw().IfGreaterThan(p => p.Age, 3); // System.ArgumentOutOfRangeException: Value should not be greater than 3. (Parameter 'person: p => p.Age')\n Actual value was 5.
person.Throw().IfGreaterThanOrEqualTo(p => p.Age, 5); // System.ArgumentOutOfRangeException: Value should not be greater than or equal to 5. (Parameter 'person: p => p.Age')\n Actual value was 6.
person.Throw().IfLessThanOrEqualTo(p => p.Age, 5); // System.ArgumentOutOfRangeException: Value should not be less than or equal to 5. (Parameter 'person: p => p.Age')\n Actual value was 4.
person.Throw().IfPositiveOrZero(p => p.Age); // System.ArgumentOutOfRangeException: Value should not be greater than or equal to 0. (Parameter 'person: p => p.Age')\n Actual value was 4.
person.Throw().IfNegativeOrZero(p => p.Age); // System.ArgumentOutOfRangeException: Value should not be less than or equal to 0. (Parameter 'person: p => p.Age')\n Actual value was -1.
person.Throw().IfOutOfRange(p => p.Age, 0, 5); // System.ArgumentOutOfRangeException: Value should be between 0 and 5. (Parameter 'person: p => p.Age')\n Actual value was -5.
person.Throw().IfInRange(p => p.Age, 0, 5); // System.ArgumentOutOfRangeException: Value should not be between 0 and 5. (Parameter 'person: p => p.Age')\n Actual value was 4.

Extensibility

You can easily extend the library by adding your own rules.

Here is a simple example:

"foo".Throw().IfFoo(); // System.ArgumentException: String shouldn't equal 'foo' (Parameter '"foo"')
namespace Throw
{
    public static class ValidatableExtensions
    {
        public static ref readonly Validatable<string> IfFoo(this in Validatable<string> validatable)
        {
            if (string.Equals(validatable.Value, "foo", StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException("String shouldn't equal 'foo'", validatable.ParamName);
            }

            return ref validatable;
        }
    }
}

Another example:

user.Throw().IfUsesFacebookOnChrome();
namespace Throw
{
    public static class ValidatableExtensions
    {
        public static ref readonly Validatable<User> IfUsesFacebookOnChrome(this in Validatable<User> validatable)
        {
            if (validatable.Value.FavoriteBrowser == Browser.Chrome && validatable.Value.FavoriteWebsite == new Uri("https://facebook.com"))
            {
                throw new UserException("User shouldn't use Facebook on Chrome!");
            }

            return ref validatable;
        }
    }
}

If you want to use the exception customizations in your extension. You can use the ExceptionThrower class which knows how to create the appropriate exception based on the customizations. For example:

namespace Throw
{
    public static class ValidatableExtensions
    {
        public static ref readonly Validatable<User> IfUsesFacebookOnChrome(this in Validatable<User> validatable)
        {
            if (validatable.Value.FavoriteBrowser == Browser.Chrome && validatable.Value.FavoriteWebsite == new Uri("https://facebook.com"))
            {
                ExceptionThrower.Throw(validatable.ParamName, validatable.ExceptionCustomizations, "User shouldn't be using Facebook on Chrome.");
            }

            return ref validatable;
        }
    }
}

This will behave as following:

user.Throw()
    .IfUsesFacebookOnChrome(); // System.ArgumentException: User shouldn't be using Facebook on Chrome. (Parameter 'user')
user.Throw("A different message.")
    .IfUsesFacebookOnChrome(); // System.ArgumentException: A different message. (Parameter 'user')
user.Throw(() => new Exception("A different exception."))
    .IfUsesFacebookOnChrome(); // System.Exception: A different exception.
user.Throw(paramName => new Exception($"A different exception. Param name: '{paramName}'"))
    .IfUsesFacebookOnChrome(); // System.Exception: A different exception. Param name: 'user'

Conditional compilation

Have a Throw() rule that you want to exclude from your release build? Simply add OnlyInDebug() to the rule and it will be excluded from non-debug builds.

"foo".Throw().IfEquals("foo").OnlyInDebug();
dotnet run -c Debug # will throw
dotnet run -c Release # won't throw

Upcoming features

  • More extension methods: Many more rules to come! Please contribute!

Contribution

Feel free to open an issue with any idea, bug, or feature request.

We are trying to be the fastest validation library, so if you have suggestions on improving the runtime speed, share them with us.

Credits

  • Dawn.Guard - An awesome, fast, and intuitive guard clause library for C#. Was a great inspiration for this library.

License

This project is licensed under the terms of the MIT license.

throw's People

Contributors

absundr avatar amantinband avatar devjonie avatar dwakel avatar jeremyespresso avatar kabanchikom avatar m-jovanovic avatar mantinbot avatar mehdihadeli avatar nirzaf avatar truegoodwill avatar yetisergey avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

throw's Issues

Missing Rethrow with generated message

Throw method accepts only parameter name argument, so i can't rethrow with my custom exception but with lib generated text, do i need to do

		try
		{
			size
				.Throw()
				.IfGreaterThan(maxSize);
		}
		catch (Exception e)
		{
			throw new MyException(e.Message);
		}

is it possilbe to add generated exception also as argument for Throw?

Add collection `.IfContains` and `IfNotContains`

Add the following extension methods for collections:

var collection = new[] { "one", "two", "three" };

collection.Throw().IfCotnains("one");
collection.Throw().IfNotContains("four");

And the corresponding methods for collection properties

var foo = new { Numbers = new[] { "one", "two", "three" } };

foo.Throw().IfCotnains(f => f.Numbers, "one");
foo.Throw().IfNotContains(f => f.Numbers, "four");

Add string .IfEndsWith .IfNotEndsWith

Add validation on whether a string ends or doesn't end with a given suffix. For example:

string name = "John"

name.Throw().IfEndsWith("hn");
name.Throw().IfNotEndsWith("123");

Also, add the corresponding extension methods for string properties. For example:

var person = new { Name = "John" };

person.Throw().IfEndsWith(p => p.Name, "Jo");
person.Throw().IfNotEndsWith(p => p.Name, "Jo");

Add option to pass `StringComparison` to string .IfEquals

Currently, we have the following capabilities:

"foo".Throw()
    .IfEquals("foo") // string.Equals("foo", "foo", StringComparison.Ordinal)
    .IfEqualsIngoreCase("FOO"); // string.Equals("foo", "foo", StringComparison.OrdinalIgnoreCase)

An overload that can be useful, is passing any of the StringComparison enum values.

The current string .IfEquals extension method can be modified to include StringComparison comparisonType = StringComparison.Ordinal)

Things to notice:

  1. The Validator.Strings.cs class has separate equals/not equals methods for the different casings. This can be merged to a single ThrowIfEquals and ThrowIfNotEquals that receives a StringComparison comparison type. The exception message can then be updated to something like
    String should not be equal to {otherString} (comparison type: {comparisonType}).
  2. We should make sure we unit test the various scenarios

Is there any plan to localize default exception messages?

Congrats on your great work making this library. For non-English speakers must be a great feature that guards throws exceptions with a localized message. Localization reduces the amount of code building and formatting strings for each validation.

Use `(string message)` constructor when exception customization is an exception type

Currently, when an exception type is specified, the exception will be created using the parameterless constructor.

It would be nice if the exception message could be set using the (string message) constructor.

Here's one example of how to achieve that. It does so without any change in configuration so it would be a possibly unexpected change in behaviour for some users. It doesn't break any of the existing unit tests however.

I'd like some feedback on how you see the issue before actually making a pull request for it:

throw exceptionCustomizations.Customization.Match(
    message => new ArgumentException(message: message ?? generalMessage, paramName: paramName),
    type => Create(type, Combine(generalMessage, paramName)),
    func => func(),
    func => func(paramName));

static string Combine(string message, string paramName) => $"{message} (Parameter '{paramName}')";

static Exception Create(Type exceptionType, string message)
{
    if (!Constructors.TryGetValue(exceptionType, out var constructor))
    {
        foreach (var constructorInfo in exceptionType.GetConstructors(Instance | Public | NonPublic))
        {
            var parameters = constructorInfo.GetParameters();
            if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string))
            {
                constructor = message => (Exception)constructorInfo.Invoke(new[] { message })!;
                Constructors[exceptionType] = constructor;
                return constructor(message);
            }
        }

        Constructors[exceptionType] = null!;
    }

    return constructor?.Invoke(message) ?? (Exception)Activator.CreateInstance(type)!;
}

private static readonly Dictionary<Type, Func<string?, Exception>?> Constructors = new();

Add general .IfEquals(other) method

Throw().IfEquals(other) is supported only for objects that implement IComperable.

Since all objects have at least a default Equals method, we can add a general .IfEquals(other) method

Add .IfNullOrWhiteSpace() .IfNullOrEmpty() for string properties

Currently, if we have an object with a nullable string property

record Person(string? Name);

We need 2 checks to do this:

person.Throw()
    .IfNull(p => p.Name)
    .IfEmpty(p => p.Name!);

Instead, this would be more fluent and performant:

person.Throw()
    .IfNullOrEmpty(p => p.Name);
person.Throw()
    .IfNullOrWhiteSpace(p => p.Name);

Add string .ThrowIfNullOrWhiteSpace()

Add the option to validate the type of a string, for example:

string productName = ""; // or null or " "
productName.ThrowIfNullOrWhiteSpace();

and

string productName = ""; // or null or " "
productName.Throw().IfNullOrWhiteSpace();

Add string .IfContains .IfNotContains

Add validation on whether a string contains or doesn't contain a given substring. For example:

string name = "John"

name.Throw().IfContains("oh");
name.Throw().IfNotContains("123");

Also, add the corresponding extension methods for string properties. For example:

var person = new { Name = "John" };

person.Throw().IfContains(p => p.Name, "oh");
person.Throw().IfNotContains(p => p.Name, "1232");

Perhaps we should also add the option to pass a StringComparison which is defaulted to StringComparison.Ordinal

add dictionary `.IfContainsKey()` `.IfNotContainsKey()`

Add the following extensions methods for dictionaries:

var dictionary = new Dictionary<string, string>
{
    ["key1"] = "value1",
    ["key2"] = "value2",
};

dictionary.Throw().IfContainsKey("key1");
dictionary.Throw().IfNotContainsKey("key3");

And the corresponding dictionary properties extension methods:

var dictionary = new Dictionary<string, string>
{
    ["key1"] = "value1",
    ["key2"] = "value2",
};

var foo = new { Values = dictionary };

foo.Throw().IfContainsKey(f => f.Values, "key1");
foo.Throw().IfNotContainsKey(f => f.Values, "key3");

We don't have dictionary validation yet, so this will hopefully be the start of a bigger feature 🚀

Add string .IfStartsWith .IfNotStartsWith

Add validation on whether a string starts or doesn't start with a given prefix. For example:

string name = "John"

name.Throw().IfStartsWith("Jo");
name.Throw().IfNotStartsWith("123");

Also, add the corresponding extension methods for string properties. For example:

var person = new { Name = "John" };

person.Throw().IfStartsWith(p => p.Name, "Jo");
person.Throw().IfNotStartsWith(p => p.Name, "Jo");

Remove dependency on JetBrains.Annotations

Would it be possible to remove the dependency on JetBrains.Annotations?
To avoid a dependency hell as much as possible I like to have a minimum of dependencies for every nuget package I use.
Since the new .NET versions have a very good support for nullable reference types I am not really sure if there is a need for those null annotations.

Add In() and IsNotIn() to Comparable, String, and Enum validators.

In(params T[])

This type of validator would accept a sequence of values (the sequence). The implementation would scan the sequence and throw an exception if any element in the sequence evaluates to the value represented by the validator.

NotIn(params T[])

This type of validator would accept a sequence of values (the sequence). The implementation would scan the sequence and throw an exception if no element in the sequence evaluates to the value represented by the validator.

Justification:

This eliminates the need for verbose checks such as x == 1 || x == 3 || x == 17 || x == 21 and reduces them to x.In(1, 3, 17, 21), while x.NotIn(1, 3, 17, 21) is the natural reduction of x != 1 && x != 3 && x != 17 && x != 21.

This type of validation is appropriate for all comparable types (string, and all the numeric data types) and all Enum types.

.netstandard2.0 support?

Looks a fantastic library and I like to use it in my work project but unfortunate I'm stuck in .net framework world so i'm unable to use this library :(
Have you ever considered supporting .netstandard2.0?

I played a little bit library source code and I managed get it compile when targeting to netstandard2.0 with langversion 10 using help of IsExternalInit and Nullable packages and re-ordering of namespace and using statements on couple files because of stylecop.
It looks like there is no actual show stoppers in library code only some tests needs slight modifications. When running tests "only" 207/442 tests fails on .net framework 4.8. Common pattern seems to be that ArgumentException message format is slightly different on .net framework vs .net core. Tests are assuming blindly .net core message format instead of using real message format implemented by runtime which I considered to be implementation bug. If runtime implementation ever changes all these test will fail.

In example following test will fail on .net framework 4.8:

    public void Throw_WhenCustomExceptionMessage_ShouldThrowArgumentExceptionWithCustomMessage()
    {
        // Arrange
        ExceptionCustomizations exceptionCustomizations = ParameterConstants.CustomMessage;

        // Act
        Action action = () => ExceptionThrower.Throw(ParameterConstants.ParamName, exceptionCustomizations);

        // Assert
        action.Should().ThrowExactly<ArgumentException>().WithMessage($"{ParameterConstants.CustomMessage} (Parameter '{ParameterConstants.ParamName}')");
    }

Message:

Expected exception message to match the equivalent of "custom message (Parameter 'paramName')", but "custom message
Parameter name: paramName" does not.

if I change test to following form (not assuming any message format implementation details) test will succeed:

        // Assert
         action.Should().ThrowExactly<ArgumentException>().WithMessage(new ArgumentException(ParameterConstants.CustomMessage, ParameterConstants.ParamName).Message);

test repo (no test modifications)
https://github.com/ikijano/throw/tree/wip-netstandard2

IfPortNot vs IfNotPort?

Hi!

Came here from your video. Nice work, this looks great.

Going thru the README I noticed this:

image

There's this pattern: IfFoo(...) -> IfNotFoo(...), IfBar(...) -> IfNotBar(...). And then there's IfPort(...) -> IfPortNot(...). Not being a native english speaker, I think that maybe the fluent syntax makes for better english ("if port not 8080" v.s. "if not port 8080") but I'm not sure on this. If it's all the same then I would personally opt to rename IfPortNot(...) to IfNotPort(...). Just my $0.02.

I'm looking forward to toy a little with this library and maybe eventually decide to use it in actual projects. Again: good work from what I've seen so far! Keep it up!

Skip null validation on nullable types to prioritize other validations

Is there a way to ignore the default null validation on nullable values if I don't care about that but I want to validate that, in case that the value is not null, for example I may want to perform other validation such as IsLongerThan(..) or anything else?

There are cases where I don't want to perform the null validation because that might be a valid value, but if there is actually a value, then I want to make sure it's no longer than a certain length or some other validation.

Example:

string? street = "123 Some Street";
Street = street.Throw().IsLongerThan(100);

Right now this is showing Warnings because string? must match notnull constraint to use it as parameter TValue.

RobiniaDocs API Explorer

Maybe You will be interested in hosting API Explorer online:
https://www.robiniadocs.com/d/throw/api/Throw.ValidatableCreationExtensions.html

This is ready-to-go, so above link can be added to README for other people to see it.
Or using ready shields.io badge:

Static Badge

[![Static Badge](https://img.shields.io/badge/API%20Documentation-RobiniaDocs-43bc00?logo=readme&logoColor=white)](https://www.robiniadocs.com/d/throw/api/Throw.ValidatableCreationExtensions.html)

https://github.com/NeuroXiq/RobiniaDocs

Regards
NeuroXiq

Add object .IfType() and .IfNotType()

Add the option to validate the type of an object, for example:

IActionResult actionResult;

actionResult.Throw().IfType<OkActionResult>();
actionResult.Throw().IfNotType<OkActionResult>();

Add collection .IfAny() .IfSingle() .IfNotSingle() .IfNone() .IfAll()

Add the following collection validations:

var collection = new[] { 1, 2, 3, 3 };

collection.Throw().IfAny(item => item == 3);
collection.Throw().IfSingle(item => item == 2);
collection.Throw().IfNotSingle(item => item == 3);
collection.Throw().IfNone(item => item == 0);
collection.Throw().IfAll(item => item > 0);

Add string .IfMatches(regex)

Add throwing an exception if the string matches a given regex

Something like this:

  1. Using string as input
"My string".Throw().IfMatches("My.*ing$")
  1. Using regex as input
"My string".Throw().IfMatches(new Regex("My.*ing$"))

and the corresponding extension methods for properties

user.Throw()IfMatches(user => user.Honorific, "^Dr")
user.Throw()IfMatches(user => user.Honorific, "^Dr")

Have you considered building an assertion library?

Hey, I'm really impressed with the design this library, it's very well thought out.

Have you considered if some of this could be repurposed or made available in a way that would be usable as an assertion library?

I mean it could today, but it would be more idiomatic to throw a specific type of assertion exception, perhaps with a .Assert()... extension that does the Validatable<TValue> capture.

Check for null public members (properties/fields) of instance

Throw if one or more of public properties or fields of instance are null.

However, usage is questionable. Here are a few examples:

  • options instance - when parameters for method are passed as single object instance with parameters in properties; but in this case, options' validator may be more preferable (to validate not only custom types but primitives, etc.);

  • type which is initialized after creation (not in constructor or partially not in constructor) and has some public properties which must be initialized; but in this case, each appropriate property can by checked for null separately.

Anyway, here's where I left off (extension methods) for my needs (I mention in case this concept is found useful):

To check everything, but excessive to loop through all members:

public static ref readonly Validatable<T> IfHasNullMembers<T>(this in Validatable<T> validatable) where T : notnull
{
    MemberInfo[] members = typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance);

    foreach (MemberInfo member in members)
    {
        object? value = default;

        if (member.MemberType == MemberTypes.Property)
        {
            value = ((PropertyInfo)member).GetValue(validatable.Value);
        }
        else if (member.MemberType == MemberTypes.Field)
        {
            value = ((FieldInfo)member).GetValue(validatable.Value);
        }
        else
        {
            // make value something not null to avoid throwing
            value = string.Empty;
        }

        if (value == null)
        {
            throw new ArgumentException("Object contains null public instance member", member.Name);
        }
    }

    return ref validatable;
}

To check properties (probably the most useful in terms of usage):

public static ref readonly Validatable<T> IfHasNullProperties<T>(this in Validatable<T> validatable) where T : notnull
{
    PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

    foreach (PropertyInfo property in properties)
    {
        if (property.GetValue(validatable.Value) == null)
        {
            throw new ArgumentException("Object contains null public instance property", property.Name);
        }
    }

    return ref validatable;
}

To check fields:

public static ref readonly Validatable<T> IfHasNullFields<T>(this in Validatable<T> validatable) where T : notnull
{
    FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);

    foreach (FieldInfo field in fields)
    {
        if (field.GetValue(validatable.Value) == null)
        {
            throw new ArgumentException("Object contains null public instance field", field.Name);
        }
    }

    return ref validatable;
}

Usage:

opts.Throw().IfHasNullProperties();
opts.Throw().IfHasNullFields();
opts.Throw().IfHasNullMembers();

Add string .IfLengthEquals(length) .IfLengthNotEquals(length)

Add throwing an exception if the string length equals/doesn't equal the given length

Something like this:

Equals:

"str".Throw().IfLengthEquals(3)

Not Equals:

"str".Throw().IfLengthNotEquals(3)

and the corresponding extension methods for properties

user.Throw().IfLengthEquals(user => user.Name, 3)
user.Throw().IfLengthNotEquals(user => user.Name, 3)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.