Giter Club home page Giter Club logo

guardclauses's Introduction

NuGetNuGet publish Ardalis.GuardClauses to nuget

Follow @ardalis   Follow @nimblepros

Guard Clauses

A simple extensible package with guard clause extensions.

A guard clause is a software pattern that simplifies complex functions by "failing fast", checking for invalid inputs up front and immediately failing if any are found.

Give a Star! ⭐

If you like or are using this project please give it a star. Thanks!

Usage

public void ProcessOrder(Order order)
{
    Guard.Against.Null(order);

    // process order here
}

// OR

public class Order
{
    private string _name;
    private int _quantity;
    private long _max;
    private decimal _unitPrice;
    private DateTime _dateCreated;

    public Order(string name, int quantity, long max, decimal unitPrice, DateTime dateCreated)
    {
        _name = Guard.Against.NullOrWhiteSpace(name);
        _quantity = Guard.Against.NegativeOrZero(quantity);
        _max = Guard.Against.Zero(max);
        _unitPrice = Guard.Against.Negative(unitPrice);
        _dateCreated = Guard.Against.OutOfSQLDateRange(dateCreated, dateCreated);
    }
}

Supported Guard Clauses

  • Guard.Against.Null (throws if input is null)
  • Guard.Against.NullOrEmpty (throws if string, guid or array input is null or empty)
  • Guard.Against.NullOrWhiteSpace (throws if string input is null, empty or whitespace)
  • Guard.Against.OutOfRange (throws if integer/DateTime/enum input is outside a provided range)
  • Guard.Against.EnumOutOfRange (throws if an enum value is outside a provided Enum range)
  • Guard.Against.OutOfSQLDateRange (throws if DateTime input is outside the valid range of SQL Server DateTime values)
  • Guard.Against.Zero (throws if number input is zero)
  • Guard.Against.Expression (use any expression you define)
  • Guard.Against.InvalidFormat (define allowed format with a regular expression or func)
  • Guard.Against.NotFound (similar to Null but for use with an id/key lookup; throws a NotFoundException)

Extending with your own Guard Clauses

To extend your own guards, you can do the following:

// Using the same namespace will make sure your code picks up your 
// extensions no matter where they are in your codebase.
namespace Ardalis.GuardClauses
{
    public static class FooGuard
    {
        public static void Foo(this IGuardClause guardClause,
            string input, 
            [CallerArgumentExpression("input")] string? parameterName = null)
        {
            if (input?.ToLower() == "foo")
                throw new ArgumentException("Should not have been foo!", parameterName);
        }
    }
}

// Usage
public void SomeMethod(string something)
{
    Guard.Against.Foo(something);
    Guard.Against.Foo(something, nameof(something)); // optional - provide parameter name
}

YouTube Overview

Ardalis.GuardClauses on YouTube

Breaking Changes in v4

  • OutOfRange for Enums now uses EnumOutOfRange
  • Custom error messages now work more consistently, which may break some unit tests

Nice Visualization of Refactoring to use Guard Clauses

Guard-Clauses.mp4

via Nicolas Carlo

References

Commercial Support

If you require commercial support to include this library in your applications, contact NimblePros

Build Notes (for maintainers)

  • Remember to update the PackageVersion in the csproj file and then a build on main should automatically publish the new package to nuget.org.
  • Add a release with form 1.3.2 to GitHub Releases in order for the package to actually be published to Nuget. Otherwise it will claim to have been successful but is lying to you.

guardclauses's People

Contributors

amal-stack avatar ardalis avatar azure-pipelines[bot] avatar coop56 avatar danny-bos-developer avatar dependabot-preview[bot] avatar dependabot[bot] avatar dvanherten avatar elendil-software avatar fiseni avatar konh avatar kylemcmaster avatar marafiq avatar miljankg avatar mirzazulfan avatar mjebrahimi avatar mojtabakiani avatar pauldbentley avatar rafaelsc avatar rlarno avatar ruaankruger avatar sadukie avatar shadynagy avatar simonnyvall avatar stap123 avatar szlatkow avatar terryjbutler avatar tiagojsrios avatar ttunstall07 avatar unleashedgh 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

guardclauses's Issues

Feature - Add ability to provide RegEx expressions

