Giter Site home page Giter Site logo

safakgur / guard Goto Github PK

View Code? Open in Web Editor NEW
714.0 24.0 51.0 1.07 MB

A high-performance, extensible argument validation library.

License: MIT License

C# 83.65% PowerShell 0.42% Vim Snippet 15.94%
guard guard-clauses arguments validation normalization c-sharp dotnet-standard fluent csharp

guard's Introduction

Guard

Warning

This project is no longer actively maintained.

I apologise to everyone who depended on Guard for my three-year-long silence. Guard was my passion project for the longest time, helping me get better at C#, automated builds, Visual Studio extensibility, documentation, and how to be part of a community regardless of how small we've been. It also kickstarted my journey of creating terrible gifs.

But my life has changed - I moved to a different country, changed jobs, lost someone very dear to me, and started spending less and less time coding outside my work hours. I always believed I'd be back and release v2 with all my bright ideas, but I just didn't know when. I only noticed today that I've been avoiding my GitHub notifications out of guilt for over a year, and I figured it was time for closure.

Why I'm stopping this

  • I can no longer find the time to maintain an open-source project, as I want to spend most of my free time with my family and on other hobbies.

  • Guard has always been in an awkward position:

    Library authors avoid adding unnecessary dependencies to their packages, and Guard didn't bring enough value to be worth being a runtime dependency. I wanted to experiment with IL weaving and source generators to make Guard a compile-time dependency that would replace call sites with simple if-then-throw statements, but I never got the chance.

    App developers didn't need Guard as their validation scenarios doesn't involve throwing argument exceptions. For example, a web API would use something like DataAnnotation attributes or FluentValidation to validate its requests. So as a library that specifically targeted library authors, Guard has always been irrelevant to app developers.

What was I looking to do in v2:

  • New style with using static and [CallerArgumentExpression]:

    // using static Dawn.Validation.Argument;
    
    Require(firstName).NotNullOrWhiteSpace();
  • Lite validations with [CallerArgumentExpression]:

    var ratio = 5.0;
    Require(ratio).Range(ratio is >= 0.0 and <= 1.0);
    // ArgumentOutOfRangeException
    // * ParamName: "ratio"
    // * ActualValue: 5
    // * Message: "Failed precondition: 'ratio is >= 0.0 and <= 1.0'."

    Hadn't yet decided whether the light validations would replace the custom ones or live in addition to them.

  • Type guards, e.g., Require<TImplementation>().NotAbstract();

  • Fixing nullable reference type issues

  • Changing the argument value to a ref field, which meant:

    • We could even work on mutable structs without copying

    • We could experiment with in-place normalisations

  • Dropping .NET Standard support

I hope this helps anyone who was waiting for the v2. Original intro below:


Logo

Guard is a fluent argument validation library that is intuitive, fast and extensible.

NuGet Build Coverage
$ dotnet add package Dawn.Guard / PM> Install-Package Dawn.Guard

Introduction

Here is a sample constructor that validates its arguments without Guard:

public Person(string name, int age)
{
    if (name == null)
        throw new ArgumentNullException(nameof(name), "Name cannot be null.");

    if (name.Length == 0)
        throw new ArgumentException("Name cannot be empty.", nameof(name));

    if (age < 0)
        throw new ArgumentOutOfRangeException(nameof(age), age, "Age cannot be negative.");

    Name = name;
    Age = age;
}

And this is how we write the same constructor with Guard:

using Dawn; // Bring Guard into scope.

public Person(string name, int age)
{
    Name = Guard.Argument(name, nameof(name)).NotNull().NotEmpty();
    Age = Guard.Argument(age, nameof(age)).NotNegative();
}

If this looks like too much allocations to you, fear not. The arguments are read-only structs that are passed by reference. See the design decisions for details and an introduction to Guard's more advanced features.

What's Wrong with Vanilla?

There is nothing wrong with writing your own checks but when you have lots of types you need to validate, the task gets very tedious, very quickly.

Let's analyze the string validation in the example without Guard:

  • We have an argument (name) that we need to be a non-null, non-empty string.
  • We check if it's null and throw an ArgumentNullException if it is.
  • We then check if it's empty and throw an ArgumentException if it is.
  • We specify the same parameter name for each validation.
  • We write an error message for each validation.
  • ArgumentNullException accepts the parameter name as its first argument and error message as its second while it's the other way around for the ArgumentException. An inconsistency that many of us sometimes find it hard to remember.