It would be nice to pass regular expression to the Guard.Against method where the expression is processed. Thinking further on the idea, I'm not sure how I would name these methods to help with readability.

Enum OutOfRange : allow to input as T

With Guard.Against.OutOfRange<TestEnum> it is possible to check that an int is a valid value for the TestEnum enum.

If a method parameter is already of type TestEnum, to check it I have to do :

Guard.Against.OutOfRange<TestEnum>((int)myEnumParam, nameof(myEnumParam));

Adding a new method OutOfRange<T>(this IGuardClause guardClause, T input, string parameterName) where T : Enum

would allow to do :
Guard.Against.OutOfRange(myEnumParam, nameof(myEnumParam));

Unable to use OutOfRange<T> with decimal types

Dear Steve,

I'm not able to use OutOfRange for decimal types like Guard.Against.OutOfRange(unitPrice, nameof(unitPrice), 0, decimal.MaxValue); when I checked the source code, I can see that OutOfRange is marked private. Please can you change that to public? This could help to use it with many types. Correct me if I'm wrong.

Thanks,
Abdul

Would be nice including [NoEnumeration] attribute

Guard.Against.Null There is [NoEnumeration] attribute for the input used in the method. Resharper siglalizes possibility of multiple enumeration eventhough the underlying method doesn't enumerate.

  • .NET SDK Version:
    .net5
    Steps to Reproduce:

README file out of date with current Guard Methods

Hi, I opened this as an issue mainly as I'm not sure what the procedure is with updating the README.md file. Does it need to be updated with the same procedure as a code change with a new version (1.3.4) and Git Release? I'm assuming this is the case as it is included with the project but am not sure!
Thanks.

Release 3.0.0

Release 3.0.0 is showing up in NuGet but there is no official 3.0.0 release here on GitHub.

Fix Tests

Recent PR with upgrade to xunit by dependabot broke tests.

Setup CI/CD with Azure Pipelines

Hi @ardalis,
I read your post about NuGet Publication Checklist and thought we can set this up using Azure Pipelines. I see that you already have CI configured with Azure Pipelines. I was wondering if I can help out with setting up a release pipeline for it so you don't have to do it manually.

Let me know if I am Ok to move ahead with it :)

Adds Support to others `JetBrains.Annotations` Attributes

JetBrains some other annotation that is not been used and can help validate better the usage of GuardClauses lib.

  • InvokerParameterNameAttribute. Checks id the value passed in a parameter is a valid parameter name.
  • RegexPatternAttribute - Indicate that the string should be a RegexPattern.

Adding them to the code-base could be very helpful.

See: https://www.jetbrains.com/help/resharper/Reference__Code_Annotation_Attributes.html#InvokerParameterNameAttribute
See: https://www.jetbrains.com/help/resharper/Reference__Code_Annotation_Attributes.html#RegexPatternAttribute

Regression issue with 1.2.6 Nuget Package

I just updated to the latest version and both DLLs in the available package seem to have regressed to the code base for version 1.2.3.

The net40 DLL for example has the following signature and is also missing the expected code in the latest few commits.

// C:\temp\ardalis.guardclauses.1.2.6\lib\net40\Ardalis.GuardClauses.dll
// Ardalis.GuardClauses, Version=1.2.3.0, Culture=neutral, PublicKeyToken=null
// Global type: <Module>
// Architecture: AnyCPU (64-bit preferred)
// Runtime: v4.0.30319
// Hash algorithm: SHA1
// Debug info: Loaded from portable PDB: C:\temp\ardalis.guardclauses.1.2.6\lib\net40\Ardalis.GuardClauses.pdb

using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: TargetFramework(".NETFramework,Version=v4.0", FrameworkDisplayName = ".NET Framework 4")]
[assembly: AssemblyCompany("Ardalis.com")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A simple package with guard clause helper methods. See docs for how to extend using your own extension methods.")]
[assembly: AssemblyFileVersion("1.2.3.0")]
[assembly: AssemblyInformationalVersion("1.2.3")]
[assembly: AssemblyProduct("Ardalis.GuardClauses")]
[assembly: AssemblyTitle("Ardalis.GuardClauses")]
[assembly: AssemblyVersion("1.2.3.0")]

Same with netstandard1.0:


// C:\temp\ardalis.guardclauses.1.2.6\lib\netstandard1.1\Ardalis.GuardClauses.dll
// Ardalis.GuardClauses, Version=1.2.3.0, Culture=neutral, PublicKeyToken=null
// Global type: <Module>
// Architecture: AnyCPU (64-bit preferred)
// Runtime: v4.0.30319
// Hash algorithm: SHA1
// Debug info: Loaded from portable PDB: C:\temp\ardalis.guardclauses.1.2.6\lib\netstandard1.1\Ardalis.GuardClauses.pdb

using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: TargetFramework(".NETStandard,Version=v1.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("Ardalis.com")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A simple package with guard clause helper methods. See docs for how to extend using your own extension methods.")]
[assembly: AssemblyFileVersion("1.2.3.0")]
[assembly: AssemblyInformationalVersion("1.2.3")]
[assembly: AssemblyProduct("Ardalis.GuardClauses")]
[assembly: AssemblyTitle("Ardalis.GuardClauses")]
[assembly: AssemblyVersion("1.2.3.0")]

These findings came from manually downloading the 1.2.6 nuget package, extracting it and viewing in ILSpy.

Question: XML comments

I'm preparing few packages, and I usually practice generating documentation files for XML comments. I just noticed the PRs in this project are still open, so I'm keen to understand the best way to do this. Most probably there are better approaches, so I look forward to implement better strategies in my projects.

Any comment would be appreciated. Thanks.

Add parameter name to InvalidEnumArgumentException

Currently, both methods for OutOfRange that throw InvalidEnumArgumentException are not setting a parameter name. I cannot say why that would be (the parameter name is an argument of the method), but it seems like a possible bug considering how this is implemented across the board for the other use cases.

I ran into this when doing assertions for Blazor component that had a setup with multiple enumeration assignments, and wanted to assert the thrown parameter name was what was expected.

Using FluentAssertions -

// test.cs
FluentActions.Invoking(() => RenderFakeComponent())
                .Should()
                .Throw<InvalidEnumArgumentException>()
                .Which.ParamName.Should().Be(nameof(State));

// component.cs
protected override async Task OnInitializedAsync()
{
    Guard.Against.OutOfRange(State, nameof(State));
}

// state.cs
public enum State 
{
    Active,
    Inactive
}

Currently, the test fails with Expected string to be "State", but found <null>

Is there a way to avoid FxCopAnalyzers warnings?

We are using the Nuget package to help force better practices

image

Is there a way for the GuardClauses to report to Roslyn that it's doing a null check so the warnings don't show? If so, I'd really like that feature

Add NotFound extension.

It's a common scenario where we need to validate if a given queried object/entity exists. Instead of checking for null, we can have a more explicit extension for this scenario. We may name it simply NotFound.

The usage will be something like

int id = 1;
Customer myCustomer = customerRepository.GetByID(id);
Guard.Against.NotFound(id, myCustomer, nameof(myCustomer));

We have two options here:

  • We can throw KeyNotFoundException
throw new KeyNotFoundException($"The querried object {objectName} with Key: {key} is not found!");
  • We can add and expose a custom exception NotFoundException in the package.
public class NotFoundException : Exception
{
	public NotFoundException(string key, string objectName) 
		: base($"Queried object {objectName} was not found, Key: {key}")
	{
	}

	public NotFoundException(string key, string objectName, Exception innerException) 
		: base($"Queried object {objectName} was not found, Key: {key}", innerException)
	{
	}
}

Confusing NullOrWhiteSpace

I ended up changing my code many places due to the confusion made by NullOrWhiteSpace clause since my expectation had been this behaves like string.IsNullOrWhiteSpace but acutally it checks whitespaces anywhere inside the string. Makes no sense to me anyways.

so this yields exception unexpectedely:
Guard.Against.NullOrWhiteSpace("some white spaces but not empty", "foo");

I'd suggest you to change implementation to this:
public static void NullOrWhiteSpace(this IGuardClause guardClause, string input, string parameterName)
{
if (string.IsNullOrWhiteSpace(input))
{
throw new ArgumentException($"Required input {parameterName} was null, empty, or consists only of white-spacecharacters.", parameterName);
}
}

thanks,
Kornel

Adds `NullOrOutOfRange` clauses, making a new option for Nullable classes/structs to `OutOfRange`.