In reality, all we need to express should be the first bullet, that we want our argument non-null and non-empty.

With Guard, if you want to guard an argument against null, you just write NotNull and that's it. If the argument is passed null, you'll get an ArgumentNullException thrown with the correct parameter name and a clear error message out of the box. The standard validations have fully documented, meaningful defaults that get out of your way and let you focus on your project.

Requirements

C# 7.2 or later is required. Guard takes advantage of almost all the new features introduced in C# 7.2. So in order to use Guard, you need to make sure your Visual Studio is up to date and you have <LangVersion>7.2</LangVersion> or later added in your .csproj file.

.NET Standard 1.0 and above are supported. Microsoft Docs lists the following platform versions as .NET Standard 1.0 compliant but keep in mind that currently, the unit tests are only targeting .NET Core 3.0.

Platform Version
.NET Core 1.0
.NET Framework 4.5
Mono 4.6
Xamarin.iOS 10.0
Xamarin.Mac 3.0
Xamarin.Android 7.0
Universal Windows Platform 10.0
Windows 8.0
Windows Phone 8.1
Windows Phone Silverlight 8.0
Unity 2018.1

More

The default branch (dev) is the development branch, so it may contain changes/features that are not published to NuGet yet. See the master branch for the latest published version.

Standard Validations

Click here for a list of the validations that are included in the library.

Design Decisions

Click here for the document that explains the motives behind the Guard's API design and more advanced features.

Extensibility

Click here to see how to add custom validations to Guard by writing simple extension methods.

Code Snippets

Code snippets can be found in the snippets folder. Currently, only the Visual Studio is supported.

guard's People

Contributors

adamecr avatar safakgur avatar serdar-eric 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

guard's Issues

Performance

The performance is not so high compared to other assertion.


                               Method |        Mean |     StdDev | Scaled | Scaled-StdDev | GcAllocations |
------------------------------------- |------------:|-----------:|-------:|--------------:|--------------:|
            Test00RunWithoutAssertion |    18.14 us |  0.1490 us |   1.00 |          0.00 |           0 B |
            Test01RunDefaultAssertion |    16.66 us |  0.5381 us |   0.92 |         0.031 |           0 B |
                    Test02CodeNotNull |    17.03 us |  0.1594 us |   0.94 |        0.0117 |           0 B |
             Test03CodeAssertArgument |    14.02 us |  2.1815 us |   0.76 |         0.115 |           0 B |
 Test04GuardAssertArgumentDocs_x0_001 | 1,789.07 us | 79.6902 us |  98.54 |          4.36 |      145.8 KB |
         Test05GuardAssertArgumentOpt |   105.01 us |  0.7094 us |   5.79 |         0.061 |           0 B |

Code with Benhmark.NET

using CodeJam.Assertions;

        [CompetitionBaseline]
        [GcAllocations(0)]
        public string Test00RunWithoutAssertion()
        {
            var result = "";
            var count = Count;
            for (var i = 0; i < count; i++)
            {
                var arg = GetArg(i);
                result = arg;
            }

            return result;
        }

        [CompetitionBenchmark(0.71, 1.14)]
        [GcAllocations(0)]
        public string Test01RunDefaultAssertion()
        {
            var result = "";
            var count = Count;
            for (var i = 0; i < count; i++)
            {
                var arg = GetArg(i);

                if (arg == null)
                    throw new ArgumentNullException(nameof(arg));
                result = arg;
            }

            return result;
        }

        [CompetitionBenchmark(0.79, 3.40)]
        [GcAllocations(0)]
        public string Test02CodeNotNull()
        {
            var result = "";
            var count = Count;
            for (var i = 0; i < count; i++)
            {
                var arg = GetArg(i);

                Code.NotNull(arg, nameof(arg));
                result = arg;
            }

            return result;
        }

        [CompetitionBenchmark(0.63, 3.30)]
        [GcAllocations(0)]
        public string Test03CodeAssertArgument()
        {
            var result = "";
            var count = Count;

            for (var i = 0; i < count; i++)
            {
                var arg = GetArg(i);

                Code.AssertArgument(arg != null, nameof(arg), "Argument should be not null");
                result = arg;
            }

            return result;
        }

        [CompetitionBenchmark(85.01, 105.74)]
        [GcAllocations(0, 149309, BinarySizeUnit.Byte)]
        public string Test04GuardAssertArgumentDocs_x0_001()
        {
            var result = "";
            var count = Count / 1000; // <= !!!

            for (var i = 0; i < count; i++)
            {
                var arg = GetArg(i);

                Guard.Argument(() => arg).NotNull().NotEmpty();
                result = arg;
            }

            return result;
        }
        [CompetitionBenchmark(4.08, 6.61)]
        [GcAllocations(0)]
        public string Test05GuardAssertArgumentOpt()
        {
            var result = "";
            var count = Count;

            for (var i = 0; i < count; i++)
            {
                var arg = GetArg(i);

                Guard.Argument(arg, nameof(arg)).NotNull();
                result = arg;
            }

            return result;
        }

https://rsdn.org/forum/prj.codejam/7256146.1

NotDefault limited to struct

Hi, I'm curious why you chose to limit NotDefault/Default to value types (struct)? I have a use case to guard against an uninitialized (null or default value) generic type parameter instance that can be either struct or class (e.g. String or Guid). C# default(T) is the exact semantics I need for both class and struct.

Is there a better way to do this than using default(T)?

Enhanced String char based guards.

Describe the solution you'd like
In the same fashion that there is currently Whitespace and NotWhitespace similar Guard methods to check for and against string individual chars "Is" methods (IsDigits, IsLetters, IsNumbers, and so on). That is, checking if a string consists entirely of Letters/Digits/Numbers etc, or not these methods.

public static ref readonly ArgumentInfo<string> Letters(in this ArgumentInfo<string> argument,
	Func<string?, string>? message = null)
{
	if (!argument.Value?.All(char.IsLetter) ?? false)
	{
		var m = message?.Invoke(argument.Value) ?? Messages.StringIsLetters(argument);
		throw Fail(new ArgumentException(m, argument.Name));
	}

	return ref argument;
}

Additionally, while implementing similar ContainsDigits, ContainsLetters, and their inverse methods could be created.

public static ref readonly ArgumentInfo<string> ContainsLetters(in this ArgumentInfo<string> argument,
	Func<string?, string>? message = null)
{
	if (!argument.Value?.Any(char.IsLetter) ?? false)
	{
		var m = message?.Invoke(argument.Value) ?? Messages.StringContainsLetters(argument);
		throw Fail(new ArgumentException(m, argument.Name));
	}

	return ref argument;
}

While this can be accomplished with the Regex methods currently, using these methods with the char methods should be considerably faster.

Alternatively, the IEnumerable methods could be expanded to contain All, NotAll, Any, and NotAny methods that take predicates to handles this functionality.

public static ref readonly ArgumentInfo<T> All<T>(in this ArgumentInfo<T> argument,
	Func<T, bool> predicate,
	Func<string?, string>? message = null)
	where T : IEnumerable<T>
{
	if (!argument.Value?.All(predicate) ?? false)
	{
		var m = message?.Invoke(argument.Value?.ToString()) ?? Messages.All(argument);
		throw Fail(new ArgumentException(m, argument.Name));
	}

	return ref argument;
}

If either of these sorts of functionality would be accepted, I would be willing to submit a pull with it.

Guard.Argument(x).Equal<T> return type error

Expected Behaviour

Guard.Argument(x).Equal{InvalidOperationException}(not x) returns an InvalidOperationException

Actual Behaviour

Guard.Argument(x).Equal{InvalidOperationException}(not x) returns an ArgumentException

Probable Cause

The implementation of Equal{T} is not respecting the type of T and is thus equivalent to the non templated variant.

Actual code uses tags of course, but markdown does not support them

CA1062 warning

How can I avoid CA1062 warnings when using for example
Guard.Argument(message, nameof(message)).NotNull();

I tried the following in .editorconfig in visual studio 2019 solution root:
dotnet_code_quality.CA1062.null_check_validation_methods = Guard.NotNull
dotnet_code_quality.CA1062.null_check_validation_methods = Guard.Argument

Another Performance Related Issue

First of, thank you for creating this wonderful library. I've been on a search for a Guard library evaluating a number of them on Nuget and have to say that yours is very comprehensive and is the most syntax friendly in my opinion. Good job!

My suggestion is about performance when it comes to member expressions. Ability to take in member expressions is what makes this library very syntax friendly and attractive to use.

I see that you compile expressions, which is obviously very expensive and you are very explicit about this in the documentation. I've also read issue #13 where this is discussed.