The current 3.2.0 Nuget Release. Don't have any support for Nullable classes/structs to OutOfRange

private static void TestStruct(int x)
{
	Guard.Against.OutOfRange(x, nameof(x), -10, 10); // OK
	Guard.Against.OutOfRange<int>(x, nameof(x), -10, 10); // Compiler Error
}

private static void TestNullableStruct(int? x)
{
	Guard.Against.OutOfRange(x, nameof(x), -10, 10); // Compiler Error
	Guard.Against.OutOfRange<int?>(x, nameof(x), -10, 10); // Compiler Error
}

private static void TestClass(Version x)
{
	var firstVersion = new Version(1, 0);
	var cuurentVersion = new Version(10, 0);

	Guard.Against.OutOfRange(x, nameof(x), firstVersion, cuurentVersion); // Compiler Error
	Guard.Against.OutOfRange<Version>(x, nameof(x), firstVersion, cuurentVersion); // Compiler Error
}

private static void TestClassWithNull(Version x)
{
	var firstVersion = new Version(1, 0);
	var cuurentVersion = new Version(10, 0);

	Guard.Against.OutOfRange(null, nameof(x), firstVersion, cuurentVersion); // Compiler Error
	Guard.Against.OutOfRange<Version>(null, nameof(x), firstVersion, cuurentVersion); // Compiler Error
}

The current main branch code-base added some support to classes to OutOfRange, but not for Nullable.

private static void TestStruct(int x)
{
	Guard.Against.OutOfRange(x, nameof(x), -10, 10); // OK
	Guard.Against.OutOfRange<int>(x, nameof(x), -10, 10); // OK
}

private static void TestNullableStruct(int? x)
{
	Guard.Against.OutOfRange(x, nameof(x), -10, 10); // Compiler Error
	Guard.Against.OutOfRange<int?>(x, nameof(x), -10, 10); // Compiler Error
}

private static void TestClass(Version x)
{
	var firstVersion = new Version(1, 0);
	var cuurentVersion = new Version(10, 0);
	
	Guard.Against.OutOfRange(x, nameof(x), firstVersion, cuurentVersion); // Compilation OK, but NullReferenceException in Runtime when x is null
	Guard.Against.OutOfRange<Version>(x, nameof(x), firstVersion, cuurentVersion);  // Compilation OK, but NullReferenceException in Runtime when x is null
}

private static void TestClassWithNull(Version x)
{
	var firstVersion = new Version(1, 0);
	var cuurentVersion = new Version(10, 0);

	Guard.Against.OutOfRange((Version)null, nameof(x), firstVersion, cuurentVersion); // Compilation OK, but NullReferenceException in Runtime when x is null
	Guard.Against.OutOfRange<Version>((Version)null, nameof(x), firstVersion, cuurentVersion); // Compilation OK, but NullReferenceException in Runtime when x is null
	
	Guard.Against.OutOfRange(null, nameof(x), firstVersion, cuurentVersion); // Compiler Error
	Guard.Against.OutOfRange<Version>(null, nameof(x), firstVersion, cuurentVersion); // Compiler Error
}

GuardClauses could add a NullOrOutOfRange clause to add support to Nullable classes/structs. And checking the input parameter fo null values, thowing ArgumentNullException insted of a NullReferenceException.

See this dotnetfiddle https://dotnetfiddle.net/nbXa8U
Kind Related: #139

when to use guard clauses and when to return status result instead?

Hi, I know throwing exceptions and using guard clauses simplify the code a lot. But, can you give a guidance on when to use them and when not to use them, and instead return status result object for example. Like, I don't see validation libraries throwing exceptions, should I use or avoid using guard clauses that throw exceptions in my business (domain) layer, maybe for performance reasons?

The C# compiler get confused with the `OutOfRange` clauses for Enums and for Structs

  • .NET SDK Version: Any
  • GuardClauses Version: 3.2.0

Steps to Reproduce:

  1. Using the current 3.2.0 Nuget Release in a console application
  2. Add this sample code:
private static void TestStruct(int x)
{
	Guard.Against.OutOfRange(x, nameof(x), -10, 10); // OK
	Guard.Against.OutOfRange<int>(x, nameof(x), -10, 10); // Compiler Error: CS1501 No overload for method 'OutOfRange' takes 4 arguments.
}