So my suggestion would be to utilize constant expressions instead. It would involve unwrapping the whole expression tree. Its a fair bit more work, but also a lot better performance, about 40-50x faster from what I've tested.

Unique values in the collection

Hi,

curious if checking the uniqueness of values (with optional comparer parameter) should be a core functionality. Say, I have an array of ints: 1,1,2 and exception is thrown because values are not unique. I'm trying to replace custom validation with the guard library.

Thank you!

[Question] Change Exception Types

Hi and thanks for the great lib!

Exception Types

Throwing custom exceptions from standard validations seems counter-intuitive and right now, the only way to do so is to use the generic Require validation.

do you have any plans on this to change it ?
we have for our micro-services our own error response ( ApiException ) with some custom types ( Type, Status, Path, AdditionalData ) and we would like to use our own exception instead of ArgumentEx.../InvalidOper...

we also saw that it is possible to extend it but this does not make any sense because we would have to re implement most of your existing functions just to throw our exception

also the Exception interceptor would not fit ( because we wouldnt be able to add additional data to the exceptions )

Nuget package

Is there a reason this library does not have a nuget package?

Add NotWhiteSpace overload that takes a string for the error message

Is your feature request related to a problem?
A description of the problem. Ex. I'm always frustrated when [...]

Unlike the .NotEmpty string guard, .NotWhiteSpace does not have an overload that takes a string argument for the error message. The only implementation of .NotWhiteSpace requires a Func<string, string> argument for providing an error message.

Describe the solution you'd like
A description of what you want to happen.

Add an overload of the NotWhiteSpace string guard: NotWhiteSpace(in this ArgumentInfo<string> argument, string message)

Status on v2 of the library

In another issue, it was mentioned that Guard v2 would probably be coming by March.

This question is to check on the current status of v2, and if there is any ETA for when it will be released.

Now that the .NET team has abandoned the !! null-check operator it would be nice to have Guard to unify all exception handling, however I'm hesitant in using it in its current form because it doesnt yet support caller expression names to automatically add the argument names to the exceptions: moving to Guard would be a downgrade compared to things like ArgumentNullException.ThrowIfNull for the common null handling.

Since !! was scheduled for .NET 7, I was contemplating the idea of just not using a library for exception throwing, as most of the exceptions we throw are ArgumentNullExceptions. With the decision to postpone that feature, I think it becomes valuable again to rely on Guard, but I wanted to do so without extra compromises.

Add kind-aware DateTime guards

Split from #40

Default DateTime.Compare ignores the Kind property. We should:

  • Add a new guard, KindSpecified, to require a DateTime argument to have kind that is either Local or Utc, so people can force callers to think about the source of their time values and specify the kinds explicitly.
  • Add non-generic overloads to Equal, NotEqual, Min, Max and InRange that accept DateTime arguments and throw if (a.Kind == Unspecified) != (b.Kind == Unspecified) (DateTime already accounts for the time difference when comparing Local values to Utc values).

The second bullet is a breaking change, so I'm adding this to the 2.0 milestone.

Excellent!

Hi,

Sorry to create an issue but this is the only way to leave a comment on GitHub :-)

This is an excellent library - well designed, nicely implemented and documented and the first one I have seen for fluent validation that actually makes sense from performance point of view :-) I guess C# 7.2 made this possible :-)

Thank you for creating this excellent library!

I have a question though: while the library's version is alpha it seems pretty complete and well tested. Is there anything that you consider should be tested or improved before it s used on production?

Compiler message validate parameter is non-null before using

I replace the following:

if (aNext is null)
{
  throw new ArgumentNullException(nameof(aNext));
}

with:

 Guard.Argument(aNext, nameof(aNext)).NotNull();

But I get the following message:

image

CA1062 In externally visible method 'Task MyBehavior<TRequest, TResponse>.Handle(TRequest aRequest, CancellationToken aCancellationToken, RequestHandlerDelegate aNext)', validate parameter 'aNext' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code econdition asserting non-null argument.

It's late at night so maybe I'm just sleepy and don't understand?

Version 1.0.0

Great work! Thank you for the quick turnaround! ๐Ÿ‘

How to Guard an ICollection member

If I have a class Student which has an ICollection<Class> Classes {get; set; }, I'm trying to do this.

Guard.Arguement(() => student)
.NotNull()
.Member(student.Classes , classes => classes.NotEmpty());

But the classes variable isn't an ICollection<Class> but Guard.ArgumentInfo<ICollection<Class>> so the NotEmpty() check isn't available on Intellisense.
The Visual Studio Intellisense doesn't auto-suggest the available methods for a collection, but it does compile and work.

Autosuggest works fine here though:
Guard.Arugment(() => student.Classes)).NotEmpty();

Following the examples here

Is this known?

[Enhancement proposal] Exception interceptors

Hello @safakgur , first of all, thanks for a great library, I like the design and have tried to use it in some of my projects.
I have played a bit and found quite tricky to log the exception before it's actually thrown. Actually, I was able to do it using logger extension

public delegate T Dlgt<T>( Guard.ArgumentInfo<T> argument);
public static T Guard<T>(this ILogger logger, T a, string name, Dlgt<T> f)
{
    try
    {
        return f.Invoke(Dawn.Guard.Argument(a, nameof(a)));
    }
    catch (Exception ex)
    {
        logger.Fatal(exception: ex, ex.Message);
        throw;
    }
}

and then

string bb = Logger.Guard(() => Guard.Argument(a, nameof(a)).NotWhiteSpace().NotNull());

But it makes the whole argument validation less readable and prone to human errors.

My proposal is to extend the ArgumentInfo with the ability to keep the reference to exception interceptor and call such interceptor before the exception is thrown when defined.
The validation will look like this (of course it's even possible to have more overloads to simplify it a bit more):

string bb = Guard.Argument(a, nameof(a), exceptionInterceptor:(ex) =>logger.Fatal(ex)).NotWhiteSpace().NotNull();

I have tried to implement it at my fork, and created the pull request.

The main changes are in AgrumentInfo<T> class. Plus added new parameters toGuard.Argument and ensured that the ArgumentInfo is copied with exceptionInterceptor when modified. The rest is just about wrapping the exception into throw argument.Exception(...).

Document inclusivity of InRange

It would be nice if the doc comments for InRange mentioned inclusivity. Without reading the code, it seems ambiguous to me whether it works like SQL between (min <= x <= max) or the way limits are often expressed with zero-based counters (min <= x < max).

Support validation/guard-clauses for collection items

I could not find a way to specify constraints for items in a collection. Did I overlook something? This seems to me like a fairly common scenario, or is it not?

Scenario
A collection of items List<SomeItem> collection;

Each item has a property

class SomeItem{
   public object SomeProperty {get; set;}
}

I wish to assert that (1) the collection is not null and that for each item within the collection that (2) it is not null and that (3) the property is not null.

Feature
I envision something like the following syntax:

Guard.Argument(collection, nameof(collection))
  .NotNull()
  // specify nested constraints that are applied to each item in the collection
  .Item(x=> x
    .NotNull()
    .Member(item => item.SomeProperty, x => x.NotNull())
  );

Use of Conditional Debug Attribute?

First off, let me say my personal experience has not been your typical enterprise-grade business software applications. So my perspective here may be a skewed. Moving on...

With the kinds of applications I usually work on, 99% of conditional bugs are going to be found during debug testing and will rarely ever occur in a production build. I was considering writing my own fluent conditions library that used conditional debug attributes so that the methods are completely omitted in the release build. The really cool thing about this is that I could use tests anywhere I want in code without worrying about final performance.

I found Guard and I'm very impressed with the thoroughness of design decisions. So I was wondering what is your opinion about using fluent conditions with conditional debug attributes? Do you feel it would be warranted to add such a feature to Guard? If not conditional debug, it could be a custom conditional variable so that the developer can turn on/off compilation of the Guard statements -- simply by declaring the variable at program start.

Consider adding SecureArgument to make the guard behavior explicit

Is your feature request related to a problem?
I've never been a fan of flag arguments. Apart from violating the SRP it also makes the code harder to read and less maintainable. It's also easier to make a mistake by passing the wrong flag value, one must always inspect the method's signature.

Describe the solution you'd like
Add a new factory method SecureArgument that does exactly what it says on the tin. Remove the secure flag from the Argument factory method to follow the SRP.

Consider:

Guard.Argument(() => value, true).NotDefault();

vs

Guard.SecureArgument(() => value).NotDefault();

IMHO the intent is much clearer and just by looking at the code there is no more guessing what that true does.

I realize this is a breaking change and would only be considered for the next major version, if at all. Nevertheless, I've already implemented it and will submit a PR.

Interface limitations

Hi,

Interfaces have limited support when you would like to set validation results. It works OK with value types and classes.
Are you planning to fix that?
interface IMyTestInterface {}

class MyTestClass { IMyTestInterface cvalue; MyTestClass(IMyTestInterface value) { cvalue = Guard.Argument(() => value).NotNull(); } }

If you will copy and try to compile code I added before you will get following error:

Severity Code Description Project File Line Suppression State
Error CS0029 Cannot implicitly convert type 'Dawn.Guard.ArgumentInfo<ConsoleApp6.IMyTestInterface>' to 'ConsoleApp6.IMyTestInterface'

I can fix that by writing ".Value" like that, but I would like to avoid that (same as for other objects):
cvalue = Guard.Argument(() => value).NotNull().Value;

Interop NotNullWhenAttribute leaks

The implementation of NotNullWhenAttribute included for interop purposes here (entire relevant section copied below) leaks.

I also have a library that uses a copy of the implementation of NotNullWhenAttribute to target new and old frameworks while allowing code to use NotNullWhen, due to this trying to reference Dawn.Guard results in a duplicate declaration error.

My library marks the implementation of NotNullWhenAttribute as internal preventing any leaking however as Dawn.Guards implementation is public this results in duplicate declaration.

Can your version be marked internal?

#if NETSTANDARD1_0 || NETSTANDARD2_0
namespace System.Diagnostics.CodeAnalysis
{
    /// <summary>Required for reference nullability annotations.</summary>
    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
    public sealed class NotNullWhenAttribute : Attribute
    {
        /// <summary>Initializes a new instance of the <see cref="NotNullWhenAttribute" /> class.</summary>
        /// <param name="returnValue">If the method returns this value, the associated parameter will not be null.</param>
        public NotNullWhenAttribute(bool returnValue) => this.ReturnValue = returnValue;

        /// <summary>Gets the return value condition.</summary>
        public bool ReturnValue { get; }
    }
}
#endif

'NotNull' validation should be available for unconstrained generic types

Is your feature request related to a problem?
I realized today that the library doesn't currently expose a NotNull validation when validating arguments of unconstrained generic types. This results in cumbersome validation for null when null doesn't make sense for the implementation when T happens to be a reference type.

Since the consumer doesn't actually know whether or not the generic type is a struct or a class, we have to validate the value in a way that works for both, and checking for null directly is not only a good solution, but even recommended.

Forcing a class constraint on the consumer changes the behavior and is not always a good solution (it is quite common for an implementation to work just fine regardless if T is a value or a reference type, as long as it is not null when it is a reference type.

Describe the solution you'd like
NotNull should be available when doing Guard.Argument<T>(T) and implemented the same way a normal null comparison is.

Linux and macOS builds

Generic xUnit theories don't initialize on some non-Windows platforms. They work on AppVeyor's Ubuntu image but fail on Travis' Trusty and macOS images. They also fail on WSL-Ubuntu 16.04.4.

Reverting a previous version of xUnit may help. An option is to convert our generic tests to non-generic ones and another is to keep Travis builds disabled until the xUnit folks release a fix.

Here are the issues on their side:

Consider adding a few more standard comparison guards

Is your feature request related to a problem?
Some standard validations present in other validation libraries seem to missing.

Describe the solution you'd like
New comparison guards:

  • GreaterThan(T)
  • GreaterThanOrEqualTo(T) - an alias to Min(T) or rather the other way around
  • LessThan(T)
  • LessThanOrEqualTo(T) - an alias to Max(T) or rather the other way around

New DateTime guards:

  • Before(T)
  • NotBefore(T) or NoSoonerThan(T)
  • After(T)
  • NotAfter(T) or NoLaterThan(T)

Note that the default DateTime.Compare() method ignores the Kind property and the comparison itself is useless if the values are not in the same time zone. I don't expect these new proposed DateTime guards to take into account every aspect of datetime comparison (https://blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.html) but they could at least make sure the Kind properties match on both values before comparison and fail if that's not the case. My hope is that this would force the developers to make a conscious choice and force all DateTime values to have the same Kind. Different time zones are not considered because anyone working with explicit time zone support likely doesn't use DateTime anyway.

I could also create a PR if you consider these guards worth adding.

Binary Backward Compatibility

Version 1.4.0 introduced a binary breaking change to the library due to the addition of new optional parameters to constructor on the ArgumentInfo struct. Similar changes occurred on other methods in versions up through version 1.6.0.

See this report for a summary of the changes from 1.3.0 to 1.4.0:
https://www.fuget.org/packages/Dawn.Guard/1.4.0/lib/netstandard2.0/diff/1.3.0/

The addition of optional parameters causes binary runtime incompatibilities between versions. This can cause problems when a project uses multiple libraries that were compiled against different versions of the Dawn package.

To Reproduce
See this git repo: https://github.com/nathanaw/dawn-guard-binary-compat

Expected behavior
Semantic versioning would have all versions that share a major version number be backwards compatible.

Actual behavior
A MissingMethodException is thrown when code compiled against a pre 1.4.0 version runs in a process that loaded the 1.4.0 or later.

Purpose of Report
While it is likely not feasible or advisable to revise prior versions, this bug report aims to highlight the impact of adding optional parameters. Future releases might consider using method overloads to add additional parameters as a means to maintain backwards compatibility.

It appears that version 1.6.0 through 1.12.0 are binary backwards compatible. There are no removals. Only additions. ๐Ÿ‘
https://www.fuget.org/packages/Dawn.Guard/1.12.0/lib/netstandard2.0/diff/1.6.0/

Praise
I am a big fan of the Dawn.Guards package. The design and implementation are impressive and highly useful for my projects. Many thanks to the contributors.

Multiple checks with same custom message

If I want to check both NotNull() and NotEmpty() with the same error message just written once, can this be done?
I'm referring to this way of putting in messages, but not put for each one again and again.

boolean guard question

I am a big fan of doing checks on every argument that comes into a given function. I'd rather have something immediately throw an exception when given invalid data than find out ten functions in that my X value is invalid. Unfortunately if not then assert throw exception checks are very spammy. As a dyslexic, Dawn.Guard is awesome.

But I'm having some trouble validating a simple boolean.


var myStuff = true;
var myOtherStuff = true;

// this works
Guard.Argument(() => stuff).True();
// this works
Guard.Argument(() => myStuff).True();
// this throws the exception
Guard.Argument(() => myStuff || myOtherStuff).True();

throws an exception:
A member expression is expected.
Parameter name: e

at Dawn.Guard.Argument[T](Expression1 e, Boolean secure) in C:\Users\ekramer\Documents\Visual Studio 2017\Projects\guard\src\Guard.Argument.cs:line 61 at Dawn.Tests.BooleanTests.GuardSupportsBooleans(Nullable1 true, Nullable`1 false) in C:\Users\ekramer\Documents\Visual Studio 2017\Projects\guard\tests\BooleanTests.cs:line 13

I went so far as to clone the repository so I could examine the unit tests for booleans and the very next line of

Guard.Argument(() => @true).True();

works just fine. The only difference is that @true is a nullable boolean.

Could someone explain what I'm doing wrong here?

thanks,
E

Add GreaterThan/LassThan guards

Split from #40

Currently, we only have inclusive comparable guards: Min (>=) and Max (<=). We should also add the exclusive ones:

  • GreaterThan (>)
  • LessThan (<)

We can also add GreaterThanOrEqualTo and LessThanOrEqualTo as aliases to Min and Max for consistency but we can't remove Min and Max before v2. It may also make sense to keep both pairs.

Support for both directory and file path validation

Is your feature request related to a problem?
A description of the problem. Ex. I'm always frustrated when [...]

Describe the solution you'd like
Support for path validation were Guard.Argument(() => name).NotValidFilePath()

Add support for 'CallerArgumentExpression' attribute

Is your feature request related to a problem?
The (value, nameof(value)) way of specifying the argument name is not necessary anymore when using C#8, since the CallerArgumentExpressionAttribute has been added to capture the name of any argument passed into the method.

Describe the solution you'd like
The Argument method should rely on [CallerArgumentExpression] to fetch the argument name automatically without having to pass it from the caller. This would considerably clean up the usage of the library while also discouraging the use of the Expression-based approach which is very slow.

I'm actually quite surprised that this hasn't been implemented yet after reading all the documentation. If authors are aware of such attribute, I'd like to read the reasons why this hasn't been put in place yet (besides being unavailable in older runtimes).

Problem with "InitContainsDuplicate" on .NET < 4.7.2

Hello,

first of all, I wan't to thank you for providing this nice library.
I'm currently trying to use it within a project that uses a rather old .NET version (4.5.2) and the following problem emerged:

To Reproduce
.NET Framework version < 4.7.2 on target machine.
Using any of the Guard.Collection<T> methods.

Expected behavior
No runtime exceptions during initialization.

Actual behavior
Throws an argument null exception at this line of code.

I did a little bit of research and found this: Prior to version 4.7.2 HashSet<T> (documentation) does not appear to have constructor that takes an integer argument (in other words the initial capacity of the HashSet<T>).

As I'm probably one of a very few devs to experience this kind of error I'm not sure how you want to proceed with this issue.

Test coverage

Right now, only the deprecated methods aren't covered. They'll be removed on 2.0, so the coverage should be 100% when that happens.

Guard variants for nullable types

I'm testing library in c# 8 environment. It looks good but I have warning where using nullables variants of standard types. For example when I'm trying this code:

public static string Test(string? text)
{
    text = Guard.Argument(() => text).NotNull();
    return text.Substring(0, 1);
}

I have two warnings:

Warning CS8634 The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'Guard.NotNull(in Guard.ArgumentInfo, string?)'. Nullability of type argument 'string?' doesn't match 'class' constraint
Warning CS8619 Nullability of reference types in value of type 'Guard.ArgumentInfo<string?>' doesn't match target type 'Guard.ArgumentInfo'

I think that string? and others need additional care?

Unacceptable performance

First, thank you guys for this cool library. The lambda and the fluent api is cool.

As other developer discussed in #13 and #39 , compiling the expression causes overhead in performance.

And today I found the overhead unacceptable when developing a IO-bounded program which process some data packets:

Data packets are produced by another program at the rate of approximately 15KB/s. The consumer process receives the packets via a tcp connection and decode the packet.

My function ReadFloat is widely used in decoding the data packet. And I found that Guard.Argument(() => pos).Min(0); has a great impact on performance.

static float ReadFloat(byte[] buffer, int pos)
{
    Guard.Argument(() => pos).Min(0);

    return BitConverter.ToSingle(buffer, pos);
}

I think the overhead is unacceptable because my program is an IO-bounded program that consumes the data packets at quite a low rate (~15KB/s), but the usage of the CPU is very high (~40% taskmgr)

So obviously this library is not high performance, and might cause problems even if you are developing a IO-bounded program. I think you should warn the developers by marking this method with Obsolete.

To Reproduce

the benchmark:

            Results:

            Without Guard:              00:00:00.0020735
            With Guard:                 00:00:13.5584228
            Guard, Not Using Lambda:    00:00:00.0011907
            Bottleneck:                 00:00:13.3689263
using Dawn;
using Microsoft.VisualBasic;
using System;
using System.Linq.Expressions;

namespace GuardPerformance
{
    class Program
    {
        static byte[] _buffer = new byte[1024];
        static readonly int Iterations = 100000;

        static void Main(string[] args)
        {
            /*

            Results:

            Without Guard:              00:00:00.0020735
            With Guard:                 00:00:13.5584228
            Guard, Not Using Lambda:    00:00:00.0011907
            Bottleneck:                 00:00:13.3689263

            */
            DateTime t;

            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                ReadFloat(_buffer, i % 1000);
            }
            Console.WriteLine($"Without Guard: {DateTime.Now - t}");


            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                ReadFloatWithGuard(_buffer, i % 1000);
            }
            Console.WriteLine($"With Guard: {DateTime.Now - t}");


            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                ReadFloatWithGuard_NotUsingLambda(_buffer, i % 1000);
            }
            Console.WriteLine($"Guard, Not Using Lambda: {DateTime.Now - t}");


            t = DateTime.Now;
            for (int i = 0; i < Iterations; i++)
            {
                Bottleneck(() => i);
            }
            Console.WriteLine($"Bottleneck: {DateTime.Now - t}");
        }

        static float ReadFloat(byte[] buffer, int pos)
        {
            if (pos < 0)
                throw new ArgumentOutOfRangeException();

            return BitConverter.ToSingle(buffer, pos);
        }

        static float ReadFloatWithGuard(byte[] buffer, int pos)
        {
            Guard.Argument(() => pos).Min(0);

            return BitConverter.ToSingle(buffer, pos);
        }

        static float ReadFloatWithGuard_NotUsingLambda(byte[] buffer, int pos)
        {
            Guard.Argument(pos, nameof(pos)).Min(0);

            return BitConverter.ToSingle(buffer, pos);
        }

        public static void Bottleneck<T>(Expression<Func<T>> e, bool secure = false)
        {
            // This causes the bad performance
            e.Compile();
        }
    }


}

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.