See this dotnetfiddle https://dotnetfiddle.net/nbXa8U

Recommendation:

  • Rename the 2 OutOfRange clauses for Enums to EnumOutOfRange. To avoid any conflict. And making the naming more mean full when we are dealing with the Enum clauses.

CA1062: Validate arguments of public methods

I get this warning quite a lot using GuardClauses - I think it's because the null check is in a separate assembly so the code analysis can't see that.

Do you find this as well; do you have any recommendations? Thanks

IGuardAssignment<T> Interface

Proposal for creating an IGuardAssignment<T> interface.

Currently, you can't chain guard clauses, which reduces the value when using it in a constructor:

    public class Order
    {
        private string _name;
        private int _quantity;
        private long _max;
        private decimal _unitPrice;
        private DateTime _dateCreated;

        public Order(string name, int quantity, long max, decimal unitPrice, DateTime dateCreated)
        {
            _name = Guard.Against.NullOrWhiteSpace(name, nameof(name));
            _quantity = Guard.Against.NegativeOrZero(quantity, nameof(quantity));
            _max = Guard.Against.Zero(max, nameof(max));
            _unitPrice = Guard.Against.Negative(unitPrice, nameof(unitPrice));
            _dateCreated = Guard.Against.OutOfSQLDateRange(dateCreated, nameof(dateCreated));
        }
    }

Proposed Implementation

    public interface IGuardAssignment<out T>
    {
        public T Value { get; }
    }

    public class GuardAssignment<T> : IGuardAssignment<T>
    {
        private GuardAssignment() { }

        public T Value { get; private set; } = default!;

        public static IGuardAssignment<T> WithValue(T value)
        {
            return new GuardAssignment<T>
            {
                Value = value
            };
        }
    }

    public static class GuardAssignmentExtensions
    {
        public static IGuardAssignment<T> AgainstNegative<T>([JetBrainsNotNull] this IGuardAssignment<T> guardClause, [JetBrainsNotNull] string parameterName) where T : struct, IComparable
        {
            if (guardClause.Value.CompareTo(default(T)) < 0)
            {
                throw new ArgumentException($"Required input {parameterName} cannot be negative.", parameterName);
            }

            return guardClause;
        }

        public static IGuardAssignment<T> AgainstZero<T>([JetBrainsNotNull] this IGuardAssignment<T> guardClause, [JetBrainsNotNull] string parameterName) where T : struct
        {
            if (EqualityComparer<T>.Default.Equals(guardClause.Value, default))
            {
                throw new ArgumentException($"Required input {parameterName} cannot be zero.", parameterName);
            }

            return guardClause;
        }

        public static IGuardAssignment<T> AgainstExpression<T>([JetBrainsNotNull] this IGuardAssignment<T> guardClause, [JetBrainsNotNull] Func<T, bool> func, [JetBrainsNotNull] string message) where T : struct
        {
            if (func(guardClause.Value))
            {
                throw new ArgumentException(message);
            }

            return guardClause;
        }
    }

Usage

    public class Foo
    {
        private readonly int _x;

        public Foo(int x)
        {
            _x = GuardAssignment<int>.WithValue(x)
                .AgainstNegative(nameof(x))
                .AgainstZero(nameof(x))
                .AgainstExpression(x => x != 5, "5 is not a valid number.")
                .Value;
        }
    }

Questions:

  • Is this useful outside of assignments?
    Normal guard clauses can't be chained, since they don't return IGuardClause. Each guard must be specified individually. Normal guards can currently be used as assignments since they return the checked value. (The Null guard clause currently only returns object, though, which requires a cast for typed assignments). My proposal can be used outside of assignments as well; it would just look like:
        public void Foo(int x)
        {
            GuardAssignment<int>.WithValue(x)
                .AgainstNegative(nameof(x))
                .AgainstZero(nameof(x));

            // ...
        }

        // or

        public void Foo(int x)
        {
            _ = GuardAssignment<int>.WithValue(x)
                .AgainstNegative(nameof(x))
                .AgainstZero(nameof(x));

            // ...
        }
  • The Against static member is gone. Why is that?
    I considered three options when making this proposal. The first one looked like GuardAssignment.Against.Check(input, argName). This one had the implementation problem of protecting against someone doing GuardAssignment.Against.Value. We needed to ensure that the last extension method called assigned the Value property. It also had the issue of having the value accessible from the guard class instance, but needing to repeatedly pass it in to each extension method. The second one looked like new GuardAssignment(input).Check(argName). This one had the issue of allowing people to work directly from the class instead of just from the interface. Hence, my current implementation.

Notes

Guards are evaluated in a first to last order.

.NET Standard 2.0

Just wanted to pick your brain a little. Should this library be using Standard 2.0?

I can't imagine a library like this would use any 2.0 features, and by targeting 1.0 we could reach a broader audience.

NuGet package out of date?

This method does not appear to be in the NuGet package:

public static void NullOrEmpty(this IGuardClause guardClause, IEnumerable input, string parameterName)

Is the NuGet package up to date?

Empty Guids

Can we have support for guarding against empty Guids?
Either with the existing NullOrEmpty method or a new Empty method.

Thanks
Richard

LengthOutOfRange

Would you consider a method such as LengthOutOfRange(min, max) for this package?

I don't know if this is too close to validation?

My use case is I often have value objects or entity properties which need to be a certain length i.e. a product code must be not null, not empty or white space, and exactly 5 characters

public const int MinValueLength = 5;

public const int MaxValueLength = 5;

public ProductCode(string value)
{
	Guard.Against.NullOrWhiteSpace(value, nameof(value));
	Guard.Against.LengthOutOfRange(value, nameof(value), MinValueLength, MaxValueLength);

	this.Value = value;
}

Possible multiple enumeration

  • .NET SDK Version: net5

Steps to Reproduce
Screenshot_157

it will show possible multiple enumeration. I see that you use JBanotations let's add NoEnumeration attribute for methods

New and Better Logo

The current logo appearing in nuget.org demonstrates my own design skills:

image

Since the project has been relatively successful and now has well over 50k downloads, a better logo is probably warranted. If someone would like to contribute one I'd appreciate it greatly. Just submit a PR with the logo assets in a subfolder and reference this issue. If there are several we'll try and pick the best of them. Thanks!

Add overloads that don't have parameterName parameter

I noticed that all of the examples in the README use the same parameterName as the input. I wonder if most often athis library will be used like this, with the same parameterName as input (and therefore a lot of duplication).

A simpler approach might be to have overloads for each method in GuardClauses.cs that don't have the parameterName parameter, and uses the nameof the input.

I just forked the repo and have an example of what I mean in this commit

With that in mind, would you accept a pull request which adds an overload with a simpler signature for each of the methods in the library?

If you would, I will happily do one!

Nick

Custom message as optional parameter or overload

In the past, I have used SolidStack.Core.Guards for guard clauses. This library allows the definition of own error messages which absolutely makes sense sometimes.

Is something similar planned for this lib in the future?

Expression Guard

An Expression-based Guard could be used to provide flexibility and could cover any case not already taken care of by the library, without the need to write your own guard clause method.

public static T AgainstExpression<T>([JetBrainsNotNull] this IGuardClause<T> guardClause, [JetBrainsNotNull] Func<T, bool> func, [JetBrainsNotNull] string message) where T : struct
{
    if (func(guardClause.Value))
    {
        throw new ArgumentException(message);
    }
    return T;
}

Add Guard.Against.Negative ?

I wonder if it would make sense to add a Guard.Against.Negative Clause ?

To avoid negative values I could use an uint type. But in some situation I prefer to use int. Especially when the input value come from outside of my projects to avoid the need of cast.

If this suggestion make sense I can do a pull request.

Add Guard.Against.Default

Altough we can always extend the clauses, it would be nice to have a Guard.Against.Default() clause, out of the box.

Discussion - Core Guards and duplication

Currently we are in a state of duplication when it comes to the method calls. We establish the guards provided by the library as CoreGuard extension methods and then provide a shortcut to get to it right off of Guard. Starting from a clean slate I may have exposed Against on the static Guard class and left the short cuts off. They were maintained mostly for backwards compatibility as the package had been release already. That was my general thinking during the implementation at least. That said, there are pros and cons to that approach.

Possible options:

Option 1:
Keep core guards straight on Guard.cs as the static methods they originally were and leave Against as a pure extension point.
Pro:

  • This library can be built out such that it's right off Guard for the majority of main use cases.
  • Extension methods of Against become obvious that they are an extension of the library.
  • No core extension methods means less public classes just to house extension methods.

Cons:

  • Feels odd that Against would be empty by default.
  • On a larger team that may not realize how the library works may not realize Against has more Guards.

Option 2:
Remove guards on Guard.cs and only have the Against extension. All guards will start with Guard.Against.
Pro:

  • One syntax to rule them all
  • Less duplication

Cons:

  • Always jumping through Against (Not sure this is really a con)
  • No way to tell custom from built-in Guards. (might be a pro?)

Option 3:
Keep the method duplication with the shortcut, but separate Guards into their own partial classes.
Pros:

  • Majority of Guards are likely shown immediately after Guard in intellisense.
  • They still also show in Against so you can't miss it.

Cons:

  • Lots of public static classes polluting intellisense
  • Still making the shortcut
  • Still need duplication of the tests to ensure correctness. Helper of some kind may help here although I can't picture it in my head yet.
  • On a larger team that may not realize how the library works may not realize Against has more Guards.

An example leveraging partial classes could look something like this:

public partial class Guard
{
    /// <summary>
    /// Throws an ArgumentNullException if input is null.
    /// </summary>
    /// <param name="input"></param>
    /// <param name="parameterName"></param>
    /// <exception cref="ArgumentNullException"></exception>
    public static void AgainstNull(object input, string parameterName) => Guard.Against.Null(input, parameterName);
}

public static class NullGuard
{
    /// <summary>
    /// Throws an ArgumentNullException if input is null.
    /// </summary>
    /// <param name="guardClause"></param>
    /// <param name="input"></param>
    /// <param name="parameterName"></param>
    /// <exception cref="ArgumentNullException"></exception>
    public static void Null(this IGuardClause guardClause, object input, string parameterName)
    {
        if (null == input)
        {
            throw new ArgumentNullException(parameterName);
        }
    }
}

Technically 1& 2 are breaking changes as we'd be removing some of the default methods. I've left that out of the con list for the purposes of this discussion.

I'm honestly not sure at this point which path I prefer or if there's a path I'm not seeing so I'm probably going to step away for a bit and see if this sparks some discussion.

Create guidelines for contribution

Discussed in #133

Originally posted by arphox October 11, 2021
Hi,
as I see in the readme there is no mentions of whether you accept contributions or give any guidelines.
How about creating a CONTRIBUTING.MD file?
See an example here: nunit/CONTRIBUTING.MD

Or just write a chapter on it in the readme.

Add C# 8 Nullable Reference Types annotations

Without using object? and adding System.Diagnostics.CodeAnalysis annotations, the C# compiler will not recognize Guard.Against.Null as a null check. This will require users of the library to use ! afterwards to silence the warning they get if they treat the value as non-null after the check.

https://twitter.com/conner_orth/status/1219885690772738048 (/cc @ConnerOrth)

I can help with this if you'd like.

Also see "What should library authors do?" in https://devblogs.microsoft.com/dotnet/embracing-nullable-reference-types/.

Combine Guards with an Any Guard

From @pauldbentley:

Something like this:

public static T Any<T>(this IGuardClause guardClause, T input, Action guards)
{
    guards();
    return input;
}
Which can be used as:

_productCode = Guard.Against.Any(productCode, () =>
{
    Guard.Against.NullOrWhiteSpace(productCode, nameof(productCode));
    Guard.Against.OutOfRange(productCode, nameof(productCode), MinProductCodeLength, MaxProductCodeLength);
 });

Is this better than just using this?

    Guard.Against.NullOrWhiteSpace(productCode, nameof(productCode));
    Guard.Against.OutOfRange(productCode, nameof(productCode), MinProductCodeLength, MaxProductCodeLength);

Partial modifier in Guard class?

Just got done listening to this podcast and was just curious on why you made Guard a partial class?

The conclusion I came to was if you wanted to split up the support guard types across multiple files as to not have a singular file.

Discussion - Guard Clauses for business rules?

Do you think is it a good idea to use Guard Clauses for business rules?
i.e
You can approve some invoices only if the invoice status is verified?
So you have something like:
Guard.Against.UnverifiedInvoice(string invoiceStatus);

ps. You should turn on discussion feature :)

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.