diff --git a/Code/Light.GuardClauses.AllProjects.sln.DotSettings b/Code/Light.GuardClauses.AllProjects.sln.DotSettings index 2c4519b4..ac545e29 100644 --- a/Code/Light.GuardClauses.AllProjects.sln.DotSettings +++ b/Code/Light.GuardClauses.AllProjects.sln.DotSettings @@ -2,16 +2,39 @@ True None False - HINT + HINT + SUGGESTION + HINT + SUGGESTION + SUGGESTION + HINT + WARNING + WARNING + DO_NOT_SHOW + DO_NOT_SHOW HINT + WARNING + DO_NOT_SHOW HINT WARNING + True <?xml version="1.0" encoding="utf-16"?><Profile name="Kenny's Kleanup"><CSReorderTypeMembers>True</CSReorderTypeMembers><CSUpdateFileHeader>True</CSUpdateFileHeader><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CppReformatCode>True</CppReformatCode><XMLReformatCode>True</XMLReformatCode><CssReformatCode>True</CssReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" ArrangeCodeBodyStyle="False" ArrangeVarStyle="False" /><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><RemoveCodeRedundancies>True</RemoveCodeRedundancies><HtmlReformatCode>True</HtmlReformatCode><JsInsertSemicolon>True</JsInsertSemicolon><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor></Profile> - C# Cleanup - Kenny's Kleanup + Built-in: Full Cleanup + Built-in: Full Cleanup + False + Required + Required + Required + Required + ExpressionBody + ExpressionBody + ExpressionBody + True + False True True + True True @@ -22,23 +45,69 @@ True True True + True TOGETHER_SAME_LINE + True + True + True + NO_INDENT True True + True + True + False True + True + False + True + 90 + 1 + 10000 + COMPACT True + True True True NEVER - NEVER - NEVER - NEVER + + + + NEVER + False + ALWAYS + IF_OWNER_IS_SINGLE_LINE + NEVER DO_NOT_CHANGE + True + True True - 500 - False + False + True + True + CHOP_IF_LONG + CHOP_IF_LONG + False + True + True + True + True + True + True + False + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + + + CHOP_IF_LONG + CHOP_ALWAYS + CHOP_IF_LONG RemoveIndent RemoveIndent + False + ByFirstAttr + 150 + False False False False @@ -192,16 +261,21 @@ </TypePattern> </Patterns> False + False False True Replace Replace False + True True False False True False + False + True + False <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> @@ -256,12 +330,4 @@ True True True - True - True - True - True - True - True - True - True - True \ No newline at end of file + True \ No newline at end of file diff --git a/Code/Light.GuardClauses.Performance/CollectionAssertions/SpanMustHaveLengthBenchmark.cs b/Code/Light.GuardClauses.Performance/CollectionAssertions/SpanMustHaveLengthBenchmark.cs index 947efbf4..03988128 100644 --- a/Code/Light.GuardClauses.Performance/CollectionAssertions/SpanMustHaveLengthBenchmark.cs +++ b/Code/Light.GuardClauses.Performance/CollectionAssertions/SpanMustHaveLengthBenchmark.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using System.Text; using BenchmarkDotNet.Attributes; +using Light.GuardClauses.ExceptionFactory; using Light.GuardClauses.Exceptions; namespace Light.GuardClauses.Performance.CollectionAssertions @@ -47,7 +48,7 @@ public static class SpanMustHaveLengthExtensions public static Span MustHaveLengthCopyByValue(this Span parameter, int length, string parameterName = null, string message = null) { if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); + Throw.InvalidSpanLength((ReadOnlySpan) parameter, length, parameterName, message); return parameter; } @@ -55,7 +56,7 @@ public static Span MustHaveLengthCopyByValue(this Span parameter, int l public static Span MustHaveLengthInParameter(in this Span parameter, int length, string parameterName = null, string message = null) { if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); + Throw.InvalidSpanLength((ReadOnlySpan) parameter, length, parameterName, message); return parameter; } @@ -63,7 +64,7 @@ public static Span MustHaveLengthInParameter(in this Span parameter, in public static ref Span MustHaveLengthInOut(ref this Span parameter, int length, string parameterName = null, string message = null) { if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); + Throw.InvalidSpanLength((ReadOnlySpan) parameter, length, parameterName, message); return ref parameter; } } diff --git a/Code/Light.GuardClauses.Performance/CommonAssertions/InvalidArgumentBenchmark.cs b/Code/Light.GuardClauses.Performance/CommonAssertions/InvalidArgumentBenchmark.cs index dbc6241c..3e922c34 100644 --- a/Code/Light.GuardClauses.Performance/CommonAssertions/InvalidArgumentBenchmark.cs +++ b/Code/Light.GuardClauses.Performance/CommonAssertions/InvalidArgumentBenchmark.cs @@ -1,5 +1,6 @@ using System; using BenchmarkDotNet.Attributes; +using Light.GuardClauses.ExceptionFactory; namespace Light.GuardClauses.Performance.CommonAssertions { @@ -34,7 +35,7 @@ public bool LightGuardClausesCustomException() public bool LightGuardClausesCustomExceptionManualInlining() { if (Condition) - Exceptions.Throw.CustomException(() => new ArgumentException(Message, ParameterName)); + Throw.CustomException(() => new ArgumentException(Message, ParameterName)); return Condition; } diff --git a/Code/Light.GuardClauses.Performance/CommonAssertions/MustHaveValueBenchmark.cs b/Code/Light.GuardClauses.Performance/CommonAssertions/MustHaveValueBenchmark.cs index e42ec29e..1458a092 100644 --- a/Code/Light.GuardClauses.Performance/CommonAssertions/MustHaveValueBenchmark.cs +++ b/Code/Light.GuardClauses.Performance/CommonAssertions/MustHaveValueBenchmark.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; +using Light.GuardClauses.ExceptionFactory; using Light.GuardClauses.Exceptions; namespace Light.GuardClauses.Performance.CommonAssertions diff --git a/Code/Light.GuardClauses.Performance/CommonAssertions/MustNotBeNullReferenceBenchmark.cs b/Code/Light.GuardClauses.Performance/CommonAssertions/MustNotBeNullReferenceBenchmark.cs index e6c2a8a6..ea6aa9ed 100644 --- a/Code/Light.GuardClauses.Performance/CommonAssertions/MustNotBeNullReferenceBenchmark.cs +++ b/Code/Light.GuardClauses.Performance/CommonAssertions/MustNotBeNullReferenceBenchmark.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using Light.GuardClauses.Exceptions; +using Light.GuardClauses.ExceptionFactory; namespace Light.GuardClauses.Performance.CommonAssertions { @@ -56,7 +56,7 @@ public static T MustNotBeNullReferenceV1(this T parameter, string parameterNa return parameter; Throw.ArgumentNull(parameterName, message); - return default(T); + return default; } return parameter; @@ -71,7 +71,7 @@ public static T MustNotBeNullReferenceV2(this T parameter, string parameterNa return parameter; Throw.ArgumentNull(parameterName, message); - return default(T); + return default; } } } \ No newline at end of file diff --git a/Code/Light.GuardClauses.Performance/ComparableAssertions/MustBeApproximatelyBenchmark.cs b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustBeApproximatelyBenchmark.cs new file mode 100644 index 00000000..57364eba --- /dev/null +++ b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustBeApproximatelyBenchmark.cs @@ -0,0 +1,25 @@ +using System; +using BenchmarkDotNet.Attributes; + +namespace Light.GuardClauses.Performance.ComparableAssertions; + +[MemoryDiagnoser] +// ReSharper disable once ClassCanBeSealed.Global -- Benchmark.NET derives from this class with dynamically created code +public class MustBeApproximatelyBenchmark +{ + [Benchmark] + public double MustBeApproximately() => 5.100001.MustBeApproximately(5.100000, 0.0001); + + [Benchmark(Baseline = true)] + public double Imperative() => Imperative(5.100001, 5.100000, 0.0001); + + private static double Imperative(double parameter, double other, double tolerance) + { + if (Math.Abs(parameter - other) > tolerance) + { + throw new ArgumentOutOfRangeException(nameof(parameter), $"Value is not approximately {other}"); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses.Performance/ComparableAssertions/MustNotBeApproximatelyBenchmark.cs b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustNotBeApproximatelyBenchmark.cs new file mode 100644 index 00000000..a65d4b84 --- /dev/null +++ b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustNotBeApproximatelyBenchmark.cs @@ -0,0 +1,25 @@ +using System; +using BenchmarkDotNet.Attributes; + +namespace Light.GuardClauses.Performance.ComparableAssertions; + +[MemoryDiagnoser] +// ReSharper disable once ClassCanBeSealed.Global -- Benchmark.NET derives from this class with dynamically created code +public class MustNotBeApproximatelyBenchmark +{ + [Benchmark] + public double MustNotBeApproximately() => 5.2.MustNotBeApproximately(5.1, 0.5); + + [Benchmark(Baseline = true)] + public double Imperative() => Imperative(5.2, 5.1, 0.5); + + private static double Imperative(double parameter, double other, double tolerance) + { + if (Math.Abs(parameter - other) < tolerance) + { + throw new ArgumentOutOfRangeException(nameof(parameter), $"Value is approximately {other}"); + } + + return parameter; + } +} \ No newline at end of file diff --git a/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj b/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj index 7ffe52c8..cbf14189 100644 --- a/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj +++ b/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/Code/Light.GuardClauses.Performance/Program.cs b/Code/Light.GuardClauses.Performance/Program.cs index 6bdd8259..b17928a0 100644 --- a/Code/Light.GuardClauses.Performance/Program.cs +++ b/Code/Light.GuardClauses.Performance/Program.cs @@ -4,18 +4,17 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; -namespace Light.GuardClauses.Performance +namespace Light.GuardClauses.Performance; + +public static class Program { - public static class Program - { - private static IConfig DefaultConfiguration => - DefaultConfig - .Instance - .AddJob(Job.Default.WithRuntime(CoreRuntime.Core70)) - .AddJob(Job.Default.WithRuntime(ClrRuntime.Net48)) - .AddDiagnoser(MemoryDiagnoser.Default, new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig())); + private static IConfig DefaultConfiguration => + DefaultConfig + .Instance + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80)) + .AddJob(Job.Default.WithRuntime(ClrRuntime.Net48)) + .AddDiagnoser(MemoryDiagnoser.Default, new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig())); - public static void Main(string[] arguments) => - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(arguments, DefaultConfiguration); - } + public static void Main(string[] arguments) => + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(arguments, DefaultConfiguration); } \ No newline at end of file diff --git a/Code/Light.GuardClauses.Source/Light.GuardClauses.Source.csproj b/Code/Light.GuardClauses.Source/Light.GuardClauses.Source.csproj index 229eeab9..0f9b0900 100644 --- a/Code/Light.GuardClauses.Source/Light.GuardClauses.Source.csproj +++ b/Code/Light.GuardClauses.Source/Light.GuardClauses.Source.csproj @@ -5,13 +5,13 @@ 12.0 Kenny Pflug Kenny Pflug - Copyright © Kenny Pflug 2016 - 2023 + Copyright © Kenny Pflug 2016, 2025 true - - + + \ No newline at end of file diff --git a/Code/Light.GuardClauses.SourceCodeTransformation/Light.GuardClauses.SourceCodeTransformation.csproj b/Code/Light.GuardClauses.SourceCodeTransformation/Light.GuardClauses.SourceCodeTransformation.csproj index f9959aed..371827b8 100644 --- a/Code/Light.GuardClauses.SourceCodeTransformation/Light.GuardClauses.SourceCodeTransformation.csproj +++ b/Code/Light.GuardClauses.SourceCodeTransformation/Light.GuardClauses.SourceCodeTransformation.csproj @@ -13,7 +13,7 @@ - + diff --git a/Code/Light.GuardClauses.SourceCodeTransformation/SourceFileMerger.cs b/Code/Light.GuardClauses.SourceCodeTransformation/SourceFileMerger.cs index 6ceb25d7..a5af7e00 100644 --- a/Code/Light.GuardClauses.SourceCodeTransformation/SourceFileMerger.cs +++ b/Code/Light.GuardClauses.SourceCodeTransformation/SourceFileMerger.cs @@ -24,17 +24,20 @@ public static void CreateSingleSourceFile(SourceFileMergeOptions options) { Console.WriteLine("Appending version header..."); stringBuilder.AppendLine("/* ------------------------------") - .AppendLine($" Light.GuardClauses {typeof(SourceFileMerger).Assembly.GetName().Version!.ToString(3)}") + .AppendLine( + $" Light.GuardClauses {typeof(SourceFileMerger).Assembly.GetName().Version!.ToString(3)}" + ) .AppendLine(" ------------------------------") .AppendLine(); } Console.WriteLine("Creating default file layout..."); stringBuilder.AppendLineIf(!options.IncludeVersionComment, "/*") - .AppendLine($@"License information for Light.GuardClauses + .AppendLine( + $@"License information for Light.GuardClauses The MIT License (MIT) -Copyright (c) 2016, 2024 Kenny Pflug mailto:kenny.pflug@live.de +Copyright (c) 2016, 2025 Kenny Pflug mailto:kenny.pflug@live.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal @@ -68,6 +71,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Text; using System.Text.RegularExpressions; {(options.IncludeJetBrainsAnnotationsUsing ? "using JetBrains.Annotations;" + Environment.NewLine : string.Empty)}using {options.BaseNamespace}.Exceptions; +using {options.BaseNamespace}.ExceptionFactory; using {options.BaseNamespace}.FrameworkExtensions; {(options.IncludeJetBrainsAnnotationsUsing ? "using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute;" : "")} @@ -82,12 +86,18 @@ namespace {options.BaseNamespace}.Exceptions {{ }} +namespace {options.BaseNamespace}.ExceptionFactory +{{ +}} + namespace {options.BaseNamespace}.FrameworkExtensions {{ -}}"); +}}" + ); if (options.IncludeJetBrainsAnnotations) { - stringBuilder.AppendLine().AppendLine(@"/* + stringBuilder.AppendLine().AppendLine( + @"/* License information for JetBrains.Annotations MIT License @@ -113,12 +123,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE namespace JetBrains.Annotations { -}"); +}" + ); } if (options.IncludeCodeAnalysisNullableAttributes) { - stringBuilder.AppendLine().AppendLine(@" + stringBuilder.AppendLine().AppendLine( + @" namespace System.Diagnostics.CodeAnalysis { /// @@ -293,12 +305,14 @@ public NotNullWhenAttribute(bool returnValue) ReturnValue = returnValue; } } -}"); +}" + ); } if (options.IncludeCallerArgumentExpressionAttribute) { - stringBuilder.AppendLine().AppendLine(@" + stringBuilder.AppendLine().AppendLine( + @" namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] @@ -311,10 +325,11 @@ public CallerArgumentExpressionAttribute(string parameterName) public string ParameterName { get; } } -}"); +}" + ); } - var csharpParseOptions = new CSharpParseOptions(LanguageVersion.CSharp10); + var csharpParseOptions = new CSharpParseOptions(LanguageVersion.CSharp12); var targetSyntaxTree = CSharpSyntaxTree.ParseText(stringBuilder.ToString(), csharpParseOptions); var targetRoot = (CompilationUnitSyntax) targetSyntaxTree.GetRoot(); @@ -323,63 +338,131 @@ public CallerArgumentExpressionAttribute(string parameterName) .OfType() .ToList(); var defaultNamespace = namespaces.First(@namespace => @namespace.Name.ToString() == $"{options.BaseNamespace}"); - var exceptionsNamespace = namespaces.First(@namespace => @namespace.Name.ToString() == $"{options.BaseNamespace}.Exceptions"); - var extensionsNamespace = namespaces.First(@namespace => @namespace.Name.ToString() == $"{options.BaseNamespace}.FrameworkExtensions"); - var jetBrainsNamespace = namespaces.FirstOrDefault(@namespace => @namespace.Name.ToString() == "JetBrains.Annotations"); + var exceptionsNamespace = + namespaces.First(@namespace => @namespace.Name.ToString() == $"{options.BaseNamespace}.Exceptions"); + var extensionsNamespace = namespaces.First( + @namespace => @namespace.Name.ToString() == $"{options.BaseNamespace}.FrameworkExtensions" + ); + var exceptionFactoryNamespace = namespaces.First( + @namespace => @namespace.Name.ToString() == $"{options.BaseNamespace}.ExceptionFactory" + ); + var jetBrainsNamespace = + namespaces.FirstOrDefault(@namespace => @namespace.Name.ToString() == "JetBrains.Annotations"); var replacedNodes = new Dictionary { [defaultNamespace] = defaultNamespace, [exceptionsNamespace] = exceptionsNamespace, - [extensionsNamespace] = extensionsNamespace + [extensionsNamespace] = extensionsNamespace, + [exceptionFactoryNamespace] = exceptionFactoryNamespace, }; if (options.IncludeJetBrainsAnnotations && jetBrainsNamespace != null) + { replacedNodes.Add(jetBrainsNamespace, jetBrainsNamespace); + } var allSourceFiles = new DirectoryInfo(options.SourceFolder).GetFiles("*.cs", SearchOption.AllDirectories) - .Where(f => !f.FullName.Contains("obj") && - !f.FullName.Contains("bin")) + .Where( + f => !f.FullName.Contains("obj") && + !f.FullName.Contains("bin") + ) .ToDictionary(f => f.Name); - // Start with Check.CommonAssertions before all other files to prepare the Check class - Console.WriteLine("Merging CommonAssertions of the Check class..."); - var currentFile = allSourceFiles["Check.CommonAssertions.cs"]; - + // Start with Check.cs before all other files to prepare the Check class + Console.WriteLine("Preparing Check class..."); + var currentFile = allSourceFiles["Check.cs"]; var sourceSyntaxTree = CSharpSyntaxTree.ParseText(currentFile.ReadContent(), csharpParseOptions); var checkClassDeclaration = (ClassDeclarationSyntax) sourceSyntaxTree.GetRoot() .DescendantNodes() - .First(node => node.IsKind(SyntaxKind.ClassDeclaration)); - checkClassDeclaration = checkClassDeclaration.WithModifiers(checkClassDeclaration.Modifiers.Remove(checkClassDeclaration.Modifiers.First(token => token.IsKind(SyntaxKind.PartialKeyword)))); + .First( + node => node.IsKind( + SyntaxKind.ClassDeclaration + ) + ); + checkClassDeclaration = checkClassDeclaration.WithModifiers( + checkClassDeclaration.Modifiers.Remove( + checkClassDeclaration.Modifiers.First(token => token.IsKind(SyntaxKind.PartialKeyword)) + ) + ); + + // Do the same thing for the Throw class + Console.WriteLine("Preparing Throw class..."); + currentFile = allSourceFiles["Throw.cs"]; + sourceSyntaxTree = CSharpSyntaxTree.ParseText(currentFile.ReadContent(), csharpParseOptions); + var throwClassDeclaration = (ClassDeclarationSyntax) sourceSyntaxTree.GetRoot() + .DescendantNodes() + .First( + node => node.IsKind( + SyntaxKind.ClassDeclaration + ) + ); + throwClassDeclaration = throwClassDeclaration.WithModifiers( + throwClassDeclaration.Modifiers.Remove( + throwClassDeclaration.Modifiers.First(token => token.IsKind(SyntaxKind.PartialKeyword)) + ) + ); // Process all other files Console.WriteLine("Merging remaining files..."); foreach (var fileName in allSourceFiles.Keys) { + if (fileName == "SpanDelegates.cs") + { + + } + if (!CheckIfFileShouldBeProcessed(options, fileName)) + { continue; + } currentFile = allSourceFiles[fileName]; sourceSyntaxTree = CSharpSyntaxTree.ParseText(currentFile.ReadContent(), csharpParseOptions); - var originalNamespace = DetermineOriginalNamespace(options, - defaultNamespace, - currentFile, - extensionsNamespace, - exceptionsNamespace, - jetBrainsNamespace); + var originalNamespace = DetermineOriginalNamespace( + options, + defaultNamespace, + currentFile, + extensionsNamespace, + exceptionsNamespace, + exceptionFactoryNamespace, + jetBrainsNamespace + ); // If the file contains assertions, add it to the existing Check class declaration if (originalNamespace == defaultNamespace && currentFile.Name.StartsWith("Check.")) { var classDeclaration = (ClassDeclarationSyntax) sourceSyntaxTree.GetRoot() .DescendantNodes() - .First(node => node.IsKind(SyntaxKind.ClassDeclaration)); - checkClassDeclaration = checkClassDeclaration.WithMembers(checkClassDeclaration.Members.AddRange(classDeclaration.Members)); + .First( + node => node.IsKind( + SyntaxKind.ClassDeclaration + ) + ); + checkClassDeclaration = + checkClassDeclaration.WithMembers(checkClassDeclaration.Members.AddRange(classDeclaration.Members)); + continue; + } + + // Do something similar for the Throw class + if (originalNamespace == exceptionFactoryNamespace && currentFile.Name.StartsWith("Throw.")) + { + var classDeclaration = (ClassDeclarationSyntax) sourceSyntaxTree.GetRoot() + .DescendantNodes() + .First( + node => node.IsKind( + SyntaxKind.ClassDeclaration + ) + ); + throwClassDeclaration = + throwClassDeclaration.WithMembers(throwClassDeclaration.Members.AddRange(classDeclaration.Members)); continue; } // Else just get the members of the first namespace and add them to the corresponding one var sourceCompilationUnit = (CompilationUnitSyntax) sourceSyntaxTree.GetRoot(); if (sourceCompilationUnit.Members.IsNullOrEmpty()) + { continue; + } var membersToAdd = ((FileScopedNamespaceDeclarationSyntax) sourceCompilationUnit.Members[0]).Members; @@ -387,15 +470,25 @@ public CallerArgumentExpressionAttribute(string parameterName) replacedNodes[originalNamespace] = currentlyEditedNamespace .WithMembers( - currentlyEditedNamespace.Members.AddRange(membersToAdd)); + currentlyEditedNamespace.Members.AddRange(membersToAdd) + ); } // After the Check class declaration is finished, insert it into the default namespace var currentDefaultNamespace = replacedNodes[defaultNamespace]; - replacedNodes[defaultNamespace] = currentDefaultNamespace.WithMembers(currentDefaultNamespace.Members.Insert(0, checkClassDeclaration)); + replacedNodes[defaultNamespace] = + currentDefaultNamespace.WithMembers(currentDefaultNamespace.Members.Insert(0, checkClassDeclaration)); + + // Do the same thing for the Throw class + var currentExceptionFactoryNamespace = replacedNodes[exceptionFactoryNamespace]; + replacedNodes[exceptionFactoryNamespace] = + currentExceptionFactoryNamespace.WithMembers( + currentExceptionFactoryNamespace.Members.Insert(0, throwClassDeclaration) + ); // Update the target compilation unit - targetRoot = targetRoot.ReplaceNodes(replacedNodes.Keys, (originalNode, _) => replacedNodes[originalNode]).NormalizeWhitespace(); + targetRoot = targetRoot.ReplaceNodes(replacedNodes.Keys, (originalNode, _) => replacedNodes[originalNode]) + .NormalizeWhitespace(); // Make types internal if necessary if (options.ChangePublicTypesToInternalTypes) @@ -403,38 +496,57 @@ public CallerArgumentExpressionAttribute(string parameterName) Console.WriteLine("Types are changed from public to internal..."); var changedTypeDeclarations = new Dictionary(); - foreach (var typeDeclaration in targetRoot.DescendantNodes().Where(node => node.IsKind(SyntaxKind.ClassDeclaration) || - node.IsKind(SyntaxKind.StructDeclaration) || - node.IsKind(SyntaxKind.EnumDeclaration) || - node.IsKind(SyntaxKind.DelegateDeclaration))) + foreach (var typeDeclaration in targetRoot.DescendantNodes().Where( + node => node.IsKind(SyntaxKind.ClassDeclaration) || + node.IsKind(SyntaxKind.StructDeclaration) || + node.IsKind(SyntaxKind.EnumDeclaration) || + node.IsKind(SyntaxKind.DelegateDeclaration) + )) { if (typeDeclaration is BaseTypeDeclarationSyntax typeDeclarationSyntax) { var publicModifier = typeDeclarationSyntax.Modifiers[0]; var adjustedModifiers = typeDeclarationSyntax.Modifiers .RemoveAt(0) - .Insert(0, Token(SyntaxKind.InternalKeyword).WithTriviaFrom(publicModifier)); + .Insert( + 0, + Token(SyntaxKind.InternalKeyword) + .WithTriviaFrom(publicModifier) + ); if (typeDeclarationSyntax is ClassDeclarationSyntax classDeclaration) + { changedTypeDeclarations[classDeclaration] = classDeclaration.WithModifiers(adjustedModifiers); + } else if (typeDeclarationSyntax is StructDeclarationSyntax structDeclaration) + { changedTypeDeclarations[structDeclaration] = structDeclaration.WithModifiers(adjustedModifiers); + } else if (typeDeclarationSyntax is EnumDeclarationSyntax enumDeclaration) + { changedTypeDeclarations[enumDeclaration] = enumDeclaration.WithModifiers(adjustedModifiers); + } } else if (typeDeclaration is DelegateDeclarationSyntax delegateDeclaration) { var publicModifier = delegateDeclaration.Modifiers[0]; var adjustedModifiers = delegateDeclaration.Modifiers .RemoveAt(0) - .Insert(0, Token(SyntaxKind.InternalKeyword).WithTriviaFrom(publicModifier)); + .Insert( + 0, + Token(SyntaxKind.InternalKeyword) + .WithTriviaFrom(publicModifier) + ); changedTypeDeclarations[delegateDeclaration] = delegateDeclaration.WithModifiers(adjustedModifiers); } } - targetRoot = targetRoot.ReplaceNodes(changedTypeDeclarations.Keys, (originalNode, _) => changedTypeDeclarations[originalNode]); + targetRoot = targetRoot.ReplaceNodes( + changedTypeDeclarations.Keys, + (originalNode, _) => changedTypeDeclarations[originalNode] + ); } // Remove assertion overloads that incorporate an exception factory if necessary @@ -443,23 +555,49 @@ public CallerArgumentExpressionAttribute(string parameterName) Console.WriteLine("Removing overloads with exception factory..."); var checkClass = (ClassDeclarationSyntax) targetRoot.DescendantNodes() - .First(node => node.IsKind(SyntaxKind.ClassDeclaration) && - node is ClassDeclarationSyntax { Identifier.Text: "Check" }); + .First( + node => node.IsKind(SyntaxKind.ClassDeclaration) && + node is ClassDeclarationSyntax + { + Identifier.Text: "Check", + } + ); var membersWithoutExceptionFactory = checkClass.Members - .Where(member => member is not MethodDeclarationSyntax method || method.ParameterList.Parameters.All(parameter => parameter.Identifier.Text != "exceptionFactory")); - targetRoot = targetRoot.ReplaceNode(checkClass, checkClass.WithMembers(new SyntaxList(membersWithoutExceptionFactory))); + .Where( + member => member is not MethodDeclarationSyntax method || + method.ParameterList.Parameters.All( + parameter => parameter.Identifier.Text != "exceptionFactory" + ) + ); + targetRoot = targetRoot.ReplaceNode( + checkClass, + checkClass.WithMembers(new (membersWithoutExceptionFactory)) + ); // Remove members from Throw class that use exception factories var throwClass = (ClassDeclarationSyntax) targetRoot.DescendantNodes() - .First(node => node.IsKind(SyntaxKind.ClassDeclaration) && - node is ClassDeclarationSyntax { Identifier.Text: "Throw" }); + .First( + node => node.IsKind(SyntaxKind.ClassDeclaration) && + node is ClassDeclarationSyntax + { + Identifier.Text: "Throw", + } + ); membersWithoutExceptionFactory = throwClass.Members - .Where(member => member is not MethodDeclarationSyntax method || method.ParameterList.Parameters.All(parameter => parameter.Identifier.Text != "exceptionFactory")); - targetRoot = targetRoot.ReplaceNode(throwClass, throwClass.WithMembers(new SyntaxList(membersWithoutExceptionFactory))); + .Where( + member => member is not MethodDeclarationSyntax method || + method.ParameterList.Parameters.All( + parameter => parameter.Identifier.Text != "exceptionFactory" + ) + ); + targetRoot = targetRoot.ReplaceNode( + throwClass, + throwClass.WithMembers(new (membersWithoutExceptionFactory)) + ); } var targetFileContent = targetRoot.ToFullString(); @@ -473,35 +611,39 @@ public CallerArgumentExpressionAttribute(string parameterName) } private static bool CheckIfFileShouldBeProcessed(SourceFileMergeOptions options, string fileName) => - fileName != "Check.CommonAssertions.cs" && + fileName != "Check.cs" && + fileName != "Throw.cs" && fileName != "CallerArgumentExpressionAttribute.cs" && (fileName != "ReSharperAnnotations.cs" || options.IncludeJetBrainsAnnotations) && (fileName != "ValidatedNotNullAttribute.cs" || options.IncludeValidatedNotNullAttribute); - private static NamespaceDeclarationSyntax DetermineOriginalNamespace(SourceFileMergeOptions options, - NamespaceDeclarationSyntax defaultNamespace, - FileInfo currentFile, - NamespaceDeclarationSyntax extensionsNamespace, - NamespaceDeclarationSyntax exceptionsNamespace, - NamespaceDeclarationSyntax? jetBrainsNamespace) + private static NamespaceDeclarationSyntax DetermineOriginalNamespace( + SourceFileMergeOptions options, + NamespaceDeclarationSyntax defaultNamespace, + FileInfo currentFile, + NamespaceDeclarationSyntax extensionsNamespace, + NamespaceDeclarationSyntax exceptionsNamespace, + NamespaceDeclarationSyntax exceptionFactoryNamespace, + NamespaceDeclarationSyntax? jetBrainsNamespace + ) { var originalNamespace = defaultNamespace; switch (currentFile.Directory?.Name) { - case "FrameworkExtensions": - originalNamespace = extensionsNamespace; - break; - case "Exceptions": - originalNamespace = exceptionsNamespace; - break; + case "FrameworkExtensions": originalNamespace = extensionsNamespace; break; + case "Exceptions": originalNamespace = exceptionsNamespace; break; + case "ExceptionFactory": originalNamespace = exceptionFactoryNamespace; break; default: { if (options.IncludeJetBrainsAnnotations && currentFile.Name == "ReSharperAnnotations.cs") + { originalNamespace = jetBrainsNamespace!; + } + break; } } return originalNamespace; } -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs index c0abf2d3..70db5a8d 100644 --- a/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs +++ b/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs @@ -17,7 +17,8 @@ public static void DoubleWithDefaultTolerance(double first, double second, bool [InlineData(1.1, 1.3, 0.5, true)] [InlineData(100.55, 100.555, 0.00001, false)] [InlineData(5.0, 14.999999, 10.0, true)] - [InlineData(5.0, 15.0, 10.0, false)] + [InlineData(5.0, 15.0, 10.0, true)] + [InlineData(4.9998, 15.0, 10.0, false)] [InlineData(5.0, 15.0001, 10.0, false)] public static void DoubleWithCustomTolerance(double first, double second, double tolerance, bool expected) => first.IsApproximately(second, tolerance).Should().Be(expected); @@ -34,8 +35,69 @@ public static void FloatWithDefaultTolerance(float first, float second, bool exp [InlineData(1.1f, 1.3f, 0.5f, true)] [InlineData(100.55f, 100.555f, 0.00001f, false)] [InlineData(5.0f, 14.999999f, 10.0f, true)] - [InlineData(5.0f, 15.0f, 10.0f, false)] + [InlineData(5.0f, 15.0f, 10.0f, true)] + [InlineData(4.99f, 15.0f, 10.0f, false)] [InlineData(5.0f, 15.0001f, 10.0f, false)] public static void FloatWithCustomTolerance(float first, float second, float tolerance, bool expected) => first.IsApproximately(second, tolerance).Should().Be(expected); -} \ No newline at end of file + +#if NET8_0 + [Theory] + [InlineData(1.1, 1.3, 0.5, true)] + [InlineData(100.55, 100.555, 0.00001, false)] + [InlineData(5.0, 14.999999, 10.0, true)] + [InlineData(5.0, 15.0, 10.0, true)] + [InlineData(5.0, 15.000001, 10.0, false)] + [InlineData(5.0, 15.0001, 10.0, false)] + public static void GenericDoubleWithCustomTolerance(double first, double second, double tolerance, bool expected) => + first.IsApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(1.1f, 1.3f, 0.5f, true)] + [InlineData(100.55f, 100.555f, 0.00001f, false)] + [InlineData(5.0f, 14.999999f, 10.0f, true)] + [InlineData(5.0f, 15.0f, 10.0f, true)] + [InlineData(5.0f, 15.01f, 10.0f, false)] + [InlineData(5.0f, 15.0001f, 10.0f, false)] + public static void GenericFloatWithCustomTolerance(float first, float second, float tolerance, bool expected) => + first.IsApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(5, 10, 10, true)] + [InlineData(5, 15, 10, true)] + [InlineData(4, 15, 10, false)] + [InlineData(-5, 5, 12, true)] + [InlineData(-100, 100, 199, false)] + [InlineData(42, 42, 1, true)] + public static void GenericIntWithCustomTolerance(int first, int second, int tolerance, bool expected) => + first.IsApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(5L, 10L, 10L, true)] + [InlineData(5L, 15L, 10L, true)] + [InlineData(5L, 16L, 10L, false)] + [InlineData(-5L, 5L, 12L, true)] + [InlineData(-100L, 100L, 199L, false)] + [InlineData(42L, 42L, 1L, true)] + public static void GenericLongWithCustomTolerance(long first, long second, long tolerance, bool expected) => + first.IsApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [MemberData(nameof(DecimalTestData))] + public static void GenericDecimalWithCustomTolerance( + decimal first, + decimal second, + decimal tolerance, + bool expected + ) => + first.IsApproximately(second, tolerance).Should().Be(expected); + + public static TheoryData DecimalTestData() => new () + { + { 1.1m, 1.3m, 0.5m, true }, + { 100.55m, 100.555m, 0.00001m, false }, + { 5.0m, 14.999999m, 10.0m, true }, + { 5.0m, 15.0m, 9.99m, false }, + }; +#endif +} diff --git a/Code/Light.GuardClauses.Tests/CommonAssertions/IsGreaterThanOrApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/CommonAssertions/IsGreaterThanOrApproximatelyTests.cs index d7d59d5c..96ccedcb 100644 --- a/Code/Light.GuardClauses.Tests/CommonAssertions/IsGreaterThanOrApproximatelyTests.cs +++ b/Code/Light.GuardClauses.Tests/CommonAssertions/IsGreaterThanOrApproximatelyTests.cs @@ -36,4 +36,59 @@ public static void FloatWithDefaultTolerance(float first, float second, bool exp [InlineData(1.0f, 2.0f, 0.1f, false)] public static void FloatWIthCustomTolerance(float first, float second, float tolerance, bool expected) => first.IsGreaterThanOrApproximately(second, tolerance).Should().Be(expected); -} \ No newline at end of file + +#if NET8_0 + [Theory] + [InlineData(15.91, 15.9, 0.1, true)] + [InlineData(24.449, 24.45, 0.0001, false)] + [InlineData(-3.12, -3.2, 0.001, true)] + [InlineData(2.369, 2.37, 0.0005, false)] + [InlineData(15.0, 14.0, 0.1, true)] // Greater than case + [InlineData(14.95, 15.0, 0.1, true)] // Approximately equal case + public static void GenericDoubleWithCustomTolerance(double first, double second, double tolerance, bool expected) => + first.IsGreaterThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(2.0f, 1.0f, 0.1f, true)] + [InlineData(1.0f, 1.0f, 0.1f, true)] + [InlineData(1.0f, 1.1f, 0.01f, false)] + [InlineData(1.0f, 2.0f, 0.1f, false)] + [InlineData(2.1f, 2.0f, 0.01f, true)] // Greater than case + public static void GenericFloatWithCustomTolerance(float first, float second, float tolerance, bool expected) => + first.IsGreaterThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(10, 5, 1, true)] // Greater than case + [InlineData(5, 5, 1, true)] // Equal case + [InlineData(5, 6, 1, true)] // Approximately equal case + [InlineData(5, 7, 1, false)] // Not greater than or approximately equal case + public static void GenericIntWithCustomTolerance(int first, int second, int tolerance, bool expected) => + first.IsGreaterThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(10L, 5L, 1L, true)] // Greater than case + [InlineData(5L, 5L, 1L, true)] // Equal case + [InlineData(5L, 6L, 1L, true)] // Approximately equal case + [InlineData(5L, 7L, 1L, false)] // Not greater than or approximately equal case + public static void GenericLongWithCustomTolerance(long first, long second, long tolerance, bool expected) => + first.IsGreaterThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [MemberData(nameof(DecimalTestData))] + public static void GenericDecimalWithCustomTolerance( + decimal first, + decimal second, + decimal tolerance, + bool expected + ) => + first.IsGreaterThanOrApproximately(second, tolerance).Should().Be(expected); + + public static TheoryData DecimalTestData() => new () + { + { 1.3m, 1.1m, 0.1m, true }, // Greater than case + { 1.1m, 1.1m, 0.1m, true }, // Equal case + { 1.0m, 1.1m, 0.2m, true }, // Approximately equal case + { 1.0m, 1.3m, 0.1m, false }, // Not greater than or approximately equal case + }; +#endif +} diff --git a/Code/Light.GuardClauses.Tests/CommonAssertions/IsLessThanOrApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/CommonAssertions/IsLessThanOrApproximatelyTests.cs index c710ec22..6e774b83 100644 --- a/Code/Light.GuardClauses.Tests/CommonAssertions/IsLessThanOrApproximatelyTests.cs +++ b/Code/Light.GuardClauses.Tests/CommonAssertions/IsLessThanOrApproximatelyTests.cs @@ -36,4 +36,56 @@ public static void FloatWithDefaultTolerance(float first, float second, bool exp [InlineData(0f, -1f, 0.9f, false)] public static void FloatWithCustomTolerance(float first, float second, float tolerance, bool expected) => first.IsLessThanOrApproximately(second, tolerance).Should().Be(expected); + +#if NET8_0 + [Theory] + [InlineData(13.25, 13.5, 0.1, true)] // Less than case + [InlineData(13.5, 13.5, 0.1, true)] // Equal case + [InlineData(13.55, 13.5, 0.1, true)] // Approximately equal case + [InlineData(13.7, 13.5, 0.1, false)] // Not less than or approximately equal case + public static void GenericDoubleWithCustomTolerance(double first, double second, double tolerance, bool expected) => + first.IsLessThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(13.25f, 13.5f, 0.1f, true)] // Less than case + [InlineData(13.5f, 13.5f, 0.1f, true)] // Equal case + [InlineData(13.55f, 13.5f, 0.1f, true)] // Approximately equal case + [InlineData(13.7f, 13.5f, 0.1f, false)] // Not less than or approximately equal case + public static void GenericFloatWithCustomTolerance(float first, float second, float tolerance, bool expected) => + first.IsLessThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(5, 10, 1, true)] // Less than case + [InlineData(5, 5, 1, true)] // Equal case + [InlineData(6, 5, 1, true)] // Approximately equal case + [InlineData(7, 5, 1, false)] // Not less than or approximately equal case + public static void GenericIntWithCustomTolerance(int first, int second, int tolerance, bool expected) => + first.IsLessThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [InlineData(5L, 10L, 1L, true)] // Less than case + [InlineData(5L, 5L, 1L, true)] // Equal case + [InlineData(6L, 5L, 1L, true)] // Approximately equal case + [InlineData(7L, 5L, 1L, false)] // Not less than or approximately equal case + public static void GenericLongWithCustomTolerance(long first, long second, long tolerance, bool expected) => + first.IsLessThanOrApproximately(second, tolerance).Should().Be(expected); + + [Theory] + [MemberData(nameof(DecimalTestData))] + public static void GenericDecimalWithCustomTolerance( + decimal first, + decimal second, + decimal tolerance, + bool expected + ) => + first.IsLessThanOrApproximately(second, tolerance).Should().Be(expected); + + public static TheoryData DecimalTestData() => new () + { + { 1.0m, 1.2m, 0.1m, true }, // Less than case + { 1.1m, 1.1m, 0.1m, true }, // Equal case + { 1.2m, 1.1m, 0.1m, true }, // Approximately equal case + { 1.3m, 1.1m, 0.1m, false }, // Not less than or approximately equal case + }; +#endif } \ No newline at end of file diff --git a/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeApproximatelyTests.cs new file mode 100644 index 00000000..32046559 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeApproximatelyTests.cs @@ -0,0 +1,236 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Light.GuardClauses.Tests.ComparableAssertions; + +public static class MustBeApproximatelyTests +{ + [Theory] + [InlineData(5.1, 5.0, 0.2)] + [InlineData(10.3, 10.3, 0.01)] + [InlineData(3.14159, 3.14, 0.002)] + [InlineData(-42.0, -42.0001, 0.001)] + public static void ValuesApproximatelyEqual_Double(double value, double other, double tolerance) => + value.MustBeApproximately(other, tolerance).Should().Be(value); + + [Theory] + [InlineData(5.1f, 5.0f, 0.2f)] + [InlineData(10.3f, 10.3f, 0.01f)] + [InlineData(3.14159f, 3.14f, 0.002f)] + [InlineData(-42.0f, -42.0001f, 0.001f)] + public static void ValuesApproximatelyEqual_Float(float value, float other, float tolerance) => + value.MustBeApproximately(other, tolerance).Should().Be(value); + + [Theory] + [InlineData(5.0, 5.3, 0.1)] + [InlineData(100.0, 99.8, 0.1)] + [InlineData(-20.0, -20.2, 0.1)] + [InlineData(0.0001, 0.0002, 0.00005)] + public static void ValuesNotApproximatelyEqual_Double(double value, double other, double tolerance) + { + var act = () => value.MustBeApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Theory] + [InlineData(5.0f, 5.3f, 0.1f)] + [InlineData(100.0f, 99.8f, 0.1f)] + [InlineData(-20.0f, -20.2f, 0.1f)] + [InlineData(0.0001f, 0.0002f, 0.00005f)] + public static void ValuesNotApproximatelyEqual_Float(float value, float other, float tolerance) + { + var act = () => value.MustBeApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void DefaultTolerance_Double() + { + // Should pass - difference is 0.00005 which is less than default tolerance 0.0001 + const double value = 1.00005; + value.MustBeApproximately(1.0).Should().Be(value); + + // Should throw - difference is 0.0002 which is greater than default tolerance 0.0001 + Action act = () => 1.0002.MustBeApproximately(1.0, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + } + + [Fact] + public static void DefaultTolerance_Float() + { + // Should pass - difference is 0.00005f which is less than default tolerance 0.0001f + const float value = 1.00005f; + value.MustBeApproximately(1.0f).Should().Be(value); + + // Should throw - difference is 0.0002f which is greater than default tolerance 0.0001f + Action act = () => 1.0002f.MustBeApproximately(1.0f, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + } + + [Fact] + public static void CustomException_Double() => + Test.CustomException( + 5.0, + 5.3, + (x, y, exceptionFactory) => x.MustBeApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Double() => + Test.CustomException( + 5.0, + 5.5, + 0.1, + (x, y, z, exceptionFactory) => x.MustBeApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void CustomException_Float() => + Test.CustomException( + 5.0f, + 5.3f, + (x, y, exceptionFactory) => x.MustBeApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Float() => + Test.CustomException( + 5.0f, + 5.5f, + 0.1f, + (x, y, z, exceptionFactory) => x.MustBeApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Double() => + 5.0.MustBeApproximately(5.05, 0.1, (_, _, _) => null).Should().Be(5.0); + + [Fact] + public static void NoCustomExceptionThrown_Float() => + 5.0f.MustBeApproximately(5.05f, 0.1f, (_, _, _) => null).Should().Be(5.0f); + + [Fact] + public static void CustomMessage_Double() => + Test.CustomMessage( + message => 100.0.MustBeApproximately(101.0, 0.5, message: message) + ); + + [Fact] + public static void CustomMessage_Float() => + Test.CustomMessage( + message => 100.0f.MustBeApproximately(101.0f, 0.5f, message: message) + ); + + [Fact] + public static void CallerArgumentExpression_Double() + { + const double seventyEightO1 = 78.1; + + var act = () => seventyEightO1.MustBeApproximately(3.0); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Double() + { + const double pi = 3.14159; + + var act = () => pi.MustBeApproximately(3.0, 0.1); + + act.Should().Throw() + .WithParameterName(nameof(pi)); + } + + [Fact] + public static void CallerArgumentExpression_Float() + { + const float seventyEightO1 = 78.1f; + + var act = () => seventyEightO1.MustBeApproximately(3.0f); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Float() + { + const float pi = 3.14159f; + + var act = () => pi.MustBeApproximately(3.0f, 0.1f); + + act.Should().Throw() + .WithParameterName(nameof(pi)); + } + +#if NET8_0 + [Theory] + [InlineData(5.1, 5.0, 0.2)] + [InlineData(10.3, 10.3, 0.01)] + [InlineData(3.14159, 3.14, 0.002)] + [InlineData(-42.0, -42.0001, 0.001)] + public static void ValuesApproximatelyEqual_Generic(double value, double other, double tolerance) => + value.MustBeApproximately(other, tolerance).Should().Be(value); + + [Theory] + [InlineData(5.0, 5.3, 0.1)] + [InlineData(100.0, 99.8, 0.1)] + [InlineData(-20.0, -20.2, 0.1)] + [InlineData(0.0001, 0.0002, 0.00005)] + public static void ValuesNotApproximatelyEqual_Generic(double value, double other, double tolerance) + { + var act = () => value.MustBeApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void CustomExceptionWithTolerance_Generic() => + Test.CustomException( + 5.0, + 5.3, + 0.2, + (x, y, t, exceptionFactory) => x.MustBeApproximately(y, t, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Generic() => + 5.0.MustBeApproximately(5.05, 0.1, (_, _, _) => null).Should().Be(5.0); + + [Fact] + public static void CustomMessage_Generic() => + Test.CustomMessage( + message => 100.0.MustBeApproximately(101.0, 0.5, message: message) + ); + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Generic() + { + const double seventyEightO1 = 78.1; + + var act = () => seventyEightO1.MustBeApproximately(3.0, 10.0); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } +#endif +} diff --git a/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeGreaterThanOrApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeGreaterThanOrApproximatelyTests.cs new file mode 100644 index 00000000..ff19f604 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeGreaterThanOrApproximatelyTests.cs @@ -0,0 +1,295 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Light.GuardClauses.Tests.ComparableAssertions; + +public static class CheckMustBeGreaterThanOrApproximatelyTests +{ + [Theory] + [InlineData(17.4, 17.3)] + [InlineData(19.9999999, 20.0)] + [InlineData(-5.49998, -5.5)] + [InlineData(0.0001, 0.0001)] + public static void EqualOrGreater_Double(double first, double second) => + first.MustBeGreaterThanOrApproximately(second).Should().Be(first); + + [Theory] + [InlineData(15.91, 15.9, 0.1)] + [InlineData(24.49999, 24.45, 0.0001)] + [InlineData(-3.12, -3.2, 0.001)] + [InlineData(2.369, 2.37, 0.05)] + public static void EqualOrGreaterWithTolerance_Double(double first, double second, double tolerance) => + first.MustBeGreaterThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(100.225f, 100.2f)] + [InlineData(-5.9f, -5.900005f)] + [InlineData(0f, -0.02f)] + [InlineData(-0.00001f, 0f)] + public static void EqualOrGreater_Float(float first, float second) => + first.MustBeGreaterThanOrApproximately(second).Should().Be(first); + + [Theory] + [InlineData(2.0f, 1.0f, 0.1f)] + [InlineData(1.0f, 1.0f, 0.1f)] + [InlineData(1.01f, 1.1f, 0.1f)] + [InlineData(1.0f, 2.0f, 1.0f)] + public static void EqualOrGreaterWithTolerance_Float(float first, float second, float tolerance) => + first.MustBeGreaterThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(5.0, 5.3, 0.1)] + [InlineData(100.0, 100.5, 0.1)] + [InlineData(-20.0, -19.8, 0.1)] + [InlineData(0.0001, 0.0003, 0.00005)] + public static void NotGreaterThanOrApproximately_Double(double value, double other, double tolerance) + { + var act = () => value.MustBeGreaterThanOrApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be greater than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Theory] + [InlineData(5.0f, 5.3f, 0.1f)] + [InlineData(100.0f, 100.5f, 0.1f)] + [InlineData(-20.0f, -19.8f, 0.1f)] + [InlineData(0.0001f, 0.0003f, 0.00005f)] + public static void NotGreaterThanOrApproximately_Float(float value, float other, float tolerance) + { + var act = () => value.MustBeGreaterThanOrApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be greater than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void DefaultTolerance_Double() + { + // Should pass - difference is 0.00005 which is less than default tolerance 0.0001 + const double value = 1.00005; + value.MustBeGreaterThanOrApproximately(1.0).Should().Be(value); + + // Should throw - difference is 0.0002 which is greater than default tolerance 0.0001 + Action act = () => 0.9998.MustBeGreaterThanOrApproximately(1.0, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + } + + [Fact] + public static void DefaultTolerance_Float() + { + // Should pass - difference is 0.00005f which is less than default tolerance 0.0001f + const float value = 1.00005f; + value.MustBeGreaterThanOrApproximately(1.0f).Should().Be(value); + + // Should throw - difference is 0.0002f which is greater than default tolerance 0.0001f + Action act = () => 0.9998f.MustBeGreaterThanOrApproximately(1.0f, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + } + + [Fact] + public static void CustomException_Double() => + Test.CustomException( + 5.0, + 5.3, + (x, y, exceptionFactory) => x.MustBeGreaterThanOrApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Double() => + Test.CustomException( + 5.0, + 5.5, + 0.1, + (x, y, z, exceptionFactory) => x.MustBeGreaterThanOrApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void CustomException_Float() => + Test.CustomException( + 5.0f, + 5.3f, + (x, y, exceptionFactory) => x.MustBeGreaterThanOrApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Float() => + Test.CustomException( + 5.0f, + 5.5f, + 0.1f, + (x, y, z, exceptionFactory) => x.MustBeGreaterThanOrApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Double() => + 5.2.MustBeGreaterThanOrApproximately(5.1, (_, _) => null).Should().Be(5.2); + + [Fact] + public static void NoCustomExceptionThrownWithTolerance_Double() => + 5.2.MustBeGreaterThanOrApproximately(5.0, 0.1, (_, _, _) => null).Should().Be(5.2); + + [Fact] + public static void NoCustomExceptionThrown_Float() => + 5.2f.MustBeGreaterThanOrApproximately(5.0f, (_, _) => null).Should().Be(5.2f); + + [Fact] + public static void NoCustomExceptionThrownWithTolerance_Float() => + 5.2f.MustBeGreaterThanOrApproximately(5.0f, 0.1f, (_, _, _) => null).Should().Be(5.2f); + + [Fact] + public static void CustomMessage_Double() => + Test.CustomMessage( + message => 100.0.MustBeGreaterThanOrApproximately(101.0, 0.5, message: message) + ); + + [Fact] + public static void CustomMessage_Float() => + Test.CustomMessage( + message => 100.0f.MustBeGreaterThanOrApproximately(101.0f, 0.5f, message: message) + ); + + [Fact] + public static void CallerArgumentExpression_Double() + { + const double seventyEightO1 = 78.1; + + var act = () => seventyEightO1.MustBeGreaterThanOrApproximately(79.0); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Double() + { + const double pi = 3.14159; + + var act = () => pi.MustBeGreaterThanOrApproximately(3.5, 0.1); + + act.Should().Throw() + .WithParameterName(nameof(pi)); + } + + [Fact] + public static void CallerArgumentExpression_Float() + { + const float seventyEightO1 = 78.1f; + + var act = () => seventyEightO1.MustBeGreaterThanOrApproximately(79.0f); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Float() + { + const float pi = 3.14159f; + + var act = () => pi.MustBeGreaterThanOrApproximately(3.5f, 0.1f); + + act.Should().Throw() + .WithParameterName(nameof(pi)); + } + +#if NET8_0 + [Theory] + [InlineData(15.91, 15.9, 0.1)] + [InlineData(24.4999, 24.45, 0.0001)] + [InlineData(-3.12, -3.2, 0.001)] + [InlineData(2.369, 2.37, 0.05)] + [InlineData(15.0, 14.0, 0.1)] // Greater than case + [InlineData(14.95, 15.0, 0.1)] // Approximately equal case + public static void EqualOrGreaterWithCustomTolerance_GenericDouble(double first, double second, double tolerance) => + first.MustBeGreaterThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(10, 5, 1)] // Greater than case + [InlineData(5, 5, 1)] // Equal case + [InlineData(5, 6, 1)] // Approximately equal case + [InlineData(5, 7, 2)] // Not greater than or approximately equal case + public static void EqualOrGreaterWithCustomTolerance_GenericInt32(int first, int second, int tolerance) => + first.MustBeGreaterThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(10L, 5L, 1L)] // Greater than case + [InlineData(5L, 5L, 1L)] // Equal case + [InlineData(5L, 6L, 1L)] // Approximately equal case + [InlineData(4L, 7L, 3L)] // Not greater than or approximately equal case + public static void EqualOrGreaterWithCustomTolerance_GenericInt64(long first, long second, long tolerance) => + first.MustBeGreaterThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [MemberData(nameof(DecimalTestData))] + public static void GenericDecimalWithCustomTolerance( + decimal first, + decimal second, + decimal tolerance + ) => + first.MustBeGreaterThanOrApproximately(second, tolerance).Should().Be(first); + + public static TheoryData DecimalTestData() => new () + { + { 1.3m, 1.1m, 0.1m }, // Greater than case + { 1.1m, 1.1m, 0.1m }, // Equal case + { 1.0m, 1.1m, 0.2m }, // Approximately equal case + { 1.292m, 1.3m, 0.1m }, // Not greater than or approximately equal case + }; + + [Theory] + [InlineData(5.0, 5.3, 0.1)] + [InlineData(100.0, 100.5, 0.1)] + [InlineData(-20.0, -19.8, 0.1)] + [InlineData(0.0001, 0.0003, 0.00005)] + public static void NotGreaterThanOrApproximately_Generic(double value, double other, double tolerance) + { + var act = () => value.MustBeGreaterThanOrApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be greater than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void CustomExceptionWithTolerance_Generic() => + Test.CustomException( + 5.0, + 5.5, + 0.1, + (x, y, t, exceptionFactory) => x.MustBeGreaterThanOrApproximately(y, t, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Generic() => + 5.2.MustBeGreaterThanOrApproximately(5.0, 0.1, (_, _, _) => null).Should().Be(5.2); + + [Fact] + public static void CustomMessage_Generic() => + Test.CustomMessage( + message => 100.0.MustBeGreaterThanOrApproximately(101.0, 0.5, message: message) + ); + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Generic() + { + const double seventyEightO1 = 78.1; + + var act = () => seventyEightO1.MustBeGreaterThanOrApproximately(79.0, 0.5); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } +#endif +} diff --git a/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeLessThanOrApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeLessThanOrApproximatelyTests.cs new file mode 100644 index 00000000..9eace403 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeLessThanOrApproximatelyTests.cs @@ -0,0 +1,295 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Light.GuardClauses.Tests.ComparableAssertions; + +public static class MustBeLessThanOrApproximatelyTests +{ + [Theory] + [InlineData(17.3, 17.4)] + [InlineData(19.9999999, 20.0)] + [InlineData(-5.5, -5.49998)] + [InlineData(0.0001, 0.0001)] + public static void EqualOrLess_Double(double first, double second) => + first.MustBeLessThanOrApproximately(second).Should().Be(first); + + [Theory] + [InlineData(15.9, 15.91, 0.1)] + [InlineData(24.45, 24.49999, 0.0001)] + [InlineData(-3.2, -3.12, 0.001)] + [InlineData(2.37, 2.369, 0.05)] + public static void EqualOrLessWithTolerance_Double(double first, double second, double tolerance) => + first.MustBeLessThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(100.2f, 100.225f)] + [InlineData(-5.900005f, -5.9f)] + [InlineData(-0.02f, 0f)] + [InlineData(0f, 0.00001f)] + public static void EqualOrLess_Float(float first, float second) => + first.MustBeLessThanOrApproximately(second).Should().Be(first); + + [Theory] + [InlineData(1.0f, 2.0f, 0.1f)] + [InlineData(1.0f, 1.0f, 0.1f)] + [InlineData(1.1f, 1.01f, 0.1f)] + [InlineData(1.0f, 2.0f, 1.0f)] + public static void EqualOrLessWithTolerance_Float(float first, float second, float tolerance) => + first.MustBeLessThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(5.3, 5.0, 0.1)] + [InlineData(100.5, 100.0, 0.1)] + [InlineData(-19.8, -20.0, 0.1)] + [InlineData(0.0003, 0.0001, 0.00005)] + public static void NotLessThanOrApproximately_Double(double value, double other, double tolerance) + { + var act = () => value.MustBeLessThanOrApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be less than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Theory] + [InlineData(5.3f, 5.0f, 0.1f)] + [InlineData(100.5f, 100.0f, 0.1f)] + [InlineData(-19.8f, -20.0f, 0.1f)] + [InlineData(0.0003f, 0.0001f, 0.00005f)] + public static void NotLessThanOrApproximately_Float(float value, float other, float tolerance) + { + var act = () => value.MustBeLessThanOrApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be less than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void DefaultTolerance_Double() + { + // Should pass - difference is 0.00005 which is less than default tolerance 0.0001 + const double value = 1.0; + value.MustBeLessThanOrApproximately(1.00005).Should().Be(value); + + // Should throw - difference is 0.0002 which is greater than default tolerance 0.0001 + Action act = () => 1.0002.MustBeLessThanOrApproximately(1.0, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + } + + [Fact] + public static void DefaultTolerance_Float() + { + // Should pass - difference is 0.00005f which is less than default tolerance 0.0001f + const float value = 1.0f; + value.MustBeLessThanOrApproximately(1.00005f).Should().Be(value); + + // Should throw - difference is 0.0002f which is greater than default tolerance 0.0001f + Action act = () => 1.0002f.MustBeLessThanOrApproximately(1.0f, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + } + + [Fact] + public static void CustomException_Double() => + Test.CustomException( + 5.3, + 5.0, + (x, y, exceptionFactory) => x.MustBeLessThanOrApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Double() => + Test.CustomException( + 5.5, + 5.0, + 0.1, + (x, y, z, exceptionFactory) => x.MustBeLessThanOrApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void CustomException_Float() => + Test.CustomException( + 5.3f, + 5.0f, + (x, y, exceptionFactory) => x.MustBeLessThanOrApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Float() => + Test.CustomException( + 5.5f, + 5.0f, + 0.1f, + (x, y, z, exceptionFactory) => x.MustBeLessThanOrApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Double() => + 5.1.MustBeLessThanOrApproximately(5.2, (_, _) => null).Should().Be(5.1); + + [Fact] + public static void NoCustomExceptionThrownWithTolerance_Double() => + 5.0.MustBeLessThanOrApproximately(5.2, 0.1, (_, _, _) => null).Should().Be(5.0); + + [Fact] + public static void NoCustomExceptionThrown_Float() => + 5.0f.MustBeLessThanOrApproximately(5.2f, (_, _) => null).Should().Be(5.0f); + + [Fact] + public static void NoCustomExceptionThrownWithTolerance_Float() => + 5.0f.MustBeLessThanOrApproximately(5.2f, 0.1f, (_, _, _) => null).Should().Be(5.0f); + + [Fact] + public static void CustomMessage_Double() => + Test.CustomMessage( + message => 101.0.MustBeLessThanOrApproximately(100.0, 0.5, message: message) + ); + + [Fact] + public static void CustomMessage_Float() => + Test.CustomMessage( + message => 101.0f.MustBeLessThanOrApproximately(100.0f, 0.5f, message: message) + ); + + [Fact] + public static void CallerArgumentExpression_Double() + { + const double seventyNinePoint0 = 79.0; + + var act = () => seventyNinePoint0.MustBeLessThanOrApproximately(78.1); + + act.Should().Throw() + .WithParameterName(nameof(seventyNinePoint0)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Double() + { + const double threePointFive = 3.5; + + var act = () => threePointFive.MustBeLessThanOrApproximately(3.14159, 0.1); + + act.Should().Throw() + .WithParameterName(nameof(threePointFive)); + } + + [Fact] + public static void CallerArgumentExpression_Float() + { + const float seventyNinePoint0 = 79.0f; + + var act = () => seventyNinePoint0.MustBeLessThanOrApproximately(78.1f); + + act.Should().Throw() + .WithParameterName(nameof(seventyNinePoint0)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Float() + { + const float threePointFive = 3.5f; + + var act = () => threePointFive.MustBeLessThanOrApproximately(3.14159f, 0.1f); + + act.Should().Throw() + .WithParameterName(nameof(threePointFive)); + } + +#if NET8_0 + [Theory] + [InlineData(15.9, 15.91, 0.1)] + [InlineData(24.45, 24.4999, 0.0001)] + [InlineData(-3.2, -3.12, 0.001)] + [InlineData(2.37, 2.369, 0.05)] + [InlineData(14.0, 15.0, 0.1)] // Less than case + [InlineData(15.0, 14.95, 0.1)] // Approximately equal case + public static void EqualOrLessWithCustomTolerance_GenericDouble(double first, double second, double tolerance) => + first.MustBeLessThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(5, 10, 1)] // Less than case + [InlineData(5, 5, 1)] // Equal case + [InlineData(6, 5, 1)] // Approximately equal case + [InlineData(7, 5, 2)] // Not less than or approximately equal case + public static void EqualOrLessWithCustomTolerance_GenericInt32(int first, int second, int tolerance) => + first.MustBeLessThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [InlineData(5L, 10L, 1L)] // Less than case + [InlineData(5L, 5L, 1L)] // Equal case + [InlineData(6L, 5L, 1L)] // Approximately equal case + [InlineData(7L, 4L, 3L)] // Not less than or approximately equal case + public static void EqualOrLessWithCustomTolerance_GenericInt64(long first, long second, long tolerance) => + first.MustBeLessThanOrApproximately(second, tolerance).Should().Be(first); + + [Theory] + [MemberData(nameof(DecimalTestData))] + public static void GenericDecimalWithCustomTolerance( + decimal first, + decimal second, + decimal tolerance + ) => + first.MustBeLessThanOrApproximately(second, tolerance).Should().Be(first); + + public static TheoryData DecimalTestData() => new () + { + { 1.1m, 1.3m, 0.1m }, // Less than case + { 1.1m, 1.1m, 0.1m }, // Equal case + { 1.1m, 1.0m, 0.2m }, // Approximately equal case + { 1.3m, 1.292m, 0.1m }, // Not less than or approximately equal case + }; + + [Theory] + [InlineData(5.3, 5.0, 0.1)] + [InlineData(100.5, 100.0, 0.1)] + [InlineData(-19.8, -20.0, 0.1)] + [InlineData(0.0003, 0.0001, 0.00005)] + public static void NotLessThanOrApproximately_Generic(double value, double other, double tolerance) + { + var act = () => value.MustBeLessThanOrApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must be less than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void CustomExceptionWithTolerance_Generic() => + Test.CustomException( + 5.5, + 5.0, + 0.1, + (x, y, t, exceptionFactory) => x.MustBeLessThanOrApproximately(y, t, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Generic() => + 5.0.MustBeLessThanOrApproximately(5.2, 0.1, (_, _, _) => null).Should().Be(5.0); + + [Fact] + public static void CustomMessage_Generic() => + Test.CustomMessage( + message => 101.0.MustBeLessThanOrApproximately(100.0, 0.5, message: message) + ); + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Generic() + { + const double seventyNinePoint0 = 79.0; + + var act = () => seventyNinePoint0.MustBeLessThanOrApproximately(78.1, 0.5); + + act.Should().Throw() + .WithParameterName(nameof(seventyNinePoint0)); + } +#endif +} \ No newline at end of file diff --git a/Code/Light.GuardClauses.Tests/ComparableAssertions/MustNotBeApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustNotBeApproximatelyTests.cs new file mode 100644 index 00000000..da9e7ea6 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustNotBeApproximatelyTests.cs @@ -0,0 +1,238 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Light.GuardClauses.Tests.ComparableAssertions; + +public static class MustNotBeApproximatelyTests +{ + [Theory] + [InlineData(5.3, 5.0, 0.2)] + [InlineData(10.4, 10.3, 0.01)] + [InlineData(3.15, 3.14, 0.001)] + [InlineData(-42.002, -42.0001, 0.001)] + public static void ValuesNotApproximatelyEqual_Double(double value, double other, double tolerance) => + value.MustNotBeApproximately(other, tolerance).Should().Be(value); + + [Theory] + [InlineData(5.3f, 5.0f, 0.2f)] + [InlineData(10.4f, 10.3f, 0.01f)] + [InlineData(3.15f, 3.14f, 0.001f)] + [InlineData(-42.002f, -42.0001f, 0.001f)] + public static void ValuesNotApproximatelyEqual_Float(float value, float other, float tolerance) => + value.MustNotBeApproximately(other, tolerance).Should().Be(value); + + [Theory] + [InlineData(5.0, 5.05, 0.1)] + [InlineData(100.0, 99.95, 0.1)] + [InlineData(-20.0, -20.05, 0.1)] + [InlineData(0.0001, 0.00015, 0.0001)] + public static void ValuesApproximatelyEqual_Double(double value, double other, double tolerance) + { + var act = () => value.MustNotBeApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Theory] + [InlineData(5.0f, 5.05f, 0.1f)] + [InlineData(100.0f, 99.95f, 0.1f)] + [InlineData(-20.0f, -20.05f, 0.1f)] + [InlineData(0.0001f, 0.00015f, 0.0001f)] + public static void ValuesApproximatelyEqual_Float(float value, float other, float tolerance) + { + var act = () => value.MustNotBeApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void DefaultTolerance_Double() + { + // Should throw - difference is 0.00005 which is less than default tolerance 0.0001 + const double value = 1.00005; + Action act = () => value.MustNotBeApproximately(1.0, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + + // Should pass - difference is 0.0002 which is greater than default tolerance 0.0001 + const double value2 = 1.0002; + value2.MustNotBeApproximately(1.0).Should().Be(value2); + } + + [Fact] + public static void DefaultTolerance_Float() + { + // Should throw - difference is 0.00005f which is less than default tolerance 0.0001f + const float value = 1.00005f; + Action act = () => value.MustNotBeApproximately(1.0f, "parameter"); + act.Should().Throw() + .WithParameterName("parameter"); + + // Should pass - difference is 0.0002f which is greater than default tolerance 0.0001f + const float value2 = 1.0002f; + value2.MustNotBeApproximately(1.0f).Should().Be(value2); + } + + [Fact] + public static void CustomException_Double() => + Test.CustomException( + 5.0, + 5.0000000001, + (x, y, exceptionFactory) => x.MustNotBeApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Double() => + Test.CustomException( + 5.0, + 5.05, + 0.1, + (x, y, z, exceptionFactory) => x.MustNotBeApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void CustomException_Float() => + Test.CustomException( + 5.0f, + 5.000001f, + (x, y, exceptionFactory) => x.MustNotBeApproximately(y, exceptionFactory) + ); + + [Fact] + public static void CustomExceptionWithTolerance_Float() => + Test.CustomException( + 5.0f, + 5.05f, + 0.1f, + (x, y, z, exceptionFactory) => x.MustNotBeApproximately(y, z, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Double() => + 5.2.MustNotBeApproximately(5.0, 0.1, (_, _, _) => null).Should().Be(5.2); + + [Fact] + public static void NoCustomExceptionThrown_Float() => + 5.2f.MustNotBeApproximately(5.0f, 0.1f, (_, _, _) => null).Should().Be(5.2f); + + [Fact] + public static void CustomMessage_Double() => + Test.CustomMessage( + message => 100.0.MustNotBeApproximately(100.05, 0.1, message: message) + ); + + [Fact] + public static void CustomMessage_Float() => + Test.CustomMessage( + message => 100.0f.MustNotBeApproximately(100.05f, 0.1f, message: message) + ); + + [Fact] + public static void CallerArgumentExpression_Double() + { + const double seventyEightO1 = 78.1; + + var act = () => seventyEightO1.MustNotBeApproximately(78.099999); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Double() + { + const double pi = 3.14159; + + var act = () => pi.MustNotBeApproximately(3.14, 0.01); + + act.Should().Throw() + .WithParameterName(nameof(pi)); + } + + [Fact] + public static void CallerArgumentExpression_Float() + { + const float seventyEightO1 = 78.1f; + + var act = () => seventyEightO1.MustNotBeApproximately(78.100005f); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Float() + { + const float pi = 3.14159f; + + var act = () => pi.MustNotBeApproximately(3.14f, 0.01f); + + act.Should().Throw() + .WithParameterName(nameof(pi)); + } + +#if NET8_0 + [Theory] + [InlineData(5.3, 5.0, 0.2)] + [InlineData(10.4, 10.3, 0.01)] + [InlineData(3.15, 3.14, 0.001)] + [InlineData(-42.002, -42.0001, 0.001)] + public static void ValuesNotApproximatelyEqual_Generic(double value, double other, double tolerance) => + value.MustNotBeApproximately(other, tolerance).Should().Be(value); + + [Theory] + [InlineData(5.0, 5.05, 0.1)] + [InlineData(100.0, 99.95, 0.1)] + [InlineData(-20.0, -20.05, 0.1)] + [InlineData(0.0001, 0.00015, 0.0001)] + public static void ValuesApproximatelyEqual_Generic(double value, double other, double tolerance) + { + var act = () => value.MustNotBeApproximately(other, tolerance, nameof(value)); + + var exceptionAssertion = act.Should().Throw().Which; + exceptionAssertion.Message.Should().Contain( + $"{nameof(value)} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}." + ); + exceptionAssertion.ParamName.Should().BeSameAs(nameof(value)); + } + + [Fact] + public static void CustomExceptionWithTolerance_Generic() => + Test.CustomException( + 5.0, + 5.05, + 0.1, + (x, y, t, exceptionFactory) => x.MustNotBeApproximately(y, t, exceptionFactory) + ); + + [Fact] + public static void NoCustomExceptionThrown_Generic() => + 5.2.MustNotBeApproximately(5.0, 0.1, (_, _, _) => null).Should().Be(5.2); + + [Fact] + public static void CustomMessage_Generic() => + Test.CustomMessage( + message => 100.0.MustNotBeApproximately(100.05, 0.1, message: message) + ); + + [Fact] + public static void CallerArgumentExpressionWithTolerance_Generic() + { + const double seventyEightO1 = 78.1; + + var act = () => seventyEightO1.MustNotBeApproximately(78.0, 0.2); + + act.Should().Throw() + .WithParameterName(nameof(seventyEightO1)); + } +#endif +} \ No newline at end of file diff --git a/Code/Light.GuardClauses.Tests/ExecuteReadOnlySpanAssertion.cs b/Code/Light.GuardClauses.Tests/ExecuteReadOnlySpanAssertion.cs new file mode 100644 index 00000000..725c9eca --- /dev/null +++ b/Code/Light.GuardClauses.Tests/ExecuteReadOnlySpanAssertion.cs @@ -0,0 +1,27 @@ +#nullable enable +using System; +using Light.GuardClauses.ExceptionFactory; + +namespace Light.GuardClauses.Tests; + +public delegate void ExecuteReadOnlySpanAssertion( + ReadOnlySpan span, + ReadOnlySpanExceptionFactory exceptionFactory +); + +public delegate void ExecuteSpanAssertion( + Span span, + ReadOnlySpanExceptionFactory exceptionFactory +); + +public delegate void ExecuteReadOnlySpanAssertion( + ReadOnlySpan span, + TValue additionalValue, + ReadOnlySpanExceptionFactory exceptionFactory +); + +public delegate void ExecuteSpanAssertion( + Span span, + TValue additionalValue, + ReadOnlySpanExceptionFactory exceptionFactory +); diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/InvalidEmailAddresses.cs b/Code/Light.GuardClauses.Tests/StringAssertions/InvalidEmailAddresses.cs new file mode 100644 index 00000000..a31d0416 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/StringAssertions/InvalidEmailAddresses.cs @@ -0,0 +1,33 @@ +using Xunit; + +namespace Light.GuardClauses.Tests.StringAssertions; + +public class InvalidEmailAddresses : TheoryData +{ + public InvalidEmailAddresses() + { + Add("plainaddress"); + Add("#@%^%#$@#$@#.com"); + Add("@domain.com"); + Add("Joe Smith "); + Add("email.domain.com"); + Add("email@domain@domain.com"); + Add(".email@domain.com"); + Add("email.@domain.com"); + Add("email..email@domain.com"); + Add("email@domain.com (Joe Smith)"); + Add("email@domain"); + Add("email@-domain.com"); + Add("email@111.222.333.44444"); + Add("email@domain..com"); + Add("email@256.256.256.256"); + } +} + +public sealed class InvalidEmailAddressesWithNull : InvalidEmailAddresses +{ + public InvalidEmailAddressesWithNull() + { + Add(null); + } +} diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/IsEmailAddressTest.cs b/Code/Light.GuardClauses.Tests/StringAssertions/IsEmailAddressTest.cs deleted file mode 100644 index 9e0b9944..00000000 --- a/Code/Light.GuardClauses.Tests/StringAssertions/IsEmailAddressTest.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FluentAssertions; -using Xunit; - -namespace Light.GuardClauses.Tests.StringAssertions; - -public sealed class IsEmailAddressTest -{ - [Theory] - [InlineData(null)] - [InlineData("plainaddress")] - [InlineData("#@%^%#$@#$@#.com")] - [InlineData("@domain.com")] - [InlineData("Joe Smith ")] - [InlineData("email.domain.com")] - [InlineData("email@domain@domain.com")] - [InlineData(".email@domain.com")] - [InlineData("email.@domain.com")] - [InlineData("email..email@domain.com")] - [InlineData("あいうえお@domain.com")] - [InlineData("email@domain.com (Joe Smith)")] - [InlineData("email@domain")] - [InlineData("email@-domain.com")] - [InlineData("email@111.222.333.44444")] - [InlineData("email@domain..com")] - public void IsNotValidEmailAddress(string email) - { - var isValid = email.IsEmailAddress(); - - isValid.Should().BeFalse(); - } - - [Theory] - [InlineData("email@domain.com")] - [InlineData("firstname.lastname@domain.com")] - [InlineData("email@subdomain.domain.com")] - [InlineData("firstname+lastname@domain.com")] - [InlineData("email@123.123.123.123")] - [InlineData("1234567890@domain.com")] - [InlineData("email@domain-one.com")] - [InlineData("_______@domain.com")] - [InlineData("email@domain.name")] - [InlineData("email@domain.co.jp")] - [InlineData("firstname-lastname@domain.com")] - public void IsValidEmailAddress(string email) - { - var isValid = email.IsEmailAddress(); - - isValid.Should().BeTrue(); - } -} \ No newline at end of file diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/IsEmailAddressTests.cs b/Code/Light.GuardClauses.Tests/StringAssertions/IsEmailAddressTests.cs new file mode 100644 index 00000000..0cd37508 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/StringAssertions/IsEmailAddressTests.cs @@ -0,0 +1,108 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Light.GuardClauses.Tests.StringAssertions; + +public sealed class IsEmailAddressTests +{ + [Theory] + [ClassData(typeof(InvalidEmailAddressesWithNull))] + public void IsNotValidEmailAddress(string email) + { + var isValid = email.IsEmailAddress(); + + isValid.Should().BeFalse(); + } + + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public void IsValidEmailAddress(string email) + { + var isValid = email.IsEmailAddress(); + + isValid.Should().BeTrue(); + } + +#if NET8_0 + [Theory] + [ClassData(typeof(InvalidEmailAddressesWithNull))] + public void IsNotValidEmailAddress_ReadOnlySpan(string email) + { + var span = new ReadOnlySpan(email?.ToCharArray() ?? []); + var isValid = span.IsEmailAddress(); + + isValid.Should().BeFalse(); + } + + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public void IsValidEmailAddress_ReadOnlySpan(string email) + { + var span = email.AsSpan(); + var isValid = span.IsEmailAddress(); + + isValid.Should().BeTrue(); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddressesWithNull))] + public void IsNotValidEmailAddress_Span(string email) + { + var span = new Span(email?.ToCharArray() ?? []); + var isValid = span.IsEmailAddress(); + + isValid.Should().BeFalse(); + } + + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public void IsValidEmailAddress_Span(string email) + { + var span = new Span(email.ToCharArray()); + var isValid = span.IsEmailAddress(); + + isValid.Should().BeTrue(); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddressesWithNull))] + public void IsNotValidEmailAddress_Memory(string email) + { + var memory = email?.ToCharArray().AsMemory() ?? Memory.Empty; + var isValid = memory.IsEmailAddress(); + + isValid.Should().BeFalse(); + } + + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public void IsValidEmailAddress_Memory(string email) + { + var memory = email.ToCharArray().AsMemory(); + var isValid = memory.IsEmailAddress(); + + isValid.Should().BeTrue(); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddressesWithNull))] + public void IsNotValidEmailAddress_ReadOnlyMemory(string email) + { + var memory = new ReadOnlyMemory(email?.ToCharArray() ?? []); + var isValid = memory.IsEmailAddress(); + + isValid.Should().BeFalse(); + } + + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public void IsValidEmailAddress_ReadOnlyMemory(string email) + { + var memory = new ReadOnlyMemory(email.ToCharArray()); + var isValid = memory.IsEmailAddress(); + + isValid.Should().BeTrue(); + } +#endif +} diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/IsEmptyOrWhiteSpaceTests.cs b/Code/Light.GuardClauses.Tests/StringAssertions/IsEmptyOrWhiteSpaceTests.cs new file mode 100644 index 00000000..2048883a --- /dev/null +++ b/Code/Light.GuardClauses.Tests/StringAssertions/IsEmptyOrWhiteSpaceTests.cs @@ -0,0 +1,120 @@ +using System; +using FluentAssertions; +using Xunit; + +namespace Light.GuardClauses.Tests.StringAssertions; + +public static class IsEmptyOrWhiteSpaceTests +{ + [Fact] + public static void EmptySpan() + { + var span = new Span([]); + span.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void WhiteSpaceSpan() + { + var span = new Span(" \t\n".ToCharArray()); + span.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void NonWhiteSpaceSpan() + { + var span = new Span("abc".ToCharArray()); + span.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void MixedSpan() + { + var span = new Span(" a\t".ToCharArray()); + span.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void EmptyReadOnlySpan() + { + var readOnlySpan = string.Empty.AsSpan(); + readOnlySpan.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void WhiteSpaceReadOnlySpan() + { + var readOnlySpan = " \t\n".AsSpan(); + readOnlySpan.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void NonWhiteSpaceReadOnlySpan() + { + var readOnlySpan = "abc".AsSpan(); + readOnlySpan.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void MixedReadOnlySpan() + { + var readOnlySpan = " a\t".AsSpan(); + readOnlySpan.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void EmptyMemory() + { + var memory = new Memory([]); + memory.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void WhiteSpaceMemory() + { + var memory = new Memory(" \t\n".ToCharArray()); + memory.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void NonWhiteSpaceMemory() + { + var memory = new Memory("abc".ToCharArray()); + memory.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void MixedMemory() + { + var memory = new Memory(" a\t".ToCharArray()); + memory.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void EmptyReadOnlyMemory() + { + var readOnlyMemory = new ReadOnlyMemory([]); + readOnlyMemory.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void WhiteSpaceReadOnlyMemory() + { + var readOnlyMemory = new ReadOnlyMemory(" \t\n".ToCharArray()); + readOnlyMemory.IsEmptyOrWhiteSpace().Should().BeTrue(); + } + + [Fact] + public static void NonWhiteSpaceReadOnlyMemory() + { + var readOnlyMemory = new ReadOnlyMemory("abc".ToCharArray()); + readOnlyMemory.IsEmptyOrWhiteSpace().Should().BeFalse(); + } + + [Fact] + public static void MixedReadOnlyMemory() + { + var readOnlyMemory = new ReadOnlyMemory(" a\t".ToCharArray()); + readOnlyMemory.IsEmptyOrWhiteSpace().Should().BeFalse(); + } +} diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/IsFileExtensionTests.cs b/Code/Light.GuardClauses.Tests/StringAssertions/IsFileExtensionTests.cs new file mode 100644 index 00000000..ae1bc19c --- /dev/null +++ b/Code/Light.GuardClauses.Tests/StringAssertions/IsFileExtensionTests.cs @@ -0,0 +1,90 @@ +using FluentAssertions; +using System; +using Xunit; + +namespace Light.GuardClauses.Tests.StringAssertions; + +public static class IsFileExtensionTests +{ + [Theory] + [MemberData(nameof(ValidFileExtensionData))] + public static void ValidFileExtensions_String(string extension) => + extension.IsFileExtension().Should().BeTrue(); + + [Theory] + [MemberData(nameof(ValidFileExtensionData))] + public static void ValidFileExtensions_Span(string extension) + { + var span = extension.ToCharArray().AsSpan(); + span.IsFileExtension().Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(ValidFileExtensionData))] + public static void ValidFileExtensions_Memory(string extension) + { + var memory = extension.ToCharArray().AsMemory(); + memory.IsFileExtension().Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(ValidFileExtensionData))] + public static void ValidFileExtensions_ReadOnlyMemory(string extension) + { + var readOnlyMemory = extension.AsMemory(); + readOnlyMemory.IsFileExtension().Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionData))] + public static void InvalidFileExtensions_String(string extension) => + extension.IsFileExtension().Should().BeFalse(); + + [Theory] + [MemberData(nameof(InvalidFileExtensionData))] + public static void InvalidFileExtensions_Span(string extension) + { + var span = extension?.ToCharArray() ?? Span.Empty; + span.IsFileExtension().Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionData))] + public static void InvalidFileExtensions_Memory(string extension) + { + var memory = extension?.ToCharArray() ?? Memory.Empty; + memory.IsFileExtension().Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionData))] + public static void InvalidFileExtensions_ReadOnlyMemory(string extension) + { + var readOnlyMemory = extension?.ToCharArray() ?? ReadOnlyMemory.Empty; + readOnlyMemory.IsFileExtension().Should().BeFalse(); + } + + public static TheoryData ValidFileExtensionData() => + [ + ".txt", + ".tar.gz", + ".docx", + ".config", + ]; + + public static TheoryData InvalidFileExtensionData() => + [ + null, + string.Empty, + "txt", // No leading period + ".", // Just a period + ".txt!", // Invalid character + ".txt ", // Contains space + ".doc/", // Invalid character + "..", // Just periods + "...", + "....", + ".txt.", // Invalid - ends with period + ".docx.", + ]; +} \ No newline at end of file diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs b/Code/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs index 8d75d3a7..f6d13149 100644 --- a/Code/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs +++ b/Code/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs @@ -9,35 +9,36 @@ namespace Light.GuardClauses.Tests.StringAssertions; public static class MustBeEmailAddressTests { [Theory] - [InlineData("email@domain.com")] - [InlineData("email@123.123.123.123")] - public static void ValidEmailAddress(string emailAddress) => emailAddress.MustBeEmailAddress().Should().BeSameAs(emailAddress); + [ClassData(typeof(ValidEmailAddresses))] + public static void ValidEmailAddress(string emailAddress) => + emailAddress.MustBeEmailAddress().Should().BeSameAs(emailAddress); [Theory] - [InlineData("plainAddress")] - [InlineData("Joe Smith ")] + [ClassData(typeof(InvalidEmailAddresses))] public static void InvalidEmailAddress(string emailAddress) { Action act = () => emailAddress.MustBeEmailAddress(); act.Should().Throw() - .And.Message.Should().Contain($"emailAddress must be a valid email address, but it actually is \"{emailAddress}\"."); + .And.Message.Should().Contain( + $"emailAddress must be a valid email address, but it actually is \"{emailAddress}\"." + ); } [Theory] - [InlineData(".email@domain.com")] - [InlineData("email.@domain.com")] + [ClassData(typeof(InvalidEmailAddresses))] public static void InvalidEmailAddressArgumentName(string emailAddress) { Action act = () => emailAddress.MustBeEmailAddress(nameof(emailAddress)); act.Should().Throw() - .And.Message.Should().Contain($"emailAddress must be a valid email address, but it actually is \"{emailAddress}\"."); + .And.Message.Should().Contain( + $"emailAddress must be a valid email address, but it actually is \"{emailAddress}\"." + ); } [Theory] - [InlineData("email@domain")] - [InlineData("email@-domain.com")] + [ClassData(typeof(InvalidEmailAddresses))] public static void InvalidEmailAddressCustomMessage(string emailAddress) { const string customMessage = "This email address is not valid"; @@ -55,7 +56,7 @@ public static void InvalidEmailCustomRegex() { const string email = "email@domain@domain.com"; - Action act = () => email.MustBeEmailAddress(CustomRegex, "email"); + Action act = () => email.MustBeEmailAddress(CustomRegex); act.Should().Throw() .And.Message.Should().Contain($"email must be a valid email address, but it actually is \"{email}\"."); @@ -82,27 +83,35 @@ public static void NullCustomRegex(string email, Regex regex) new () { { null, CustomRegex }, - { "invalidEmailAddress", null } + { "invalidEmailAddress", null }, }; [Fact] public static void CustomException() => - Test.CustomException("email.domain.com", - (input, exceptionFactory) => input.MustBeEmailAddress(exceptionFactory)); + Test.CustomException( + "email.domain.com", + (input, exceptionFactory) => input.MustBeEmailAddress(exceptionFactory) + ); [Fact] public static void CustomMessage() => - Test.CustomMessage(message => "#@%^%#$@#$@#.com".MustBeEmailAddress(message: message)); + Test.CustomMessage( + message => "#@%^%#$@#$@#.com".MustBeEmailAddress(message: message) + ); [Fact] public static void CustomExceptionCustomRegex() => - Test.CustomException("invalidEmailAddress", - CustomRegex, - (i, r, exceptionFactory) => i.MustBeEmailAddress(r, exceptionFactory)); + Test.CustomException( + "invalidEmailAddress", + CustomRegex, + (i, r, exceptionFactory) => i.MustBeEmailAddress(r, exceptionFactory) + ); [Fact] public static void CustomMessageCustomRegex() => - Test.CustomMessage(message => "invalidEmailAddress".MustBeEmailAddress(CustomRegex, message: message)); + Test.CustomMessage( + message => "invalidEmailAddress".MustBeEmailAddress(CustomRegex, message: message) + ); [Fact] public static void CallerArgumentExpression() @@ -114,4 +123,346 @@ public static void CallerArgumentExpression() act.Should().Throw() .WithParameterName(nameof(email)); } -} \ No newline at end of file + +#if NET8_0 + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public static void ValidEmailAddress_ReadOnlySpan(string email) + { + var result = email.AsSpan().MustBeEmailAddress(); + result.ToString().Should().Be(email); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddress_ReadOnlySpan(string email) + { + var act = () => + { + var readOnlySpan = email.AsSpan(); + readOnlySpan.MustBeEmailAddress(); + }; + act.Should().Throw() + .And.Message.Should() + .Contain($"readOnlySpan must be a valid email address, but it actually is \"{email}\"."); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressArgumentName_ReadOnlySpan(string email) + { + Action act = () => email.AsSpan().MustBeEmailAddress(nameof(email)); + act.Should().Throw() + .And.Message.Should().Contain(nameof(email)); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressCustomMessage_ReadOnlySpan(string email) + { + const string customMessage = "This email address is not valid"; + Action act = () => email.AsSpan().MustBeEmailAddress(nameof(email), customMessage); + act.Should().Throw() + .And.Message.Should().Contain(customMessage); + } + + [Fact] + public static void CallerArgumentExpression_ReadOnlySpan() + { + var act = () => + { + var email = "This is not an email address".AsSpan(); + email.MustBeEmailAddress(); + }; + act.Should().Throw() + .WithParameterName("email"); + } + + [Fact] + public static void CustomException_ReadOnlySpan() => + Test.CustomSpanException( + "email.domain.com".AsSpan(), + (input, exceptionFactory) => input.MustBeEmailAddress(exceptionFactory) + ); + + [Fact] + public static void CustomExceptionCustomRegex_ReadOnlySpan() => + Test.CustomSpanException( + "invalidEmailAddress".AsSpan(), + CustomRegex, + (i, r, exceptionFactory) => i.MustBeEmailAddress(r, exceptionFactory) + ); + + [Fact] + public static void CustomMessageCustomRegex_ReadOnlySpan() => + Test.CustomMessage( + message => "invalidEmailAddress".AsSpan().MustBeEmailAddress(CustomRegex, message: message) + ); + + // Tests for Span + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public static void ValidEmailAddress_Span(string email) + { + var emailChars = email.ToCharArray(); + var span = new Span(emailChars); + var result = span.MustBeEmailAddress(); + new string(result).Should().Be(email); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddress_Span(string email) + { + var emailChars = email.ToCharArray(); + var act = () => + { + var span = new Span(emailChars); + span.MustBeEmailAddress(); + }; + act.Should().Throw() + .And.Message.Should().Contain($"span must be a valid email address, but it actually is \"{email}\"."); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressArgumentName_Span(string email) + { + var emailChars = email.ToCharArray(); + var act = () => + { + var span = new Span(emailChars); + span.MustBeEmailAddress(nameof(span)); + }; + act.Should().Throw() + .And.Message.Should().Contain("span"); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressCustomMessage_Span(string email) + { + const string customMessage = "This email address is not valid"; + var emailChars = email.ToCharArray(); + var act = () => + { + var span = new Span(emailChars); + span.MustBeEmailAddress(nameof(span), customMessage); + }; + act.Should().Throw() + .And.Message.Should().Contain(customMessage); + } + + [Fact] + public static void CallerArgumentExpression_Span() + { + var act = () => + { + var emailChars = "This is not an email address".ToCharArray(); + var span = new Span(emailChars); + span.MustBeEmailAddress(); + }; + act.Should().Throw() + .WithParameterName("span"); + } + + [Fact] + public static void CustomException_Span() + { + var emailChars = "email.domain.com".ToCharArray(); + var span = new Span(emailChars); + Test.CustomSpanException( + span, + (input, exceptionFactory) => input.MustBeEmailAddress(exceptionFactory) + ); + } + + [Fact] + public static void CustomExceptionCustomRegex_Span() + { + var emailChars = "invalidEmailAddress".ToCharArray(); + var span = new Span(emailChars); + Test.CustomSpanException( + span, + CustomRegex, + (i, r, exceptionFactory) => i.MustBeEmailAddress(r, exceptionFactory) + ); + } + + [Fact] + public static void CustomMessageCustomRegex_Span() => + Test.CustomMessage( + message => + { + var span = new Span("invalidEmailAddress".ToCharArray()); + span.MustBeEmailAddress(CustomRegex, message: message); + } + ); + + // Tests for Memory + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public static void ValidEmailAddress_Memory(string email) + { + var memory = email.ToCharArray().AsMemory(); + var result = memory.MustBeEmailAddress(); + result.ToString().Should().Be(email); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddress_Memory(string email) + { + var memory = email.ToCharArray().AsMemory(); + Action act = () => memory.MustBeEmailAddress(); + act.Should().Throw() + .And.Message.Should().Contain($"memory must be a valid email address, but it actually is \"{email}\"."); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressArgumentName_Memory(string email) + { + var memory = email.ToCharArray().AsMemory(); + Action act = () => memory.MustBeEmailAddress(nameof(memory)); + act.Should().Throw() + .And.Message.Should().Contain(nameof(memory)); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressCustomMessage_Memory(string email) + { + const string customMessage = "This email address is not valid"; + var memory = email.ToCharArray().AsMemory(); + Action act = () => memory.MustBeEmailAddress(nameof(memory), customMessage); + act.Should().Throw() + .And.Message.Should().Contain(customMessage); + } + + [Fact] + public static void CallerArgumentExpression_Memory() + { + var act = () => + { + var memory = "This is not an email address".ToCharArray().AsMemory(); + memory.MustBeEmailAddress(); + }; + act.Should().Throw() + .WithParameterName("memory"); + } + + [Fact] + public static void CustomException_Memory() + { + var memory = "email.domain.com".ToCharArray().AsMemory(); + Test.CustomMemoryException( + memory, + (input, exceptionFactory) => input.MustBeEmailAddress(exceptionFactory) + ); + } + + [Fact] + public static void CustomExceptionCustomRegex_Memory() + { + var memory = "invalidEmailAddress".ToCharArray().AsMemory(); + Test.CustomMemoryException( + memory, + CustomRegex, + (i, r, exceptionFactory) => i.MustBeEmailAddress(r, exceptionFactory) + ); + } + + [Fact] + public static void CustomMessageCustomRegex_Memory() + { + var memory = "invalidEmailAddress".AsMemory(); + Test.CustomMessage( + message => memory.MustBeEmailAddress(CustomRegex, message: message) + ); + } + + // Tests for ReadOnlyMemory + [Theory] + [ClassData(typeof(ValidEmailAddresses))] + public static void ValidEmailAddress_ReadOnlyMemory(string email) + { + var memory = email.AsMemory(); + var result = memory.MustBeEmailAddress(); + result.ToString().Should().Be(email); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddress_ReadOnlyMemory(string email) + { + var memory = email.AsMemory(); + Action act = () => memory.MustBeEmailAddress(); + act.Should().Throw() + .And.Message.Should().StartWith($"memory must be a valid email address, but it actually is \"{email}\"."); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressArgumentName_ReadOnlyMemory(string email) + { + var readOnlyMemory = email.AsMemory(); + Action act = () => readOnlyMemory.MustBeEmailAddress(nameof(readOnlyMemory)); + act.Should().Throw() + .And.Message.Should().Contain(nameof(readOnlyMemory)); + } + + [Theory] + [ClassData(typeof(InvalidEmailAddresses))] + public static void InvalidEmailAddressCustomMessage_ReadOnlyMemory(string email) + { + const string customMessage = "This email address is not valid"; + var readOnlyMemory = email.AsMemory(); + Action act = () => readOnlyMemory.MustBeEmailAddress(nameof(readOnlyMemory), customMessage); + act.Should().Throw() + .And.Message.Should().Contain(customMessage); + } + + [Fact] + public static void CallerArgumentExpression_ReadOnlyMemory() + { + var act = () => + { + var readOnlyMemory = "This is not an email address".AsMemory(); + readOnlyMemory.MustBeEmailAddress(); + }; + act.Should().Throw() + .WithParameterName("readOnlyMemory"); + } + + [Fact] + public static void CustomException_ReadOnlyMemory() + { + var readOnlyMemory = "email.domain.com".AsMemory(); + Test.CustomMemoryException( + readOnlyMemory, + (input, exceptionFactory) => input.MustBeEmailAddress(exceptionFactory) + ); + } + + [Fact] + public static void CustomExceptionCustomRegex_ReadOnlyMemory() + { + var readOnlyMemory = "invalidEmailAddress".AsMemory(); + Test.CustomMemoryException( + readOnlyMemory, + CustomRegex, + (i, r, exceptionFactory) => i.MustBeEmailAddress(r, exceptionFactory) + ); + } + + [Fact] + public static void CustomMessageCustomRegex_ReadOnlyMemory() + { + var readOnlyMemory = "invalidEmailAddress".AsMemory(); + Test.CustomMessage( + message => readOnlyMemory.MustBeEmailAddress(CustomRegex, message: message) + ); + } +#endif +} diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/MustBeFileExtensionTests.cs b/Code/Light.GuardClauses.Tests/StringAssertions/MustBeFileExtensionTests.cs new file mode 100644 index 00000000..a5df0736 --- /dev/null +++ b/Code/Light.GuardClauses.Tests/StringAssertions/MustBeFileExtensionTests.cs @@ -0,0 +1,305 @@ +using System; +using FluentAssertions; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; +using Xunit; + +namespace Light.GuardClauses.Tests.StringAssertions; + +public static class MustBeFileExtensionTests +{ + public static readonly TheoryData InvalidFileExtensionsData = + [ + "txt", + ".jpg/", + ".", + "..", + "...", + "....", + ".docx.", + ]; + + public static readonly TheoryData ValidFileExtensionsData = + [ + ".txt", + ".jpg", + ".tar.gz", + ]; + + [Theory] + [MemberData(nameof(ValidFileExtensionsData))] + public static void ValidFileExtensions(string input) => + input.MustBeFileExtension().Should().BeSameAs(input); + + [Fact] + public static void StringIsNull() + { + var nullString = default(string); + + // ReSharper disable once ExpressionIsAlwaysNull + var act = () => nullString.MustBeFileExtension(nameof(nullString)); + + act.Should().Throw() + .WithParameterName(nameof(nullString)); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void InvalidFileExtensions(string invalidString) + { + var act = () => invalidString.MustBeFileExtension(nameof(invalidString)); + + act.Should().Throw() + .And.Message.Should().Contain( + $"invalidString must be a valid file extension, but it actually is {invalidString.ToStringOrNull()}" + ); + } + + [Fact] + public static void CustomExceptionStringNull() => + Test.CustomException( + default(string), + (@null, exceptionFactory) => @null.MustBeFileExtension(exceptionFactory) + ); + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void CustomExceptionInvalidFileExtensions(string invalidString) => + Test.CustomException( + invalidString, + (@string, exceptionFactory) => @string.MustBeFileExtension(exceptionFactory) + ); + + [Fact] + public static void CallerArgumentExpression() + { + const string invalidString = "txt"; + + var act = () => invalidString.MustBeFileExtension(); + + act.Should().Throw() + .WithParameterName(nameof(invalidString)); + } + + [Theory] + [MemberData(nameof(ValidFileExtensionsData))] + public static void ValidFileExtensions_ReadOnlySpan(string input) + { + var span = input.AsSpan(); + + var result = span.MustBeFileExtension(); + + result.Equals(span, StringComparison.Ordinal).Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void InvalidFileExtensions_ReadOnlySpan(string invalidString) + { + var act = () => + { + var span = new ReadOnlySpan(invalidString?.ToCharArray() ?? []); + span.MustBeFileExtension("parameterName"); + }; + + act.Should().Throw() + .And.Message.Should().Contain( + $"parameterName must be a valid file extension, but it actually is {invalidString}" + ); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void CustomExceptionInvalidFileExtensions_ReadOnlySpan(string invalidString) + { + var exception = new Exception(); + + var act = () => + { + var span = new ReadOnlySpan(invalidString?.ToCharArray() ?? []); + span.MustBeFileExtension(_ => exception); + }; + + act.Should().Throw().Which.Should().BeSameAs(exception); + } + + [Fact] + public static void CallerArgumentExpression_ReadOnlySpan() + { + var act = () => + { + var invalidSpan = "txt".AsSpan(); + invalidSpan.MustBeFileExtension(); + }; + + act.Should().Throw() + .WithParameterName("invalidSpan"); + } + + [Theory] + [MemberData(nameof(ValidFileExtensionsData))] + public static void ValidFileExtensions_Span(string input) + { + var span = input.AsSpan().ToArray().AsSpan(); + + var result = span.MustBeFileExtension(); + + result.ToString().Should().Be(input); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void InvalidFileExtensions_Span(string invalidString) + { + var act = () => + { + var span = new Span(invalidString?.ToCharArray() ?? []); + span.MustBeFileExtension("parameterName"); + }; + + act.Should().Throw() + .And.Message.Should().Contain( + $"parameterName must be a valid file extension, but it actually is {invalidString}" + ); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void CustomExceptionInvalidFileExtensions_Span(string invalidString) + { + var exception = new Exception(); + + var act = () => + { + var span = new Span(invalidString?.ToCharArray() ?? []); + span.MustBeFileExtension(_ => exception); + }; + + act.Should().Throw().Which.Should().BeSameAs(exception); + } + + [Fact] + public static void CallerArgumentExpression_Span() + { + var act = () => + { + var charArray = "txt".ToCharArray(); + var invalidSpan = new Span(charArray); + invalidSpan.MustBeFileExtension(); + }; + + act.Should().Throw() + .WithParameterName("invalidSpan"); + } + + [Theory] + [MemberData(nameof(ValidFileExtensionsData))] + public static void ValidFileExtensions_Memory(string input) + { + var memory = input.ToCharArray().AsMemory(); + + var result = memory.MustBeFileExtension(); + + result.ToString().Should().Be(input); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void InvalidFileExtensions_Memory(string invalidString) + { + var act = () => + { + var memory = new Memory(invalidString?.ToCharArray() ?? []); + memory.MustBeFileExtension("parameterName"); + }; + + act.Should().Throw() + .And.Message.Should().Contain( + $"parameterName must be a valid file extension, but it actually is {invalidString}" + ); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void CustomExceptionInvalidFileExtensions_Memory(string invalidString) + { + var exception = new Exception(); + + var act = () => + { + var memory = new Memory(invalidString?.ToCharArray() ?? []); + memory.MustBeFileExtension(_ => exception); + }; + + act.Should().Throw().Which.Should().BeSameAs(exception); + } + + [Fact] + public static void CallerArgumentExpression_Memory() + { + var act = () => + { + var charArray = "txt".ToCharArray(); + var invalidMemory = new Memory(charArray); + invalidMemory.MustBeFileExtension(); + }; + + act.Should().Throw() + .WithParameterName("invalidMemory"); + } + + [Theory] + [MemberData(nameof(ValidFileExtensionsData))] + public static void ValidFileExtensions_ReadOnlyMemory(string input) + { + var memory = input.AsMemory(); + + var result = memory.MustBeFileExtension(); + + result.Equals(memory).Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void InvalidFileExtensions_ReadOnlyMemory(string invalidString) + { + var act = () => + { + var memory = new ReadOnlyMemory(invalidString?.ToCharArray() ?? []); + memory.MustBeFileExtension("parameterName"); + }; + + act.Should().Throw() + .And.Message.Should().Contain( + $"parameterName must be a valid file extension, but it actually is {invalidString}" + ); + } + + [Theory] + [MemberData(nameof(InvalidFileExtensionsData))] + public static void CustomExceptionInvalidFileExtensions_ReadOnlyMemory(string invalidString) + { + var exception = new Exception(); + + var act = () => + { + var memory = new ReadOnlyMemory(invalidString?.ToCharArray() ?? []); + memory.MustBeFileExtension(_ => exception); + }; + + act.Should().Throw().Which.Should().BeSameAs(exception); + } + + [Fact] + public static void CallerArgumentExpression_ReadOnlyMemory() + { + var act = void () => + { + var invalidMemory = "txt".AsMemory(); + invalidMemory.MustBeFileExtension(); + }; + + act.Should().Throw() + .WithParameterName("invalidMemory"); + } +} diff --git a/Code/Light.GuardClauses.Tests/StringAssertions/ValidEmailAddresses.cs b/Code/Light.GuardClauses.Tests/StringAssertions/ValidEmailAddresses.cs new file mode 100644 index 00000000..d134ce1b --- /dev/null +++ b/Code/Light.GuardClauses.Tests/StringAssertions/ValidEmailAddresses.cs @@ -0,0 +1,28 @@ +using Xunit; + +namespace Light.GuardClauses.Tests.StringAssertions; + +public sealed class ValidEmailAddresses : TheoryData +{ + public ValidEmailAddresses() + { + Add("email@domain.com"); + Add("firstname.lastname@domain.com"); + Add("email@subdomain.domain.com"); + Add("firstname+lastname@domain.com"); + Add("email@123.123.123.123"); + Add("1234567890@domain.com"); + Add("email@domain-one.com"); + Add("_______@domain.com"); + Add("email@domain.name"); + Add("email@domain.co.jp"); + Add("firstname-lastname@domain.com"); + Add("email@domain.museum"); // Long TLD (>4 chars) + Add("email@domain.travel"); // Another long TLD + Add("email@domain.photography"); // Even longer TLD + Add("email@[IPv6:2001:db8::1]"); // IPv6 format + Add("\"quoted\"@domain.com"); // Quoted local part + Add("user.name+tag+sorting@example.com"); // Gmail-style + addressing + Add("あいうえお@domain.com"); // Unicode character test + } +} diff --git a/Code/Light.GuardClauses.Tests/Test.cs b/Code/Light.GuardClauses.Tests/Test.cs index 6ee89578..d2956eab 100644 --- a/Code/Light.GuardClauses.Tests/Test.cs +++ b/Code/Light.GuardClauses.Tests/Test.cs @@ -1,11 +1,12 @@ -using System; +#nullable enable + +using System; using FluentAssertions; using FluentAssertions.Specialized; +using Light.GuardClauses.ExceptionFactory; using Xunit.Abstractions; using Xunit.Sdk; -#nullable enable - namespace Light.GuardClauses.Tests; public static class Test @@ -48,7 +49,108 @@ Exception ExceptionFactory(T parameter) } } - public static void CustomException(T1 first, T2 second, Action> executeAssertion) + public static void CustomSpanException( + ReadOnlySpan invalidValue, + ExecuteReadOnlySpanAssertion executeAssertion + ) + { + T[]? capturedParameter = null; + + Exception ExceptionFactory(ReadOnlySpan parameter) + { + capturedParameter = parameter.ToArray(); + return Exception; + } + + try + { + executeAssertion(invalidValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedParameter.Should().Equal(invalidValue.ToArray()); + } + } + + public static void CustomSpanException(Span invalidValue, ExecuteSpanAssertion executeAssertion) + { + T[]? capturedParameter = null; + + Exception ExceptionFactory(ReadOnlySpan parameter) + { + capturedParameter = parameter.ToArray(); + return Exception; + } + + try + { + executeAssertion(invalidValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedParameter.Should().Equal(invalidValue.ToArray()); + } + } + + public static void CustomMemoryException( + Memory invalidValue, + Action, ReadOnlySpanExceptionFactory> executeAssertion + ) + { + Memory capturedParameter = default; + + Exception ExceptionFactory(ReadOnlySpan parameter) + { + capturedParameter = parameter.ToArray(); + return Exception; + } + + try + { + executeAssertion(invalidValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedParameter.ToArray().Should().Equal(invalidValue.ToArray()); + } + } + + public static void CustomMemoryException( + ReadOnlyMemory invalidValue, + Action, ReadOnlySpanExceptionFactory> executeAssertion + ) + { + ReadOnlyMemory capturedParameter = default; + + Exception ExceptionFactory(ReadOnlySpan parameter) + { + capturedParameter = parameter.ToArray(); + return Exception; + } + + try + { + executeAssertion(invalidValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedParameter.ToArray().Should().Equal(invalidValue.ToArray()); + } + } + + public static void CustomException( + T1 first, + T2 second, + Action> executeAssertion + ) { T1? capturedFirst = default; T2? capturedSecond = default; @@ -73,7 +175,128 @@ Exception ExceptionFactory(T1 x, T2 y) } } - public static void CustomException(T1 first, T2 second, T3 third, Action> executeAssertion) + public static void CustomSpanException( + ReadOnlySpan invalidValue, + T2 additionalValue, + ExecuteReadOnlySpanAssertion executeAssertion + ) + { + T1[]? capturedFirst = null; + T2? capturedSecond = default; + + Exception ExceptionFactory(ReadOnlySpan parameter, T2 second) + { + capturedFirst = parameter.ToArray(); + capturedSecond = second; + return Exception; + } + + try + { + executeAssertion(invalidValue, additionalValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedFirst.Should().Equal(invalidValue.ToArray()); + capturedSecond.Should().Be(additionalValue); + } + } + + public static void CustomSpanException( + Span invalidValue, + T2 additionalValue, + ExecuteSpanAssertion executeAssertion + ) + { + T1[]? capturedFirst = null; + T2? capturedSecond = default; + + Exception ExceptionFactory(ReadOnlySpan parameter, T2 second) + { + capturedFirst = parameter.ToArray(); + capturedSecond = second; + return Exception; + } + + try + { + executeAssertion(invalidValue, additionalValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedFirst.Should().Equal(invalidValue.ToArray()); + capturedSecond.Should().Be(additionalValue); + } + } + + public static void CustomMemoryException( + Memory invalidValue, + T2 additionalValue, + Action, T2, ReadOnlySpanExceptionFactory> executeAssertion + ) + { + Memory capturedFirst = default; + T2? capturedSecond = default; + + Exception ExceptionFactory(ReadOnlySpan first, T2 second) + { + capturedFirst = first.ToArray(); + capturedSecond = second; + return Exception; + } + + try + { + executeAssertion(invalidValue, additionalValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedFirst.ToArray().Should().Equal(invalidValue.ToArray()); + capturedSecond.Should().Be(additionalValue); + } + } + + public static void CustomMemoryException( + ReadOnlyMemory invalidValue, + T2 additionalValue, + Action, T2, ReadOnlySpanExceptionFactory> executeAssertion + ) + { + ReadOnlyMemory capturedFirst = default; + T2? capturedSecond = default; + + Exception ExceptionFactory(ReadOnlySpan first, T2 second) + { + capturedFirst = first.ToArray(); + capturedSecond = second; + return Exception; + } + + try + { + executeAssertion(invalidValue, additionalValue, ExceptionFactory); + throw new XunitException("The assertion should have thrown a custom exception at this point."); + } + catch (ExceptionDummy exception) + { + exception.Should().BeSameAs(exception); + capturedFirst.ToArray().Should().Equal(invalidValue.ToArray()); + capturedSecond.Should().Be(additionalValue); + } + } + + public static void CustomException( + T1 first, + T2 second, + T3 third, + Action> executeAssertion + ) { T1? capturedFirst = default; T2? capturedSecond = default; @@ -115,8 +338,9 @@ public static void CustomMessage(Action executeAssertion) wh } } - private sealed class ExceptionDummy : Exception { }; - - public static void WriteExceptionTo(this ExceptionAssertions exceptionAssertions, ITestOutputHelper output) where T : Exception => + public static void WriteExceptionTo(this ExceptionAssertions exceptionAssertions, ITestOutputHelper output) + where T : Exception => output.WriteLine(exceptionAssertions.Which.ToString()); -} \ No newline at end of file + + private sealed class ExceptionDummy : Exception; +} diff --git a/Code/Light.GuardClauses.sln.DotSettings b/Code/Light.GuardClauses.sln.DotSettings index 47d8c240..aceafc4d 100644 --- a/Code/Light.GuardClauses.sln.DotSettings +++ b/Code/Light.GuardClauses.sln.DotSettings @@ -2,16 +2,32 @@ True None False + WARNING + WARNING + DO_NOT_SHOW HINT HINT + WARNING HINT WARNING + True <?xml version="1.0" encoding="utf-16"?><Profile name="Kenny's Kleanup"><CSReorderTypeMembers>True</CSReorderTypeMembers><CSUpdateFileHeader>True</CSUpdateFileHeader><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CppReformatCode>True</CppReformatCode><XMLReformatCode>True</XMLReformatCode><CssReformatCode>True</CssReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" ArrangeCodeBodyStyle="False" ArrangeVarStyle="False" /><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><RemoveCodeRedundancies>True</RemoveCodeRedundancies><HtmlReformatCode>True</HtmlReformatCode><JsInsertSemicolon>True</JsInsertSemicolon><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor></Profile> - C# Cleanup - Kenny's Kleanup + Kenny's Kleanup + Built-in: Full Cleanup + False + Required + Required + Required + Required + ExpressionBody + ExpressionBody + ExpressionBody + True + False True True + True True @@ -22,23 +38,64 @@ True True True + True TOGETHER_SAME_LINE + NO_INDENT True True + True + True + False True + True + False + True + 1 + 3 + COMPACT True + True True True NEVER - NEVER - NEVER - NEVER + + + + NEVER + False + ALWAYS + IF_OWNER_IS_SINGLE_LINE + NEVER DO_NOT_CHANGE + True + True True - 500 - False + False + True + True + CHOP_IF_LONG + CHOP_IF_LONG + False + True + True + True + True + True + False + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + + + CHOP_IF_LONG + CHOP_ALWAYS + CHOP_IF_LONG RemoveIndent RemoveIndent + False + ByFirstAttr + 150 + False False False False @@ -192,16 +249,21 @@ </TypePattern> </Patterns> False + False False True Replace Replace False + True True False False True False + False + True + False <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> @@ -255,4 +317,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/Code/Light.GuardClauses/CallerArgumentExpressionAttribute.cs b/Code/Light.GuardClauses/CallerArgumentExpressionAttribute.cs index 36a1f72c..f5df6107 100644 --- a/Code/Light.GuardClauses/CallerArgumentExpressionAttribute.cs +++ b/Code/Light.GuardClauses/CallerArgumentExpressionAttribute.cs @@ -2,14 +2,12 @@ // ReSharper disable once CheckNamespace -- CallerArgumentExpression must be in exactly this namespace namespace System.Runtime.CompilerServices; -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +[AttributeUsage(AttributeTargets.Parameter)] internal sealed class CallerArgumentExpressionAttribute : Attribute { - public CallerArgumentExpressionAttribute(string parameterName) - { + public CallerArgumentExpressionAttribute(string parameterName) => ParameterName = parameterName; - } public string ParameterName { get; } } -#endif \ No newline at end of file +#endif diff --git a/Code/Light.GuardClauses/Check.CommonAssertions.cs b/Code/Light.GuardClauses/Check.CommonAssertions.cs deleted file mode 100644 index 0104f58a..00000000 --- a/Code/Light.GuardClauses/Check.CommonAssertions.cs +++ /dev/null @@ -1,714 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; -using Light.GuardClauses.Exceptions; -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -namespace Light.GuardClauses; - -/// -/// The class provides access to all assertions of Light.GuardClauses. -/// -public static partial class Check -{ - /// - /// Ensures that the specified object reference is not null, or otherwise throws an . - /// - /// The object reference to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeNull([NotNull, ValidatedNotNull, NoEnumeration] this T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : class - { - if (parameter is null) - Throw.ArgumentNull(parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified object reference is not null, or otherwise throws your custom exception. - /// - /// The reference to be checked. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeNull([NotNull, ValidatedNotNull, NoEnumeration] this T? parameter, Func exceptionFactory) where T : class - { - if (parameter is null) - Throw.CustomException(exceptionFactory); - return parameter; - } - - /// - /// Ensures that the specified parameter is not the default value, or otherwise throws an - /// for reference types, or an for value types. - /// - /// The value to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is a reference type and null. - /// Thrown when is a value type and the default value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeDefault([NotNull, ValidatedNotNull] this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (default(T) is null) - { - if (parameter is null) - Throw.ArgumentNull(parameterName, message); - return parameter; - } - - if (EqualityComparer.Default.Equals(parameter, default!)) - Throw.ArgumentDefault(parameterName, message); - - // If we end up here, we have a value type which cannot be null -#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - return parameter; -#pragma warning restore CS8777 - } - - /// - /// Ensures that the specified parameter is not the default value, or otherwise throws your custom exception. - /// - /// The value to be checked. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is the default value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeDefault([NotNull, ValidatedNotNull] this T parameter, Func exceptionFactory) - { - if (default(T) is null) - { - if (parameter is null) - Throw.CustomException(exceptionFactory); - return parameter; - } - - if (EqualityComparer.Default.Equals(parameter, default!)) - Throw.CustomException(exceptionFactory); - - // If we end up here, we have a value type which cannot be null -#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - return parameter; -#pragma warning restore CS8777 - - } - - /// - /// Ensures that the specified parameter is not null when is a reference type, or otherwise - /// throws an . PLEASE NOTICE: you should only use this assertion in generic contexts, - /// use by default. - /// - /// The value to be checked for null. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is a reference type and is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeNullReference([NotNull, ValidatedNotNull, NoEnumeration] this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (default(T) != null) - { - // If we end up here, parameter cannot be null -#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - return parameter; -#pragma warning restore CS8777 // Parameter must have a non-null value when exiting. - } - - if (parameter is null) - Throw.ArgumentNull(parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified parameter is not null when is a reference type, or otherwise - /// throws your custom exception. PLEASE NOTICE: you should only use this assertion in generic contexts, - /// use by default. - /// - /// The value to be checked for null. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is a reference type and is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeNullReference([NotNull, ValidatedNotNull, NoEnumeration] this T parameter, Func exceptionFactory) - { - if (default(T) != null) - { - // If we end up here, parameter cannot be null -#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - return parameter; -#pragma warning restore CS8777 // Parameter must have a non-null value when exiting. - } - - if (parameter is null) - Throw.CustomException(exceptionFactory); - return parameter; - } - - /// - /// Ensures that can be cast to and returns the cast value, or otherwise throws a . - /// - /// The value to be cast. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when cannot be cast to . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeOfType([NotNull, ValidatedNotNull, NoEnumeration] this object? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message) is T castValue) - return castValue; - - Throw.InvalidTypeCast(parameter, typeof(T), parameterName, message); - return default; - } - - /// - /// Ensures that can be cast to and returns the cast value, or otherwise throws your custom exception. - /// - /// The value to be cast. - /// The delegate that creates your custom exception. The is passed to this delegate. - /// Your custom exception thrown when cannot be cast to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeOfType([NotNull, ValidatedNotNull, NoEnumeration] this object? parameter, Func exceptionFactory) - { - if (parameter is T castValue) - return castValue; - - Throw.CustomException(exceptionFactory, parameter); - return default; - } - - /// - /// Checks if the specified value is a valid enum value of its type. This is true when the specified value - /// is one of the constants defined in the enum, or a valid flags combination when the enum type is marked - /// with the . - /// - /// The type of the enum. - /// The enum value to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsValidEnumValue(this T parameter) where T : struct, Enum - => EnumInfo.IsValidEnumValue(parameter); - - /// - /// Ensures that the specified enum value is valid, or otherwise throws an . An enum value - /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type - /// is marked with the . - /// - /// The type of the enum. - /// The value to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is no valid enum value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustBeValidEnumValue(this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : struct, Enum - { - if (!EnumInfo.IsValidEnumValue(parameter)) - Throw.EnumValueNotDefined(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified enum value is valid, or otherwise throws your custom exception. An enum value - /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type - /// is marked with the . - /// - /// The type of the enum. - /// The value to be checked. - /// The delegate that creates your custom exception. The is passed to this delegate. - /// Your custom exception thrown when is no valid enum value, or when is no enum type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static T MustBeValidEnumValue(this T parameter, Func exceptionFactory) where T : struct, Enum - { - if (!EnumInfo.IsValidEnumValue(parameter)) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Checks if the specified GUID is an empty one. - /// - /// The GUID to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsEmpty(this Guid parameter) => parameter == Guid.Empty; - - /// - /// Ensures that the specified GUID is not empty, or otherwise throws an . - /// - /// The GUID to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is an empty GUID. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Guid MustNotBeEmpty(this Guid parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter == Guid.Empty) - Throw.EmptyGuid(parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified GUID is not empty, or otherwise throws your custom exception. - /// - /// The GUID to be checked. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is an empty GUID. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static Guid MustNotBeEmpty(this Guid parameter, Func exceptionFactory) - { - if (parameter == Guid.Empty) - Throw.CustomException(exceptionFactory); - return parameter; - } - - /// - /// Checks if the specified is true and throws an in this case. - /// - /// The condition to be checked. The exception is thrown when it is true. - /// The message that will be passed to the (optional). - /// Thrown when is true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvalidOperation(bool condition, string? message = null) - { - if (condition) - Throw.InvalidOperation(message); - } - - /// - /// Checks if the specified is true and throws an in this case. - /// - /// The condition to be checked. The exception is thrown when it is true. - /// The message that will be passed to the . - /// Thrown when is true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvalidState(bool condition, string? message = null) - { - if (condition) - Throw.InvalidState(message); - } - - /// - /// Checks if the specified is true and throws an in this case. - /// - /// The condition to be checked. The exception is thrown when it is true. - /// The name of the parameter (optional). - /// The message that will be passed to the (optional). - /// Thrown when is true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvalidArgument(bool condition, string? parameterName = null, string? message = null) - { - if (condition) - Throw.Argument(parameterName, message); - } - - /// - /// Checks if the specified is true and throws your custom exception in this case. - /// - /// The condition to be checked. The exception is thrown when it is true. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static void InvalidArgument(bool condition, Func exceptionFactory) - { - if (condition) - Throw.CustomException(exceptionFactory); - } - - /// - /// Checks if the specified is true and throws your custom exception in this case. - /// - /// The condition to be checked. The exception is thrown when it is true. - /// The value that is checked in the . This value is passed to the . - /// The delegate that creates your custom exception. The is passed to this delegate. - /// Your custom exception thrown when is true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static void InvalidArgument(bool condition, T parameter, Func exceptionFactory) - { - if (condition) - Throw.CustomException(exceptionFactory, parameter); - } - - /// - /// Ensures that the specified nullable has a value and returns it, or otherwise throws a . - /// - /// The nullable to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has no value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustHaveValue([NotNull, NoEnumeration] this T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : struct - { - if (!parameter.HasValue) - Throw.NullableHasNoValue(parameterName, message); - - return parameter.Value; - } - - /// - /// Ensures that the specified nullable has a value and returns it, or otherwise throws your custom exception. - /// - /// The nullable to be checked. - /// The delegate that creates your custom exception. - /// Thrown when has no value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static T MustHaveValue([NotNull, NoEnumeration] this T? parameter, Func exceptionFactory) where T : struct - { - if (!parameter.HasValue) - Throw.CustomException(exceptionFactory); - - return parameter.Value; - } - - /// - /// Checks if and point to the same object. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable StringLiteralTypo - [ContractAnnotation("parameter:notNull => true, other:notnull; parameter:notNull => false, other:canbenull; other:notnull => true, parameter:notnull; other:notnull => false, parameter:canbenull")] - // ReSharper restore StringLiteralTypo - public static bool IsSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other) where T : class => - ReferenceEquals(parameter, other); - - /// - /// Ensures that and do not point to the same object instance, or otherwise - /// throws a . - /// - /// The first reference to be checked. - /// The second reference to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when both and point to the same object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : class - { - if (ReferenceEquals(parameter, other)) - Throw.SameObjectReference(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that and do not point to the same object instance, or otherwise - /// throws your custom exception. - /// - /// The first reference to be checked. - /// The second reference to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Thrown when both and point to the same object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, T? other, Func exceptionFactory) where T : class - { - if (ReferenceEquals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that is equal to using the default equality comparer, or otherwise throws a . - /// - /// The first value to be compared. - /// The other value to be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are not equal. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustBe(this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!EqualityComparer.Default.Equals(parameter, other)) - Throw.ValuesNotEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that is equal to using the default equality comparer, or otherwise throws your custom exception. - /// - /// The first value to be compared. - /// The other value to be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when and are not equal. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustBe(this T parameter, T other, Func exceptionFactory) - { - if (!EqualityComparer.Default.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other); - return parameter; - } - - /// - /// Ensures that is equal to using the specified equality comparer, or otherwise throws a . - /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are not equal. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustBe(this T parameter, T other, IEqualityComparer equalityComparer, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) - Throw.ValuesNotEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that is equal to using the specified equality comparer, or otherwise throws your custom exception. - /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// Your custom exception thrown when and are not equal, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustBe(this T parameter, T other, IEqualityComparer equalityComparer, Func, Exception> exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (equalityComparer is null || !equalityComparer.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); - return parameter; - } - - /// - /// Ensures that is not equal to using the default equality comparer, or otherwise throws a . - /// - /// The first value to be compared. - /// The other value to be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are equal. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustNotBe(this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (EqualityComparer.Default.Equals(parameter, other)) - Throw.ValuesEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that is not equal to using the default equality comparer, or otherwise throws your custom exception. - /// - /// The first value to be compared. - /// The other value to be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when and are equal. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustNotBe(this T parameter, T other, Func exceptionFactory) - { - if (EqualityComparer.Default.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other); - return parameter; - } - - /// - /// Ensures that is not equal to using the specified equality comparer, or otherwise throws a . - /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are equal. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustNotBe(this T parameter, T other, IEqualityComparer equalityComparer, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) - Throw.ValuesEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that is not equal to using the specified equality comparer, or otherwise throws your custom exception. - /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// Your custom exception thrown when and are equal, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustNotBe(this T parameter, T other, IEqualityComparer equalityComparer, Func, Exception> exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (equalityComparer is null || equalityComparer.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); - return parameter; - } - - /// - /// Checks if the specified value is approximately the same as the other value, using the given tolerance. - /// - /// The first value to be compared. - /// The second value to be compared. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if and are equal or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this double value, double other, double tolerance) => - Math.Abs(value - other) < tolerance; - - /// - /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001. - /// - /// The first value to be compared. - /// The second value to be compared. - /// - /// True if and are equal or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this double value, double other) => - Math.Abs(value - other) < 0.0001; - - /// - /// Checks if the specified value is approximately the same as the other value, using the given tolerance. - /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if and are equal or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this float value, float other, float tolerance) => - Math.Abs(value - other) < tolerance; - - /// - /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001f. - /// - /// The first value to be compared. - /// The second value to be compared. - /// - /// True if and are equal or if their absolute difference - /// is smaller than 0.0001f, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this float value, float other) => - Math.Abs(value - other) < 0.0001f; - - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. - /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is greater than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this double value, double other, double tolerance) => - value > other || value.IsApproximately(other, tolerance); - - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is greater than or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this double value, double other) => - value > other || value.IsApproximately(other); - - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. - /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is greater than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this float value, float other, float tolerance) => - value > other || value.IsApproximately(other, tolerance); - - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001f. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is greater than or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this float value, float other) => - value > other || value.IsApproximately(other); - - /// - /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. - /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is less than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this double value, double other, double tolerance) => - value < other || value.IsApproximately(other, tolerance); - - /// - /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is less than or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this double value, double other) => - value < other || value.IsApproximately(other); - - /// - /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. - /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is less than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this float value, float other, float tolerance) => - value < other || value.IsApproximately(other, tolerance); - - /// - /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001f. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is less than or if their absolute difference - /// is smaller than 0.0001f, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this float value, float other) => - value < other || value.IsApproximately(other); -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.ComparableAssertions.cs b/Code/Light.GuardClauses/Check.ComparableAssertions.cs deleted file mode 100644 index 0eb90208..00000000 --- a/Code/Light.GuardClauses/Check.ComparableAssertions.cs +++ /dev/null @@ -1,411 +0,0 @@ -using System; -using JetBrains.Annotations; -using System.Runtime.CompilerServices; -using Light.GuardClauses.Exceptions; -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -namespace Light.GuardClauses; - -public static partial class Check -{ - /* - * ------------------------------------- - * Must Not Be Less Than - * Must Be Greater Than or Equal To - * ------------------------------------- - */ - /// - /// Ensures that the specified is not less than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeLessThan([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) - Throw.MustNotBeLessThan(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeLessThan([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs off - if (parameter is null || parameter.CompareTo(other) < 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /// - /// Ensures that the specified is not less than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeGreaterThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) - Throw.MustBeGreaterThanOrEqualTo(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeGreaterThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) < 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /* - * ------------------------------------- - * Must Be Less Than - * Must Not Be Greater Than or Equal To - * ------------------------------------- - */ - /// - /// Ensures that the specified is less than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is not less than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeLessThan([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) - Throw.MustBeLessThan(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is not less than , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeLessThan([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) >= 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /// - /// Ensures that the specified is less than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is not less than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeGreaterThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) - Throw.MustNotBeGreaterThanOrEqualTo(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is not less than , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeGreaterThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) >= 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /* - * ------------------------------------- - * Must Be Greater Than - * Must Not Be Less Than or Equal To - * ------------------------------------- - */ - /// - /// Ensures that the specified is greater than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be less than . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than or equal to . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) - Throw.MustBeGreaterThan(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be less than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than or equal to , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) <= 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /// - /// Ensures that the specified is greater than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be less than . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than or equal to . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeLessThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) - Throw.MustNotBeLessThanOrEqualTo(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be less than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than or equal to , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeLessThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) <= 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /* - * ------------------------------------- - * Must Not Be Greater Than - * Must Be Less Than or Equal To - * ------------------------------------- - */ - /// - /// Ensures that the specified is not greater than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is greater than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) - Throw.MustNotBeGreaterThan(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is greater than , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) > 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /// - /// Ensures that the specified is not greater than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is greater than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeLessThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) - Throw.MustBeLessThanOrEqualTo(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. - /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is greater than , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeLessThanOrEqualTo([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) > 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - - /* - * ------------------------------------- - * Ranges - * ------------------------------------- - */ - /// - /// Checks if the value is within the specified range. - /// - /// The comparable to be checked. - /// The range where must be in-between. - /// True if the parameter is within the specified range, else false. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsIn([NotNull, ValidatedNotNull] this T parameter, Range range) where T : IComparable => range.IsValueWithinRange(parameter); - - /// - /// Checks if the value is not within the specified range. - /// - /// The comparable to be checked. - /// The range where must not be in-between. - /// True if the parameter is not within the specified range, else false. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsNotIn([NotNull, ValidatedNotNull] this T parameter, Range range) where T : IComparable => !range.IsValueWithinRange(parameter); - - /// - /// Ensures that is within the specified range, or otherwise throws an . - /// - /// The type of the parameter to be checked. - /// The parameter to be checked. - /// The range where must be in-between. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not within . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeIn([NotNull, ValidatedNotNull] this T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (!range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) - Throw.MustBeInRange(parameter, range, parameterName, message); - return parameter; - } - - /// - /// Ensures that is within the specified range, or otherwise throws your custom exception. - /// - /// The parameter to be checked. - /// The range where must be in-between. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is not within , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeIn([NotNull, ValidatedNotNull] this T parameter, Range range, Func, Exception> exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || !range.IsValueWithinRange(parameter)) - Throw.CustomException(exceptionFactory, parameter!, range); - return parameter; - } - - /// - /// Ensures that is not within the specified range, or otherwise throws an . - /// - /// The type of the parameter to be checked. - /// The parameter to be checked. - /// The range where must not be in-between. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is within . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeIn([NotNull, ValidatedNotNull] this T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable - { - if (range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) - Throw.MustNotBeInRange(parameter, range, parameterName, message); - return parameter; - } - - /// - /// Ensures that is not within the specified range, or otherwise throws your custom exception. - /// - /// The parameter to be checked. - /// The range where must not be in-between. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is within , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeIn([NotNull, ValidatedNotNull] this T parameter, Range range, Func, Exception> exceptionFactory) where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || range.IsValueWithinRange(parameter)) - Throw.CustomException(exceptionFactory, parameter!, range); - return parameter; - } -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.Contains.cs b/Code/Light.GuardClauses/Check.Contains.cs new file mode 100644 index 00000000..5e5c9f23 --- /dev/null +++ b/Code/Light.GuardClauses/Check.Contains.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the string contains the specified value using the given comparison type. + /// + /// The string to be checked. + /// The other string. + /// One of the enumeration values that specifies the rules for the search. + /// True if contains , else false. + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("string:null => halt; value:null => halt")] + public static bool Contains( + // ReSharper disable once RedundantNullableFlowAttribute -- Caller might have NRTs turned off + [NotNull] [ValidatedNotNull] this string @string, + string value, + StringComparison comparisonType + ) => + @string.MustNotBeNull(nameof(@string)).IndexOf(value.MustNotBeNull(nameof(value)), comparisonType) >= 0; +} diff --git a/Code/Light.GuardClauses/Check.DateTimeAssertions.cs b/Code/Light.GuardClauses/Check.DateTimeAssertions.cs deleted file mode 100644 index feee6b22..00000000 --- a/Code/Light.GuardClauses/Check.DateTimeAssertions.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using JetBrains.Annotations; -using System.Runtime.CompilerServices; -using Light.GuardClauses.Exceptions; - -namespace Light.GuardClauses; - -public static partial class Check -{ - /// - /// Ensures that the specified uses , or otherwise throws an . - /// - /// The date time to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not use . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime MustBeUtc(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Kind != DateTimeKind.Utc) - Throw.MustBeUtcDateTime(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified uses , or otherwise throws your custom exception. - /// - /// The date time to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when does not use . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static DateTime MustBeUtc(this DateTime parameter, Func exceptionFactory) - { - if (parameter.Kind != DateTimeKind.Utc) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the specified uses , or otherwise throws an . - /// - /// The date time to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not use . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime MustBeLocal(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Kind != DateTimeKind.Local) - Throw.MustBeLocalDateTime(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified uses , or otherwise throws your custom exception. - /// - /// The date time to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when does not use . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static DateTime MustBeLocal(this DateTime parameter, Func exceptionFactory) - { - if (parameter.Kind != DateTimeKind.Local) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the specified uses , or otherwise throws an . - /// - /// The date time to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not use . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime MustBeUnspecified(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Kind != DateTimeKind.Unspecified) - Throw.MustBeUnspecifiedDateTime(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified uses , or otherwise throws your custom exception. - /// - /// The date time to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when does not use . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static DateTime MustBeUnspecified(this DateTime parameter, Func exceptionFactory) - { - if (parameter.Kind != DateTimeKind.Unspecified) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.DerivesFrom.cs b/Code/Light.GuardClauses/Check.DerivesFrom.cs new file mode 100644 index 00000000..8be3fd34 --- /dev/null +++ b/Code/Light.GuardClauses/Check.DerivesFrom.cs @@ -0,0 +1,76 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified type derives from the other type. Internally, this method uses + /// by default so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// + /// The type info to be checked. + /// The base class that should derive from. + /// Thrown when or is null. + [ContractAnnotation("type:null => halt; baseClass:null => halt")] + public static bool DerivesFrom( + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type baseClass + ) + { + baseClass.MustNotBeNull(nameof(baseClass)); + + var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; + while (currentBaseType != null) + { + if (currentBaseType.IsEquivalentTypeTo(baseClass)) + { + return true; + } + + currentBaseType = currentBaseType.BaseType; + } + + return false; + } + + /// + /// Checks if the specified type derives from the other type. This overload uses the specified + /// to compare the types. + /// + /// The type info to be checked. + /// The base class that should derive from. + /// The equality comparer used to compare the types. + /// Thrown when , or , or is null. + [ContractAnnotation("type:null => halt; baseClass:null => halt; typeComparer:null => halt")] + public static bool DerivesFrom( + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type baseClass, + [NotNull] [ValidatedNotNull] IEqualityComparer typeComparer + ) + { + baseClass.MustNotBeNull(nameof(baseClass)); + typeComparer.MustNotBeNull(nameof(typeComparer)); + + var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; + while (currentBaseType != null) + { + if (typeComparer.Equals(currentBaseType, baseClass)) + { + return true; + } + + currentBaseType = currentBaseType.BaseType; + } + + return false; + } +} diff --git a/Code/Light.GuardClauses/Check.EnumerableAssertions.cs b/Code/Light.GuardClauses/Check.EnumerableAssertions.cs deleted file mode 100644 index a67209bb..00000000 --- a/Code/Light.GuardClauses/Check.EnumerableAssertions.cs +++ /dev/null @@ -1,661 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; -using Light.GuardClauses.Exceptions; -using Light.GuardClauses.FrameworkExtensions; -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -namespace Light.GuardClauses; - -public static partial class Check -{ - /// - /// Ensures that the collection has the specified number of items, or otherwise throws an . - /// - /// The collection to be checked. - /// The number of items the collection must have. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not have the specified number of items. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveCount([NotNull, ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where TCollection : class, IEnumerable - { - if (parameter!.Count(parameterName, message) != count) - Throw.InvalidCollectionCount(parameter, count, parameterName, message); - return parameter; - } - - /// - /// Ensures that the collection has the specified number of items, or otherwise throws your custom exception. - /// - /// The collection to be checked. - /// The number of items the collection must have. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not have the specified number of items, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveCount([NotNull, ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is null || parameter.Count() != count) - Throw.CustomException(exceptionFactory, parameter, count); - return parameter; - } - - /// - /// Checks if the specified collection is null or empty. - /// - /// The collection to be checked. - /// True if the collection is null or empty, else false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> true, collection:canbenull; => false, collection:notnull")] - public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? collection) => - collection is null || collection.Count() == 0; - - /// - /// Ensures that the collection is not null or empty, or otherwise throws an . - /// - /// The collection to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has no items. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotBeNullOrEmpty([NotNull, ValidatedNotNull] this TCollection? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where TCollection : class, IEnumerable - { - if (parameter.Count(parameterName, message) == 0) - Throw.EmptyCollection(parameterName, message); - return parameter; - } - - /// - /// Ensures that the collection is not null or empty, or otherwise throws your custom exception. - /// - /// The collection to be checked. - /// The delegate that creates your custom exception. - /// Thrown when has no items, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotBeNullOrEmpty([NotNull, ValidatedNotNull] this TCollection? parameter, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is null || parameter.Count() == 0) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the collection contains the specified item, or otherwise throws a . - /// - /// The collection to be checked. - /// The item that must be part of the collection. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustContain([NotNull, ValidatedNotNull] this TCollection? parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where TCollection : class, IEnumerable - { - if (parameter is ICollection collection) - { - if (!collection.Contains(item)) - Throw.MissingItem(parameter, item, parameterName, message); - return parameter; - } - - if (!parameter.MustNotBeNull(parameterName, message).Contains(item)) - Throw.MissingItem(parameter, item, parameterName, message); - return parameter; - } - - /// - /// Ensures that the collection contains the specified item, or otherwise throws your custom exception. - /// - /// The collection to be checked. - /// The item that must be part of the collection. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not contain , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustContain([NotNull, ValidatedNotNull] this TCollection? parameter, TItem item, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is ICollection collection) - { - if (!collection.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); - return parameter; - } - - if (parameter is null || !parameter.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); - return parameter; - } - - /// - /// Ensures that the collection does not contain the specified item, or otherwise throws an . - /// - /// The collection to be checked. - /// The item that must not be part of the collection. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotContain([NotNull, ValidatedNotNull] this TCollection? parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where TCollection : class, IEnumerable - { - if (parameter is ICollection collection) - { - if (collection.Contains(item)) - Throw.ExistingItem(parameter, item, parameterName, message); - return parameter; - } - - if (parameter.MustNotBeNull(parameterName, message).Contains(item)) - Throw.ExistingItem(parameter, item, parameterName, message); - return parameter; - } - - /// - /// Ensures that the collection does not contain the specified item, or otherwise throws your custom exception. - /// - /// The collection to be checked. - /// The item that must not be part of the collection. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when contains . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotContain([NotNull, ValidatedNotNull] this TCollection? parameter, TItem item, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is ICollection collection) - { - if (collection.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); - return parameter; - } - - if (parameter is null || parameter.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); - return parameter; - } - - /// - /// Checks if the given is one of the specified . - /// - /// The item to be checked. - /// The collection that might contain the . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests - public static bool IsOneOf(this TItem item, [NotNull, ValidatedNotNull] IEnumerable items) - { - if (items is ICollection collection) - return collection.Contains(item); - - if (items is string @string && item is char character) - return @string.IndexOf(character) != -1; - - return items.MustNotBeNull(nameof(items)).ContainsViaForeach(item); - } - - /// - /// Ensures that the value is one of the specified items, or otherwise throws a . - /// - /// The value to be checked. - /// The items that should contain the value. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not equal to one of the specified . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests - public static TItem MustBeOneOf(this TItem parameter, [NotNull, ValidatedNotNull] IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - // ReSharper disable PossibleMultipleEnumeration - if (!parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) - Throw.ValueNotOneOf(parameter, items, parameterName, message); - return parameter; - // ReSharper restore PossibleMultipleEnumeration - } - - /// - /// Ensures that the value is one of the specified items, or otherwise throws your custom exception. - /// - /// The value to be checked. - /// The items that should contain the value. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is not equal to one of the specified , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static TItem MustBeOneOf(this TItem parameter, [NotNull, ValidatedNotNull] TCollection items, Func exceptionFactory) where TCollection : class, IEnumerable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (items is null || !parameter.IsOneOf(items)) - Throw.CustomException(exceptionFactory, parameter, items!); - return parameter; - } - - /// - /// Ensures that the value is not one of the specified items, or otherwise throws a . - /// - /// The value to be checked. - /// The items that must not contain the value. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is equal to one of the specified . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests - public static TItem MustNotBeOneOf(this TItem parameter, [NotNull, ValidatedNotNull] IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - // ReSharper disable PossibleMultipleEnumeration - if (parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) - Throw.ValueIsOneOf(parameter, items, parameterName, message); - return parameter; - // ReSharper restore PossibleMultipleEnumeration - } - - /// - /// Ensures that the value is not one of the specified items, or otherwise throws your custom exception. - /// - /// The value to be checked. - /// The items that must not contain the value. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is equal to one of the specified , or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static TItem MustNotBeOneOf(this TItem parameter, [NotNull, ValidatedNotNull] TCollection items, Func exceptionFactory) where TCollection : class, IEnumerable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (items is null || parameter.IsOneOf(items)) - Throw.CustomException(exceptionFactory, parameter, items!); - return parameter; - } - - /// - /// Ensures that the collection has at least the specified number of items, or otherwise throws an . - /// - /// The collection to be checked. - /// The number of items the collection should have at least. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain at least the specified number of items. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMinimumCount([NotNull, ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where TCollection : class, IEnumerable - { - if (parameter.Count(parameterName, message) < count) - Throw.InvalidMinimumCollectionCount(parameter, count, parameterName, message); - return parameter; - } - - /// - /// Ensures that the collection has at least the specified number of items, or otherwise throws your custom exception. - /// - /// The collection to be checked. - /// The number of items the collection should have at least. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not contain at least the specified number of items, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMinimumCount([NotNull, ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is null || parameter.Count() < count) - Throw.CustomException(exceptionFactory, parameter, count); - return parameter; - } - - /// - /// Ensures that the collection has at most the specified number of items, or otherwise throws an . - /// - /// The collection to be checked. - /// The number of items the collection should have at most. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain at most the specified number of items. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMaximumCount([NotNull, ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where TCollection : class, IEnumerable - { - if (parameter.Count(parameterName, message) > count) - Throw.InvalidMaximumCollectionCount(parameter, count, parameterName, message); - return parameter; - } - - /// - /// Ensures that the collection has at most the specified number of items, or otherwise throws your custom exception. - /// - /// The collection to be checked. - /// The number of items the collection should have at most. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not contain at most the specified number of items, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMaximumCount([NotNull, ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is null || parameter.Count() > count) - Throw.CustomException(exceptionFactory, parameter, count); - return parameter; - } - - /// - /// Ensures that the span has the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The length that the span must have. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not have the specified length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustHaveLength(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span has the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length that the span must have. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not have the specified length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustHaveLength(this Span parameter, int length, SpanExceptionFactory exceptionFactory) - { - if (parameter.Length != length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span has the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length that the span must have. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Your custom exception thrown when does not have the specified length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span has the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length that the span must have. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not have the specified length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length != length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is longer than the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The value that the span must be longer than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThan(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length <= length) - Throw.SpanMustBeLongerThan(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be longer than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThan(this Span parameter, int length, SpanExceptionFactory exceptionFactory) - { - if (parameter.Length <= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is longer than the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The value that the span must be longer than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThan(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length <= length) - Throw.SpanMustBeLongerThan(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be longer than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThan(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length <= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThanOrEqualTo(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length < length) - Throw.SpanMustBeLongerThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThanOrEqualTo(this Span parameter, int length, SpanExceptionFactory exceptionFactory) - { - if (parameter.Length < length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThanOrEqualTo(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length < length) - Throw.SpanMustBeLongerThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThanOrEqualTo(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length < length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is shorter than the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThan(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length >= length) - Throw.SpanMustBeShorterThan(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThan(this Span parameter, int length, SpanExceptionFactory exceptionFactory) - { - if (parameter.Length >= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is shorter than the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length >= length) - Throw.SpanMustBeShorterThan(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length >= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThanOrEqualTo(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length > length) - Throw.SpanMustBeShorterThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThanOrEqualTo(this Span parameter, int length, SpanExceptionFactory exceptionFactory) - { - if (parameter.Length > length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . - /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThanOrEqualTo(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Length > length) - Throw.SpanMustBeShorterThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThanOrEqualTo(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length > length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.Equals.cs b/Code/Light.GuardClauses/Check.Equals.cs new file mode 100644 index 00000000..05f38e66 --- /dev/null +++ b/Code/Light.GuardClauses/Check.Equals.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified strings are equal, using the given comparison rules. + /// + /// The first string to compare. + /// The second string to compare. + /// One of the enumeration values that specifies the rules for the comparison. + /// True if the two strings are considered equal, else false. + /// Thrown when is no valid enum value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Equals(this string? @string, string? value, StringComparisonType comparisonType) + { + if ((int) comparisonType < 6) + { + return string.Equals(@string, value, (StringComparison) comparisonType); + } + + switch (comparisonType) + { + case StringComparisonType.OrdinalIgnoreWhiteSpace: + return @string.EqualsOrdinalIgnoreWhiteSpace(value); + case StringComparisonType.OrdinalIgnoreCaseIgnoreWhiteSpace: + return @string.EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(value); + default: + Throw.EnumValueNotDefined(comparisonType, nameof(comparisonType)); + return false; + } + } +} diff --git a/Code/Light.GuardClauses/Check.Implements.cs b/Code/Light.GuardClauses/Check.Implements.cs new file mode 100644 index 00000000..e0643d4c --- /dev/null +++ b/Code/Light.GuardClauses/Check.Implements.cs @@ -0,0 +1,79 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the type implements the specified interface type. Internally, this method uses + /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// + /// The type to be checked. + /// The interface type that should implement. + /// Thrown when or is null. + [ContractAnnotation("type:null => halt; interfaceType:null => halt")] + public static bool Implements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type interfaceType + ) + { + type.MustNotBeNull(); + interfaceType.MustNotBeNull(); + + var implementedInterfaces = type.GetInterfaces(); + for (var i = 0; i < implementedInterfaces.Length; ++i) + { + if (interfaceType.IsEquivalentTypeTo(implementedInterfaces[i])) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the type implements the specified interface type. This overload uses the specified + /// to compare the interface types. + /// + /// The type to be checked. + /// The interface type that should implement. + /// The equality comparer used to compare the interface types. + /// Thrown when , or , or is null. + [ContractAnnotation("type:null => halt; interfaceType:null => halt; typeComparer:null => halt")] + public static bool Implements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type interfaceType, + [NotNull] [ValidatedNotNull] IEqualityComparer typeComparer + ) + { + type.MustNotBeNull(); + interfaceType.MustNotBeNull(); + typeComparer.MustNotBeNull(); + + var implementedInterfaces = type.GetInterfaces(); + for (var i = 0; i < implementedInterfaces.Length; ++i) + { + if (typeComparer.Equals(implementedInterfaces[i], interfaceType)) + { + return true; + } + } + + return false; + } +} diff --git a/Code/Light.GuardClauses/Check.InheritsFrom.cs b/Code/Light.GuardClauses/Check.InheritsFrom.cs new file mode 100644 index 00000000..6e2ad444 --- /dev/null +++ b/Code/Light.GuardClauses/Check.InheritsFrom.cs @@ -0,0 +1,61 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the given type derives from the specified base class or interface type. Internally, this method uses + /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// + /// The type to be checked. + /// The type describing an interface or base class that should derive from or implement. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt")] + public static bool InheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type baseClassOrInterfaceType + ) => + baseClassOrInterfaceType + .MustNotBeNull(nameof(baseClassOrInterfaceType)) + .IsInterface ? + type.Implements(baseClassOrInterfaceType) : + type.DerivesFrom(baseClassOrInterfaceType); + + /// + /// Checks if the given type derives from the specified base class or interface type. This overload uses the specified + /// to compare the types. + /// + /// The type to be checked. + /// The type describing an interface or base class that should derive from or implement. + /// The equality comparer used to compare the types. + /// Thrown when , or , or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt; typeComparer:null => halt")] + public static bool InheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type baseClassOrInterfaceType, + [NotNull] [ValidatedNotNull] IEqualityComparer typeComparer + ) => + baseClassOrInterfaceType + .MustNotBeNull(nameof(baseClassOrInterfaceType)) + .IsInterface ? + type.Implements(baseClassOrInterfaceType, typeComparer) : + type.DerivesFrom(baseClassOrInterfaceType, typeComparer); +} diff --git a/Code/Light.GuardClauses/Check.InvalidArgument.cs b/Code/Light.GuardClauses/Check.InvalidArgument.cs new file mode 100644 index 00000000..29bf0ec8 --- /dev/null +++ b/Code/Light.GuardClauses/Check.InvalidArgument.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified is true and throws an in this case. + /// + /// The condition to be checked. The exception is thrown when it is true. + /// The name of the parameter (optional). + /// The message that will be passed to the (optional). + /// Thrown when is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidArgument(bool condition, string? parameterName = null, string? message = null) + { + if (condition) + { + Throw.Argument(parameterName, message); + } + } + + /// + /// Checks if the specified is true and throws your custom exception in this case. + /// + /// The condition to be checked. The exception is thrown when it is true. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static void InvalidArgument(bool condition, Func exceptionFactory) + { + if (condition) + { + Throw.CustomException(exceptionFactory); + } + } + + /// + /// Checks if the specified is true and throws your custom exception in this case. + /// + /// The condition to be checked. The exception is thrown when it is true. + /// The value that is checked in the . This value is passed to the . + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static void InvalidArgument(bool condition, T parameter, Func exceptionFactory) + { + if (condition) + { + Throw.CustomException(exceptionFactory, parameter); + } + } +} diff --git a/Code/Light.GuardClauses/Check.InvalidOperation.cs b/Code/Light.GuardClauses/Check.InvalidOperation.cs new file mode 100644 index 00000000..e0b3b79f --- /dev/null +++ b/Code/Light.GuardClauses/Check.InvalidOperation.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified is true and throws an in this case. + /// + /// The condition to be checked. The exception is thrown when it is true. + /// The message that will be passed to the (optional). + /// Thrown when is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidOperation(bool condition, string? message = null) + { + if (condition) + { + Throw.InvalidOperation(message); + } + } +} diff --git a/Code/Light.GuardClauses/Check.InvalidState.cs b/Code/Light.GuardClauses/Check.InvalidState.cs new file mode 100644 index 00000000..496ec78a --- /dev/null +++ b/Code/Light.GuardClauses/Check.InvalidState.cs @@ -0,0 +1,23 @@ +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified is true and throws an in this case. + /// + /// The condition to be checked. The exception is thrown when it is true. + /// The message that will be passed to the . + /// Thrown when is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidState(bool condition, string? message = null) + { + if (condition) + { + Throw.InvalidState(message); + } + } +} diff --git a/Code/Light.GuardClauses/Check.IsApproximately.cs b/Code/Light.GuardClauses/Check.IsApproximately.cs new file mode 100644 index 00000000..02ef1010 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsApproximately.cs @@ -0,0 +1,81 @@ +using System; +using System.Runtime.CompilerServices; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified value is approximately the same as the other value, using the given tolerance. + /// + /// The first value to be compared. + /// The second value to be compared. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if and are equal or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this double value, double other, double tolerance) => + Math.Abs(value - other) <= tolerance; + + /// + /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001. + /// + /// The first value to be compared. + /// The second value to be compared. + /// + /// True if and are equal or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this double value, double other) => + Math.Abs(value - other) <= 0.0001; + + /// + /// Checks if the specified value is approximately the same as the other value, using the given tolerance. + /// + /// The first value to be compared. + /// The second value to be compared. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if and are equal or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this float value, float other, float tolerance) => + Math.Abs(value - other) <= tolerance; + + /// + /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001f. + /// + /// The first value to be compared. + /// The second value to be compared. + /// + /// True if and are equal or if their absolute difference + /// is smaller than 0.0001f, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this float value, float other) => + Math.Abs(value - other) <= 0.0001f; + +#if NET8_0 + /// + /// Checks if the specified value is approximately the same as the other value, using the given tolerance. + /// + /// The first value to be compared. + /// The second value to be compared. + /// The tolerance indicating how much the two values may differ from each other. + /// The type that implements the interface. + /// + /// True if and are equal or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this T value, T other, T tolerance) where T : INumber => + T.Abs(value - other) <= tolerance; +#endif +} diff --git a/Code/Light.GuardClauses/Check.IsDigit.cs b/Code/Light.GuardClauses/Check.IsDigit.cs new file mode 100644 index 00000000..fb7756aa --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsDigit.cs @@ -0,0 +1,12 @@ +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified character is a digit. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDigit(this char character) => char.IsDigit(character); +} diff --git a/Code/Light.GuardClauses/Check.IsEmailAddress.cs b/Code/Light.GuardClauses/Check.IsEmailAddress.cs new file mode 100644 index 00000000..396e2fdf --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsEmailAddress.cs @@ -0,0 +1,101 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using JetBrains.Annotations; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified string is an email address using the default email regular expression + /// defined in . + /// + /// The string to be checked if it is an email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("emailAddress:null => false")] + public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress) => + emailAddress != null && RegularExpressions.EmailRegex.IsMatch(emailAddress); + + /// + /// Checks if the specified string is an email address using the provided regular expression for validation. + /// + /// The string to be checked. + /// The regular expression that determines whether the input string is an email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("emailAddress:null => false; emailAddressPattern:null => halt")] + public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress, Regex emailAddressPattern) => + emailAddress != null && emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress); + +#if NET8_0 + /// + /// Checks if the specified span is an email address using the default email regular expression + /// defined in . + /// + /// The span to be checked. + public static bool IsEmailAddress(this Span emailAddress) => + RegularExpressions.EmailRegex.IsMatch(emailAddress); + + /// + /// Checks if the specified span is an email address using the provided regular expression for validation. + /// + /// The span to be checked. + /// The regular expression that determines whether the input string is an email address. + /// Thrown when is null. + public static bool IsEmailAddress(this Span emailAddress, Regex emailAddressPattern) => + emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress); + + /// + /// Checks if the specified span is an email address using the default email regular expression + /// defined in . + /// + /// The span to be checked. + public static bool IsEmailAddress(this ReadOnlySpan emailAddress) => + RegularExpressions.EmailRegex.IsMatch(emailAddress); + + /// + /// Checks if the specified span is an email address using the provided regular expression for validation. + /// + /// The span to be checked. + /// The regular expression that determines whether the input string is an email address. + /// Thrown when is null. + public static bool IsEmailAddress(this ReadOnlySpan emailAddress, Regex emailAddressPattern) => + emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress); + + /// + /// Checks if the specified character memory is an email address using the default email regular expression + /// defined in . + /// + /// The character memory to be checked. + public static bool IsEmailAddress(this Memory emailAddress) => + RegularExpressions.EmailRegex.IsMatch(emailAddress.Span); + + /// + /// Checks if the specified character memory is an email address using the provided regular expression for validation. + /// + /// The character memory to be checked. + /// The regular expression that determines whether the input string is an email address. + /// Thrown when is null. + public static bool IsEmailAddress(this Memory emailAddress, Regex emailAddressPattern) => + emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress.Span); + + /// + /// Checks if the specified character memory is an email address using the default email regular expression + /// defined in . + /// + /// The character memory to be checked. + public static bool IsEmailAddress(this ReadOnlyMemory emailAddress) => + RegularExpressions.EmailRegex.IsMatch(emailAddress.Span); + + /// + /// Checks if the specified character memory is an email address using the provided regular expression for validation. + /// + /// The character memory to be checked. + /// The regular expression that determines whether the input string is an email address. + /// Thrown when is null. + public static bool IsEmailAddress(this ReadOnlyMemory emailAddress, Regex emailAddressPattern) => + emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress.Span); +#endif +} diff --git a/Code/Light.GuardClauses/Check.IsEmpty.cs b/Code/Light.GuardClauses/Check.IsEmpty.cs new file mode 100644 index 00000000..46574bb0 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsEmpty.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified GUID is an empty one. + /// + /// The GUID to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmpty(this Guid parameter) => parameter == Guid.Empty; +} diff --git a/Code/Light.GuardClauses/Check.IsEmptyOrWhiteSpace.cs b/Code/Light.GuardClauses/Check.IsEmptyOrWhiteSpace.cs new file mode 100644 index 00000000..580c677d --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsEmptyOrWhiteSpace.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified span is empty or contains only white space characters. + /// + /// The span to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmptyOrWhiteSpace(this Span span) => ((ReadOnlySpan) span).IsEmptyOrWhiteSpace(); + + /// + /// Checks if the specified span is empty or contains only white space characters. + /// + /// The span to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmptyOrWhiteSpace(this ReadOnlySpan span) + { + if (span.IsEmpty) + { + return true; + } + + foreach (var character in span) + { + if (!character.IsWhiteSpace()) + { + return false; + } + } + + return true; + } + + /// + /// Checks if the specified memory is empty or contains only white space characters. + /// + /// The memory to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmptyOrWhiteSpace(this Memory memory) => memory.Span.IsEmptyOrWhiteSpace(); + + /// + /// Checks if the specified memory is empty or contains only white space characters. + /// + /// The memory to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmptyOrWhiteSpace(this ReadOnlyMemory memory) => memory.Span.IsEmptyOrWhiteSpace(); +} diff --git a/Code/Light.GuardClauses/Check.IsEquivalentTypeTo.cs b/Code/Light.GuardClauses/Check.IsEquivalentTypeTo.cs new file mode 100644 index 00000000..585abaab --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsEquivalentTypeTo.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the two specified types are equivalent. This is true when both types are equal or + /// when one type is a constructed generic type and the other type is the corresponding generic type definition. + /// + /// The first type to be checked. + /// The other type to be checked. + /// + /// True if both types are null, or if both are equal, or if one type + /// is a constructed generic type and the other one is the corresponding generic type definition, else false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEquivalentTypeTo(this Type? type, Type? other) => + ReferenceEquals(type, other) || + (type is not null && + other is not null && + (type == other || + (type.IsConstructedGenericType != other.IsConstructedGenericType && + CheckTypeEquivalency(type, other)))); + + private static bool CheckTypeEquivalency(Type type, Type other) + { + if (type.IsConstructedGenericType) + { + return type.GetGenericTypeDefinition() == other; + } + + return other.GetGenericTypeDefinition() == type; + } +} diff --git a/Code/Light.GuardClauses/Check.IsFileExtension.cs b/Code/Light.GuardClauses/Check.IsFileExtension.cs new file mode 100644 index 00000000..4e814527 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsFileExtension.cs @@ -0,0 +1,87 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified string represents a valid file extension. + /// + /// + /// The string to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the string is a valid file extension, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFileExtension([NotNullWhen(true)] this string? value) => + value != null && IsFileExtension(value.AsSpan()); + + /// + /// Checks if the specified character span represents a valid file extension. + /// + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the span is a valid file extension, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFileExtension(this Span value) => IsFileExtension((ReadOnlySpan) value); + + /// + /// Checks if the specified character memory represents a valid file extension. + /// + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the span is a valid file extension, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFileExtension(this ReadOnlyMemory value) => IsFileExtension(value.Span); + + /// + /// Checks if the specified character memory represents a valid file extension. + /// + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the span is a valid file extension, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFileExtension(this Memory value) => IsFileExtension(value.Span); + + /// + /// Checks if the specified character span represents a valid file extension. + /// + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the span is a valid file extension, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFileExtension(this ReadOnlySpan value) + { + // ReSharper disable once UseIndexFromEndExpression -- cannot use index from end expression in .NET Standard 2.0 + if (value.Length <= 1 || value[0] != '.' || value[value.Length - 1] == '.') + { + return false; + } + + var hasAlphanumeric = false; + for (var i = 1; i < value.Length; i++) + { + var character = value[i]; + if (character.IsLetterOrDigit()) + { + hasAlphanumeric = true; + } + else if (character != '.') + { + return false; + } + } + + return hasAlphanumeric; + } +} diff --git a/Code/Light.GuardClauses/Check.IsGreaterThanOrApproximately.cs b/Code/Light.GuardClauses/Check.IsGreaterThanOrApproximately.cs new file mode 100644 index 00000000..487540b4 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsGreaterThanOrApproximately.cs @@ -0,0 +1,80 @@ +using System.Runtime.CompilerServices; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is greater than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsGreaterThanOrApproximately(this double value, double other, double tolerance) => + value > other || value.IsApproximately(other, tolerance); + + /// + /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// True if is greater than or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsGreaterThanOrApproximately(this double value, double other) => + value > other || value.IsApproximately(other); + + /// + /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is greater than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsGreaterThanOrApproximately(this float value, float other, float tolerance) => + value > other || value.IsApproximately(other, tolerance); + + /// + /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001f. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// True if is greater than or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsGreaterThanOrApproximately(this float value, float other) => + value > other || value.IsApproximately(other); + +#if NET8_0 + /// + /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// The type that implements the interface. + /// + /// True if is greater than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsGreaterThanOrApproximately(this T value, T other, T tolerance) where T : INumber => + value > other || value.IsApproximately(other, tolerance); +#endif +} diff --git a/Code/Light.GuardClauses/Check.IsIn.cs b/Code/Light.GuardClauses/Check.IsIn.cs new file mode 100644 index 00000000..b33484fc --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsIn.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.CompilerServices; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the value is within the specified range. + /// + /// The comparable to be checked. + /// The range where must be in-between. + /// True if the parameter is within the specified range, else false. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsIn([NotNull] [ValidatedNotNull] this T parameter, Range range) + where T : IComparable => + range.IsValueWithinRange(parameter); +} diff --git a/Code/Light.GuardClauses/Check.IsLessThanOrApproximately.cs b/Code/Light.GuardClauses/Check.IsLessThanOrApproximately.cs new file mode 100644 index 00000000..89b109ba --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsLessThanOrApproximately.cs @@ -0,0 +1,80 @@ +using System.Runtime.CompilerServices; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is less than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this double value, double other, double tolerance) => + value < other || value.IsApproximately(other, tolerance); + + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// True if is less than or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this double value, double other) => + value < other || value.IsApproximately(other); + + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is less than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this float value, float other, float tolerance) => + value < other || value.IsApproximately(other, tolerance); + + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001f. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// True if is less than or if their absolute difference + /// is smaller than 0.0001f, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this float value, float other) => + value < other || value.IsApproximately(other); + +#if NET8_0 + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// The type that implements the interface. + /// + /// True if is less than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this T value, T other, T tolerance) where T : INumber => + value < other || value.IsApproximately(other, tolerance); +#endif +} diff --git a/Code/Light.GuardClauses/Check.IsLetter.cs b/Code/Light.GuardClauses/Check.IsLetter.cs new file mode 100644 index 00000000..2639fc99 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsLetter.cs @@ -0,0 +1,12 @@ +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified character is a letter. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLetter(this char character) => char.IsLetter(character); +} diff --git a/Code/Light.GuardClauses/Check.IsLetterOrDigit.cs b/Code/Light.GuardClauses/Check.IsLetterOrDigit.cs new file mode 100644 index 00000000..1acd0304 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsLetterOrDigit.cs @@ -0,0 +1,12 @@ +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified character is a letter or digit. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLetterOrDigit(this char character) => char.IsLetterOrDigit(character); +} diff --git a/Code/Light.GuardClauses/Check.IsNewLine.cs b/Code/Light.GuardClauses/Check.IsNewLine.cs new file mode 100644 index 00000000..80979bab --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsNewLine.cs @@ -0,0 +1,18 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the string is either "\n" or "\r\n". This is done independently of the current value of . + /// + /// The string to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> false, parameter:canbenull; => true, parameter:notnull")] + public static bool IsNewLine([NotNullWhen(true)] this string? parameter) => + parameter == "\n" || parameter == "\r\n"; +} diff --git a/Code/Light.GuardClauses/Check.IsNotIn.cs b/Code/Light.GuardClauses/Check.IsNotIn.cs new file mode 100644 index 00000000..3cf76a6b --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsNotIn.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.CompilerServices; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the value is not within the specified range. + /// + /// The comparable to be checked. + /// The range where must not be in-between. + /// True if the parameter is not within the specified range, else false. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNotIn([NotNull] [ValidatedNotNull] this T parameter, Range range) + where T : IComparable => + !range.IsValueWithinRange(parameter); +} diff --git a/Code/Light.GuardClauses/Check.IsNullOrEmpty.cs b/Code/Light.GuardClauses/Check.IsNullOrEmpty.cs new file mode 100644 index 00000000..d8580017 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsNullOrEmpty.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified collection is null or empty. + /// + /// The collection to be checked. + /// True if the collection is null or empty, else false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> true, collection:canbenull; => false, collection:notnull")] + public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? collection) => + collection is null || collection.Count() == 0; + + /// + /// Checks if the specified string is null or empty. + /// + /// The string to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] + public static bool IsNullOrEmpty([NotNullWhen(false)] this string? @string) => string.IsNullOrEmpty(@string); +} diff --git a/Code/Light.GuardClauses/Check.IsNullOrWhiteSpace.cs b/Code/Light.GuardClauses/Check.IsNullOrWhiteSpace.cs new file mode 100644 index 00000000..c67f171d --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsNullOrWhiteSpace.cs @@ -0,0 +1,17 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified string is null, empty, or contains only white space. + /// + /// The string to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] + public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? @string) => + string.IsNullOrWhiteSpace(@string); +} diff --git a/Code/Light.GuardClauses/Check.IsOneOf.cs b/Code/Light.GuardClauses/Check.IsOneOf.cs new file mode 100644 index 00000000..25b00636 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsOneOf.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.FrameworkExtensions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the given is one of the specified . + /// + /// The item to be checked. + /// The collection that might contain the . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("items:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests + public static bool IsOneOf(this TItem item, [NotNull] [ValidatedNotNull] IEnumerable items) + { + if (items is ICollection collection) + { + return collection.Contains(item); + } + + if (items is string @string && item is char character) + { + return @string.IndexOf(character) != -1; + } + + return items.MustNotBeNull(nameof(items)).ContainsViaForeach(item); + } +} diff --git a/Code/Light.GuardClauses/Check.IsOpenConstructedGenericType.cs b/Code/Light.GuardClauses/Check.IsOpenConstructedGenericType.cs new file mode 100644 index 00000000..7d8348ec --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsOpenConstructedGenericType.cs @@ -0,0 +1,26 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the given is a generic type that has open generic parameters, + /// but is no generic type definition. + /// + /// The type to be checked. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static bool IsOpenConstructedGenericType([NotNull] [ValidatedNotNull] this Type type) => + type.MustNotBeNull(nameof(type)).IsGenericType && + type.ContainsGenericParameters && + type.IsGenericTypeDefinition == false; +} diff --git a/Code/Light.GuardClauses/Check.IsOrDerivesFrom.cs b/Code/Light.GuardClauses/Check.IsOrDerivesFrom.cs new file mode 100644 index 00000000..ed74beee --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsOrDerivesFrom.cs @@ -0,0 +1,48 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the given is equal to the specified or if it derives from it. Internally, this + /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrDerivesFrom( + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type otherType + ) => + type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType); + + /// + /// Checks if the given is equal to the specified or if it derives from it. This overload uses the specified + /// to compare the types. + /// + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// The equality comparer used to compare the types. + /// Thrown when , or , or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] + public static bool IsOrDerivesFrom( + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type otherType, + [NotNull] [ValidatedNotNull] IEqualityComparer typeComparer + ) => + typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || + type.DerivesFrom(otherType, typeComparer); +} diff --git a/Code/Light.GuardClauses/Check.IsOrImplements.cs b/Code/Light.GuardClauses/Check.IsOrImplements.cs new file mode 100644 index 00000000..aea0e108 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsOrImplements.cs @@ -0,0 +1,58 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the given is equal to the specified or if it implements it. Internally, this + /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// + /// The type to be checked. + /// The type that is equivalent to or the interface type that implements. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrImplements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type otherType + ) => + type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType); + + /// + /// Checks if the given is equal to the specified or if it implements it. This overload uses the specified + /// to compare the types. + /// + /// , + /// The type to be checked. + /// The type that is equivalent to or the interface type that implements. + /// The equality comparer used to compare the interface types. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrImplements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type otherType, + [NotNull] [ValidatedNotNull] IEqualityComparer typeComparer + ) => + typeComparer.MustNotBeNull(nameof(typeComparer)).Equals( + type.MustNotBeNull(nameof(type)), + otherType.MustNotBeNull(nameof(otherType)) + ) || + type.Implements(otherType, typeComparer); +} diff --git a/Code/Light.GuardClauses/Check.IsOrInheritsFrom.cs b/Code/Light.GuardClauses/Check.IsOrInheritsFrom.cs new file mode 100644 index 00000000..62549f48 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsOrInheritsFrom.cs @@ -0,0 +1,56 @@ +#if NET8_0 +using System.Diagnostics.CodeAnalysis; +#endif +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the given is equal to the specified or if it derives from it or implements it. + /// Internally, this method uses so that constructed generic types and their corresponding generic type definitions + /// are regarded as equal. + /// + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrInheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type otherType + ) => + type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType); + + + /// + /// Checks if the given is equal to the specified or if it derives from it or implements it. + /// This overload uses the specified to compare the types. + /// + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// The equality comparer used to compare the types. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] + public static bool IsOrInheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull] [ValidatedNotNull] this Type type, + [NotNull] [ValidatedNotNull] Type otherType, + [NotNull] [ValidatedNotNull] IEqualityComparer typeComparer + ) => + typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || + type.InheritsFrom(otherType, typeComparer); +} diff --git a/Code/Light.GuardClauses/Check.IsSameAs.cs b/Code/Light.GuardClauses/Check.IsSameAs.cs new file mode 100644 index 00000000..8d4c0636 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsSameAs.cs @@ -0,0 +1,19 @@ +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if and point to the same object. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable StringLiteralTypo + [ContractAnnotation( + "parameter:notNull => true, other:notnull; parameter:notNull => false, other:canbenull; other:notnull => true, parameter:notnull; other:notnull => false, parameter:canbenull" + )] + // ReSharper restore StringLiteralTypo + public static bool IsSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other) where T : class => + ReferenceEquals(parameter, other); +} diff --git a/Code/Light.GuardClauses/Check.IsSubstringOf.cs b/Code/Light.GuardClauses/Check.IsSubstringOf.cs new file mode 100644 index 00000000..7812ef22 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsSubstringOf.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the string is a substring of the other string. + /// + /// The string to be checked. + /// The other string. + /// True if is a substring of , else false. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("value:null => halt; other:null => halt")] + // ReSharper disable RedundantNullableFlowAttribute + public static bool IsSubstringOf( + [NotNull] [ValidatedNotNull] this string value, + [NotNull] [ValidatedNotNull] string other + ) => + other.MustNotBeNull(nameof(other)).Contains(value); + // ReSharper restore RedundantNullableFlowAttribute + + /// + /// Checks if the string is a substring of the other string. + /// + /// The string to be checked. + /// The other string. + /// One of the enumeration values that specifies the rules for the search. + /// True if is a substring of , else false. + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("value:null => halt; other:null => halt")] + // ReSharper disable RedundantNullableFlowAttribute + public static bool IsSubstringOf( + [NotNull] [ValidatedNotNull] this string value, + [NotNull] [ValidatedNotNull] string other, + StringComparison comparisonType + ) => + other.MustNotBeNull(nameof(other)).IndexOf(value, comparisonType) != -1; + // ReSharper disable RedundantNullableFlowAttribute +} diff --git a/Code/Light.GuardClauses/Check.IsTrimmed.cs b/Code/Light.GuardClauses/Check.IsTrimmed.cs new file mode 100644 index 00000000..727de57f --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsTrimmed.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified string is trimmed, i.e. it does not start or end with + /// white space characters. Inputting an empty string will return true. When null is passed, + /// you can control the return value with which will + /// return true by default. + /// + /// The string to be checked. + /// + /// The value indicating whether true or false should be returned from this method when the + /// is null. The default value is true. + /// + /// + /// True if the is trimmed, else false. An empty string will result in true. + /// You can control the return value with when the + /// is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmed(this string? parameter, bool regardNullAsTrimmed = true) => + parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmed(); + + /// + /// Checks if the specified character span is trimmed, i.e. it does not start or end with + /// white space characters. Inputting an empty span will return true. + /// + /// The character span to be checked. + /// True if the is trimmed, else false. An empty span will result in true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmed(this ReadOnlySpan parameter) => + parameter.Length == 0 || + !parameter[0].IsWhiteSpace() && + !parameter[parameter.Length - 1].IsWhiteSpace(); +} diff --git a/Code/Light.GuardClauses/Check.IsTrimmedAtEnd.cs b/Code/Light.GuardClauses/Check.IsTrimmedAtEnd.cs new file mode 100644 index 00000000..644ba56a --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsTrimmedAtEnd.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified string is trimmed at the end, i.e. it does not end with + /// white space characters. Inputting an empty string will return true. + /// + /// The string to be checked. + /// + /// The value indicating whether true or false should be returned from this method when the + /// is null. The default value is true. + /// + /// + /// True if the is trimmed at the end, else false. + /// An empty string will result in true. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmedAtEnd(this string? parameter, bool regardNullAsTrimmed = true) => + parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtEnd(); + + /// + /// Checks if the specified character span is trimmed at the end, i.e. it does not end with + /// white space characters. Inputting an empty span will return true. + /// + /// The character span to be checked. + /// + /// True if the is trimmed at the end, else false. + /// An empty span will result in true. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmedAtEnd(this ReadOnlySpan parameter) => + parameter.Length == 0 || !parameter[parameter.Length - 1].IsWhiteSpace(); +} diff --git a/Code/Light.GuardClauses/Check.IsTrimmedAtStart.cs b/Code/Light.GuardClauses/Check.IsTrimmedAtStart.cs new file mode 100644 index 00000000..3e2e3d18 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsTrimmedAtStart.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified string is trimmed at the start, i.e. it does not start with + /// white space characters. Inputting an empty string will return true. + /// + /// The string to be checked. + /// + /// The value indicating whether true or false should be returned from this method when the + /// is null. The default value is true. + /// + /// + /// True if the is trimmed at the start, else false. + /// An empty string will result in true. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmedAtStart(this string? parameter, bool regardNullAsTrimmed = true) => + parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtStart(); + + /// + /// Checks if the specified character span is trimmed at the start, i.e. it does not start with + /// white space characters. Inputting an empty span will return true. + /// + /// The character span to be checked. + /// + /// True if the is trimmed at the start, else false. + /// An empty span will result in true. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmedAtStart(this ReadOnlySpan parameter) => + parameter.Length == 0 || !parameter[0].IsWhiteSpace(); +} diff --git a/Code/Light.GuardClauses/Check.IsValidEnumValue.cs b/Code/Light.GuardClauses/Check.IsValidEnumValue.cs new file mode 100644 index 00000000..4869f250 --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsValidEnumValue.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified value is a valid enum value of its type. This is true when the specified value + /// is one of the constants defined in the enum, or a valid flags combination when the enum type is marked + /// with the . + /// + /// The type of the enum. + /// The enum value to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsValidEnumValue(this T parameter) where T : struct, Enum => + EnumInfo.IsValidEnumValue(parameter); +} diff --git a/Code/Light.GuardClauses/Check.IsWhiteSpace.cs b/Code/Light.GuardClauses/Check.IsWhiteSpace.cs new file mode 100644 index 00000000..4a18250f --- /dev/null +++ b/Code/Light.GuardClauses/Check.IsWhiteSpace.cs @@ -0,0 +1,12 @@ +using System.Runtime.CompilerServices; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Checks if the specified character is a white space character. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhiteSpace(this char character) => char.IsWhiteSpace(character); +} diff --git a/Code/Light.GuardClauses/Check.MustBe.cs b/Code/Light.GuardClauses/Check.MustBe.cs new file mode 100644 index 00000000..99a7121d --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBe.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that is equal to using the default equality comparer, or otherwise throws a . + /// + /// The first value to be compared. + /// The other value to be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when and are not equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBe( + this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!EqualityComparer.Default.Equals(parameter, other)) + { + Throw.ValuesNotEqual(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that is equal to using the default equality comparer, or otherwise throws your custom exception. + /// + /// The first value to be compared. + /// The other value to be compared. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when and are not equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBe(this T parameter, T other, Func exceptionFactory) + { + if (!EqualityComparer.Default.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that is equal to using the specified equality comparer, or otherwise throws a . + /// + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when and are not equal. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustBe( + this T parameter, + T other, + IEqualityComparer equalityComparer, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) + { + Throw.ValuesNotEqual(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that is equal to using the specified equality comparer, or otherwise throws your custom exception. + /// + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when and are not equal, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustBe( + this T parameter, + T other, + IEqualityComparer equalityComparer, + Func, Exception> exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (equalityComparer is null || !equalityComparer.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); + } + + return parameter; + } + + /// + /// Ensures that the two strings are equal using the specified , or otherwise throws a . + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustBe( + this string? parameter, + string? other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!string.Equals(parameter, other, comparisonType)) + { + Throw.ValuesNotEqual(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the two strings are equal using the specified , or otherwise throws your custom exception. + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is not equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustBe( + this string? parameter, + string? other, + StringComparison comparisonType, + Func exceptionFactory + ) + { + if (!string.Equals(parameter, other, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, other, comparisonType); + } + + return parameter; + } + + /// + /// Ensures that the two strings are equal using the specified , or otherwise throws a . + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustBe( + this string? parameter, + string? other, + StringComparisonType comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.Equals(other, comparisonType)) + { + Throw.ValuesNotEqual(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the two strings are equal using the specified , or otherwise throws your custom exception. + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is not equal to . + /// Thrown when is not a valid value from the enum. + public static string? MustBe( + this string? parameter, + string? other, + StringComparisonType comparisonType, + Func exceptionFactory + ) + { + if (!parameter.Equals(other, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, other, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeAbsoluteUri.cs b/Code/Light.GuardClauses/Check.MustBeAbsoluteUri.cs new file mode 100644 index 00000000..b4d3f754 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeAbsoluteUri.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified URI is an absolute one, or otherwise throws a . + /// + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not an absolute URI. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeAbsoluteUri( + [NotNull] [ValidatedNotNull] this Uri? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri == false) + { + Throw.MustBeAbsoluteUri(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified URI is an absolute one, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when is not an absolute URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeAbsoluteUri( + [NotNull] [ValidatedNotNull] this Uri? parameter, + Func exceptionFactory + ) + { + if (parameter is null || parameter.IsAbsoluteUri == false) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeApproximately.cs b/Code/Light.GuardClauses/Check.MustBeApproximately.cs new file mode 100644 index 00000000..77bdf05b --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeApproximately.cs @@ -0,0 +1,297 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is not + /// less than 0.0001. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeApproximately( + this double parameter, + double other, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) => + parameter.MustBeApproximately(other, 0.0001, parameterName, message); + + /// + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is not + /// less than 0.0001. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeApproximately( + this double parameter, + double other, + Func exceptionFactory + ) + { + if (!parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is not + /// less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeApproximately( + this double parameter, + double other, + double tolerance, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// Your custom exception thrown when the absolute difference between and + /// is not less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeApproximately( + this double parameter, + double other, + double tolerance, + Func exceptionFactory + ) + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + + /// + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is + /// not less than 0.0001f. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeApproximately( + this float parameter, + float other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + parameter.MustBeApproximately(other, 0.0001f, parameterName, message); + + /// + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is not + /// less than 0.0001. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeApproximately( + this float parameter, + float other, + Func exceptionFactory + ) + { + if (!parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is not + /// less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeApproximately( + this float parameter, + float other, + float tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , , and + /// are passed to this delegate. + /// + /// + /// Your custom exception thrown when the absolute difference between and + /// is not less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeApproximately( + this float parameter, + float other, + float tolerance, + Func exceptionFactory + ) + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + +#if NET8_0 + /// + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The type that implements the interface. + /// + /// Thrown when the absolute difference between and is not + /// less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeApproximately( + this T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : INumber + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// The type that implements the interface. + /// + /// Your custom exception thrown when the absolute difference between and + /// is not less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeApproximately( + this T parameter, + T other, + T tolerance, + Func exceptionFactory + ) where T : INumber + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } +#endif +} diff --git a/Code/Light.GuardClauses/Check.MustBeEmailAddress.cs b/Code/Light.GuardClauses/Check.MustBeEmailAddress.cs new file mode 100644 index 00000000..8cd52c74 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeEmailAddress.cs @@ -0,0 +1,444 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is a valid email address using the default email regular expression + /// defined in , or otherwise throws an . + /// + /// The email address that will be validated. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeEmailAddress( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress()) + { + Throw.InvalidEmailAddress(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is a valid email address using the default email regular expression + /// defined in , or otherwise throws your custom exception. + /// + /// The email address that will be validated. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeEmailAddress( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (!parameter.IsEmailAddress()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } + + /// + /// Ensures that the string is a valid email address using the provided regular expression, + /// or otherwise throws an . + /// + /// The email address that will be validated. + /// The regular expression that determines if the input string is a valid email. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] + public static string MustBeEmailAddress( + [NotNull] [ValidatedNotNull] this string? parameter, + Regex emailAddressPattern, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress(emailAddressPattern)) + { + Throw.InvalidEmailAddress(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is a valid email address using the provided regular expression, + /// or otherwise throws your custom exception. + /// + /// The email address that will be validated. + /// The regular expression that determines if the input string is a valid email. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] + public static string MustBeEmailAddress( + [NotNull] [ValidatedNotNull] this string? parameter, + Regex emailAddressPattern, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (emailAddressPattern is null || !parameter.IsEmailAddress(emailAddressPattern)) + { + Throw.CustomException(exceptionFactory, parameter, emailAddressPattern!); + } + + return parameter; + } + +#if NET8_0 + /// + /// Ensures that the span represents a valid email address using the default email regular expression + /// defined in , or otherwise throws an . + /// + /// The span that will be validated as an email address. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeEmailAddress( + this ReadOnlySpan parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsEmailAddress()) + { + Throw.InvalidEmailAddress(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the default email regular expression + /// defined in , or otherwise throws your custom exception. + /// + /// The span that will be validated as an email address. + /// The delegate that creates your custom exception. A string created from is passed to this delegate. + /// Your custom exception thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeEmailAddress( + this ReadOnlySpan parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (!parameter.IsEmailAddress()) + { + Throw.CustomSpanException(exceptionFactory, parameter); + } + + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the provided regular expression, + /// or otherwise throws an . + /// + /// The span that will be validated as an email address. + /// The regular expression that determines if the span represents a valid email. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("emailAddressPattern:null => halt")] + public static ReadOnlySpan MustBeEmailAddress( + this ReadOnlySpan parameter, + Regex emailAddressPattern, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)); + + if (!parameter.IsEmailAddress(emailAddressPattern)) + { + Throw.InvalidEmailAddress(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the provided regular expression, + /// or otherwise throws your custom exception. + /// + /// The span that will be validated as an email address. + /// The regular expression that determines if the span represents a valid email. + /// The delegate that creates your custom exception. A string created from and are passed to this delegate. + /// Your custom exception thrown when is no valid email address or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeEmailAddress( + this ReadOnlySpan parameter, + Regex emailAddressPattern, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (emailAddressPattern is null || !parameter.IsEmailAddress(emailAddressPattern)) + { + Throw.CustomSpanException(exceptionFactory, parameter, emailAddressPattern!); + } + + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the default email regular expression + /// defined in , or otherwise throws an . + /// + /// The span that will be validated as an email address. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeEmailAddress( + this Span parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustBeEmailAddress(parameterName, message); + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the default email regular expression + /// defined in , or otherwise throws your custom exception. + /// + /// The span that will be validated as an email address. + /// The delegate that creates your custom exception. A string created from is passed to this delegate. + /// Your custom exception thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeEmailAddress( + this Span parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + ((ReadOnlySpan) parameter).MustBeEmailAddress(exceptionFactory); + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the provided regular expression, + /// or otherwise throws an . + /// + /// The span that will be validated as an email address. + /// The regular expression that determines if the span represents a valid email. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("emailAddressPattern:null => halt")] + public static Span MustBeEmailAddress( + this Span parameter, + Regex emailAddressPattern, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustBeEmailAddress(emailAddressPattern, parameterName, message); + return parameter; + } + + /// + /// Ensures that the span represents a valid email address using the provided regular expression, + /// or otherwise throws your custom exception. + /// + /// The span that will be validated as an email address. + /// The regular expression that determines if the span represents a valid email. + /// The delegate that creates your custom exception. A string created from and are passed to this delegate. + /// Your custom exception thrown when is no valid email address or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeEmailAddress( + this Span parameter, + Regex emailAddressPattern, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + ((ReadOnlySpan) parameter).MustBeEmailAddress(emailAddressPattern, exceptionFactory); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the default email regular expression + /// defined in , or otherwise throws an . + /// + /// The memory that will be validated as an email address. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory MustBeEmailAddress( + this Memory parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter.Span).MustBeEmailAddress(parameterName, message); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the default email regular expression + /// defined in , or otherwise throws your custom exception. + /// + /// The memory that will be validated as an email address. + /// The delegate that creates your custom exception. A string created from is passed to this delegate. + /// Your custom exception thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory MustBeEmailAddress( + this Memory parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + ((ReadOnlySpan) parameter.Span).MustBeEmailAddress(exceptionFactory); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the provided regular expression, + /// or otherwise throws an . + /// + /// The memory that will be validated as an email address. + /// The regular expression that determines if the memory represents a valid email. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("emailAddressPattern:null => halt")] + public static Memory MustBeEmailAddress( + this Memory parameter, + Regex emailAddressPattern, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter.Span).MustBeEmailAddress(emailAddressPattern, parameterName, message); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the provided regular expression, + /// or otherwise throws your custom exception. + /// + /// The memory that will be validated as an email address. + /// The regular expression that determines if the memory represents a valid email. + /// The delegate that creates your custom exception. A string created from and are passed to this delegate. + /// Your custom exception thrown when is no valid email address or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory MustBeEmailAddress( + this Memory parameter, + Regex emailAddressPattern, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + ((ReadOnlySpan) parameter.Span).MustBeEmailAddress(emailAddressPattern, exceptionFactory); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the default email regular expression + /// defined in , or otherwise throws an . + /// + /// The memory that will be validated as an email address. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory MustBeEmailAddress( + this ReadOnlyMemory parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + parameter.Span.MustBeEmailAddress(parameterName, message); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the default email regular expression + /// defined in , or otherwise throws your custom exception. + /// + /// The memory that will be validated as an email address. + /// The delegate that creates your custom exception. A string created from is passed to this delegate. + /// Your custom exception thrown when is no valid email address. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory MustBeEmailAddress( + this ReadOnlyMemory parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + parameter.Span.MustBeEmailAddress(exceptionFactory); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the provided regular expression, + /// or otherwise throws an . + /// + /// The memory that will be validated as an email address. + /// The regular expression that determines if the memory represents a valid email. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid email address. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("emailAddressPattern:null => halt")] + public static ReadOnlyMemory MustBeEmailAddress( + this ReadOnlyMemory parameter, + Regex emailAddressPattern, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + parameter.Span.MustBeEmailAddress(emailAddressPattern, parameterName, message); + return parameter; + } + + /// + /// Ensures that the memory represents a valid email address using the provided regular expression, + /// or otherwise throws your custom exception. + /// + /// The memory that will be validated as an email address. + /// The regular expression that determines if the memory represents a valid email. + /// The delegate that creates your custom exception. A string created from and are passed to this delegate. + /// Your custom exception thrown when is no valid email address or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory MustBeEmailAddress( + this ReadOnlyMemory parameter, + Regex emailAddressPattern, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + parameter.Span.MustBeEmailAddress(emailAddressPattern, exceptionFactory); + return parameter; + } +#endif +} diff --git a/Code/Light.GuardClauses/Check.MustBeFileExtension.cs b/Code/Light.GuardClauses/Check.MustBeFileExtension.cs new file mode 100644 index 00000000..ae24e8b1 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeFileExtension.cs @@ -0,0 +1,204 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is a valid file extension, or otherwise throws an . + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not a valid file extension. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeFileExtension( + [NotNull][ValidatedNotNull] this string? parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsFileExtension()) + { + Throw.NotFileExtension(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is a valid file extension, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not a valid file extension. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeFileExtension( + [NotNull][ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (parameter is null || !parameter.IsFileExtension()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } + + /// + /// Ensures that the character span is a valid file extension, or otherwise throws a . + /// + /// The character span to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The original character span. + /// Thrown when is not a valid file extension. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeFileExtension( + this Span parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan)parameter).MustBeFileExtension(parameterName, message); + return parameter; + } + + /// + /// Ensures that the character span is a valid file extension, or otherwise throws your custom exception. + /// + /// The character span to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// The original character span. + /// Your custom exception thrown when is not a valid file extension. + public static Span MustBeFileExtension( + this Span parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + ((ReadOnlySpan)parameter).MustBeFileExtension(exceptionFactory); + return parameter; + } + + /// + /// Ensures that the character memory is a valid file extension, or otherwise throws a . + /// + /// The character memory to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The original character memory. + /// Thrown when is not a valid file extension. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory MustBeFileExtension( + this Memory parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan)parameter.Span).MustBeFileExtension(parameterName, message); + return parameter; + } + + /// + /// Ensures that the character memory is a valid file extension, or otherwise throws your custom exception. + /// + /// The character memory to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// The original character memory. + /// Your custom exception thrown when is not a valid file extension. + public static Memory MustBeFileExtension( + this Memory parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + ((ReadOnlySpan)parameter.Span).MustBeFileExtension(exceptionFactory); + return parameter; + } + + /// + /// Ensures that the read-only character memory is a valid file extension, or otherwise throws a . + /// + /// The read-only character memory to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The original read-only character memory. + /// Thrown when is not a valid file extension. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory MustBeFileExtension( + this ReadOnlyMemory parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + parameter.Span.MustBeFileExtension(parameterName, message); + return parameter; + } + + /// + /// Ensures that the read-only character memory is a valid file extension, or otherwise throws your custom exception. + /// + /// The read-only character memory to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// The original read-only character memory. + /// Your custom exception thrown when is not a valid file extension. + public static ReadOnlyMemory MustBeFileExtension( + this ReadOnlyMemory parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + parameter.Span.MustBeFileExtension(exceptionFactory); + return parameter; + } + + /// + /// Ensures that the read-only character span is a valid file extension, or otherwise throws a . + /// + /// The read-only character span to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The original read-only character span. + /// Thrown when is not a valid file extension. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeFileExtension( + this ReadOnlySpan parameter, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsFileExtension()) + { + Throw.NotFileExtension(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the read-only character span is a valid file extension, or otherwise throws your custom exception. + /// + /// The read-only character span to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// The original read-only character span. + /// Your custom exception thrown when is not a valid file extension. + public static ReadOnlySpan MustBeFileExtension( + this ReadOnlySpan parameter, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (!parameter.IsFileExtension()) + { + Throw.CustomSpanException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeGreaterThan.cs b/Code/Light.GuardClauses/Check.MustBeGreaterThan.cs new file mode 100644 index 00000000..2e66b68f --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeGreaterThan.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is greater than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is less than or equal to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeGreaterThan( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) + { + Throw.MustBeGreaterThan(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than or equal to , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeGreaterThan( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) <= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeGreaterThanOrApproximately.cs b/Code/Light.GuardClauses/Check.MustBeGreaterThanOrApproximately.cs new file mode 100644 index 00000000..a0cd3958 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeGreaterThanOrApproximately.cs @@ -0,0 +1,292 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately( + this double parameter, + double other, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) => + parameter.MustBeGreaterThanOrApproximately(other, 0.0001, parameterName, message); + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately( + this double parameter, + double other, + Func exceptionFactory + ) + { + if (!parameter.IsGreaterThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately( + this double parameter, + double other, + double tolerance, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.MustBeGreaterThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately( + this double parameter, + double other, + double tolerance, + Func exceptionFactory + ) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately( + this float parameter, + float other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + parameter.MustBeGreaterThanOrApproximately(other, 0.0001f, parameterName, message); + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately( + this float parameter, + float other, + Func exceptionFactory + ) + { + if (!parameter.IsGreaterThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately( + this float parameter, + float other, + float tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.MustBeGreaterThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately( + this float parameter, + float other, + float tolerance, + Func exceptionFactory + ) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + +#if NET8_0 + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The type that implements the interface. + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeGreaterThanOrApproximately( + this T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : INumber + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.MustBeGreaterThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// The type that implements the interface. + /// + /// Your custom exception thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeGreaterThanOrApproximately( + this T parameter, + T other, + T tolerance, + Func exceptionFactory + ) where T : INumber + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } +#endif +} diff --git a/Code/Light.GuardClauses/Check.MustBeGreaterThanOrEqualTo.cs b/Code/Light.GuardClauses/Check.MustBeGreaterThanOrEqualTo.cs new file mode 100644 index 00000000..213f8250 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeGreaterThanOrEqualTo.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is not less than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is less than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeGreaterThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) + { + Throw.MustBeGreaterThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeGreaterThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) < 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeHttpOrHttpsUrl.cs b/Code/Light.GuardClauses/Check.MustBeHttpOrHttpsUrl.cs new file mode 100644 index 00000000..cbcea62d --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeHttpOrHttpsUrl.cs @@ -0,0 +1,59 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws an . + /// + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when uses a different scheme than "http" or "https". + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpOrHttpsUrl( + [NotNull] [ValidatedNotNull] this Uri? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustBeAbsoluteUri(parameterName, message).Scheme.Equals("https") == false && + parameter.Scheme.Equals("http") == false) + { + Throw.UriMustHaveOneSchemeOf(parameter, ["https", "http"], parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than "http" or "https", or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpOrHttpsUrl( + [NotNull] [ValidatedNotNull] this Uri? parameter, + Func exceptionFactory + ) + { + if (parameter.MustBeAbsoluteUri(exceptionFactory).Scheme.Equals("https") == false && + parameter.Scheme.Equals("http") == false) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeHttpUrl.cs b/Code/Light.GuardClauses/Check.MustBeHttpUrl.cs new file mode 100644 index 00000000..717be5f2 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeHttpUrl.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified URI has the "http" scheme, or otherwise throws an . + /// + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when uses a different scheme than "http". + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpUrl( + [NotNull] [ValidatedNotNull] this Uri? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + parameter.MustHaveScheme("http", parameterName, message); + + /// + /// Ensures that the specified URI has the "http" scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than "http", or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpUrl( + [NotNull] [ValidatedNotNull] this Uri? parameter, + Func exceptionFactory + ) => + parameter.MustHaveScheme("http", exceptionFactory); +} diff --git a/Code/Light.GuardClauses/Check.MustBeHttpsUrl.cs b/Code/Light.GuardClauses/Check.MustBeHttpsUrl.cs new file mode 100644 index 00000000..4e2169c9 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeHttpsUrl.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified URI has the "https" scheme, or otherwise throws an . + /// + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when uses a different scheme than "https". + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpsUrl( + [NotNull] [ValidatedNotNull] this Uri? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + parameter.MustHaveScheme("https", parameterName, message); + + /// + /// Ensures that the specified URI has the "https" scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than "https", or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpsUrl( + [NotNull] [ValidatedNotNull] this Uri? parameter, + Func exceptionFactory + ) => + parameter.MustHaveScheme("https", exceptionFactory); +} diff --git a/Code/Light.GuardClauses/Check.MustBeIn.cs b/Code/Light.GuardClauses/Check.MustBeIn.cs new file mode 100644 index 00000000..061c236a --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeIn.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that is within the specified range, or otherwise throws an . + /// + /// The type of the parameter to be checked. + /// The parameter to be checked. + /// The range where must be in-between. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not within . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeIn( + [NotNull] [ValidatedNotNull] this T parameter, + Range range, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (!range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) + { + Throw.MustBeInRange(parameter, range, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that is within the specified range, or otherwise throws your custom exception. + /// + /// The parameter to be checked. + /// The range where must be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is not within , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeIn( + [NotNull] [ValidatedNotNull] this T parameter, + Range range, + Func, Exception> exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || !range.IsValueWithinRange(parameter)) + { + Throw.CustomException(exceptionFactory, parameter!, range); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeLessThan.cs b/Code/Light.GuardClauses/Check.MustBeLessThan.cs new file mode 100644 index 00000000..7667730a --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeLessThan.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is less than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be greater than . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is not less than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeLessThan( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) + { + Throw.MustBeLessThan(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be greater than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is not less than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeLessThan( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) >= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeLessThanOrApproximately.cs b/Code/Light.GuardClauses/Check.MustBeLessThanOrApproximately.cs new file mode 100644 index 00000000..f48bf5da --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeLessThanOrApproximately.cs @@ -0,0 +1,292 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately( + this double parameter, + double other, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) => + parameter.MustBeLessThanOrApproximately(other, 0.0001, parameterName, message); + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately( + this double parameter, + double other, + Func exceptionFactory + ) + { + if (!parameter.IsLessThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately( + this double parameter, + double other, + double tolerance, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.MustBeLessThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately( + this double parameter, + double other, + double tolerance, + Func exceptionFactory + ) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately( + this float parameter, + float other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + parameter.MustBeLessThanOrApproximately(other, 0.0001f, parameterName, message); + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately( + this float parameter, + float other, + Func exceptionFactory + ) + { + if (!parameter.IsLessThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately( + this float parameter, + float other, + float tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.MustBeLessThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately( + this float parameter, + float other, + float tolerance, + Func exceptionFactory + ) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + +#if NET8_0 + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The type that implements the interface. + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeLessThanOrApproximately( + this T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : INumber + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.MustBeLessThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// The type that implements the interface. + /// + /// Your custom exception thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeLessThanOrApproximately( + this T parameter, + T other, + T tolerance, + Func exceptionFactory + ) where T : INumber + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } +#endif +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.MustBeLessThanOrEqualTo.cs b/Code/Light.GuardClauses/Check.MustBeLessThanOrEqualTo.cs new file mode 100644 index 00000000..5ac96d38 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeLessThanOrEqualTo.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is not greater than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is greater than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeLessThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) + { + Throw.MustBeLessThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is greater than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeLessThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) > 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeLocal.cs b/Code/Light.GuardClauses/Check.MustBeLocal.cs new file mode 100644 index 00000000..6f2c31b2 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeLocal.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified uses , or otherwise throws an . + /// + /// The date time to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not use . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DateTime MustBeLocal( + this DateTime parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Kind != DateTimeKind.Local) + { + Throw.MustBeLocalDateTime(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified uses , or otherwise throws your custom exception. + /// + /// The date time to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when does not use . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static DateTime MustBeLocal(this DateTime parameter, Func exceptionFactory) + { + if (parameter.Kind != DateTimeKind.Local) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeLongerThan.cs b/Code/Light.GuardClauses/Check.MustBeLongerThan.cs new file mode 100644 index 00000000..7b6cc14b --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeLongerThan.cs @@ -0,0 +1,148 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is longer than the specified length, or otherwise throws a . + /// + /// The string to be checked. + /// The length that the string must be longer than. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length shorter than or equal to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeLongerThan( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).Length <= length) + { + Throw.StringNotLongerThan(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is longer than the specified length, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The length that the string must be longer than. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length shorter than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeLongerThan( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + Func exceptionFactory + ) + { + if (parameter is null || parameter.Length <= length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is longer than the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The value that the span must be longer than. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is shorter than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeLongerThan( + this Span parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustBeLongerThan(length, parameterName, message); + return parameter; + } + + /// + /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be longer than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeLongerThan( + this Span parameter, + int length, + SpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length <= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is longer than the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The value that the span must be longer than. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is shorter than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeLongerThan( + this ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Length <= length) + { + Throw.SpanMustBeLongerThan(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be longer than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeLongerThan( + this ReadOnlySpan parameter, + int length, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length <= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeLongerThanOrEqualTo.cs b/Code/Light.GuardClauses/Check.MustBeLongerThanOrEqualTo.cs new file mode 100644 index 00000000..f85231b9 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeLongerThanOrEqualTo.cs @@ -0,0 +1,148 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is longer than or equal to the specified length, or otherwise throws a . + /// + /// The string to be checked. + /// The length that the string must be longer than or equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length shorter than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeLongerThanOrEqualTo( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).Length < length) + { + Throw.StringNotLongerThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is longer than or equal to the specified length, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The length that the string must be longer than or equal to. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length shorter than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeLongerThanOrEqualTo( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + Func exceptionFactory + ) + { + if (parameter is null || parameter.Length < length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The value that the span must be longer than or equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is shorter than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeLongerThanOrEqualTo( + this Span parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustBeLongerThanOrEqualTo(length, parameterName, message); + return parameter; + } + + /// + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The value that the span must be longer than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeLongerThanOrEqualTo( + this Span parameter, + int length, + SpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length < length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The value that the span must be longer than or equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is shorter than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeLongerThanOrEqualTo( + this ReadOnlySpan parameter, + int length, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (parameter.Length < length) + { + Throw.SpanMustBeLongerThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The value that the span must be longer than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeLongerThanOrEqualTo( + this ReadOnlySpan parameter, + int length, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length < length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeNewLine.cs b/Code/Light.GuardClauses/Check.MustBeNewLine.cs new file mode 100644 index 00000000..dcba3dbd --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeNewLine.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is either "\n" or "\r\n", or otherwise throws a . This is done independently of the current value of . + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not equal to "\n" or "\r\n". + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeNewLine( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsNewLine()) + { + Throw.NotNewLine(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is either "\n" or "\r\n", or otherwise throws your custom exception. This is done independently of the current value of . + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is not equal to "\n" or "\r\n". + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeNewLine( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (!parameter.IsNewLine()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeOfType.cs b/Code/Light.GuardClauses/Check.MustBeOfType.cs new file mode 100644 index 00000000..7e9f44ca --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeOfType.cs @@ -0,0 +1,48 @@ + +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that can be cast to and returns the cast value, or otherwise throws a . + /// + /// The value to be cast. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when cannot be cast to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeOfType([NotNull, ValidatedNotNull, NoEnumeration] this object? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (parameter.MustNotBeNull(parameterName, message) is T castValue) + return castValue; + + Throw.InvalidTypeCast(parameter, typeof(T), parameterName, message); + return default; + } + + /// + /// Ensures that can be cast to and returns the cast value, or otherwise throws your custom exception. + /// + /// The value to be cast. + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when cannot be cast to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeOfType([NotNull, ValidatedNotNull, NoEnumeration] this object? parameter, Func exceptionFactory) + { + if (parameter is T castValue) + return castValue; + + Throw.CustomException(exceptionFactory, parameter); + return default; + } +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.MustBeOneOf.cs b/Code/Light.GuardClauses/Check.MustBeOneOf.cs new file mode 100644 index 00000000..edcf7b4e --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeOneOf.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the value is one of the specified items, or otherwise throws a . + /// + /// The value to be checked. + /// The items that should contain the value. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not equal to one of the specified . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("items:null => halt")] + public static TItem MustBeOneOf( + this TItem parameter, + // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests + [NotNull] [ValidatedNotNull] IEnumerable items, + [CallerArgumentExpression("parameter")] + string? parameterName = null, + string? message = null + ) + { + // ReSharper disable PossibleMultipleEnumeration + if (!parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) + { + Throw.ValueNotOneOf(parameter, items, parameterName, message); + } + + return parameter; + // ReSharper restore PossibleMultipleEnumeration + } + + /// + /// Ensures that the value is one of the specified items, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The items that should contain the value. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is not equal to one of the specified , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("items:null => halt")] + public static TItem MustBeOneOf( + this TItem parameter, + [NotNull] [ValidatedNotNull] TCollection items, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (items is null || !parameter.IsOneOf(items)) + { + Throw.CustomException(exceptionFactory, parameter, items!); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeRelativeUri.cs b/Code/Light.GuardClauses/Check.MustBeRelativeUri.cs new file mode 100644 index 00000000..9eee840b --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeRelativeUri.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified URI is a relative one, or otherwise throws an . + /// + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is an absolute URI. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeRelativeUri( + [NotNull] [ValidatedNotNull] this Uri? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri) + { + Throw.MustBeRelativeUri(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified URI is a relative one, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is an absolute URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeRelativeUri( + [NotNull] [ValidatedNotNull] this Uri? parameter, + Func exceptionFactory + ) + { + if (parameter is null || parameter.IsAbsoluteUri) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeShorterThan.cs b/Code/Light.GuardClauses/Check.MustBeShorterThan.cs new file mode 100644 index 00000000..7e47e1ef --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeShorterThan.cs @@ -0,0 +1,148 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.Exceptions; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is shorter than the specified length, or otherwise throws a . + /// + /// The string to be checked. + /// The length that the string must be shorter than. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length greater than or equal to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeShorterThan( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).Length >= length) + { + Throw.StringNotShorterThan(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is shorter than the specified length, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The length that the string must be shorter than. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length greater than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeShorterThan( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + Func exceptionFactory + ) + { + if (parameter is null || parameter.Length >= length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is shorter than the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The length value that the span must be shorter than. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeShorterThan( + this Span parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustBeShorterThan(length, parameterName, message); + return parameter; + } + + /// + /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be shorter than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeShorterThan( + this Span parameter, + int length, + SpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length >= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is shorter than the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The length value that the span must be shorter than. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeShorterThan( + this ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Length >= length) + { + Throw.SpanMustBeShorterThan(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be shorter than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeShorterThan( + this ReadOnlySpan parameter, + int length, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length >= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeShorterThanOrEqualTo.cs b/Code/Light.GuardClauses/Check.MustBeShorterThanOrEqualTo.cs new file mode 100644 index 00000000..dc9903f1 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeShorterThanOrEqualTo.cs @@ -0,0 +1,148 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws a . + /// + /// The string to be checked. + /// The length that the string must be shorter than or equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length greater than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeShorterThanOrEqualTo( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).Length > length) + { + Throw.StringNotShorterThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The length that the string must be shorter than or equal to. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length greater than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeShorterThanOrEqualTo( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + Func exceptionFactory + ) + { + if (parameter is null || parameter.Length > length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The length value that the span must be shorter than or equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is longer than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeShorterThanOrEqualTo( + this Span parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustBeShorterThanOrEqualTo(length, parameterName, message); + return parameter; + } + + /// + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be shorter than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeShorterThanOrEqualTo( + this Span parameter, + int length, + SpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length > length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The length value that the span must be shorter than or equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is longer than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeShorterThanOrEqualTo( + this ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Length > length) + { + Throw.SpanMustBeShorterThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be shorter than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeShorterThanOrEqualTo( + this ReadOnlySpan parameter, + int length, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length > length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeSubstringOf.cs b/Code/Light.GuardClauses/Check.MustBeSubstringOf.cs new file mode 100644 index 00000000..81d8cda3 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeSubstringOf.cs @@ -0,0 +1,128 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is a substring of the specified other string, or otherwise throws a . + /// + /// The string to be checked. + /// The other string that must contain . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) + { + Throw.NotSubstring(parameter, value, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string that must contain . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || !value.Contains(parameter)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + + return parameter; + } + + /// + /// Ensures that the string is a substring of the specified other string, or otherwise throws a . + /// + /// The string to be checked. + /// The other string that must contain . + /// One of the enumeration values that specifies the rules for the search. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + + value.MustNotBeNull(nameof(value), message); + parameter.MustNotBeNull(parameterName, message); + if (value.IndexOf(parameter, comparisonType) == -1) + { + Throw.NotSubstring(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string that must contain . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || value.IndexOf(parameter, comparisonType) == -1) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeTrimmed.cs b/Code/Light.GuardClauses/Check.MustBeTrimmed.cs new file mode 100644 index 00000000..ea9fb3cb --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeTrimmed.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is not null and trimmed, or otherwise throws a . + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not trimmed, i.e. they start or end with white space characters. + /// Empty strings are regarded as trimmed. + /// + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmed( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsTrimmed()) + { + Throw.NotTrimmed(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is not null and trimmed, or otherwise throws your custom exception. + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not trimmed. Empty strings are regarded as trimmed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmed( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (parameter is null || !parameter.AsSpan().IsTrimmed()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeTrimmedAtEnd.cs b/Code/Light.GuardClauses/Check.MustBeTrimmedAtEnd.cs new file mode 100644 index 00000000..f37319cc --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeTrimmedAtEnd.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is not null and trimmed at the end, or otherwise throws a . + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not trimmed at the end, i.e. they end with white space characters. + /// Empty strings are regarded as trimmed. + /// + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtEnd( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtEnd()) + { + Throw.NotTrimmedAtEnd(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is not null and trimmed at the end, or otherwise throws your custom exception. + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not trimmed at the end. Empty strings are regarded as trimmed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtEnd( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (parameter is null || !parameter.AsSpan().IsTrimmedAtEnd()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeTrimmedAtStart.cs b/Code/Light.GuardClauses/Check.MustBeTrimmedAtStart.cs new file mode 100644 index 00000000..6096052f --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeTrimmedAtStart.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is not null and trimmed at the start, or otherwise throws a . + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not trimmed at the start, i.e. they start with white space characters. + /// Empty strings are regarded as trimmed. + /// + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtStart( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtStart()) + { + Throw.NotTrimmedAtStart(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is not null and trimmed at the start, or otherwise throws your custom exception. + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not trimmed at the start. Empty strings are regarded as trimmed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtStart( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (parameter is null || !parameter.AsSpan().IsTrimmedAtStart()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeUnspecified.cs b/Code/Light.GuardClauses/Check.MustBeUnspecified.cs new file mode 100644 index 00000000..d4c806da --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeUnspecified.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified uses , or otherwise throws an . + /// + /// The date time to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not use . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DateTime MustBeUnspecified( + this DateTime parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Kind != DateTimeKind.Unspecified) + { + Throw.MustBeUnspecifiedDateTime(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified uses , or otherwise throws your custom exception. + /// + /// The date time to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when does not use . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static DateTime MustBeUnspecified(this DateTime parameter, Func exceptionFactory) + { + if (parameter.Kind != DateTimeKind.Unspecified) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeUtc.cs b/Code/Light.GuardClauses/Check.MustBeUtc.cs new file mode 100644 index 00000000..87cbc2bd --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeUtc.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified uses , or otherwise throws an . + /// + /// The date time to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not use . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DateTime MustBeUtc( + this DateTime parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Kind != DateTimeKind.Utc) + { + Throw.MustBeUtcDateTime(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified uses , or otherwise throws your custom exception. + /// + /// The date time to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when does not use . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static DateTime MustBeUtc(this DateTime parameter, Func exceptionFactory) + { + if (parameter.Kind != DateTimeKind.Utc) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustBeValidEnumValue.cs b/Code/Light.GuardClauses/Check.MustBeValidEnumValue.cs new file mode 100644 index 00000000..c078cb34 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustBeValidEnumValue.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified enum value is valid, or otherwise throws an . An enum value + /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type + /// is marked with the . + /// + /// The type of the enum. + /// The value to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is no valid enum value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustBeValidEnumValue( + this T parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + where T : struct, Enum + { + if (!EnumInfo.IsValidEnumValue(parameter)) + { + Throw.EnumValueNotDefined(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified enum value is valid, or otherwise throws your custom exception. An enum value + /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type + /// is marked with the . + /// + /// The type of the enum. + /// The value to be checked. + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when is no valid enum value, or when is no enum type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static T MustBeValidEnumValue(this T parameter, Func exceptionFactory) + where T : struct, Enum + { + if (!EnumInfo.IsValidEnumValue(parameter)) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustContain.cs b/Code/Light.GuardClauses/Check.MustContain.cs new file mode 100644 index 00000000..7158a377 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustContain.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the collection contains the specified item, or otherwise throws a . + /// + /// The collection to be checked. + /// The item that must be part of the collection. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustContain( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + TItem item, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where TCollection : class, IEnumerable + { + if (parameter is ICollection collection) + { + if (!collection.Contains(item)) + { + Throw.MissingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + if (!parameter.MustNotBeNull(parameterName, message).Contains(item)) + { + Throw.MissingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the collection contains the specified item, or otherwise throws your custom exception. + /// + /// The collection to be checked. + /// The item that must be part of the collection. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustContain( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + TItem item, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is ICollection collection) + { + if (!collection.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + + if (parameter is null || !parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + + /// + /// Ensures that the string contains the specified substring, or otherwise throws a . + /// + /// The string to be checked. + /// The substring that must be part of . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string? value, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) + { + Throw.StringDoesNotContain(parameter, value, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string contains the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The substring that must be part of . + /// The delegate that creates you custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || !parameter.Contains(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + + return parameter; + } + + /// + /// Ensures that the string contains the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The substring that must be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).IndexOf( + value.MustNotBeNull(nameof(value), message), + comparisonType + ) < + 0) + { + Throw.StringDoesNotContain(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string contains the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The substring that must be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates you custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || parameter.IndexOf(value, comparisonType) < 0) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustEndWith.cs b/Code/Light.GuardClauses/Check.MustEndWith.cs new file mode 100644 index 00000000..3f15afde --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustEndWith.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- Caller might have NRTs switched off + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string ends with the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The other string must end with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not end with . + /// Thrown when or is null. + /// Thrown when is not a valid value. + public static string MustEndWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType = StringComparison.CurrentCulture, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).EndsWith(value, comparisonType)) + { + Throw.StringDoesNotEndWith(parameter, value, comparisonType, parameterName, message); + } + return parameter; + } + + /// + /// Ensures that the string ends with the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The other string must end with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not end with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustEndWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !parameter.EndsWith(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; + } + + /// + /// Ensures that the string ends with the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The other string must end with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not end with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustEndWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !parameter.EndsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveCount.cs b/Code/Light.GuardClauses/Check.MustHaveCount.cs new file mode 100644 index 00000000..cf1755b4 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveCount.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the collection has the specified number of items, or otherwise throws an . + /// + /// The collection to be checked. + /// The number of items the collection must have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not have the specified number of items. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveCount( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + int count, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where TCollection : class, IEnumerable + { + if (parameter!.Count(parameterName, message) != count) + { + Throw.InvalidCollectionCount(parameter, count, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the collection has the specified number of items, or otherwise throws your custom exception. + /// + /// The collection to be checked. + /// The number of items the collection must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified number of items, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveCount( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + int count, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is null || parameter.Count() != count) + { + Throw.CustomException(exceptionFactory, parameter, count); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveLength.cs b/Code/Light.GuardClauses/Check.MustHaveLength.cs new file mode 100644 index 00000000..a7d8d4f4 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveLength.cs @@ -0,0 +1,148 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string has the specified length, or otherwise throws a . + /// + /// The string to be checked. + /// The asserted length of the string. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length other than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLength( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).Length != length) + { + Throw.StringLengthNotEqualTo(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string has the specified length, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The asserted length of the string. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length other than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLength( + [NotNull] [ValidatedNotNull] this string? parameter, + int length, + Func exceptionFactory + ) + { + if (parameter is null || parameter.Length != length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span has the specified length, or otherwise throws an . + /// + /// The span to be checked. + /// The length that the span must have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustHaveLength( + this Span parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + ((ReadOnlySpan) parameter).MustHaveLength(length, parameterName, message); + return parameter; + } + + /// + /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length that the span must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustHaveLength( + this Span parameter, + int length, + SpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length != length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } + + /// + /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length that the span must have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustHaveLength( + this ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Length != length) + { + Throw.InvalidSpanLength(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length that the span must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustHaveLength( + this ReadOnlySpan parameter, + int length, + ReadOnlySpanExceptionFactory exceptionFactory + ) + { + if (parameter.Length != length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs b/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs new file mode 100644 index 00000000..c190c980 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs @@ -0,0 +1,60 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string's length is within the specified range, or otherwise throws a . + /// + /// The string to be checked. + /// The range where the string's length must be in-between. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the length of is not with the specified . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLengthIn( + [NotNull] [ValidatedNotNull] this string? parameter, + Range range, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!range.IsValueWithinRange(parameter.MustNotBeNull(parameterName, message).Length)) + { + Throw.StringLengthNotInRange(parameter, range, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string's length is within the specified range, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The range where the string's length must be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or its length is not within the specified range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLengthIn( + [NotNull] [ValidatedNotNull] this string? parameter, + Range range, + Func, Exception> exceptionFactory + ) + { + if (parameter is null || !range.IsValueWithinRange(parameter.Length)) + { + Throw.CustomException(exceptionFactory, parameter, range); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveMaximumCount.cs b/Code/Light.GuardClauses/Check.MustHaveMaximumCount.cs new file mode 100644 index 00000000..1f37f9f5 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveMaximumCount.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the collection has at most the specified number of items, or otherwise throws an . + /// + /// The collection to be checked. + /// The number of items the collection should have at most. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain at most the specified number of items. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMaximumCount( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + int count, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where TCollection : class, IEnumerable + { + if (parameter.Count(parameterName, message) > count) + { + Throw.InvalidMaximumCollectionCount(parameter, count, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the collection has at most the specified number of items, or otherwise throws your custom exception. + /// + /// The collection to be checked. + /// The number of items the collection should have at most. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain at most the specified number of items, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMaximumCount( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + int count, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is null || parameter.Count() > count) + { + Throw.CustomException(exceptionFactory, parameter, count); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveMinimumCount.cs b/Code/Light.GuardClauses/Check.MustHaveMinimumCount.cs new file mode 100644 index 00000000..94b101b9 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveMinimumCount.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the collection has at least the specified number of items, or otherwise throws an . + /// + /// The collection to be checked. + /// The number of items the collection should have at least. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain at least the specified number of items. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMinimumCount( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + int count, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where TCollection : class, IEnumerable + { + if (parameter.Count(parameterName, message) < count) + { + Throw.InvalidMinimumCollectionCount(parameter, count, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the collection has at least the specified number of items, or otherwise throws your custom exception. + /// + /// The collection to be checked. + /// The number of items the collection should have at least. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain at least the specified number of items, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMinimumCount( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + int count, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is null || parameter.Count() < count) + { + Throw.CustomException(exceptionFactory, parameter, count); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveOneSchemeOf.cs b/Code/Light.GuardClauses/Check.MustHaveOneSchemeOf.cs new file mode 100644 index 00000000..61c333f2 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveOneSchemeOf.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the URI has one of the specified schemes, or otherwise throws an . + /// + /// The URI to be checked. + /// One of these strings must be equal to the scheme of the URI. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the scheme is not equal to one of the specified schemes. + /// Thrown when is relative and thus has no scheme. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; schemes:null => halt")] + public static Uri MustHaveOneSchemeOf( + [NotNull] [ValidatedNotNull] this Uri? parameter, + IEnumerable schemes, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + // ReSharper disable PossibleMultipleEnumeration + parameter.MustBeAbsoluteUri(parameterName, message); + + if (schemes is ICollection collection) + { + if (!collection.Contains(parameter.Scheme)) + { + Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); + } + + return parameter; + } + + if (!schemes.MustNotBeNull(nameof(schemes), message).Contains(parameter.Scheme)) + { + Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); + } + + return parameter; + // ReSharper restore PossibleMultipleEnumeration + } + + /// + /// Ensures that the URI has one of the specified schemes, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// One of these strings must be equal to the scheme of the URI. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the scheme is not equal to one of the specified schemes, or when is a relative URI, or when is null. + /// Thrown when is null. + /// The type of the collection containing the schemes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveOneSchemeOf( + [NotNull] [ValidatedNotNull] this Uri? parameter, + TCollection schemes, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is null || !parameter.IsAbsoluteUri) + { + Throw.CustomException(exceptionFactory, parameter, schemes); + } + + if (schemes is ICollection collection) + { + if (!collection.Contains(parameter.Scheme)) + { + Throw.CustomException(exceptionFactory, parameter, schemes); + } + + return parameter; + } + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (schemes is null || !schemes.Contains(parameter.Scheme)) + { + Throw.CustomException(exceptionFactory, parameter, schemes!); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveScheme.cs b/Code/Light.GuardClauses/Check.MustHaveScheme.cs new file mode 100644 index 00000000..1176b032 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveScheme.cs @@ -0,0 +1,84 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the has the specified scheme, or otherwise throws an . + /// + /// The URI to be checked. + /// The scheme that the URI should have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when uses a different scheme than the specified one. + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveScheme( + [NotNull] [ValidatedNotNull] this Uri? parameter, + string scheme, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (string.Equals(parameter.MustBeAbsoluteUri(parameterName, message).Scheme, scheme) == false) + { + Throw.UriMustHaveScheme(parameter, scheme, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the has the specified scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The scheme that the URI should have. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than the specified one, or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveScheme( + [NotNull] [ValidatedNotNull] this Uri? parameter, + string scheme, + Func exceptionFactory + ) + { + if (string.Equals(parameter.MustBeAbsoluteUri(exceptionFactory).Scheme, scheme) == false) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } + + /// + /// Ensures that the has the specified scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The scheme that the URI should have. + /// The delegate that creates the exception to be thrown. and are passed to this delegate. + /// Your custom exception thrown when uses a different scheme than the specified one, or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveScheme( + [NotNull] [ValidatedNotNull] this Uri? parameter, + string scheme, + Func exceptionFactory + ) + { + if (parameter is null || !parameter.IsAbsoluteUri || parameter.Scheme.Equals(scheme) == false) + { + Throw.CustomException(exceptionFactory, parameter, scheme); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveValue.cs b/Code/Light.GuardClauses/Check.MustHaveValue.cs new file mode 100644 index 00000000..4af6c874 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveValue.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified nullable has a value and returns it, or otherwise throws a . + /// + /// The nullable to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has no value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustHaveValue( + [NotNull, NoEnumeration] this T? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : struct + { + if (!parameter.HasValue) + { + Throw.NullableHasNoValue(parameterName, message); + } + + return parameter.Value; + } + + /// + /// Ensures that the specified nullable has a value and returns it, or otherwise throws your custom exception. + /// + /// The nullable to be checked. + /// The delegate that creates your custom exception. + /// Thrown when has no value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static T MustHaveValue([NotNull, NoEnumeration] this T? parameter, Func exceptionFactory) + where T : struct + { + if (!parameter.HasValue) + { + Throw.CustomException(exceptionFactory); + } + + return parameter.Value; + } +} diff --git a/Code/Light.GuardClauses/Check.MustMatch.cs b/Code/Light.GuardClauses/Check.MustMatch.cs new file mode 100644 index 00000000..fcf57288 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustMatch.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string matches the specified regular expression, or otherwise throws a . + /// + /// The string to be checked. + /// The regular expression used for pattern matching. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not match the specified regular expression. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; regex:null => halt")] + public static string MustMatch( + [NotNull] [ValidatedNotNull] this string? parameter, + Regex regex, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!regex.MustNotBeNull(nameof(regex), message).IsMatch(parameter.MustNotBeNull(parameterName, message))) + { + Throw.StringDoesNotMatch(parameter, regex, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string matches the specified regular expression, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The regular expression used for pattern matching. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not match the specified regular expression, + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string MustMatch( + [NotNull] [ValidatedNotNull] this string? parameter, + Regex regex, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || regex is null || !regex.IsMatch(parameter)) + { + Throw.CustomException(exceptionFactory, parameter, regex!); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBe.cs b/Code/Light.GuardClauses/Check.MustNotBe.cs new file mode 100644 index 00000000..ab562a4e --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBe.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that is not equal to using the default equality comparer, or otherwise throws a . + /// + /// The first value to be compared. + /// The other value to be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when and are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustNotBe( + this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (EqualityComparer.Default.Equals(parameter, other)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + return parameter; + } + + /// + /// Ensures that is not equal to using the default equality comparer, or otherwise throws your custom exception. + /// + /// The first value to be compared. + /// The other value to be compared. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when and are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustNotBe(this T parameter, T other, Func exceptionFactory) + { + if (EqualityComparer.Default.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + return parameter; + } + + /// + /// Ensures that is not equal to using the specified equality comparer, or otherwise throws a . + /// + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when and are equal. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustNotBe( + this T parameter, + T other, + IEqualityComparer equalityComparer, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + return parameter; + } + + /// + /// Ensures that is not equal to using the specified equality comparer, or otherwise throws your custom exception. + /// + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when and are equal, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustNotBe( + this T parameter, + T other, + IEqualityComparer equalityComparer, + Func, Exception> exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (equalityComparer is null || equalityComparer.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); + } + return parameter; + } + + /// + /// Ensures that the two strings are not equal using the specified , or otherwise throws a . + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustNotBe( + this string? parameter, + string? other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (string.Equals(parameter, other, comparisonType)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when is equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustNotBe( + this string? parameter, + string? other, + StringComparison comparisonType, + Func exceptionFactory + ) + { + if (string.Equals(parameter, other, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the two strings are not equal using the specified , or otherwise throws a . + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustNotBe( + this string? parameter, + string? other, + StringComparisonType comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Equals(other, comparisonType)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. + /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when is equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustNotBe( + this string? parameter, + string? other, + StringComparisonType comparisonType, + Func exceptionFactory + ) + { + if (parameter.Equals(other, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, other, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeApproximately.cs b/Code/Light.GuardClauses/Check.MustNotBeApproximately.cs new file mode 100644 index 00000000..909ffe01 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeApproximately.cs @@ -0,0 +1,297 @@ +using System; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +#if NET8_0 +using System.Numerics; +#endif + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustNotBeApproximately( + this double parameter, + double other, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) => + parameter.MustNotBeApproximately(other, 0.0001, parameterName, message); + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustNotBeApproximately( + this double parameter, + double other, + Func exceptionFactory + ) + { + if (parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is + /// less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustNotBeApproximately( + this double parameter, + double other, + double tolerance, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (parameter.IsApproximately(other, tolerance)) + { + Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// Your custom exception thrown when the absolute difference between and + /// is less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustNotBeApproximately( + this double parameter, + double other, + double tolerance, + Func exceptionFactory + ) + { + if (parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001f. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustNotBeApproximately( + this float parameter, + float other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + parameter.MustNotBeApproximately(other, 0.0001f, parameterName, message); + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustNotBeApproximately( + this float parameter, + float other, + Func exceptionFactory + ) + { + if (parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is + /// less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustNotBeApproximately( + this float parameter, + float other, + float tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.IsApproximately(other, tolerance)) + { + Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , , and + /// are passed to this delegate. + /// + /// + /// Your custom exception thrown when the absolute difference between and + /// is less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustNotBeApproximately( + this float parameter, + float other, + float tolerance, + Func exceptionFactory + ) + { + if (parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + +#if NET8_0 + /// + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws an . + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// The type that implements the interface. + /// + /// Thrown when the absolute difference between and is + /// less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustNotBeApproximately( + this T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : INumber + { + if (parameter.IsApproximately(other, tolerance)) + { + Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// The type that implements the interface. + /// + /// Your custom exception thrown when the absolute difference between and + /// is less than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T MustNotBeApproximately( + this T parameter, + T other, + T tolerance, + Func exceptionFactory + ) where T : INumber + { + if (parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } +#endif +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.MustNotBeDefault.cs b/Code/Light.GuardClauses/Check.MustNotBeDefault.cs new file mode 100644 index 00000000..e5a8fcd5 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeDefault.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified parameter is not the default value, or otherwise throws an + /// for reference types, or an for value types. + /// + /// The value to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is a reference type and null. + /// Thrown when is a value type and the default value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeDefault( + [NotNull, ValidatedNotNull] this T parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (default(T) is null) + { + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + + return parameter; + } + + if (EqualityComparer.Default.Equals(parameter, default!)) + { + Throw.ArgumentDefault(parameterName, message); + } + +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + return parameter; +#pragma warning restore CS8777 + } + + /// + /// Ensures that the specified parameter is not the default value, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is the default value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeDefault( + [NotNull, ValidatedNotNull] this T parameter, + Func exceptionFactory + ) + { + if (default(T) is null) + { + if (parameter is null) + { + Throw.CustomException(exceptionFactory); + } + + return parameter; + } + + if (EqualityComparer.Default.Equals(parameter, default!)) + { + Throw.CustomException(exceptionFactory); + } + +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + return parameter; +#pragma warning restore CS8777 + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeEmpty.cs b/Code/Light.GuardClauses/Check.MustNotBeEmpty.cs new file mode 100644 index 00000000..675e769b --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeEmpty.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified GUID is not empty, or otherwise throws an . + /// + /// The GUID to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is an empty GUID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid MustNotBeEmpty( + this Guid parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter == Guid.Empty) + { + Throw.EmptyGuid(parameterName, message); + } + return parameter; + } + + /// + /// Ensures that the specified GUID is not empty, or otherwise throws your custom exception. + /// + /// The GUID to be checked. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is an empty GUID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static Guid MustNotBeEmpty(this Guid parameter, Func exceptionFactory) + { + if (parameter == Guid.Empty) + { + Throw.CustomException(exceptionFactory); + } + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeGreaterThan.cs b/Code/Light.GuardClauses/Check.MustNotBeGreaterThan.cs new file mode 100644 index 00000000..c64ff6c7 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeGreaterThan.cs @@ -0,0 +1,46 @@ +using System; +using JetBrains.Annotations; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is not greater than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is greater than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) + Throw.MustNotBeGreaterThan(parameter, other, parameterName, message); + return parameter; + } + + /// + /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is greater than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) > 0) + Throw.CustomException(exceptionFactory, parameter!, other); + return parameter; + } +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.MustNotBeGreaterThanOrEqualTo.cs b/Code/Light.GuardClauses/Check.MustNotBeGreaterThanOrEqualTo.cs new file mode 100644 index 00000000..7ffde4d2 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeGreaterThanOrEqualTo.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is less than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be greater than . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is not less than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeGreaterThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) + { + Throw.MustNotBeGreaterThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be greater than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is not less than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeGreaterThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) >= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeIn.cs b/Code/Light.GuardClauses/Check.MustNotBeIn.cs new file mode 100644 index 00000000..8cf809fb --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeIn.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that is not within the specified range, or otherwise throws an . + /// + /// The type of the parameter to be checked. + /// The parameter to be checked. + /// The range where must not be in-between. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is within . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeIn( + [NotNull] [ValidatedNotNull] this T parameter, + Range range, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) + { + Throw.MustNotBeInRange(parameter, range, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that is not within the specified range, or otherwise throws your custom exception. + /// + /// The parameter to be checked. + /// The range where must not be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is within , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeIn( + [NotNull] [ValidatedNotNull] this T parameter, + Range range, + Func, Exception> exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || range.IsValueWithinRange(parameter)) + { + Throw.CustomException(exceptionFactory, parameter!, range); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeLessThan.cs b/Code/Light.GuardClauses/Check.MustNotBeLessThan.cs new file mode 100644 index 00000000..ccb762b8 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeLessThan.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is not less than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is less than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeLessThan( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) + { + Throw.MustNotBeLessThan(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeLessThan( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) < 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeLessThanOrEqualTo.cs b/Code/Light.GuardClauses/Check.MustNotBeLessThanOrEqualTo.cs new file mode 100644 index 00000000..14211f6a --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeLessThanOrEqualTo.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified is greater than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is less than or equal to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeLessThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) + { + Throw.MustNotBeLessThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. + /// + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than or equal to , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeLessThanOrEqualTo( + [NotNull] [ValidatedNotNull] this T parameter, + T other, + Func exceptionFactory + ) where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) <= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeNull.cs b/Code/Light.GuardClauses/Check.MustNotBeNull.cs new file mode 100644 index 00000000..76a451fa --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeNull.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified object reference is not null, or otherwise throws an . + /// + /// The object reference to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeNull( + [NotNull, ValidatedNotNull, NoEnumeration] this T? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + where T : class + { + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified object reference is not null, or otherwise throws your custom exception. + /// + /// The reference to be checked. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeNull( + [NotNull, ValidatedNotNull, NoEnumeration] this T? parameter, + Func exceptionFactory + ) + where T : class + { + if (parameter is null) + { + Throw.CustomException(exceptionFactory); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeNullOrEmpty.cs b/Code/Light.GuardClauses/Check.MustNotBeNullOrEmpty.cs new file mode 100644 index 00000000..7beedb7b --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeNullOrEmpty.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the collection is not null or empty, or otherwise throws an . + /// + /// The collection to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has no items. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotBeNullOrEmpty( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where TCollection : class, IEnumerable + { + if (parameter.Count(parameterName, message) == 0) + { + Throw.EmptyCollection(parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the collection is not null or empty, or otherwise throws your custom exception. + /// + /// The collection to be checked. + /// The delegate that creates your custom exception. + /// Thrown when has no items, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotBeNullOrEmpty( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is null || parameter.Count() == 0) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } + + /// + /// Ensures that the specified string is not null or empty, or otherwise throws an or . + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is an empty string. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotBeNullOrEmpty( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + + if (parameter.Length == 0) + { + Throw.EmptyString(parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified string is not null or empty, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is an empty string or null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static string MustNotBeNullOrEmpty( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (parameter.IsNullOrEmpty()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeNullOrWhiteSpace.cs b/Code/Light.GuardClauses/Check.MustNotBeNullOrWhiteSpace.cs new file mode 100644 index 00000000..de13b509 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeNullOrWhiteSpace.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws an , an , or a . + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains only white space. + /// Thrown when is an empty string. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotBeNullOrWhiteSpace( + [NotNull] [ValidatedNotNull] this string? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + parameter.MustNotBeNullOrEmpty(parameterName, message); + + foreach (var character in parameter) + { + if (!character.IsWhiteSpace()) + { + return parameter; + } + } + + Throw.WhiteSpaceString(parameter, parameterName, message); + return null; + } + + /// + /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null, empty, or contains only white space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory: null => halt")] + public static string MustNotBeNullOrWhiteSpace( + [NotNull] [ValidatedNotNull] this string? parameter, + Func exceptionFactory + ) + { + if (parameter.IsNullOrWhiteSpace()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeNullReference.cs b/Code/Light.GuardClauses/Check.MustNotBeNullReference.cs new file mode 100644 index 00000000..b6b5ee51 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeNullReference.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the specified parameter is not null when is a reference type, or otherwise + /// throws an . PLEASE NOTICE: you should only use this assertion in generic contexts, + /// use by default. + /// + /// The value to be checked for null. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is a reference type and is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeNullReference( + [NotNull, ValidatedNotNull, NoEnumeration] + this T parameter, + [CallerArgumentExpression("parameter")] + string? parameterName = null, + string? message = null + ) + { + if (default(T) != null) + { + // If we end up here, parameter cannot be null +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + return parameter; +#pragma warning restore CS8777 + } + + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the specified parameter is not null when is a reference type, or otherwise + /// throws your custom exception. PLEASE NOTICE: you should only use this assertion in generic contexts, + /// use by default. + /// + /// The value to be checked for null. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is a reference type and is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeNullReference( + [NotNull, ValidatedNotNull, NoEnumeration] + this T parameter, + Func exceptionFactory + ) + { + if (default(T) != null) + { + // If we end up here, parameter cannot be null +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + return parameter; +#pragma warning restore CS8777 + } + + if (parameter is null) + { + Throw.CustomException(exceptionFactory); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeOneOf.cs b/Code/Light.GuardClauses/Check.MustNotBeOneOf.cs new file mode 100644 index 00000000..4ad82f82 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeOneOf.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the value is not one of the specified items, or otherwise throws a . + /// + /// The value to be checked. + /// The items that must not contain the value. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is equal to one of the specified . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("items:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests + public static TItem MustNotBeOneOf( + this TItem parameter, + [NotNull] [ValidatedNotNull] IEnumerable items, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + // ReSharper disable PossibleMultipleEnumeration + if (parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) + { + Throw.ValueIsOneOf(parameter, items, parameterName, message); + } + + return parameter; + // ReSharper restore PossibleMultipleEnumeration + } + + /// + /// Ensures that the value is not one of the specified items, or otherwise throws your custom exception. + /// + /// The value to be checked. + /// The items that must not contain the value. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is equal to one of the specified , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("items:null => halt")] + public static TItem MustNotBeOneOf( + this TItem parameter, + [NotNull] [ValidatedNotNull] TCollection items, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (items is null || parameter.IsOneOf(items)) + { + Throw.CustomException(exceptionFactory, parameter, items!); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeSameAs.cs b/Code/Light.GuardClauses/Check.MustNotBeSameAs.cs new file mode 100644 index 00000000..59e9ef4e --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeSameAs.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that and do not point to the same object instance, or otherwise + /// throws a . + /// + /// The first reference to be checked. + /// The second reference to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when both and point to the same object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? MustNotBeSameAs( + [NoEnumeration] this T? parameter, + [NoEnumeration] T? other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : class + { + if (ReferenceEquals(parameter, other)) + { + Throw.SameObjectReference(parameter, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that and do not point to the same object instance, or otherwise + /// throws your custom exception. + /// + /// The first reference to be checked. + /// The second reference to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Thrown when both and point to the same object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, T? other, Func exceptionFactory) where T : class + { + if (ReferenceEquals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotBeSubstringOf.cs b/Code/Light.GuardClauses/Check.MustNotBeSubstringOf.cs new file mode 100644 index 00000000..98cc45ca --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotBeSubstringOf.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . + /// + /// The string to be checked. + /// The other string that must not contain . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) + { + Throw.Substring(parameter, value, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string that must not contain . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || value.Contains(parameter)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + + return parameter; + } + + /// + /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . + /// + /// The string to be checked. + /// The other string that must not contain . + /// One of the enumeration values that specifies the rules for the search. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + value.MustNotBeNull(nameof(value), message); + parameter.MustNotBeNull(parameterName, message); + if (value.IndexOf(parameter, comparisonType) != -1) + { + Throw.Substring(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string that must not contain . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || value.IndexOf(parameter, comparisonType) != -1) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotContain.cs b/Code/Light.GuardClauses/Check.MustNotContain.cs new file mode 100644 index 00000000..ddadc325 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotContain.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the collection does not contain the specified item, or otherwise throws an . + /// + /// The collection to be checked. + /// The item that must not be part of the collection. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotContain( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + TItem item, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where TCollection : class, IEnumerable + { + if (parameter is ICollection collection) + { + if (collection.Contains(item)) + { + Throw.ExistingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + if (parameter.MustNotBeNull(parameterName, message).Contains(item)) + { + Throw.ExistingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the collection does not contain the specified item, or otherwise throws your custom exception. + /// + /// The collection to be checked. + /// The item that must not be part of the collection. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when contains . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotContain( + [NotNull] [ValidatedNotNull] this TCollection? parameter, + TItem item, + Func exceptionFactory + ) where TCollection : class, IEnumerable + { + if (parameter is ICollection collection) + { + if (collection.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + + if (parameter is null || parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + + /// + /// Ensures that the string does not contain the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The string that must not be part of . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) + { + Throw.StringContains(parameter, value, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The string that must not be part of . + /// The delegate that creates your custom exception (optional). and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || parameter.Contains(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + + return parameter; + } + + /// + /// Ensures that the string does not contain the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The string that must not be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).IndexOf( + value.MustNotBeNull(nameof(value), message), + comparisonType + ) >= + 0) + { + Throw.StringContains(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The string that must not be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception (optional). , , and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotContain( + [NotNull] [ValidatedNotNull] this string? parameter, + string value, + StringComparison comparisonType, + Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || parameter.IndexOf(value, comparisonType) >= 0) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotEndWith.cs b/Code/Light.GuardClauses/Check.MustNotEndWith.cs new file mode 100644 index 00000000..380dbbaa --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotEndWith.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- Caller might have NRTs turned off + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string does not end with the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The other string must not end with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when ends with . + /// Thrown when or is null. + /// Thrown when is not a valid value. + public static string MustNotEndWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType = StringComparison.CurrentCulture, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).EndsWith(value, comparisonType)) + { + Throw.StringEndsWith(parameter, value, comparisonType, parameterName, message); + } + return parameter; + } + + /// + /// Ensures that the string does not end with the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string must not end with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when ends with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotEndWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.EndsWith(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; + } + + /// + /// Ensures that the string does not end with the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string must not end with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when ends with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotEndWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.EndsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustNotStartWith.cs b/Code/Light.GuardClauses/Check.MustNotStartWith.cs new file mode 100644 index 00000000..05570686 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustNotStartWith.cs @@ -0,0 +1,187 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- Caller might have NRTs turned off + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string does not start with the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The other string that must not start with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when starts with . + /// Thrown when or is null. + public static string MustNotStartWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType = StringComparison.CurrentCulture, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) + { + if (parameter.MustNotBeNull(parameterName, message).StartsWith(value, comparisonType)) + { + Throw.StringStartsWith(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the string does not start with the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string that must not start with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotStartWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.StartsWith(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; + } + + /// + /// Ensures that the string does not start with the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string that must not start with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotStartWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.StartsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; + } + + /// + /// Ensures that the span does not start with the specified value, or otherwise throws a . + /// + /// The span to be checked. + /// The other span that must not start with. + /// One of the enumeration values that specifies the rules for the search. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when starts with . + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustNotStartWith( + this ReadOnlySpan parameter, + ReadOnlySpan value, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.StartsWith(value, comparisonType)) + { + Throw.StringStartsWith(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the span does not start with the specified value, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The other span that must not start with. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustNotStartWith( + this ReadOnlySpan parameter, + ReadOnlySpan value, + ReadOnlySpansExceptionFactory exceptionFactory + ) + where T : IEquatable + { + if (parameter.StartsWith(value)) + { + Throw.CustomSpanException(exceptionFactory, parameter, value); + } + + return parameter; + } + + /// + /// Ensures that the span does not start with the specified value, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The other span that must not start with. + /// One of the enumeration values that specifies the rules for the search. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + /// + /// Thrown when is not a valid value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustNotStartWith( + this ReadOnlySpan parameter, + ReadOnlySpan value, + StringComparison comparisonType, + ReadOnlySpansExceptionFactory exceptionFactory + ) + { + if (parameter.StartsWith(value, comparisonType)) + { + Throw.CustomSpanException(exceptionFactory, parameter, value, comparisonType); + } + + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.MustStartWith.cs b/Code/Light.GuardClauses/Check.MustStartWith.cs new file mode 100644 index 00000000..166c903d --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustStartWith.cs @@ -0,0 +1,98 @@ +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable RedundantNullableFlowAttribute -- Caller might have NRTs turned off + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the string starts with the specified value, or otherwise throws a . + /// + /// The string to be checked. + /// The other string must start with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not start with . + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustStartWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType = StringComparison.CurrentCulture, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (!parameter.MustNotBeNull(parameterName, message).StartsWith(value, comparisonType)) + { + Throw.StringDoesNotStartWith(parameter, value, comparisonType, parameterName, message); + } + return parameter; + } + + /// + /// Ensures that the string starts with the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string must start with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustStartWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !parameter.StartsWith(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; + } + + /// + /// Ensures that the string starts with the specified value, or otherwise throws your custom exception. + /// + /// The string to be checked. + /// The other string must start with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustStartWith( + [NotNull, ValidatedNotNull] this string? parameter, + [NotNull, ValidatedNotNull] string value, + StringComparison comparisonType, + [NotNull, ValidatedNotNull] Func exceptionFactory + ) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || !parameter.StartsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; + } +} diff --git a/Code/Light.GuardClauses/Check.StringAssertions.cs b/Code/Light.GuardClauses/Check.StringAssertions.cs deleted file mode 100644 index e8fff609..00000000 --- a/Code/Light.GuardClauses/Check.StringAssertions.cs +++ /dev/null @@ -1,1586 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; -using JetBrains.Annotations; -using Light.GuardClauses.Exceptions; -using Light.GuardClauses.FrameworkExtensions; -using System.Runtime.CompilerServices; -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -namespace Light.GuardClauses; - -public static partial class Check -{ - /// - /// Checks if the specified string is null or empty. - /// - /// The string to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] - public static bool IsNullOrEmpty([NotNullWhen(false)] this string? @string) => string.IsNullOrEmpty(@string); - - /// - /// Ensures that the specified string is not null or empty, or otherwise throws an or . - /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is an empty string. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotBeNullOrEmpty([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter is null) - Throw.ArgumentNull(parameterName, message); - if (parameter.Length == 0) - Throw.EmptyString(parameterName, message); - - return parameter; - } - - /// - /// Ensures that the specified string is not null or empty, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is an empty string or null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static string MustNotBeNullOrEmpty([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (parameter.IsNullOrEmpty()) - Throw.CustomException(exceptionFactory, parameter); - - return parameter; - } - - /// - /// Checks if the specified string is null, empty, or contains only white space. - /// - /// The string to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] - public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? @string) => string.IsNullOrWhiteSpace(@string); - - /// - /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws an , an , or a . - /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains only white space. - /// Thrown when is an empty string. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotBeNullOrWhiteSpace([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - parameter.MustNotBeNullOrEmpty(parameterName, message); - - foreach (var character in parameter) - { - if (!character.IsWhiteSpace()) - return parameter; - } - - Throw.WhiteSpaceString(parameter, parameterName, message); - return null; - } - - /// - /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null, empty, or contains only white space. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory: null => halt")] - public static string MustNotBeNullOrWhiteSpace([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (parameter.IsNullOrWhiteSpace()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Checks if the specified character is a white space character. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsWhiteSpace(this char character) => char.IsWhiteSpace(character); - - /// - /// Checks if the specified character is a letter. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLetter(this char character) => char.IsLetter(character); - - /// - /// Checks if the specified character is a letter or digit. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLetterOrDigit(this char character) => char.IsLetterOrDigit(character); - - /// - /// Checks if the specified character is a digit. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDigit(this char character) => char.IsDigit(character); - - /// - /// Ensures that the two strings are equal using the specified , or otherwise throws a . - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustBe(this string? parameter, string? other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!string.Equals(parameter, other, comparisonType)) - Throw.ValuesNotEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the two strings are equal using the specified , or otherwise throws your custom exception. - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is not equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustBe(this string? parameter, string? other, StringComparison comparisonType, Func exceptionFactory) - { - if (!string.Equals(parameter, other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other, comparisonType); - return parameter; - } - - /// - /// Ensures that the two strings are equal using the specified , or otherwise throws a . - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustBe(this string? parameter, string? other, StringComparisonType comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.Equals(other, comparisonType)) - Throw.ValuesNotEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the two strings are equal using the specified , or otherwise throws your custom exception. - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is not equal to . - /// Thrown when is not a valid value from the enum. - public static string? MustBe(this string? parameter, string? other, StringComparisonType comparisonType, Func exceptionFactory) - { - if (!parameter.Equals(other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other, comparisonType); - return parameter; - } - - /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws a . - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (string.Equals(parameter, other, comparisonType)) - Throw.ValuesEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// Your custom exception thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparison comparisonType, Func exceptionFactory) - { - if (string.Equals(parameter, other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other); - return parameter; - } - - /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws a . - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparisonType comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Equals(other, comparisonType)) - Throw.ValuesEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// Your custom exception thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparisonType comparisonType, Func exceptionFactory) - { - if (parameter.Equals(other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other, comparisonType); - return parameter; - } - - /// - /// Ensures that the string matches the specified regular expression, or otherwise throws a . - /// - /// The string to be checked. - /// The regular expression used for pattern matching. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not match the specified regular expression. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; regex:null => halt")] - public static string MustMatch([NotNull, ValidatedNotNull] this string? parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!regex.MustNotBeNull(nameof(regex), message).IsMatch(parameter.MustNotBeNull(parameterName, message))) - Throw.StringDoesNotMatch(parameter, regex, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string matches the specified regular expression, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The regular expression used for pattern matching. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not match the specified regular expression, - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string MustMatch([NotNull, ValidatedNotNull] this string? parameter, Regex regex, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || regex is null || !regex.IsMatch(parameter)) - Throw.CustomException(exceptionFactory, parameter, regex!); - return parameter; - } - - /// - /// Checks if the specified strings are equal, using the given comparison rules. - /// - /// The first string to compare. - /// The second string to compare. - /// One of the enumeration values that specifies the rules for the comparison. - /// True if the two strings are considered equal, else false. - /// Thrown when is no valid enum value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Equals(this string? @string, string? value, StringComparisonType comparisonType) - { - if ((int) comparisonType < 6) - return string.Equals(@string, value, (StringComparison) comparisonType); - if (comparisonType == StringComparisonType.OrdinalIgnoreWhiteSpace) - return @string.EqualsOrdinalIgnoreWhiteSpace(value); - if (comparisonType == StringComparisonType.OrdinalIgnoreCaseIgnoreWhiteSpace) - return @string.EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(value); - - Throw.EnumValueNotDefined(comparisonType, nameof(comparisonType)); - return false; - } - - /// - /// Ensures that the string contains the specified substring, or otherwise throws a . - /// - /// The string to be checked. - /// The substring that must be part of . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([NotNull, ValidatedNotNull] this string? parameter, string? value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) - { - Throw.StringDoesNotContain(parameter, value, parameterName, message); - } - return parameter; - } - - /// - /// Ensures that the string contains the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The substring that must be part of . - /// The delegate that creates you custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([NotNull, ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !parameter.Contains(value)) - { - Throw.CustomException(exceptionFactory, parameter, value!); - } - return parameter; - } - - /// - /// Ensures that the string contains the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The substring that must be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).IndexOf(value.MustNotBeNull(nameof(value), message), comparisonType) < 0) - { - Throw.StringDoesNotContain(parameter, value, comparisonType, parameterName, message); - } - return parameter; - } - - /// - /// Ensures that the string contains the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The substring that must be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates you custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null. - /// - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || parameter.IndexOf(value, comparisonType) < 0) - { - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - } - return parameter; - } - - /// - /// Ensures that the string does not contain the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The string that must not be part of . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([NotNull, ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) - { - Throw.StringContains(parameter, value, parameterName, message); - } - return parameter; - } - - /// - /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The string that must not be part of . - /// The delegate that creates your custom exception (optional). and are passed to this delegate. - /// - /// Your custom exception thrown when contains , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([NotNull, ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || parameter.Contains(value)) - Throw.CustomException(exceptionFactory, parameter, value!); - return parameter; - } - - /// - /// Ensures that the string does not contain the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The string that must not be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).IndexOf(value.MustNotBeNull(nameof(value), message), comparisonType) >= 0) - Throw.StringContains(parameter, value, comparisonType, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The string that must not be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception (optional). , , and are passed to this delegate. - /// - /// Your custom exception thrown when contains , - /// or when is null, - /// or when is null. - /// - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || parameter.IndexOf(value, comparisonType) >= 0) - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - return parameter; - } - - /// - /// Checks if the string contains the specified value using the given comparison type. - /// - /// The string to be checked. - /// The other string. - /// One of the enumeration values that specifies the rules for the search. - /// True if contains , else false. - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("string:null => halt; value:null => halt")] - // ReSharper disable once RedundantNullableFlowAttribute - public static bool Contains([NotNull, ValidatedNotNull] this string @string, string value, StringComparison comparisonType) => - @string.MustNotBeNull(nameof(@string)).IndexOf(value.MustNotBeNull(nameof(value)), comparisonType) >= 0; - - /// - /// Checks if the string is a substring of the other string. - /// - /// The string to be checked. - /// The other string. - /// True if is a substring of , else false. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("value:null => halt; other:null => halt")] - // ReSharper disable RedundantNullableFlowAttribute - public static bool IsSubstringOf([NotNull, ValidatedNotNull] this string value, [NotNull, ValidatedNotNull] string other) => - other.MustNotBeNull(nameof(other)).Contains(value); - // ReSharper restore RedundantNullableFlowAttribute - - /// - /// Checks if the string is a substring of the other string. - /// - /// The string to be checked. - /// The other string. - /// One of the enumeration values that specifies the rules for the search. - /// True if is a substring of , else false. - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("value:null => halt; other:null => halt")] - // ReSharper disable RedundantNullableFlowAttribute - public static bool IsSubstringOf([NotNull, ValidatedNotNull] this string value, [NotNull, ValidatedNotNull] string other, StringComparison comparisonType) => - other.MustNotBeNull(nameof(other)).IndexOf(value, comparisonType) != -1; - // ReSharper disable RedundantNullableFlowAttribute - - /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws a . - /// - /// The string to be checked. - /// The other string that must contain . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) - Throw.NotSubstring(parameter, value, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string that must contain . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !value.Contains(parameter)) - Throw.CustomException(exceptionFactory, parameter, value!); - return parameter; - } - - /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws a . - /// - /// The string to be checked. - /// The other string that must contain . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (value.MustNotBeNull(nameof(value), message).IndexOf(parameter.MustNotBeNull(parameterName, message), comparisonType) == -1) - Throw.NotSubstring(parameter, value, comparisonType, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string that must contain . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null. - /// - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || value.IndexOf(parameter, comparisonType) == -1) - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - return parameter; - } - - /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . - /// - /// The string to be checked. - /// The other string that must not contain . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) - Throw.Substring(parameter, value, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string that must not contain . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when contains , - /// or when is null, - /// or when is null. - /// - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || value.Contains(parameter)) - Throw.CustomException(exceptionFactory, parameter, value!); - return parameter; - } - - /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . - /// - /// The string to be checked. - /// The other string that must not contain . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (value.MustNotBeNull(nameof(value), message).IndexOf(parameter.MustNotBeNull(parameterName, message), comparisonType) != -1) - Throw.Substring(parameter, value, comparisonType, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string that must not contain . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when contains , - /// or when is null, - /// or when is null. - /// - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([NotNull, ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || value.IndexOf(parameter, comparisonType) != -1) - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - return parameter; - } - - /// - /// Ensures that the string starts with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must start with. - /// One of the enumeration values that specifies the rules for the search (optional). The default value is . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not start with . - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustStartWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType = StringComparison.CurrentCulture, - [CallerArgumentExpression("parameter")] string? parameterName = null, - string? message = null - ) - { - if (!parameter.MustNotBeNull(parameterName, message).StartsWith(value, comparisonType)) - { - Throw.StringDoesNotStartWith(parameter, value, comparisonType, parameterName, message); - } - return parameter; - } - - /// - /// Ensures that the string starts with the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string must start with. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not start with , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustStartWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || !parameter.StartsWith(value)) - { - Throw.CustomException(exceptionFactory, parameter, value!); - } - return parameter; - } - - /// - /// Ensures that the string starts with the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string must start with. - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not start with , - /// or when is null, - /// or when is null. - /// - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustStartWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || !parameter.StartsWith(value, comparisonType)) - { - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - } - return parameter; - } - - /// - /// Ensures that the string does not start with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string that must not start with. - /// One of the enumeration values that specifies the rules for the search (optional). The default value is . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when starts with . - /// Thrown when or is null. - public static string MustNotStartWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType = StringComparison.CurrentCulture, - [CallerArgumentExpression("parameter")] - string? parameterName = null, - string? message = null - ) - { - if (parameter.MustNotBeNull(parameterName, message).StartsWith(value, comparisonType)) - { - Throw.StringStartsWith(parameter, value, comparisonType, parameterName, message); - } - - return parameter; - } - - /// - /// Ensures that the string does not start with the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string that must not start with. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not start with , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustNotStartWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || parameter.StartsWith(value)) - { - Throw.CustomException(exceptionFactory, parameter, value!); - } - return parameter; - } - - /// - /// Ensures that the string does not start with the specified value, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The other string that must not start with. - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not start with , - /// or when is null, - /// or when is null. - /// - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustNotStartWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || parameter.StartsWith(value, comparisonType)) - { - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - } - return parameter; - } - - /// - /// Ensures that the string ends with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must end with. - /// One of the enumeration values that specifies the rules for the search (optional). The default value is . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not end with . - /// Thrown when or is null. - /// Thrown when is not a valid value. - public static string MustEndWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType = StringComparison.CurrentCulture, - [CallerArgumentExpression("parameter")] string? parameterName = null, - string? message = null - ) - { - if (!parameter.MustNotBeNull(parameterName, message).EndsWith(value, comparisonType)) - { - Throw.StringDoesNotEndWith(parameter, value, comparisonType, parameterName, message); - } - return parameter; - } - - /// - /// Ensures that the string ends with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must end with. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not end with , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustEndWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || !parameter.EndsWith(value)) - { - Throw.CustomException(exceptionFactory, parameter, value!); - } - return parameter; - } - - /// - /// Ensures that the string ends with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must end with. - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not end with , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustEndWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || !parameter.EndsWith(value, comparisonType)) - { - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - } - return parameter; - } - - /// - /// Ensures that the string does not end with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must not end with. - /// One of the enumeration values that specifies the rules for the search (optional). The default value is . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when ends with . - /// Thrown when or is null. - /// Thrown when is not a valid value. - public static string MustNotEndWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType = StringComparison.CurrentCulture, - [CallerArgumentExpression("parameter")] string? parameterName = null, - string? message = null - ) - { - if (parameter.MustNotBeNull(parameterName, message).EndsWith(value, comparisonType)) - { - Throw.StringEndsWith(parameter, value, comparisonType, parameterName, message); - } - return parameter; - } - - /// - /// Ensures that the string does not end with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must not end with. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when ends with , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustNotEndWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || parameter.EndsWith(value)) - { - Throw.CustomException(exceptionFactory, parameter, value!); - } - return parameter; - } - - /// - /// Ensures that the string does not end with the specified value, or otherwise throws a . - /// - /// The string to be checked. - /// The other string must not end with. - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when ends with , - /// or when is null, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] - public static string MustNotEndWith( - [NotNull, ValidatedNotNull] this string? parameter, - [NotNull, ValidatedNotNull] string value, - StringComparison comparisonType, - [NotNull, ValidatedNotNull] Func exceptionFactory - ) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off - if (parameter is null || value is null || parameter.EndsWith(value, comparisonType)) - { - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - } - return parameter; - } - - /// - /// Checks if the specified string is an email address using the default email regular expression - /// defined in . - /// - /// The string to be checked if it is an email address. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("emailAddress:null => false")] - public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress) => - emailAddress != null && RegularExpressions.EmailRegex.IsMatch(emailAddress); - - /// - /// Checks if the specified string is an email address using the provided regular expression for validation. - /// - /// The string to be checked. - /// The regular expression that determines whether the input string is an email address. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("emailAddress:null => false; emailAddressPattern:null => halt")] - public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress, Regex emailAddressPattern) => - emailAddress != null && emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress); - - /// - /// Ensures that the string is a valid email address using the default email regular expression - /// defined in , or otherwise throws an . - /// - /// The email address that will be validated. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is no valid email address. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeEmailAddress([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress()) - Throw.InvalidEmailAddress(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is a valid email address using the default email regular expression - /// defined in , or otherwise throws your custom exception. - /// - /// The email address that will be validated. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or no valid email address. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeEmailAddress([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (!parameter.IsEmailAddress()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the string is a valid email address using the provided regular expression, - /// or otherwise throws an . - /// - /// The email address that will be validated. - /// The regular expression that determines if the input string is a valid email. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is no valid email address. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] - public static string MustBeEmailAddress([NotNull, ValidatedNotNull] this string? parameter, Regex emailAddressPattern, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress(emailAddressPattern)) - Throw.InvalidEmailAddress(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is a valid email address using the provided regular expression, - /// or otherwise throws your custom exception. - /// - /// The email address that will be validated. - /// The regular expression that determines if the input string is a valid email. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or no valid email address. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] - public static string MustBeEmailAddress([NotNull, ValidatedNotNull] this string? parameter, Regex emailAddressPattern, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (emailAddressPattern is null || !parameter.IsEmailAddress(emailAddressPattern)) - Throw.CustomException(exceptionFactory, parameter, emailAddressPattern!); - return parameter; - } - - /// - /// Ensures that the string is shorter than the specified length, or otherwise throws a . - /// - /// The string to be checked. - /// The length that the string must be shorter than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length greater than or equal to . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThan([NotNull, ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Length >= length) - Throw.StringNotShorterThan(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is shorter than the specified length, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The length that the string must be shorter than. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length greater than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThan([NotNull, ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) - { - if (parameter is null || parameter.Length >= length) - Throw.CustomException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws a . - /// - /// The string to be checked. - /// The length that the string must be shorter than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length greater than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThanOrEqualTo([NotNull, ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Length > length) - Throw.StringNotShorterThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The length that the string must be shorter than or equal to. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length greater than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThanOrEqualTo([NotNull, ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) - { - if (parameter is null || parameter.Length > length) - Throw.CustomException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the string has the specified length, or otherwise throws a . - /// - /// The string to be checked. - /// The asserted length of the string. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length other than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLength([NotNull, ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Length != length) - Throw.StringLengthNotEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string has the specified length, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The asserted length of the string. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length other than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLength([NotNull, ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) - { - if (parameter is null || parameter.Length != length) - Throw.CustomException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the string is longer than the specified length, or otherwise throws a . - /// - /// The string to be checked. - /// The length that the string must be longer than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length shorter than or equal to . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThan([NotNull, ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Length <= length) - Throw.StringNotLongerThan(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is longer than the specified length, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The length that the string must be longer than. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length shorter than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThan([NotNull, ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) - { - if (parameter is null || parameter.Length <= length) - Throw.CustomException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the string is longer than or equal to the specified length, or otherwise throws a . - /// - /// The string to be checked. - /// The length that the string must be longer than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length shorter than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThanOrEqualTo([NotNull, ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Length < length) - Throw.StringNotLongerThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is longer than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The length that the string must be longer than or equal to. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length shorter than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThanOrEqualTo([NotNull, ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) - { - if (parameter is null || parameter.Length < length) - Throw.CustomException(exceptionFactory, parameter, length); - return parameter; - } - - /// - /// Ensures that the string's length is within the specified range, or otherwise throws a . - /// - /// The string to be checked. - /// The range where the string's length must be in-between. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the length of is not with the specified . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLengthIn([NotNull, ValidatedNotNull] this string? parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!range.IsValueWithinRange(parameter.MustNotBeNull(parameterName, message).Length)) - Throw.StringLengthNotInRange(parameter, range, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string's length is within the specified range, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The range where the string's length must be in-between. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or its length is not within the specified range. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLengthIn([NotNull, ValidatedNotNull] this string? parameter, Range range, Func, Exception> exceptionFactory) - { - if (parameter is null || !range.IsValueWithinRange(parameter.Length)) - Throw.CustomException(exceptionFactory, parameter, range); - return parameter; - } - - /// - /// Checks if the string is either "\n" or "\r\n". This is done independently of the current value of . - /// - /// The string to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> false, parameter:canbenull; => true, parameter:notnull")] - public static bool IsNewLine([NotNullWhen(true)] this string? parameter) => parameter == "\n" || parameter == "\r\n"; - - /// - /// Ensures that the string is either "\n" or "\r\n", or otherwise throws a . This is done independently of the current value of . - /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not equal to "\n" or "\r\n". - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeNewLine([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsNewLine()) - Throw.NotNewLine(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is either "\n" or "\r\n", or otherwise throws your custom exception. This is done independently of the current value of . - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is not equal to "\n" or "\r\n". - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeNewLine([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (!parameter.IsNewLine()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Checks if the specified string is trimmed, i.e. it does not start or end with - /// white space characters. Inputting an empty string will return true. When null is passed, - /// you can control the return value with which will - /// return true by default. - /// - /// The string to be checked. - /// - /// The value indicating whether true or false should be returned from this method when the - /// is null. The default value is true. - /// - /// - /// True if the is trimmed, else false. An empty string will result in true. - /// You can control the return value with when the - /// is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmed(this string? parameter, bool regardNullAsTrimmed = true) => - parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmed(); - - /// - /// Checks if the specified character span is trimmed, i.e. it does not start or end with - /// white space characters. Inputting an empty span will return true. - /// - /// The character span to be checked. - /// True if the is trimmed, else false. An empty span will result in true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmed(this ReadOnlySpan parameter) => - parameter.Length == 0 || - !parameter[0].IsWhiteSpace() && - !parameter[parameter.Length - 1].IsWhiteSpace(); - - - /// - /// Ensures that the string is not null and trimmed, or otherwise throws a . - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// - /// Thrown when is not trimmed, i.e. they start or end with white space characters. - /// Empty strings are regarded as trimmed. - /// - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmed([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsTrimmed()) - Throw.NotTrimmed(parameter, parameterName, message); - return parameter; - - } - - /// - /// Ensures that the string is not null and trimmed, or otherwise throws your custom exception. - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or not trimmed. Empty strings are regarded as trimmed. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmed([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (parameter is null || !parameter.AsSpan().IsTrimmed()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Checks if the specified string is trimmed at the start, i.e. it does not start with - /// white space characters. Inputting an empty string will return true. - /// - /// The string to be checked. - /// - /// The value indicating whether true or false should be returned from this method when the - /// is null. The default value is true. - /// - /// - /// True if the is trimmed at the start, else false. - /// An empty string will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtStart(this string? parameter, bool regardNullAsTrimmed = true) => - parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtStart(); - - /// - /// Checks if the specified character span is trimmed at the start, i.e. it does not start with - /// white space characters. Inputting an empty span will return true. - /// - /// The character span to be checked. - /// - /// True if the is trimmed at the start, else false. - /// An empty span will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtStart(this ReadOnlySpan parameter) => - parameter.Length == 0 || !parameter[0].IsWhiteSpace(); - - /// - /// Ensures that the string is not null and trimmed at the start, or otherwise throws a . - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// - /// Thrown when is not trimmed at the start, i.e. they start with white space characters. - /// Empty strings are regarded as trimmed. - /// - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtStart([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtStart()) - Throw.NotTrimmedAtStart(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is not null and trimmed at the start, or otherwise throws your custom exception. - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or not trimmed at the start. Empty strings are regarded as trimmed. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtStart([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (parameter is null || !parameter.AsSpan().IsTrimmedAtStart()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Checks if the specified string is trimmed at the end, i.e. it does not end with - /// white space characters. Inputting an empty string will return true. - /// - /// The string to be checked. - /// - /// The value indicating whether true or false should be returned from this method when the - /// is null. The default value is true. - /// - /// - /// True if the is trimmed at the start, else false. - /// An empty string will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtEnd(this string? parameter, bool regardNullAsTrimmed = true) => - parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtEnd(); - - /// - /// Checks if the specified character span is trimmed at the end, i.e. it does not end with - /// white space characters. Inputting an empty span will return true. - /// - /// The character span to be checked. - /// - /// True if the is trimmed at the end, else false. - /// An empty span will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtEnd(this ReadOnlySpan parameter) => - parameter.Length == 0 || !parameter[parameter.Length - 1].IsWhiteSpace(); - - /// - /// Ensures that the string is not null and trimmed at the end, or otherwise throws a . - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// - /// Thrown when is not trimmed at the end, i.e. they end with white space characters. - /// Empty strings are regarded as trimmed. - /// - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtEnd([NotNull, ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtEnd()) - Throw.NotTrimmedAtEnd(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the string is not null and trimmed at the end, or otherwise throws your custom exception. - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or not trimmed at the end. Empty strings are regarded as trimmed. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtEnd([NotNull, ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (parameter is null || !parameter.AsSpan().IsTrimmedAtEnd()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.TypeAssertions.cs b/Code/Light.GuardClauses/Check.TypeAssertions.cs deleted file mode 100644 index 3fd8a0dc..00000000 --- a/Code/Light.GuardClauses/Check.TypeAssertions.cs +++ /dev/null @@ -1,332 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; -#if NET8_0 -using System.Diagnostics.CodeAnalysis; -#endif -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -namespace Light.GuardClauses; - -public static partial class Check -{ - /// - /// Checks if the two specified types are equivalent. This is true when both types are equal or - /// when one type is a constructed generic type and the other type is the corresponding generic type definition. - /// - /// The first type to be checked. - /// The other type to be checked. - /// - /// True if both types are null, or if both are equal, or if one type - /// is a constructed generic type and the other one is the corresponding generic type definition, else false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsEquivalentTypeTo(this Type? type, Type? other) => - ReferenceEquals(type, other) || - !(type is null) && - !(other is null) && - (type == other || - type.IsConstructedGenericType != other.IsConstructedGenericType && - CheckTypeEquivalency(type, other)); - - private static bool CheckTypeEquivalency(Type type, Type other) - { - if (type.IsConstructedGenericType) - return type.GetGenericTypeDefinition() == other; - return other.GetGenericTypeDefinition() == type; - } - - /// - /// Checks if the type implements the specified interface type. Internally, this method uses - /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The interface type that should implement. - /// Thrown when or is null. - [ContractAnnotation("type:null => halt; interfaceType:null => halt")] - public static bool Implements( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type interfaceType - // ReSharper restore RedundantNullableFlowAttribute - ) - { - type.MustNotBeNull(); - interfaceType.MustNotBeNull(); - - var implementedInterfaces = type.GetInterfaces(); - for (var i = 0; i < implementedInterfaces.Length; ++i) - { - if (interfaceType.IsEquivalentTypeTo(implementedInterfaces[i])) - return true; - } - - return false; - } - - /// - /// Checks if the type implements the specified interface type. This overload uses the specified - /// to compare the interface types. - /// - /// The type to be checked. - /// The interface type that should implement. - /// The equality comparer used to compare the interface types. - /// Thrown when , or , or is null. - [ContractAnnotation("type:null => halt; interfaceType:null => halt; typeComparer:null => halt")] - public static bool Implements( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type interfaceType, - [NotNull, ValidatedNotNull] IEqualityComparer typeComparer - // ReSharper restore RedundantNullableFlowAttribute - ) - { - type.MustNotBeNull(); - interfaceType.MustNotBeNull(); - typeComparer.MustNotBeNull(); - - var implementedInterfaces = type.GetInterfaces(); - for (var i = 0; i < implementedInterfaces.Length; ++i) - { - if (typeComparer.Equals(implementedInterfaces[i], interfaceType)) - return true; - } - - return false; - } - - /// - /// Checks if the given is equal to the specified or if it implements it. Internally, this - /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The type that is equivalent to or the interface type that implements. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrImplements( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type otherType) => - // ReSharper restore RedundantNullableFlowAttribute - type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType); - - /// - /// Checks if the given is equal to the specified or if it implements it. This overload uses the specified - /// to compare the types. - /// - /// , - /// The type to be checked. - /// The type that is equivalent to or the interface type that implements. - /// The equality comparer used to compare the interface types. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrImplements( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type otherType, - [NotNull, ValidatedNotNull] IEqualityComparer typeComparer) => - // ReSharper restore RedundantNullableFlowAttribute - typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type.MustNotBeNull(nameof(type)), otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType, typeComparer); - - /// - /// Checks if the specified type derives from the other type. Internally, this method uses - /// by default so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type info to be checked. - /// The base class that should derive from. - /// Thrown when or is null. - [ContractAnnotation("type:null => halt; baseClass:null => halt")] - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static bool DerivesFrom([NotNull, ValidatedNotNull] this Type type, [NotNull, ValidatedNotNull] Type baseClass) - // ReSharper restore RedundantNullableFlowAttribute - { - baseClass.MustNotBeNull(nameof(baseClass)); - - var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; - while (currentBaseType != null) - { - if (currentBaseType.IsEquivalentTypeTo(baseClass)) - return true; - - currentBaseType = currentBaseType.BaseType; - } - - return false; - } - - /// - /// Checks if the specified type derives from the other type. This overload uses the specified - /// to compare the types. - /// - /// The type info to be checked. - /// The base class that should derive from. - /// The equality comparer used to compare the types. - /// Thrown when , or , or is null. - [ContractAnnotation("type:null => halt; baseClass:null => halt; typeComparer:null => halt")] - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static bool DerivesFrom([NotNull, ValidatedNotNull] this Type type, [NotNull, ValidatedNotNull] Type baseClass, [NotNull, ValidatedNotNull] IEqualityComparer typeComparer) - // ReSharper restore RedundantNullableFlowAttribute - { - baseClass.MustNotBeNull(nameof(baseClass)); - typeComparer.MustNotBeNull(nameof(typeComparer)); - - var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; - while (currentBaseType != null) - { - if (typeComparer.Equals(currentBaseType, baseClass)) - return true; - - currentBaseType = currentBaseType.BaseType; - } - - return false; - } - - /// - /// Checks if the given is equal to the specified or if it derives from it. Internally, this - /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static bool IsOrDerivesFrom([NotNull, ValidatedNotNull] this Type type, [NotNull, ValidatedNotNull] Type otherType) => - // ReSharper restore RedundantNullableFlowAttribute - type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType); - - /// - /// Checks if the given is equal to the specified or if it derives from it. This overload uses the specified - /// to compare the types. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// The equality comparer used to compare the types. - /// Thrown when , or , or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static bool IsOrDerivesFrom([NotNull, ValidatedNotNull] this Type type, [NotNull, ValidatedNotNull] Type otherType, [NotNull, ValidatedNotNull] IEqualityComparer typeComparer) => - // ReSharper restore RedundantNullableFlowAttribute - typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType, typeComparer); - - - /// - /// Checks if the given type derives from the specified base class or interface type. Internally, this method uses - /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The type describing an interface or base class that should derive from or implement. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt")] - public static bool InheritsFrom( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type baseClassOrInterfaceType) => - // ReSharper restore RedundantNullableFlowAttribute - baseClassOrInterfaceType.MustNotBeNull(nameof(baseClassOrInterfaceType)) - .IsInterface ? - type.Implements(baseClassOrInterfaceType) : - type.DerivesFrom(baseClassOrInterfaceType); - - /// - /// Checks if the given type derives from the specified base class or interface type. This overload uses the specified - /// to compare the types. - /// - /// The type to be checked. - /// The type describing an interface or base class that should derive from or implement. - /// The equality comparer used to compare the types. - /// Thrown when , or , or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt; typeComparer:null => halt")] - public static bool InheritsFrom( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type baseClassOrInterfaceType, - [NotNull, ValidatedNotNull] IEqualityComparer typeComparer) => - // ReSharper restore RedundantNullableFlowAttribute - baseClassOrInterfaceType.MustNotBeNull(nameof(baseClassOrInterfaceType)) - .IsInterface ? - type.Implements(baseClassOrInterfaceType, typeComparer) : - type.DerivesFrom(baseClassOrInterfaceType, typeComparer); - - /// - /// Checks if the given is equal to the specified or if it derives from it or implements it. - /// Internally, this method uses so that constructed generic types and their corresponding generic type definitions - /// are regarded as equal. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrInheritsFrom( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type otherType) => - // ReSharper restore RedundantNullableFlowAttribute - type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType); - - - /// - /// Checks if the given is equal to the specified or if it derives from it or implements it. - /// This overload uses the specified to compare the types. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// The equality comparer used to compare the types. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] - public static bool IsOrInheritsFrom( -#if NET8_0 - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#endif - // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - [NotNull, ValidatedNotNull] this Type type, - [NotNull, ValidatedNotNull] Type otherType, - [NotNull, ValidatedNotNull] IEqualityComparer typeComparer) => - // ReSharper restore RedundantNullableFlowAttribute - typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType, typeComparer); - - - /// - /// Checks if the given is a generic type that has open generic parameters, - /// but is no generic type definition. - /// - /// The type to be checked. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt")] - // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static bool IsOpenConstructedGenericType([NotNull, ValidatedNotNull] this Type type) => - type.MustNotBeNull(nameof(type)).IsGenericType && - type.ContainsGenericParameters && - type.IsGenericTypeDefinition == false; -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.UriAssertions.cs b/Code/Light.GuardClauses/Check.UriAssertions.cs deleted file mode 100644 index a45d6c81..00000000 --- a/Code/Light.GuardClauses/Check.UriAssertions.cs +++ /dev/null @@ -1,288 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Light.GuardClauses.Exceptions; -using System.Runtime.CompilerServices; -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -namespace Light.GuardClauses; - -public static partial class Check -{ - /// - /// Ensures that the specified URI is an absolute one, or otherwise throws a . - /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not an absolute URI. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeAbsoluteUri([NotNull, ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri == false) - Throw.MustBeAbsoluteUri(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified URI is an absolute one, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// Your custom exception thrown when is not an absolute URI, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeAbsoluteUri([NotNull, ValidatedNotNull] this Uri? parameter, Func exceptionFactory) - { - if (parameter is null || parameter.IsAbsoluteUri == false) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the specified URI is a relative one, or otherwise throws an . - /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is an absolute URI. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeRelativeUri([NotNull, ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri) - Throw.MustBeRelativeUri(parameter, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified URI is a relative one, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is an absolute URI, or when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeRelativeUri([NotNull, ValidatedNotNull] this Uri? parameter, Func exceptionFactory) - { - if (parameter is null || parameter.IsAbsoluteUri) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the has the specified scheme, or otherwise throws an . - /// - /// The URI to be checked. - /// The scheme that the URI should have. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than the specified one. - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveScheme([NotNull, ValidatedNotNull] this Uri? parameter, string scheme, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (string.Equals(parameter.MustBeAbsoluteUri(parameterName, message).Scheme, scheme) == false) - Throw.UriMustHaveScheme(parameter, scheme, parameterName, message); - return parameter; - } - - /// - /// Ensures that the has the specified scheme, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The scheme that the URI should have. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than the specified one, - /// or when is a relative URI, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveScheme([NotNull, ValidatedNotNull] this Uri? parameter, string scheme, Func exceptionFactory) - { - if (string.Equals(parameter.MustBeAbsoluteUri(exceptionFactory).Scheme, scheme) == false) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the has the specified scheme, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The scheme that the URI should have. - /// The delegate that creates the exception to be thrown. and are passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than the specified one, - /// or when is a relative URI, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveScheme([NotNull, ValidatedNotNull] this Uri? parameter, string scheme, Func exceptionFactory) - { - if (parameter is null || !parameter.IsAbsoluteUri || parameter.Scheme.Equals(scheme) == false) - Throw.CustomException(exceptionFactory, parameter, scheme); - return parameter; - } - - /// - /// Ensures that the specified URI has the "https" scheme, or otherwise throws an . - /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than "https". - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpsUrl([NotNull, ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustHaveScheme("https", parameterName, message); - - /// - /// Ensures that the specified URI has the "https" scheme, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than "https", - /// or when is a relative URI, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpsUrl([NotNull, ValidatedNotNull] this Uri? parameter, Func exceptionFactory) => parameter.MustHaveScheme("https", exceptionFactory); - - /// - /// Ensures that the specified URI has the "http" scheme, or otherwise throws an . - /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than "http". - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpUrl([NotNull, ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustHaveScheme("http", parameterName, message); - - /// - /// Ensures that the specified URI has the "http" scheme, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than "http", - /// or when is a relative URI, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpUrl([NotNull, ValidatedNotNull] this Uri? parameter, Func exceptionFactory) => parameter.MustHaveScheme("http", exceptionFactory); - - /// - /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws an . - /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than "http" or "https". - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpOrHttpsUrl([NotNull, ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustBeAbsoluteUri(parameterName, message).Scheme.Equals("https") == false && parameter.Scheme.Equals("http") == false) - Throw.UriMustHaveOneSchemeOf(parameter, new[] { "https", "http" }, parameterName, message); - return parameter; - } - - /// - /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than "http" or "https", - /// or when is a relative URI, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpOrHttpsUrl([NotNull, ValidatedNotNull] this Uri? parameter, Func exceptionFactory) - { - if (parameter.MustBeAbsoluteUri(exceptionFactory).Scheme.Equals("https") == false && parameter.Scheme.Equals("http") == false) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - - /// - /// Ensures that the URI has one of the specified schemes, or otherwise throws an . - /// - /// The URI to be checked. - /// One of these strings must be equal to the scheme of the URI. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the scheme is not equal to one of the specified schemes. - /// Thrown when is relative and thus has no scheme. - /// Throw when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; schemes:null => halt")] - public static Uri MustHaveOneSchemeOf([NotNull, ValidatedNotNull] this Uri? parameter, IEnumerable schemes, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - // ReSharper disable PossibleMultipleEnumeration - parameter.MustBeAbsoluteUri(parameterName, message); - - if (schemes is ICollection collection) - { - if (!collection.Contains(parameter.Scheme)) - Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); - return parameter; - } - - if (!schemes.MustNotBeNull(nameof(schemes), message).Contains(parameter.Scheme)) - Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); - return parameter; - // ReSharper restore PossibleMultipleEnumeration - } - - /// - /// Ensures that the URI has one of the specified schemes, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// One of these strings must be equal to the scheme of the URI. - /// The delegate that creates your custom exception. - /// - /// Your custom exception thrown when the scheme is not equal to one of the specified schemes, - /// or when is a relative URI, - /// or when is null. - /// - /// Throw when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveOneSchemeOf([NotNull, ValidatedNotNull] this Uri? parameter, TCollection schemes, Func exceptionFactory) where TCollection : class, IEnumerable - { - if (parameter is null || !parameter.IsAbsoluteUri) - Throw.CustomException(exceptionFactory, parameter, schemes); - - if (schemes is ICollection collection) - { - if (!collection.Contains(parameter.Scheme)) - Throw.CustomException(exceptionFactory, parameter, schemes); - return parameter; - } - - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (schemes is null || !schemes.Contains(parameter.Scheme)) - Throw.CustomException(exceptionFactory, parameter, schemes!); - return parameter; - } -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.cs b/Code/Light.GuardClauses/Check.cs new file mode 100644 index 00000000..1edb58ed --- /dev/null +++ b/Code/Light.GuardClauses/Check.cs @@ -0,0 +1,7 @@ +namespace Light.GuardClauses; + +/// +/// The class provides access to all assertions of Light.GuardClauses. +/// +// ReSharper disable once RedundantTypeDeclarationBody -- required for Source Code Transformation +public static partial class Check { } diff --git a/Code/Light.GuardClauses/EnumInfo.cs b/Code/Light.GuardClauses/EnumInfo.cs index d10ad442..49232e99 100644 --- a/Code/Light.GuardClauses/EnumInfo.cs +++ b/Code/Light.GuardClauses/EnumInfo.cs @@ -21,7 +21,7 @@ public static class EnumInfo where T : struct, Enum typeof(T).GetCustomAttribute(Types.FlagsAttributeType) != null; /// - /// Gets the flags pattern when is true. If the enum is not a flags enum, then 0UL is returned. + /// Gets the flags pattern when is true. If the enum is not a flags enum, then 0UL is returned. /// public static readonly ulong FlagsPattern; @@ -40,10 +40,12 @@ static EnumInfo() #else EnumConstantsArray = (T[]) Enum.GetValues(typeof(T)); #endif - EnumConstants = new (EnumConstantsArray); + EnumConstants = new ReadOnlyMemory(EnumConstantsArray); if (!IsFlagsEnum) + { return; + } for (var i = 0; i < EnumConstantsArray.Length; ++i) { @@ -63,8 +65,12 @@ private static bool IsValidValue(T parameter) { var comparer = EqualityComparer.Default; for (var i = 0; i < EnumConstantsArray.Length; ++i) + { if (comparer.Equals(EnumConstantsArray[i], parameter)) + { return true; + } + } return false; } @@ -90,12 +96,11 @@ private static ulong ConvertToUInt64(T value) case 8: return Unsafe.As(ref value); default: ThrowUnknownEnumSize(); - return default; + return 0UL; } } - private static void ThrowUnknownEnumSize() - { - throw new InvalidOperationException($"The enum type \"{typeof(T)}\" has an unknown size of {EnumSize}. This means that the underlying enum type is not one of the supported ones."); - } -} \ No newline at end of file + private static void ThrowUnknownEnumSize() => throw new InvalidOperationException( + $"The enum type \"{typeof(T)}\" has an unknown size of {EnumSize}. This means that the underlying enum type is not one of the supported ones." + ); +} diff --git a/Code/Light.GuardClauses/EquivalentTypeComparer.cs b/Code/Light.GuardClauses/EquivalentTypeComparer.cs index 02784ac4..6123f9eb 100644 --- a/Code/Light.GuardClauses/EquivalentTypeComparer.cs +++ b/Code/Light.GuardClauses/EquivalentTypeComparer.cs @@ -15,7 +15,7 @@ public sealed class EquivalentTypeComparer : IEqualityComparer /// /// Gets a singleton instance of the equality comparer. /// - public static readonly EquivalentTypeComparer Instance = new EquivalentTypeComparer(); + public static readonly EquivalentTypeComparer Instance = new (); /// /// Checks if the two types are equivalent (using ). @@ -35,8 +35,8 @@ public sealed class EquivalentTypeComparer : IEqualityComparer /// The type whose hash code is requested. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetHashCode(Type type) => - // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract type is null ? 0 : type.IsConstructedGenericType ? type.GetGenericTypeDefinition().GetHashCode() : type.GetHashCode(); -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/SpanDelegates.cs b/Code/Light.GuardClauses/ExceptionFactory/SpanDelegates.cs new file mode 100644 index 00000000..2abcf034 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/SpanDelegates.cs @@ -0,0 +1,32 @@ +using System; + +namespace Light.GuardClauses.ExceptionFactory; + +/// +/// Represents a delegate that receives a span and a value as parameters and that produces an exception. +/// +public delegate Exception SpanExceptionFactory(Span span, T value); + +/// +/// Represents a delegate that receives a read-only span and produces an exception. +/// +public delegate Exception ReadOnlySpanExceptionFactory(ReadOnlySpan span); + +/// +/// Represents a delegate that receives a read-only span and a value as parameters and that produces an exception. +/// +public delegate Exception ReadOnlySpanExceptionFactory(ReadOnlySpan span, T value); + +/// +/// Represents a delegate that receives two spans and produces an exception. +/// +public delegate Exception ReadOnlySpansExceptionFactory(ReadOnlySpan span1, ReadOnlySpan span2); + +/// +/// Represents a delegate that receives two spans and a value as parameters and that produces an exception. +/// +public delegate Exception ReadOnlySpansExceptionFactory( + ReadOnlySpan span1, + ReadOnlySpan span2, + T value +); diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.Argument.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.Argument.cs new file mode 100644 index 00000000..b72caece --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.Argument.cs @@ -0,0 +1,16 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws an using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void Argument(string? parameterName = null, string? message = null) => + throw new ArgumentException(message ?? $"{parameterName ?? "The value"} is invalid.", parameterName); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ArgumentDefault.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ArgumentDefault.cs new file mode 100644 index 00000000..7a63b47f --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ArgumentDefault.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value is the default value of its + /// type, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ArgumentDefault(string? parameterName = null, string? message = null) => + throw new ArgumentDefaultException( + parameterName, + message ?? $"{parameterName ?? "The value"} must not be the default value." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ArgumentNull.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ArgumentNull.cs new file mode 100644 index 00000000..0a35f341 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ArgumentNull.cs @@ -0,0 +1,16 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ArgumentNull(string? parameterName = null, string? message = null) => + throw new ArgumentNullException(parameterName, message ?? $"{parameterName ?? "The value"} must not be null."); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.CustomException.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.CustomException.cs new file mode 100644 index 00000000..48d97cfc --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.CustomException.cs @@ -0,0 +1,113 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the exception that is returned by . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(); + + /// + /// Throws the exception that is returned by . is + /// passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory, T parameter) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(parameter); + + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory, T1 first, T2 second) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second); + + /// + /// Throws the exception that is returned by . , + /// , and are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException( + Func exceptionFactory, + T1 first, + T2 second, + T3 third + ) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second, third); + + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException( + SpanExceptionFactory exceptionFactory, + Span span, + T value + ) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory)).Invoke(span, value); + + /// + /// Throws the exception that is returned by . is + /// passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException( + ReadOnlySpanExceptionFactory exceptionFactory, + ReadOnlySpan span + ) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span); + + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException( + ReadOnlySpanExceptionFactory exceptionFactory, + ReadOnlySpan span, + T value + ) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span, value); + + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException( + ReadOnlySpansExceptionFactory exceptionFactory, + ReadOnlySpan first, + ReadOnlySpan second + ) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second); + + /// + /// Throws the exception that is returned by . , + /// , and are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException( + ReadOnlySpansExceptionFactory exceptionFactory, + ReadOnlySpan first, + ReadOnlySpan second, + T third + ) => + throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second, third); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.DateTime.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.DateTime.cs new file mode 100644 index 00000000..865b60c2 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.DateTime.cs @@ -0,0 +1,61 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a date time is not using + /// , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeUtcDateTime( + DateTime parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidDateTimeException( + parameterName, + message ?? + $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Utc}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"." + ); + + /// + /// Throws the default indicating that a date time is not using + /// , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLocalDateTime( + DateTime parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidDateTimeException( + parameterName, + message ?? + $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Local}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"." + ); + + /// + /// Throws the default indicating that a date time is not using + /// , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeUnspecifiedDateTime( + DateTime parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidDateTimeException( + parameterName, + message ?? + $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Unspecified}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyCollection.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyCollection.cs new file mode 100644 index 00000000..fc3eb729 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyCollection.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a collection has no items, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EmptyCollection(string? parameterName = null, string? message = null) => + throw new EmptyCollectionException( + parameterName, + message ?? $"{parameterName ?? "The collection"} must not be an empty collection, but it actually is." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyGuid.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyGuid.cs new file mode 100644 index 00000000..1a85deeb --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyGuid.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a GUID is empty, using the optional + /// parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EmptyGuid(string? parameterName = null, string? message = null) => + throw new EmptyGuidException( + parameterName, + message ?? $"{parameterName ?? "The value"} must be a valid GUID, but it actually is an empty one." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyString.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyString.cs new file mode 100644 index 00000000..03ccb67d --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.EmptyString.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is empty, using the optional + /// parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EmptyString(string? parameterName = null, string? message = null) => + throw new EmptyStringException( + parameterName, + message ?? $"{parameterName ?? "The string"} must not be an empty string, but it actually is." + ); +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.EnumValueNotDefined.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.EnumValueNotDefined.cs new file mode 100644 index 00000000..b4848375 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.EnumValueNotDefined.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value is not one of the + /// constants defined in an enum, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EnumValueNotDefined( + T parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : Enum => + throw new EnumValueNotDefinedException( + parameterName, + message ?? + $"{parameterName ?? "The value"} \"{parameter}\" must be one of the defined constants of enum \"{parameter.GetType()}\", but it actually is not." + ); +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ExistingItem.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ExistingItem.cs new file mode 100644 index 00000000..376a8f99 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ExistingItem.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a collection contains the specified item + /// that should not be part of it, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ExistingItem( + IEnumerable parameter, + TItem item, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ExistingItemException( + parameterName, + message ?? + new StringBuilder() + .AppendLine( + $"{parameterName ?? "The collection"} must not contain {item.ToStringOrNull()}, but it actually does." + ) + .AppendCollectionContent(parameter) + .ToString() + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidCollectionCount.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidCollectionCount.cs new file mode 100644 index 00000000..d67f05c7 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidCollectionCount.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a collection has an invalid + /// number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidCollectionCount( + IEnumerable parameter, + int count, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The collection"} must have count {count}, but it actually has count {parameter.Count()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidEmailAddress.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidEmailAddress.cs new file mode 100644 index 00000000..ebf4c441 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidEmailAddress.cs @@ -0,0 +1,42 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidEmailAddress( + string parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidEmailAddressException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be a valid email address, but it actually is \"{parameter}\"." + ); + + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidEmailAddress( + ReadOnlySpan parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidEmailAddressException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be a valid email address, but it actually is \"{parameter.ToString()}\"." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumCollectionCount.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumCollectionCount.cs new file mode 100644 index 00000000..85730d77 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumCollectionCount.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a collection has more than a + /// maximum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMaximumCollectionCount( + IEnumerable parameter, + int count, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The collection"} must have at most count {count}, but it actually has count {parameter.Count()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMinimumCollectionCount.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMinimumCollectionCount.cs new file mode 100644 index 00000000..4914ad15 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMinimumCollectionCount.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a collection has less than a + /// minimum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMinimumCollectionCount( + IEnumerable parameter, + int count, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The collection"} must have at least count {count}, but it actually has count {parameter.Count()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidOperation.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidOperation.cs new file mode 100644 index 00000000..0452941c --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidOperation.cs @@ -0,0 +1,15 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidOperation(string? message = null) => throw new InvalidOperationException(message); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidState.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidState.cs new file mode 100644 index 00000000..cbba7f9b --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidState.cs @@ -0,0 +1,15 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidState(string? message = null) => throw new InvalidStateException(message); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidTypeCast.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidTypeCast.cs new file mode 100644 index 00000000..4831b186 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidTypeCast.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a reference cannot be downcast, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidTypeCast( + object? parameter, + Type targetType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new TypeCastException( + parameterName, + message ?? + $"{parameterName ?? "The value"} {parameter.ToStringOrNull()} cannot be cast to \"{targetType}\"." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MissingItem.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MissingItem.cs new file mode 100644 index 00000000..0d4b4cba --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MissingItem.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a collection is not containing the + /// specified item, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MissingItem( + IEnumerable parameter, + TItem item, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new MissingItemException( + parameterName, + message ?? + new StringBuilder() + .AppendLine( + $"{parameterName ?? "The collection"} must contain {item.ToStringOrNull()}, but it actually does not." + ) + .AppendCollectionContent(parameter) + .ToString() + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeApproximately.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeApproximately.cs new file mode 100644 index 00000000..e4ba742e --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeApproximately.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value must be approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeApproximately( + T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThan.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThan.cs new file mode 100644 index 00000000..cec5dcb2 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThan.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must be greater + /// than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeGreaterThan( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be greater than {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThanOrApproximately.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThanOrApproximately.cs new file mode 100644 index 00000000..a67a5715 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThanOrApproximately.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value must be greater than or approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeGreaterThanOrApproximately( + T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be greater than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThanOrEqualTo.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThanOrEqualTo.cs new file mode 100644 index 00000000..ce70b4ef --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeGreaterThanOrEqualTo.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must be greater + /// than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeGreaterThanOrEqualTo( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be greater than or equal to {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThan.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThan.cs new file mode 100644 index 00000000..9d913ed0 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThan.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must be less + /// than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLessThan( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? $"{parameterName ?? "The value"} must be less than {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThanOrApproximately.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThanOrApproximately.cs new file mode 100644 index 00000000..40827d82 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThanOrApproximately.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value must be less than or approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLessThanOrApproximately( + T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be less than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}." + ); +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThanOrEqualTo.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThanOrEqualTo.cs new file mode 100644 index 00000000..7afb4109 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeLessThanOrEqualTo.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must be less + /// than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLessThanOrEqualTo( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be less than or equal to {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeApproximately.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeApproximately.cs new file mode 100644 index 00000000..3b8adfab --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeApproximately.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value must not be approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeApproximately( + T parameter, + T other, + T tolerance, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}." + ); +} \ No newline at end of file diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeGreaterThan.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeGreaterThan.cs new file mode 100644 index 00000000..743df303 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeGreaterThan.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must not be + /// greater than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeGreaterThan( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be greater than {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeGreaterThanOrEqualTo.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeGreaterThanOrEqualTo.cs new file mode 100644 index 00000000..82df4823 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeGreaterThanOrEqualTo.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must not be + /// greater than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeGreaterThanOrEqualTo( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be greater than or equal to {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeLessThan.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeLessThan.cs new file mode 100644 index 00000000..e1f30f7b --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeLessThan.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must not be + /// less than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeLessThan( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be less than {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeLessThanOrEqualTo.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeLessThanOrEqualTo.cs new file mode 100644 index 00000000..bb534623 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeLessThanOrEqualTo.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a comparable value must not be + /// less than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeLessThanOrEqualTo( + T parameter, + T boundary, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be less than or equal to {boundary}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.NotFileExtension.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotFileExtension.cs new file mode 100644 index 00000000..1cc1b811 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotFileExtension.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not a valid file extension. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotFileExtension(string? parameter, string? parameterName, string? message) => + throw new StringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be a valid file extension, but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string is not a valid file extension. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotFileExtension(ReadOnlySpan parameter, string? parameterName, string? message) => + throw new StringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be a valid file extension, but it actually is {parameter.ToString()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.NotNewLine.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotNewLine.cs new file mode 100644 index 00000000..e8717069 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotNewLine.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not equal to "\n" or "\r\n". + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotNewLine(string? parameter, string? parameterName, string? message) => + throw new StringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be either \"\\n\" or \"\\r\\n\", but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmed.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmed.cs new file mode 100644 index 00000000..a1f91e79 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmed.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not trimmed. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotTrimmed(string? parameter, string? parameterName, string? message) => + throw new StringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be trimmed, but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmedAtEnd.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmedAtEnd.cs new file mode 100644 index 00000000..ba7875ae --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmedAtEnd.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not trimmed at the end. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotTrimmedAtEnd(string? parameter, string? parameterName, string? message) => + throw new StringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be trimmed at the end, but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmedAtStart.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmedAtStart.cs new file mode 100644 index 00000000..047fe010 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.NotTrimmedAtStart.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not trimmed at the start. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotTrimmedAtStart(string? parameter, string? parameterName, string? message) => + throw new StringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be trimmed at the start, but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.NullableHasNoValue.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.NullableHasNoValue.cs new file mode 100644 index 00000000..f477ef47 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.NullableHasNoValue.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a has + /// no value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NullableHasNoValue(string? parameterName = null, string? message = null) => + throw new NullableHasNoValueException( + parameterName, + message ?? $"{parameterName ?? "The nullable"} must have a value, but it actually is null." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.Range.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.Range.cs new file mode 100644 index 00000000..a4a14ef2 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.Range.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value is not within a specified + /// range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeInRange( + T parameter, + Range range, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}." + ); + + /// + /// Throws the default indicating that a value is within a specified + /// range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeInRange( + T parameter, + Range range, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : IComparable => + throw new ArgumentOutOfRangeException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.SameObjectReference.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.SameObjectReference.cs new file mode 100644 index 00000000..8db1ebeb --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.SameObjectReference.cs @@ -0,0 +1,26 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that two references point to the same + /// object, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SameObjectReference( + T? parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) where T : class => + throw new SameObjectReferenceException( + parameterName, + message ?? + $"{parameterName ?? "The reference"} must not point to object \"{parameter}\", but it actually does." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.Span.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.Span.cs new file mode 100644 index 00000000..a3c04bf2 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.Span.cs @@ -0,0 +1,100 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a span has an invalid length, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidSpanLength( + ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The read-only span"} must have length {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a span is not longer than the + /// specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeLongerThan( + ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The span"} must be longer than {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a span is not longer than and + /// not equal to the specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeLongerThanOrEqualTo( + ReadOnlySpan parameter, + int length, + [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The span"} must be longer than or equal to {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a span is not shorter than the + /// specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeShorterThan( + ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The span"} must be shorter than {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a span is not shorter than the + /// specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeShorterThanOrEqualTo( + ReadOnlySpan parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The span"} must be shorter than or equal to {length}, but it actually has length {parameter.Length}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringContains.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringContains.cs new file mode 100644 index 00000000..e072d3bb --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringContains.cs @@ -0,0 +1,85 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string does not contain another string as + /// a substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotContain( + string parameter, + string substring, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string does not contain another string as + /// a substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotContain( + string parameter, + string substring, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()} ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string does contain another string as a + /// substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringContains( + string parameter, + string substring, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring, but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string does contain another string as a + /// substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringContains( + string parameter, + string substring, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotEndWith.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotEndWith.cs new file mode 100644 index 00000000..6f0cecea --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotEndWith.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string does not end with another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotEndWith( + string parameter, + string other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must end with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotMatch.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotMatch.cs new file mode 100644 index 00000000..61fc5eb1 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotMatch.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string does not match a regular + /// expression, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotMatch( + string parameter, + Regex regex, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringDoesNotMatchException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must match the regular expression \"{regex}\", but it actually is \"{parameter}\"." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotStartWith.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotStartWith.cs new file mode 100644 index 00000000..e51e8020 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringDoesNotStartWith.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string does not start with another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotStartWith( + string parameter, + string other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must start with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringEndsWith.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringEndsWith.cs new file mode 100644 index 00000000..476f035c --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringEndsWith.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string ends with another one, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringEndsWith( + string parameter, + string other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not end with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringLength.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringLength.cs new file mode 100644 index 00000000..d392362f --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringLength.cs @@ -0,0 +1,117 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not shorter than the given + /// length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotShorterThan( + string parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringLengthException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be shorter than {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a string is not shorter or equal to the + /// given length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotShorterThanOrEqualTo( + string parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringLengthException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be shorter or equal to {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a string has a different length than the + /// specified one, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringLengthNotEqualTo( + string parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringLengthException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must have length {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a string is not longer than the given + /// length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotLongerThan( + string parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringLengthException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be longer than {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a string is not longer than or equal to + /// the given length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotLongerThanOrEqualTo( + string parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringLengthException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be longer than or equal to {length}, but it actually has length {parameter.Length}." + ); + + /// + /// Throws the default indicating that a string's length is not in within the + /// given range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringLengthNotInRange( + string parameter, + Range range, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new StringLengthException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.StringStartsWith.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringStartsWith.cs new file mode 100644 index 00000000..ad19dc6e --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.StringStartsWith.cs @@ -0,0 +1,49 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string does start with another one, using + /// the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringStartsWith( + string parameter, + string other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not start with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string does start with another one, using + /// the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringStartsWith( + ReadOnlySpan parameter, + ReadOnlySpan other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not start with \"{other.ToString()}\" ({comparisonType}), but it actually is {parameter.ToString()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.Substring.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.Substring.cs new file mode 100644 index 00000000..0d4f93dc --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.Substring.cs @@ -0,0 +1,85 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string is not a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotSubstring( + string parameter, + string other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string is not a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotSubstring( + string parameter, + string other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string is a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void Substring( + string parameter, + string other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that a string is a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void Substring( + string parameter, + string other, + StringComparison comparisonType, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new SubstringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.Uri.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.Uri.cs new file mode 100644 index 00000000..4f7a8047 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.Uri.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a URI is relative instead of absolute, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeAbsoluteUri( + Uri parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new RelativeUriException( + parameterName, + message ?? $"{parameterName ?? "The URI"} must be an absolute URI, but it actually is \"{parameter}\"." + ); + + /// + /// Throws the default indicating that a URI is absolute instead of relative, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeRelativeUri( + Uri parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new AbsoluteUriException( + parameterName, + message ?? $"{parameterName ?? "The URI"} must be a relative URI, but it actually is \"{parameter}\"." + ); + + /// + /// Throws the default indicating that a URI has an unexpected scheme, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void UriMustHaveScheme( + Uri parameter, + string scheme, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidUriSchemeException( + parameterName, + message ?? + $"{parameterName ?? "The URI"} must use the scheme \"{scheme}\", but it actually is \"{parameter}\"." + ); + + /// + /// Throws the default indicating that a URI does not use one of a set of + /// expected schemes, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void UriMustHaveOneSchemeOf( + Uri parameter, + IEnumerable schemes, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new InvalidUriSchemeException( + parameterName, + message ?? + new StringBuilder().AppendLine($"{parameterName ?? "The URI"} must use one of the following schemes") + .AppendItemsWithNewLine(schemes) + .AppendLine($"but it actually is \"{parameter}\".") + .ToString() + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ValueIsOneOf.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ValueIsOneOf.cs new file mode 100644 index 00000000..65809d1e --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ValueIsOneOf.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value is one of a specified collection + /// of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValueIsOneOf( + TItem parameter, + IEnumerable items, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ValueIsOneOfException( + parameterName, + message ?? + new StringBuilder().AppendLine($"{parameterName ?? "The value"} must not be one of the following items") + .AppendItemsWithNewLine(items) + .AppendLine($"but it actually is {parameter.ToStringOrNull()}.") + .ToString() + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ValueNotOneOf.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ValueNotOneOf.cs new file mode 100644 index 00000000..746b4578 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ValueNotOneOf.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a value is not one of a specified + /// collection of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValueNotOneOf( + TItem parameter, + IEnumerable items, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ValueIsNotOneOfException( + parameterName, + message ?? + new StringBuilder().AppendLine($"{parameterName ?? "The value"} must be one of the following items") + .AppendItemsWithNewLine(items) + .AppendLine($"but it actually is {parameter.ToStringOrNull()}.") + .ToString() + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ValuesEqual.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ValuesEqual.cs new file mode 100644 index 00000000..05157e61 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ValuesEqual.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; +using Light.GuardClauses.FrameworkExtensions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that two values are not equal, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValuesNotEqual( + T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ValuesNotEqualException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}." + ); + + /// + /// Throws the default indicating that two values are equal, using the optional + /// parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValuesEqual( + T parameter, + T other, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new ValuesEqualException( + parameterName, + message ?? + $"{parameterName ?? "The value"} must not be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.WhiteSpaceString.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.WhiteSpaceString.cs new file mode 100644 index 00000000..bae38b27 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.WhiteSpaceString.cs @@ -0,0 +1,26 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that a string contains only white space, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void WhiteSpaceString( + string parameter, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) => + throw new WhiteSpaceStringException( + parameterName, + message ?? + $"{parameterName ?? "The string"} must not contain only white space, but it actually is \"{parameter}\"." + ); +} diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.cs new file mode 100644 index 00000000..0ff2aab4 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.cs @@ -0,0 +1,7 @@ +namespace Light.GuardClauses.ExceptionFactory; + +/// +/// Provides static factory methods that throw default exceptions. +/// +// ReSharper disable once RedundantTypeDeclarationBody - requried for the Source Code Transformation +public static partial class Throw { } diff --git a/Code/Light.GuardClauses/Exceptions/Throw.cs b/Code/Light.GuardClauses/Exceptions/Throw.cs deleted file mode 100644 index 5b3bbf9c..00000000 --- a/Code/Light.GuardClauses/Exceptions/Throw.cs +++ /dev/null @@ -1,672 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Text; -using System.Text.RegularExpressions; -using JetBrains.Annotations; -using Light.GuardClauses.FrameworkExtensions; - -namespace Light.GuardClauses.Exceptions; - -/// -/// Provides static factory methods that throw default exceptions. -/// -public static class Throw -{ - /// - /// Throws the default , using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ArgumentNull(string? parameterName = null, string? message = null) => - throw new ArgumentNullException(parameterName, message ?? $"{parameterName ?? "The value"} must not be null."); - - /// - /// Throws the default indicating that a value is the default value of its type, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ArgumentDefault(string? parameterName = null, string? message = null) => - throw new ArgumentDefaultException(parameterName, message ?? $"{parameterName ?? "The value"} must not be the default value."); - - /// - /// Throws the default indicating that a reference cannot be downcast, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidTypeCast(object? parameter, Type targetType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new TypeCastException(parameterName, message ?? $"{parameterName ?? "The value"} {parameter.ToStringOrNull()} cannot be cast to \"{targetType}\"."); - - /// - /// Throws the default indicating that a value is not one of the constants defined in an enum, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EnumValueNotDefined(T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : Enum => - throw new EnumValueNotDefinedException(parameterName, message ?? $"{parameterName ?? "The value"} \"{parameter}\" must be one of the defined constants of enum \"{parameter.GetType()}\", but it actually is not."); - - /// - /// Throws the default indicating that a GUID is empty, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EmptyGuid(string? parameterName = null, string? message = null) => - throw new EmptyGuidException(parameterName, message ?? $"{parameterName ?? "The value"} must be a valid GUID, but it actually is an empty one."); - - /// - /// Throws an using the optional message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidOperation(string? message = null) => throw new InvalidOperationException(message); - - /// - /// Throws an using the optional message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidState(string? message = null) => throw new InvalidStateException(message); - - /// - /// Throws an using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void Argument(string? parameterName = null, string? message = null) => - throw new ArgumentException(message ?? $"{parameterName ?? "The value"} is invalid.", parameterName); - - /// - /// Throws an using the optional message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidEmailAddress(string parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidEmailAddressException(parameterName, message ?? $"{parameterName ?? "The string"} must be a valid email address, but it actually is \"{parameter}\"."); - - /// - /// Throws the default indicating that a has no value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NullableHasNoValue(string? parameterName = null, string? message = null) => - throw new NullableHasNoValueException(parameterName, message ?? $"{parameterName ?? "The nullable"} must have a value, but it actually is null."); - - /// - /// Throws the default indicating that a comparable value must not be less than the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeLessThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be less than {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must be less than the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeLessThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must not be less than or equal to the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeLessThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be less than or equal to {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must not be greater than or equal to the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeGreaterThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be greater than or equal to {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must be greater than or equal to the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeGreaterThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than or equal to {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must be greater than the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeGreaterThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must not be greater than the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeGreaterThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be greater than {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a comparable value must be less than or equal to the given boundary value, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeLessThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than or equal to {boundary}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a value is not within a specified range, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeInRange(T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that a value is within a specified range, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeInRange(T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : IComparable => - throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}."); - - /// - /// Throws the default indicating that two references point to the same object, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SameObjectReference(T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) where T : class => - throw new SameObjectReferenceException(parameterName, message ?? $"{parameterName ?? "The reference"} must not point to object \"{parameter}\", but it actually does."); - - /// - /// Throws the default indicating that a string is empty, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EmptyString(string? parameterName = null, string? message = null) => - throw new EmptyStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be an empty string, but it actually is."); - - /// - /// Throws the default indicating that a string contains only white space, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void WhiteSpaceString(string parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new WhiteSpaceStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain only white space, but it actually is \"{parameter}\"."); - - /// - /// Throws the default indicating that a string does not match a regular expression, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotMatch(string parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringDoesNotMatchException(parameterName, message ?? $"{parameterName ?? "The string"} must match the regular expression \"{regex}\", but it actually is \"{parameter}\"."); - - /// - /// Throws the default indicating that a string does not contain another string as a substring, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotContain(string parameter, string substring, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string does not contain another string as a substring, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotContain(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()} ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string does contain another string as a substring, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringContains(string parameter, string substring, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string does contain another string as a substring, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringContains(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is not a substring of another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotSubstring(string parameter, string other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is not a substring of another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotSubstring(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is a substring of another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void Substring(string parameter, string other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is a substring of another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void Substring(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string does not start with another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotStartWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must start with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string does start with another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringStartsWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not start with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string does not end with another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotEndWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must end with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string ends with another one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringEndsWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not end with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is not shorter than the given length, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotShorterThan(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be shorter than {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a string is not shorter or equal to the given length, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotShorterThanOrEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be shorter or equal to {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a string has a different length than the specified one, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringLengthNotEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must have length {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a string is not longer than the given length, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotLongerThan(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be longer than {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a string is not longer or equal to the given length, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotLongerThanOrEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a string's length is not in between the given range, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringLengthNotInRange(string parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a string is not equal to "\n" or "\r\n". - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotNewLine(string? parameter, string? parameterName, string? message) => - throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be either \"\\n\" or \"\\r\\n\", but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is not trimmed. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotTrimmed(string? parameter, string? parameterName, string? message) => - throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is not trimmed at the start. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotTrimmedAtStart(string? parameter, string? parameterName, string? message) => - throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed at the start, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a string is not trimmed at the end. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotTrimmedAtEnd(string? parameter, string? parameterName, string? message) => - throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed at the end, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that two values are not equal, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValuesNotEqual(T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new ValuesNotEqualException(parameterName, message ?? $"{parameterName ?? "The value"} must be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that two values are equal, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValuesEqual(T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new ValuesEqualException(parameterName, message ?? $"{parameterName ?? "The value"} must not be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); - - /// - /// Throws the default indicating that a collection has an invalid number of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have count {count}, but it actually has count {parameter.Count()}."); - - /// - /// Throws the default indicating that a span has an invalid length, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidSpanLength(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must have length {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span has an invalid length, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidSpanLength(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The read-only span"} must have length {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a collection has less than a minimum number of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidMinimumCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have at least count {count}, but it actually has count {parameter.Count()}."); - - /// - /// Throws the default indicating that a span is not longer than the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThan(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not longer than the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThan(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not longer than and not equal to the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThanOrEqualTo(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not longer than and not equal to the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThanOrEqualTo(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not shorter than the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThan(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not shorter than the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThanOrEqualTo(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than or equal to {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not shorter than the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThanOrEqualTo(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than or equal to {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a span is not shorter than the specified length. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThan(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than {length}, but it actually has length {parameter.Length}."); - - /// - /// Throws the default indicating that a collection has more than a maximum number of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidMaximumCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have at most count {count}, but it actually has count {parameter.Count()}."); - - /// - /// Throws the default indicating that a collection has no items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EmptyCollection(string? parameterName = null, string? message = null) => - throw new EmptyCollectionException(parameterName, message ?? $"{parameterName ?? "The collection"} must not be an empty collection, but it actually is."); - - /// - /// Throws the default indicating that a collection is not containing the specified item, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MissingItem(IEnumerable parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new MissingItemException(parameterName, - message ?? - new StringBuilder().AppendLine($"{parameterName ?? "The collection"} must contain {item.ToStringOrNull()}, but it actually does not.") - .AppendCollectionContent(parameter) - .ToString()); - - /// - /// Throws the default indicating that a collection contains the specified item that should not be part of it, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ExistingItem(IEnumerable parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new ExistingItemException(parameterName, - message ?? - new StringBuilder().AppendLine($"{parameterName ?? "The collection"} must not contain {item.ToStringOrNull()}, but it actually does.") - .AppendCollectionContent(parameter) - .ToString()); - - /// - /// Throws the default indicating that a value is not one of a specified collection of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValueNotOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new ValueIsNotOneOfException(parameterName, - message ?? - new StringBuilder().AppendLine($"{parameterName ?? "The value"} must be one of the following items") - .AppendItemsWithNewLine(items) - .AppendLine($"but it actually is {parameter.ToStringOrNull()}.") - .ToString()); - - /// - /// Throws the default indicating that a value is one of a specified collection of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValueIsOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new ValueIsOneOfException(parameterName, - message ?? - new StringBuilder().AppendLine($"{parameterName ?? "The value"} must not be one of the following items") - .AppendItemsWithNewLine(items) - .AppendLine($"but it actually is {parameter.ToStringOrNull()}.") - .ToString()); - - /// - /// Throws the default indicating that a URI is relative instead of absolute, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeAbsoluteUri(Uri parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new RelativeUriException(parameterName, message ?? $"{parameterName ?? "The URI"} must be an absolute URI, but it actually is \"{parameter}\"."); - - /// - /// Throws the default indicating that a URI is absolute instead of relative, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeRelativeUri(Uri parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new AbsoluteUriException(parameterName, message ?? $"{parameterName ?? "The URI"} must be a relative URI, but it actually is \"{parameter}\"."); - - /// - /// Throws the default indicating that a URI has an unexpected scheme, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void UriMustHaveScheme(Uri parameter, string scheme, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidUriSchemeException(parameterName, message ?? $"{parameterName ?? "The URI"} must use the scheme \"{scheme}\", but it actually is \"{parameter}\"."); - - /// - /// Throws the default indicating that a URI does not use one of a set of expected schemes, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void UriMustHaveOneSchemeOf(Uri parameter, IEnumerable schemes, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidUriSchemeException(parameterName, - message ?? - new StringBuilder().AppendLine($"{parameterName ?? "The URI"} must use one of the following schemes") - .AppendItemsWithNewLine(schemes) - .AppendLine($"but it actually is \"{parameter}\".") - .ToString()); - - /// - /// Throws the default indicating that a date time is not using , using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeUtcDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Utc}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); - - /// - /// Throws the default indicating that a date time is not using , using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeLocalDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Local}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); - - /// - /// Throws the default indicating that a date time is not using , using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeUnspecifiedDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => - throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Unspecified}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); - - /// - /// Throws the exception that is returned by . - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory) => - throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(); - - /// - /// Throws the exception that is returned by . is passed to . - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory, T parameter) => - throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(parameter); - - /// - /// Throws the exception that is returned by . and are passed to . - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory, T1 first, T2 second) => - throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second); - - /// - /// Throws the exception that is returned by . , , and are passed to . - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory, T1 first, T2 second, T3 third) => - throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second, third); - - /// - /// Throws the exception that is returned by . and are passed to . - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomSpanException(SpanExceptionFactory exceptionFactory, in Span span, T value) => - throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span, value); - - /// - /// Throws the exception that is returned by . and are passed to . - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomSpanException(ReadOnlySpanExceptionFactory exceptionFactory, in ReadOnlySpan span, T value) => - throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span, value); - - -} \ No newline at end of file diff --git a/Code/Light.GuardClauses/FrameworkExtensions/EnumerableExtensions.cs b/Code/Light.GuardClauses/FrameworkExtensions/EnumerableExtensions.cs index f204458e..9ecd452c 100644 --- a/Code/Light.GuardClauses/FrameworkExtensions/EnumerableExtensions.cs +++ b/Code/Light.GuardClauses/FrameworkExtensions/EnumerableExtensions.cs @@ -2,8 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using System.Runtime.CompilerServices; +using JetBrains.Annotations; using Light.GuardClauses.Exceptions; using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; @@ -25,7 +25,7 @@ public static class EnumerableExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("source:null => halt; source:notnull => notnull")] // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static IList AsList([NotNull, ValidatedNotNull] this IEnumerable source) => + public static IList AsList([NotNull] [ValidatedNotNull] this IEnumerable source) => source as IList ?? source.ToList(); /// @@ -39,9 +39,13 @@ public static IList AsList([NotNull, ValidatedNotNull] this IEnumerable /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("source:null => halt; source:notnull => notnull; createCollection:null => halt")] - // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static IList AsList([NotNull, ValidatedNotNull] this IEnumerable source, Func, IList> createCollection) => - source as IList ?? createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); + public static IList AsList( + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + [NotNull] [ValidatedNotNull] this IEnumerable source, + Func, IList> createCollection + ) => + source as IList ?? + createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); /// /// Tries to downcast the specified enumerable to an array, or creates a new array with the specified items. @@ -53,7 +57,8 @@ public static IList AsList([NotNull, ValidatedNotNull] this IEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("source:null => halt; source:notnull => notnull")] // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static T[] AsArray([NotNull, ValidatedNotNull] this IEnumerable source) => source as T[] ?? source.ToArray(); + public static T[] AsArray([NotNull] [ValidatedNotNull] this IEnumerable source) => + source as T[] ?? source.ToArray(); /// /// Performs the action on each item of the specified enumerable. If the enumerable contains items that are null, this @@ -65,8 +70,12 @@ public static IList AsList([NotNull, ValidatedNotNull] this IEnumerable /// The value indicating whether this method should throw a when any of the items is null (optional). Defaults to true. /// Thrown when or is null. /// Thrown when contains a value that is null and is set to true. - // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static IEnumerable ForEach([NotNull, ValidatedNotNull] this IEnumerable enumerable, Action action, bool throwWhenItemIsNull = true) + public static IEnumerable ForEach( + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + [NotNull] [ValidatedNotNull] this IEnumerable enumerable, + Action action, + bool throwWhenItemIsNull = true + ) { // ReSharper disable PossibleMultipleEnumeration action.MustNotBeNull(nameof(action)); @@ -79,7 +88,14 @@ public static IEnumerable ForEach([NotNull, ValidatedNotNull] this IEnumer var item = list[i]; if (item is null) { - if (throwWhenItemIsNull) throw new CollectionException(nameof(enumerable), $"The collection contains null at index {i}."); + if (throwWhenItemIsNull) + { + throw new CollectionException( + nameof(enumerable), + $"The collection contains null at index {i}." + ); + } + continue; } @@ -92,7 +108,14 @@ public static IEnumerable ForEach([NotNull, ValidatedNotNull] this IEnumer { if (item is null) { - if (throwWhenItemIsNull) throw new CollectionException(nameof(enumerable), $"The collection contains null at index {i}."); + if (throwWhenItemIsNull) + { + throw new CollectionException( + nameof(enumerable), + $"The collection contains null at index {i}." + ); + } + ++i; continue; } @@ -117,7 +140,7 @@ public static IEnumerable ForEach([NotNull, ValidatedNotNull] this IEnumer [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("source:null => halt; source:notnull => notnull")] // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static IReadOnlyList AsReadOnlyList([NotNull, ValidatedNotNull] this IEnumerable source) => + public static IReadOnlyList AsReadOnlyList([NotNull] [ValidatedNotNull] this IEnumerable source) => source as IReadOnlyList ?? source.ToList(); /// @@ -132,8 +155,12 @@ public static IReadOnlyList AsReadOnlyList([NotNull, ValidatedNotNull] thi [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("source:null => halt; source:notnull => notnull; createCollection:null => halt")] // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static IReadOnlyList AsReadOnlyList([NotNull, ValidatedNotNull] this IEnumerable source, [NotNull, ValidatedNotNull] Func, IReadOnlyList> createCollection) => - source as IReadOnlyList ?? createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); + public static IReadOnlyList AsReadOnlyList( + [NotNull] [ValidatedNotNull] this IEnumerable source, + [NotNull] [ValidatedNotNull] Func, IReadOnlyList> createCollection + ) => + source as IReadOnlyList ?? + createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); // ReSharper restore RedundantNullableFlowAttribute @@ -141,16 +168,21 @@ public static IReadOnlyList AsReadOnlyList([NotNull, ValidatedNotNull] thi /// Gets the count of the specified enumerable. /// /// The enumerable whose count should be determined. - /// Thrown when is null. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("enumerable:null => halt")] // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static int Count([NotNull, ValidatedNotNull] this IEnumerable enumerable) + public static int Count([NotNull] [ValidatedNotNull] this IEnumerable enumerable) { if (enumerable is ICollection collection) + { return collection.Count; + } + if (enumerable is string @string) + { return @string.Length; + } return DetermineCountViaEnumerating(enumerable); } @@ -159,37 +191,54 @@ public static int Count([NotNull, ValidatedNotNull] this IEnumerable enumerable) /// Gets the count of the specified enumerable. /// /// The enumerable whose count should be determined. - /// The name of the parameter that is passed to the (optional). - /// The message that is passed to the (optional). - /// Thrown when is null. + /// The name of the parameter that is passed to the (optional). + /// The message that is passed to the (optional). + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("enumerable:null => halt")] - public static int Count([NotNull, ValidatedNotNull] this IEnumerable? enumerable, string? parameterName, string? message) + public static int Count( + [NotNull] [ValidatedNotNull] this IEnumerable? enumerable, + string? parameterName, + string? message + ) { if (enumerable is ICollection collection) + { return collection.Count; + } + if (enumerable is string @string) + { return @string.Length; + } return DetermineCountViaEnumerating(enumerable, parameterName, message); } - + /// /// Gets the count of the specified enumerable. /// /// The enumerable whose count should be determined. - /// Thrown when is null. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("enumerable:null => halt")] // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests - public static int GetCount([NotNull, ValidatedNotNull] this IEnumerable enumerable) + public static int GetCount([NotNull] [ValidatedNotNull] this IEnumerable enumerable) { if (enumerable is ICollection collection) + { return collection.Count; + } + if (enumerable is string @string) + { return @string.Length; + } + if (TryGetCollectionOfTCount(enumerable, out var count)) + { return count; + } return DetermineCountViaEnumerating(enumerable); } @@ -198,19 +247,31 @@ public static int GetCount([NotNull, ValidatedNotNull] this IEnumerable en /// Gets the count of the specified enumerable. /// /// The enumerable whose count should be determined. - /// The name of the parameter that is passed to the (optional). - /// The message that is passed to the (optional). - /// Thrown when is null. + /// The name of the parameter that is passed to the (optional). + /// The message that is passed to the (optional). + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("enumerable:null => halt")] - public static int GetCount([NotNull, ValidatedNotNull] this IEnumerable enumerable, string? parameterName, string? message = null) + public static int GetCount( + [NotNull] [ValidatedNotNull] this IEnumerable enumerable, + string? parameterName, + string? message = null + ) { if (enumerable is ICollection collection) + { return collection.Count; + } + if (enumerable is string @string) + { return @string.Length; + } + if (TryGetCollectionOfTCount(enumerable, out var count)) + { return count; + } return DetermineCountViaEnumerating(enumerable, parameterName, message); } @@ -222,6 +283,7 @@ private static bool TryGetCollectionOfTCount([NoEnumeration] this IEnumerable count = collectionOfT.Count; return true; } + if (enumerable is IReadOnlyCollection readOnlyCollection) { count = readOnlyCollection.Count; @@ -237,24 +299,36 @@ private static int DetermineCountViaEnumerating(IEnumerable? enumerable) var count = 0; var enumerator = enumerable.MustNotBeNull(nameof(enumerable)).GetEnumerator(); while (enumerator.MoveNext()) + { count++; + } + if (enumerator is IDisposable disposable) { disposable.Dispose(); } + return count; } - private static int DetermineCountViaEnumerating([NotNull] IEnumerable? enumerable, string? parameterName, string? message) + private static int DetermineCountViaEnumerating( + [NotNull] IEnumerable? enumerable, + string? parameterName, + string? message + ) { var count = 0; var enumerator = enumerable.MustNotBeNull(parameterName, message).GetEnumerator(); while (enumerator.MoveNext()) + { count++; + } + if (enumerator is IDisposable disposable) { disposable.Dispose(); } + return count; } @@ -264,9 +338,11 @@ internal static bool ContainsViaForeach(this IEnumerable items, TI foreach (var i in items) { if (equalityComparer.Equals(i, item)) + { return true; + } } return false; } -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses/FrameworkExtensions/ExpressionExtensions.cs b/Code/Light.GuardClauses/FrameworkExtensions/ExpressionExtensions.cs index c695215b..28ff16c9 100644 --- a/Code/Light.GuardClauses/FrameworkExtensions/ExpressionExtensions.cs +++ b/Code/Light.GuardClauses/FrameworkExtensions/ExpressionExtensions.cs @@ -20,14 +20,21 @@ public static class ExpressionExtensions /// /// Throw when the is not of the shape "object => object.Property". /// - // ReSharper disable once RedundantNullableFlowAttribute - NotNull is not redundant, see Issue72NotNullAttributeTests - public static PropertyInfo ExtractProperty([NotNull, ValidatedNotNull] this Expression> expression) + public static PropertyInfo ExtractProperty( + // ReSharper disable once RedundantNullableFlowAttribute - NotNull is not redundant, see Issue72NotNullAttributeTests + [NotNull] [ValidatedNotNull] this Expression> expression + ) { expression.MustNotBeNull(nameof(expression)); var memberExpression = expression.Body as MemberExpression; if (!(memberExpression?.Member is PropertyInfo propertyInfo)) - throw new ArgumentException("The specified expression is not valid. Please use an expression like the following one: o => o.Property", nameof(expression)); + { + throw new ArgumentException( + "The specified expression is not valid. Please use an expression like the following one: o => o.Property", + nameof(expression) + ); + } return propertyInfo; } @@ -42,15 +49,22 @@ public static PropertyInfo ExtractProperty([NotNull, ValidatedNotN /// /// Throw when the is not of the shape "object => object.Field". /// - // ReSharper disable once RedundantNullableFlowAttribute - NotNull is not redundant, see Issue72NotNullAttributeTests - public static FieldInfo ExtractField([NotNull, ValidatedNotNull] this Expression> expression) + public static FieldInfo ExtractField( + // ReSharper disable once RedundantNullableFlowAttribute - NotNull is not redundant, see Issue72NotNullAttributeTests + [NotNull] [ValidatedNotNull] this Expression> expression + ) { expression.MustNotBeNull(nameof(expression)); var memberExpression = expression.Body as MemberExpression; if (!(memberExpression?.Member is FieldInfo fieldInfo)) - throw new ArgumentException("The specified expression is not valid. Please use an expression like the following one: o => o.Field", nameof(expression)); + { + throw new ArgumentException( + "The specified expression is not valid. Please use an expression like the following one: o => o.Field", + nameof(expression) + ); + } return fieldInfo; } -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHash.cs b/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHash.cs index 2e36ae8f..92a28800 100644 --- a/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHash.cs +++ b/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHash.cs @@ -83,7 +83,14 @@ public static int CreateHashCode(T1 value1, T2 value2, T3 va /// Creates a hash code from the six specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -99,7 +106,15 @@ public static int CreateHashCode(T1 value1, T2 value2, T /// Creates a hash code from the seven specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -116,7 +131,16 @@ public static int CreateHashCode(T1 value1, T2 value /// Creates a hash code from the eight specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -134,7 +158,17 @@ public static int CreateHashCode(T1 value1, T2 v /// Creates a hash code from the nine specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -153,7 +187,18 @@ public static int CreateHashCode(T1 value1, /// Creates a hash code from the ten specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -173,7 +218,19 @@ public static int CreateHashCode(T1 val /// Creates a hash code from the eleven specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10, + T11 value11 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -194,7 +251,20 @@ public static int CreateHashCode(T /// Creates a hash code from the eleven specified values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10, + T11 value11, + T12 value12 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -216,7 +286,21 @@ public static int CreateHashCode [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12, T13 value13) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10, + T11 value11, + T12 value12, + T13 value13 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -239,7 +323,22 @@ public static int CreateHashCode [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12, T13 value13, T14 value14) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10, + T11 value11, + T12 value12, + T13 value13, + T14 value14 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -263,7 +362,23 @@ public static int CreateHashCode [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12, T13 value13, T14 value14, T15 value15) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10, + T11 value11, + T12 value12, + T13 value13, + T14 value14, + T15 value15 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -288,7 +403,24 @@ public static int CreateHashCode [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12, T13 value13, T14 value14, T15 value15, T16 value16) + public static int CreateHashCode( + T1 value1, + T2 value2, + T3 value3, + T4 value4, + T5 value5, + T6 value6, + T7 value7, + T8 value8, + T9 value9, + T10 value10, + T11 value11, + T12 value12, + T13 value13, + T14 value14, + T15 value15, + T16 value16 + ) { var hash = FirstPrime; CombineIntoHash(ref hash, value1); @@ -315,5 +447,6 @@ public static int CreateHashCodehash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0);. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CombineIntoHash(ref int hash, T value) => hash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0); -} \ No newline at end of file + public static void CombineIntoHash(ref int hash, T value) => + hash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0); +} diff --git a/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHashBuilder.cs b/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHashBuilder.cs index 7bb438fa..c411eeb5 100644 --- a/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHashBuilder.cs +++ b/Code/Light.GuardClauses/FrameworkExtensions/MultiplyAddHashBuilder.cs @@ -3,10 +3,10 @@ namespace Light.GuardClauses.FrameworkExtensions; /// -/// Represents a builder for the algorithm that does not allocate. +/// Represents a builder for the algorithm that does not allocate. /// Should only be used in cases where the overload for sixteen values is not enough or a dedicated /// initial hash must be provided (e.g. for test reasons). -/// Instantiate the builder with the method. You have to instantiate a new builder +/// Instantiate the builder with the method. You have to instantiate a new builder /// for each hash code that you want to calculate. /// public struct MultiplyAddHashBuilder @@ -17,7 +17,7 @@ public struct MultiplyAddHashBuilder private MultiplyAddHashBuilder(int initialHash) => _hash = initialHash; /// - /// Combines the given value into the hash using the method. + /// Combines the given value into the hash using the method. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public MultiplyAddHashBuilder CombineIntoHash(T value) @@ -33,9 +33,8 @@ public MultiplyAddHashBuilder CombineIntoHash(T value) public int BuildHash() => _hash; /// - /// Initializes a new instance of with the specified initial hash. + /// Initializes a new instance of with the specified initial hash. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MultiplyAddHashBuilder Create(int initialHash = MultiplyAddHash.FirstPrime) => new MultiplyAddHashBuilder(initialHash); - -} \ No newline at end of file + public static MultiplyAddHashBuilder Create(int initialHash = MultiplyAddHash.FirstPrime) => new (initialHash); +} diff --git a/Code/Light.GuardClauses/FrameworkExtensions/TextExtensions.cs b/Code/Light.GuardClauses/FrameworkExtensions/TextExtensions.cs index 10f3b7c7..96901a96 100644 --- a/Code/Light.GuardClauses/FrameworkExtensions/TextExtensions.cs +++ b/Code/Light.GuardClauses/FrameworkExtensions/TextExtensions.cs @@ -35,36 +35,71 @@ public static class TextExtensions typeof(bool), typeof(double), typeof(decimal), - typeof(float) + typeof(float), ] ); private static bool IsUnquotedType() { if (typeof(T) == typeof(int)) + { return true; + } + if (typeof(T) == typeof(long)) + { return true; + } + if (typeof(T) == typeof(short)) + { return true; + } + if (typeof(T) == typeof(sbyte)) + { return true; + } + if (typeof(T) == typeof(uint)) + { return true; + } + if (typeof(T) == typeof(ulong)) + { return true; + } + if (typeof(T) == typeof(ushort)) + { return true; + } + if (typeof(T) == typeof(byte)) + { return true; + } + if (typeof(T) == typeof(bool)) + { return true; + } + if (typeof(T) == typeof(double)) + { return true; + } + if (typeof(T) == typeof(decimal)) + { return true; + } + if (typeof(T) == typeof(float)) + { return true; + } return false; } @@ -77,7 +112,8 @@ private static bool IsUnquotedType() /// The text that is returned when is null (defaults to "null"). [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("=> notnull")] - public static string ToStringOrNull(this T value, string nullText = "null") => value?.ToStringRepresentation() ?? nullText; + public static string ToStringOrNull(this T value, string nullText = "null") => + value?.ToStringRepresentation() ?? nullText; /// /// Returns the string representation of . This is done by calling . If the type of @@ -85,13 +121,15 @@ private static bool IsUnquotedType() /// /// The value whose string representation is requested. [ContractAnnotation("value:null => halt; value:notnull => notnull")] - public static string? ToStringRepresentation([NotNull, ValidatedNotNull] this T value) + public static string? ToStringRepresentation([NotNull] [ValidatedNotNull] this T value) { value.MustNotBeNullReference(nameof(value)); var content = value.ToString(); if (IsUnquotedType() || content.IsNullOrEmpty()) + { return content; + } // ReSharper disable UseIndexFromEndExpression -- not possible in netstandard2.0 if (content.Length <= 126) @@ -123,7 +161,12 @@ private static bool IsUnquotedType() [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; items:null => halt; stringBuilder:notnull => notnull")] // ReSharper disable RedundantNullableFlowAttribute - public static StringBuilder AppendCollectionContent([NotNull, ValidatedNotNull] this StringBuilder stringBuilder, [NotNull, ValidatedNotNull] IEnumerable items, string headerLine = "Content of the collection:", bool finishWithNewLine = true) => + public static StringBuilder AppendCollectionContent( + [NotNull] [ValidatedNotNull] this StringBuilder stringBuilder, + [NotNull] [ValidatedNotNull] IEnumerable items, + string headerLine = "Content of the collection:", + bool finishWithNewLine = true + ) => stringBuilder.MustNotBeNull(nameof(stringBuilder)) .AppendLine(headerLine) .AppendItemsWithNewLine(items, finishWithNewLine: finishWithNewLine); @@ -139,7 +182,12 @@ public static StringBuilder AppendCollectionContent([NotNull, ValidatedNotNul /// Thrown when or is null. [ContractAnnotation("stringBuilder:null => halt; items:null => halt; stringBuilder:notnull => notnull")] // ReSharper disable RedundantNullableFlowAttribute - public static StringBuilder AppendItems([NotNull, ValidatedNotNull] this StringBuilder stringBuilder, [NotNull, ValidatedNotNull] IEnumerable items, string itemSeparator = ", ", string emptyCollectionText = "empty collection") + public static StringBuilder AppendItems( + [NotNull] [ValidatedNotNull] this StringBuilder stringBuilder, + [NotNull] [ValidatedNotNull] IEnumerable items, + string itemSeparator = ", ", + string emptyCollectionText = "empty collection" + ) // ReSharper restore RedundantNullableFlowAttribute { stringBuilder.MustNotBeNull(nameof(stringBuilder)); @@ -148,15 +196,22 @@ public static StringBuilder AppendItems([NotNull, ValidatedNotNull] this Stri var currentIndex = 0; var itemsCount = list.Count; if (itemsCount == 0) + { return stringBuilder.Append(emptyCollectionText); + } while (true) { stringBuilder.Append(list[currentIndex].ToStringOrNull()); if (currentIndex < itemsCount - 1) + { stringBuilder.Append(itemSeparator); + } else + { return stringBuilder; + } + ++currentIndex; } } @@ -172,7 +227,12 @@ public static StringBuilder AppendItems([NotNull, ValidatedNotNull] this Stri [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; items:null => halt; stringBuilder:notnull => notnull")] // ReSharper disable RedundantNullableFlowAttribute - public static StringBuilder AppendItemsWithNewLine([NotNull, ValidatedNotNull] this StringBuilder stringBuilder, [NotNull, ValidatedNotNull] IEnumerable items, string emptyCollectionText = "empty collection", bool finishWithNewLine = true) => + public static StringBuilder AppendItemsWithNewLine( + [NotNull] [ValidatedNotNull] this StringBuilder stringBuilder, + [NotNull] [ValidatedNotNull] IEnumerable items, + string emptyCollectionText = "empty collection", + bool finishWithNewLine = true + ) => stringBuilder.AppendItems(items, DefaultNewLineSeparator, emptyCollectionText) .AppendLineIf(finishWithNewLine); // ReSharper restore RedundantNullableFlowAttribute @@ -186,11 +246,18 @@ public static StringBuilder AppendItemsWithNewLine([NotNull, ValidatedNotNull /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; stringBuilder:notnull => notnull")] - // ReSharper disable once RedundantNullableFlowAttribute - public static StringBuilder AppendIf([NotNull, ValidatedNotNull] this StringBuilder stringBuilder, bool condition, string value) + public static StringBuilder AppendIf( + // ReSharper disable once RedundantNullableFlowAttribute + [NotNull] [ValidatedNotNull] this StringBuilder stringBuilder, + bool condition, + string value + ) { if (condition) + { stringBuilder.MustNotBeNull(nameof(stringBuilder)).Append(value); + } + return stringBuilder; } @@ -203,11 +270,18 @@ public static StringBuilder AppendIf([NotNull, ValidatedNotNull] this StringBuil /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; stringBuilder:notnull => notnull")] - // ReSharper disable once RedundantNullableFlowAttribute - public static StringBuilder AppendLineIf([NotNull, ValidatedNotNull] this StringBuilder stringBuilder, bool condition, string value = "") + public static StringBuilder AppendLineIf( + // ReSharper disable once RedundantNullableFlowAttribute + [NotNull] [ValidatedNotNull] this StringBuilder stringBuilder, + bool condition, + string value = "" + ) { if (condition) + { stringBuilder.MustNotBeNull(nameof(stringBuilder)).AppendLine(value); + } + return stringBuilder; } @@ -217,7 +291,10 @@ public static StringBuilder AppendLineIf([NotNull, ValidatedNotNull] this String /// /// Thrown when any parameter is null. // ReSharper disable RedundantNullableFlowAttribute - public static StringBuilder AppendExceptionMessages([NotNull, ValidatedNotNull] this StringBuilder stringBuilder, [NotNull, ValidatedNotNull] Exception exception) + public static StringBuilder AppendExceptionMessages( + [NotNull] [ValidatedNotNull] this StringBuilder stringBuilder, + [NotNull] [ValidatedNotNull] Exception exception + ) // ReSharper restore RedundantNullableFlowAttribute { stringBuilder.MustNotBeNull(nameof(stringBuilder)); @@ -228,7 +305,9 @@ public static StringBuilder AppendExceptionMessages([NotNull, ValidatedNotNull] // ReSharper disable once PossibleNullReferenceException stringBuilder.AppendLine(exception.Message); if (exception.InnerException is null) + { return stringBuilder; + } stringBuilder.AppendLine(); exception = exception.InnerException; @@ -241,7 +320,7 @@ public static StringBuilder AppendExceptionMessages([NotNull, ValidatedNotNull] /// /// Thrown when is null. // ReSharper disable once RedundantNullableFlowAttribute - public static string GetAllExceptionMessages([NotNull, ValidatedNotNull] this Exception exception) => + public static string GetAllExceptionMessages([NotNull] [ValidatedNotNull] this Exception exception) => new StringBuilder().AppendExceptionMessages(exception).ToString(); /// @@ -251,13 +330,19 @@ public static string GetAllExceptionMessages([NotNull, ValidatedNotNull] this Ex public static bool EqualsOrdinalIgnoreWhiteSpace(this string? x, string? y) { if (ReferenceEquals(x, y)) + { return true; + } if (x is null || y is null) + { return false; + } if (x.Length == 0) + { return y.Length == 0; + } var indexX = 0; var indexY = 0; @@ -271,7 +356,9 @@ public static bool EqualsOrdinalIgnoreWhiteSpace(this string? x, string? y) (wasYSuccessful = y.TryAdvanceToNextNonWhiteSpaceCharacter(ref indexY))) { if (x[indexX++] != y[indexY++]) + { return false; + } } return wasXSuccessful == wasYSuccessful; @@ -284,13 +371,19 @@ public static bool EqualsOrdinalIgnoreWhiteSpace(this string? x, string? y) public static bool EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(this string? x, string? y) { if (ReferenceEquals(x, y)) + { return true; + } if (x is null || y is null) + { return false; + } if (x.Length == 0) + { return y.Length == 0; + } var indexX = 0; var indexY = 0; @@ -304,7 +397,9 @@ public static bool EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(this string? x, strin (wasYSuccessful = y.TryAdvanceToNextNonWhiteSpaceCharacter(ref indexY))) { if (char.ToLowerInvariant(x[indexX++]) != char.ToLowerInvariant(y[indexY++])) + { return false; + } } return wasXSuccessful == wasYSuccessful; @@ -315,11 +410,13 @@ private static bool TryAdvanceToNextNonWhiteSpaceCharacter(this string @string, while (currentIndex < @string.Length) { if (!char.IsWhiteSpace(@string[currentIndex])) + { return true; + } ++currentIndex; } return false; } -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses/Light.GuardClauses.csproj b/Code/Light.GuardClauses/Light.GuardClauses.csproj index 2d7d6dcb..0f55b64d 100644 --- a/Code/Light.GuardClauses/Light.GuardClauses.csproj +++ b/Code/Light.GuardClauses/Light.GuardClauses.csproj @@ -9,7 +9,7 @@ Kenny Pflug enable true - Copyright © Kenny Pflug 2016, 2024 + Copyright © Kenny Pflug 2016, 2025 12 true true @@ -26,18 +26,21 @@ true README.md -Light.GuardClauses 12.0.0 +Light.GuardClauses 13.0.0 -------------------------------- -- Added support for NotNullAttribute -- Added Range.InclusiveBetween and Range.ExclusiveBetween to create Range instances even more easily -- breaking: String assertions that use a StringComparison parameter now throw an ArgumentException instead of forwarding an invalid value to the exception factory +- new assertions: IsApproximately<T>, IsLessThanOrApproximately<T>, IsGreaterThanOrApproximately<T>, MustBeApproximately, MustNotBeApproximately, MustBeLessThanOrApproximately, MustBeGreaterThanOrApproximately, IsEmptyOrWhiteSpace, IsFileExtension +- email regex is now precompiled on .NET 8 and newer, the regex is compiled at runtime on .NET Standard 2.0 and 2.1 +- breaking: Throw class is now located in new namespace Light.GuardClauses.ExceptionFactory +- breaking: Throw members regarding spans now only support ReadOnlySpan<T>, in keywords were removed +- breaking: IsApproximately now uses less-than-or-equal-to operator (<=) instead of less-than operator (<) +- breaking: Email regex is less strict and support additional patterns like domains with more than 3 letters (e.g. .info or .travel) - - + + diff --git a/Code/Light.GuardClauses/OrdinalIgnoreCaseIgnoreWhiteSpaceComparer.cs b/Code/Light.GuardClauses/OrdinalIgnoreCaseIgnoreWhiteSpaceComparer.cs index 5ed7ed66..fc1f2d7b 100644 --- a/Code/Light.GuardClauses/OrdinalIgnoreCaseIgnoreWhiteSpaceComparer.cs +++ b/Code/Light.GuardClauses/OrdinalIgnoreCaseIgnoreWhiteSpaceComparer.cs @@ -5,7 +5,7 @@ namespace Light.GuardClauses; /// -/// Represents an that compares strings using the +/// Represents an that compares strings using the /// ordinal sort rules, ignoring the case and the white space characters. /// public sealed class OrdinalIgnoreCaseIgnoreWhiteSpaceComparer : IEqualityComparer @@ -14,7 +14,7 @@ public sealed class OrdinalIgnoreCaseIgnoreWhiteSpaceComparer : IEqualityCompare /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the case and /// the white space of the provided strings. /// - /// Thrown when or are null. + /// Thrown when or are null. public bool Equals(string? x, string? y) { x.MustNotBeNull(nameof(x)); @@ -27,7 +27,7 @@ public bool Equals(string? x, string? y) /// Gets the hash code for the specified string. The hash code is created only from the non-white space characters /// which are interpreted as case-insensitive. /// - /// Thrown when is null. + /// Thrown when is null. public int GetHashCode(string @string) { @string.MustNotBeNull(nameof(@string)); @@ -37,9 +37,11 @@ public int GetHashCode(string @string) foreach (var character in @string) { if (!character.IsWhiteSpace()) + { hashBuilder.CombineIntoHash(char.ToLowerInvariant(character)); + } } return hashBuilder.BuildHash(); } -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses/OrdinalIgnoreWhiteSpaceComparer.cs b/Code/Light.GuardClauses/OrdinalIgnoreWhiteSpaceComparer.cs index 07cb57c7..7a150e62 100644 --- a/Code/Light.GuardClauses/OrdinalIgnoreWhiteSpaceComparer.cs +++ b/Code/Light.GuardClauses/OrdinalIgnoreWhiteSpaceComparer.cs @@ -5,7 +5,7 @@ namespace Light.GuardClauses; /// -/// Represents an that compares strings using the +/// Represents an that compares strings using the /// ordinal sort rules and ignoring the white space characters. /// public sealed class OrdinalIgnoreWhiteSpaceComparer : IEqualityComparer @@ -14,7 +14,7 @@ public sealed class OrdinalIgnoreWhiteSpaceComparer : IEqualityComparer /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the white space /// of the provided strings. /// - /// Thrown when or are null. + /// Thrown when or are null. public bool Equals(string? x, string? y) { x.MustNotBeNull(nameof(x)); @@ -26,7 +26,7 @@ public bool Equals(string? x, string? y) /// /// Gets the hash code for the specified string. The hash code is created only from the non-white space characters. /// - /// Thrown when is null. + /// Thrown when is null. public int GetHashCode(string @string) { @string.MustNotBeNull(nameof(@string)); @@ -35,9 +35,11 @@ public int GetHashCode(string @string) foreach (var character in @string) { if (!character.IsWhiteSpace()) + { hashCodeBuilder.CombineIntoHash(character); + } } return hashCodeBuilder.BuildHash(); } -} \ No newline at end of file +} diff --git a/Code/Light.GuardClauses/Range.cs b/Code/Light.GuardClauses/Range.cs index 623df259..8ef1f306 100644 --- a/Code/Light.GuardClauses/Range.cs +++ b/Code/Light.GuardClauses/Range.cs @@ -1,8 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; -using Light.GuardClauses.FrameworkExtensions; using System.Runtime.CompilerServices; +using Light.GuardClauses.FrameworkExtensions; namespace Light.GuardClauses; @@ -62,7 +62,7 @@ public Range(T from, T to, bool isFromInclusive = true, bool isToInclusive = tru /// True if value is within range, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsValueWithinRange(T value) => - value.MustNotBeNullReference(nameof(value)).CompareTo(From) >= _expectedLowerBoundaryResult && + value.MustNotBeNullReference(nameof(value)).CompareTo(From) >= _expectedLowerBoundaryResult && value.CompareTo(To) <= _expectedUpperBoundaryResult; /// @@ -72,7 +72,7 @@ public bool IsValueWithinRange(T value) => /// The value that indicates the inclusive lower boundary of the resulting range. /// A value you can use to fluently define the upper boundary of a new range. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RangeFromInfo FromInclusive(T value) => new RangeFromInfo(value, true); + public static RangeFromInfo FromInclusive(T value) => new (value, true); /// /// Use this method to create a range in a fluent style using method chaining. @@ -81,7 +81,7 @@ public bool IsValueWithinRange(T value) => /// The value that indicates the exclusive lower boundary of the resulting range. /// A value you can use to fluently define the upper boundary of a new range. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RangeFromInfo FromExclusive(T value) => new RangeFromInfo(value, false); + public static RangeFromInfo FromExclusive(T value) => new (value, false); /// /// The nested can be used to fluently create a . @@ -113,7 +113,7 @@ public RangeFromInfo(T from, bool isFromInclusive) /// Thrown when is less than the lower boundary value. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Range ToExclusive(T value) => new Range(_from, value, _isFromInclusive, false); + public Range ToExclusive(T value) => new (_from, value, _isFromInclusive, false); /// /// Use this method to create a range in a fluent style using method chaining. @@ -125,15 +125,14 @@ public RangeFromInfo(T from, bool isFromInclusive) /// Thrown when is less than the lower boundary value. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Range ToInclusive(T value) => new Range(_from, value, _isFromInclusive); + public Range ToInclusive(T value) => new (_from, value, _isFromInclusive); } /// - public override string ToString() => - $"Range from {CreateRangeDescriptionText()}"; + public override string ToString() => $"Range from {CreateRangeDescriptionText()}"; /// - /// Returns either "inclusive" or "exclusive", depending on whether is true or false. + /// Returns either "inclusive" or "exclusive", depending on whether is true or false. /// public string LowerBoundaryText { @@ -142,7 +141,7 @@ public string LowerBoundaryText } /// - /// Returns either "inclusive" or "exclusive", depending on whether is true or false. + /// Returns either "inclusive" or "exclusive", depending on whether is true or false. /// public string UpperBoundaryText { @@ -164,7 +163,10 @@ public bool Equals(Range other) { if (IsFromInclusive != other.IsFromInclusive || IsToInclusive != other.IsToInclusive) + { return false; + } + var comparer = EqualityComparer.Default; return comparer.Equals(From, other.From) && comparer.Equals(To, other.To); @@ -173,7 +175,11 @@ public bool Equals(Range other) /// public override bool Equals(object? other) { - if (other is null) return false; + if (other is null) + { + return false; + } + return other is Range range && Equals(range); } @@ -194,7 +200,7 @@ public override bool Equals(object? other) } /// -/// Provides methods to simplify the creation of instances. +/// Provides methods to simplify the creation of instances. /// public static class Range { @@ -243,11 +249,11 @@ public static class Range /// The count of this enumerable will be used to create the index range. Please ensure that this enumerable /// is actually a collection, not a lazy enumerable. /// - /// Thrown when is null. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Range For(IEnumerable enumerable) => - new (0, enumerable.Count(), isFromInclusive: true, isToInclusive: false); - + new (0, enumerable.Count(), true, false); + /// /// Creates a range for the specified enumerable that encompasses all valid indexes. /// @@ -255,10 +261,10 @@ public static Range For(IEnumerable enumerable) => /// The count of this enumerable will be used to create the index range. Please ensure that this enumerable /// is actually a collection, not a lazy enumerable. /// - /// Thrown when is null. + /// Thrown when is null. public static Range For(IEnumerable enumerable) => - new (0, enumerable.GetCount(), isFromInclusive: true, isToInclusive: false); - + new (0, enumerable.GetCount(), true, false); + /// /// Creates a range for the specified span that encompasses all valid indexes. /// @@ -267,8 +273,8 @@ public static Range For(IEnumerable enumerable) => /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Range For(ReadOnlySpan span) => - new (0, span.Length, isFromInclusive: true, isToInclusive: false); - + new (0, span.Length, true, false); + /// /// Creates a range for the specified span that encompasses all valid indexes. /// @@ -277,7 +283,7 @@ public static Range For(ReadOnlySpan span) => /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Range For(Span span) => - new (0 , span.Length, isFromInclusive: true, isToInclusive: false); + new (0, span.Length, true, false); /// /// Creates a range for the specified memory that encompasses all valid indexes. @@ -287,8 +293,8 @@ public static Range For(Span span) => /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Range For(Memory memory) => - new (0, memory.Length, isFromInclusive: true, isToInclusive: false); - + new (0, memory.Length, true, false); + /// /// Creates a range for the specified memory that encompasses all valid indexes. /// @@ -297,8 +303,8 @@ public static Range For(Memory memory) => /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Range For(ReadOnlyMemory memory) => - new (0, memory.Length, isFromInclusive: true, isToInclusive: false); - + new (0, memory.Length, true, false); + /// /// Creates a range for the specified memory that encompasses all valid indexes. /// @@ -307,5 +313,5 @@ public static Range For(ReadOnlyMemory memory) => /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Range For(ArraySegment segment) => - new (0, segment.Count, isFromInclusive: true, isToInclusive: false); + new (0, segment.Count, true, false); } diff --git a/Code/Light.GuardClauses/RegularExpressions.cs b/Code/Light.GuardClauses/RegularExpressions.cs index 62ff37ae..238f17ce 100644 --- a/Code/Light.GuardClauses/RegularExpressions.cs +++ b/Code/Light.GuardClauses/RegularExpressions.cs @@ -5,14 +5,31 @@ namespace Light.GuardClauses; /// /// Provides regular expressions that are used in string assertions. /// +#if NET8_0 +public static partial class RegularExpressions +#else public static class RegularExpressions +#endif { + /// + /// Gets the string that represents the . + /// + // This is an AI-generated regex. I don't have any clue and I find it way to complex to ever understand it. + public const string EmailRegexText = + @"^(?:(?:""(?:(?:[^""\\]|\\.)*)""|[\p{L}\p{N}!#$%&'*+\-/=?^_`{|}~-]+(?:\.[\p{L}\p{N}!#$%&'*+\-/=?^_`{|}~-]+)*)@(?:(?:[A-Za-z0-9](?:[A-Za-z0-9\-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}|(?:\[(?:IPv6:[0-9A-Fa-f:.]+)\])|(?:25[0-5]|2[0-4]\d|[01]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)){3}))$"; + /// /// Gets the default regular expression for email validation. /// This pattern is based on https://www.rhyous.com/2010/06/15/csharp-email-regular-expression/ and /// was modified to satisfy all tests of https://blogs.msdn.microsoft.com/testing123/2009/02/06/email-address-test-cases/. /// - public static readonly Regex EmailRegex = new Regex( - @"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*@((((\w+\-?)+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$", - RegexOptions.CultureInvariant | RegexOptions.ECMAScript); -} \ No newline at end of file + public static readonly Regex EmailRegex = +#if NET8_0 + GenerateEmailRegex(); + + [GeneratedRegex(EmailRegexText, RegexOptions.ECMAScript | RegexOptions.CultureInvariant)] + private static partial Regex GenerateEmailRegex(); +#else + new (EmailRegexText, RegexOptions.ECMAScript | RegexOptions.CultureInvariant | RegexOptions.Compiled); +#endif +} diff --git a/Code/Light.GuardClauses/SpanDelegates.cs b/Code/Light.GuardClauses/SpanDelegates.cs deleted file mode 100644 index 2e3e7d51..00000000 --- a/Code/Light.GuardClauses/SpanDelegates.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Light.GuardClauses; - -/// -/// Represents a delegate that receives a span and a value as parameters and that produces an exception. -/// -public delegate Exception SpanExceptionFactory(Span span, T value); - -/// -/// Represents a delegate that receives a read-only span and a value as parameters and that produces an exception. -/// -public delegate Exception ReadOnlySpanExceptionFactory(ReadOnlySpan span, T value); \ No newline at end of file diff --git a/Code/Light.GuardClauses/StringComparisonType.cs b/Code/Light.GuardClauses/StringComparisonType.cs index f1274988..93768c85 100644 --- a/Code/Light.GuardClauses/StringComparisonType.cs +++ b/Code/Light.GuardClauses/StringComparisonType.cs @@ -4,9 +4,9 @@ /// Specifies the culture, case , and sort rules when comparing strings. /// /// -/// This enum is en extension of , adding +/// This enum is en extension of , adding /// capabilities to ignore white space when making string equality comparisons. -/// See the when +/// See the when /// you want to compare in such a way. /// public enum StringComparisonType @@ -54,5 +54,5 @@ public enum StringComparisonType /// Compare strings using ordinal sort rules, ignoring the case and ignoring the /// white space characters of the strings being compared. /// - OrdinalIgnoreCaseIgnoreWhiteSpace = 7 -} \ No newline at end of file + OrdinalIgnoreCaseIgnoreWhiteSpace = 7, +} diff --git a/Code/Light.GuardClauses/Types.cs b/Code/Light.GuardClauses/Types.cs index 410c4630..f340a057 100644 --- a/Code/Light.GuardClauses/Types.cs +++ b/Code/Light.GuardClauses/Types.cs @@ -11,4 +11,4 @@ public abstract class Types /// Gets the type. /// public static readonly Type FlagsAttributeType = typeof(FlagsAttribute); -} \ No newline at end of file +} diff --git a/Code/Version.props b/Code/Version.props index 1914f2fb..83928e4b 100644 --- a/Code/Version.props +++ b/Code/Version.props @@ -1,5 +1,5 @@  - 12.0.0 + 13.0.0 \ No newline at end of file diff --git a/LICENSE b/LICENSE index 40200061..e3ea1a23 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 - 2023 Kenny Pflug mailto:kenny.pflug@live.de +Copyright (c) 2016 Kenny Pflug mailto:kenny.pflug@live.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Light.GuardClauses.SingleFile.cs b/Light.GuardClauses.SingleFile.cs index 95c3eae9..d984d0b0 100644 --- a/Light.GuardClauses.SingleFile.cs +++ b/Light.GuardClauses.SingleFile.cs @@ -1,11 +1,11 @@ /* ------------------------------ - Light.GuardClauses 10.3.0 + Light.GuardClauses 13.0.0 ------------------------------ License information for Light.GuardClauses The MIT License (MIT) -Copyright (c) 2016, 2023 Kenny Pflug mailto:kenny.pflug@live.de +Copyright (c) 2016, 2025 Kenny Pflug mailto:kenny.pflug@live.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -39,7 +39,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System.Text.RegularExpressions; using JetBrains.Annotations; using Light.GuardClauses.Exceptions; +using Light.GuardClauses.ExceptionFactory; using Light.GuardClauses.FrameworkExtensions; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; #nullable enable annotations namespace Light.GuardClauses @@ -47,1939 +49,2306 @@ namespace Light.GuardClauses /// /// The class provides access to all assertions of Light.GuardClauses. /// + // ReSharper disable once RedundantTypeDeclarationBody -- required for Source Code Transformation internal static class Check { /// - /// Ensures that the specified object reference is not null, or otherwise throws an . + /// Ensures that the specified URI is an absolute one, or otherwise throws a . /// - /// The object reference to be checked. + /// The URI to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not an absolute URI. /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeNull([ValidatedNotNull, NoEnumeration] this T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : class + public static Uri MustBeAbsoluteUri([NotNull][ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter is null) - Throw.ArgumentNull(parameterName, message); + if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri == false) + { + Throw.MustBeAbsoluteUri(parameter, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified object reference is not null, or otherwise throws your custom exception. + /// Ensures that the specified URI is an absolute one, or otherwise throws your custom exception. /// - /// The reference to be checked. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is null. + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when is not an absolute URI, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeNull([ValidatedNotNull, NoEnumeration] this T? parameter, Func exceptionFactory) - where T : class + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeAbsoluteUri([NotNull][ValidatedNotNull] this Uri? parameter, Func exceptionFactory) { - if (parameter is null) - Throw.CustomException(exceptionFactory); + if (parameter is null || parameter.IsAbsoluteUri == false) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the specified parameter is not the default value, or otherwise throws an - /// for reference types, or an for value types. + /// Checks if the given is one of the specified . /// - /// The value to be checked. + /// The item to be checked. + /// The collection that might contain the . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("items:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests + public static bool IsOneOf(this TItem item, [NotNull][ValidatedNotNull] IEnumerable items) + { + if (items is ICollection collection) + { + return collection.Contains(item); + } + + if (items is string @string && item is char character) + { + return @string.IndexOf(character) != -1; + } + + return items.MustNotBeNull(nameof(items)).ContainsViaForeach(item); + } + + /// + /// Ensures that the collection does not contain the specified item, or otherwise throws an . + /// + /// The collection to be checked. + /// The item that must not be part of the collection. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is a reference type and null. - /// Thrown when is a value type and the default value. + /// Thrown when contains . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeDefault([ValidatedNotNull] this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static TCollection MustNotContain([NotNull][ValidatedNotNull] this TCollection? parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where TCollection : class, IEnumerable { - if (default(T)is null) + if (parameter is ICollection collection) { - if (parameter is null) - Throw.ArgumentNull(parameterName, message); + if (collection.Contains(item)) + { + Throw.ExistingItem(parameter, item, parameterName, message); + } + return parameter; } - if (EqualityComparer.Default.Equals(parameter, default !)) - Throw.ArgumentDefault(parameterName, message); + if (parameter.MustNotBeNull(parameterName, message).Contains(item)) + { + Throw.ExistingItem(parameter, item, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified parameter is not the default value, or otherwise throws your custom exception. + /// Ensures that the collection does not contain the specified item, or otherwise throws your custom exception. /// - /// The value to be checked. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is the default value. + /// The collection to be checked. + /// The item that must not be part of the collection. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when contains . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeDefault([ValidatedNotNull] this T parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotContain([NotNull][ValidatedNotNull] this TCollection? parameter, TItem item, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (default(T)is null) + if (parameter is ICollection collection) { - if (parameter is null) - Throw.CustomException(exceptionFactory); + if (collection.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + return parameter; } - if (EqualityComparer.Default.Equals(parameter, default !)) - Throw.CustomException(exceptionFactory); + if (parameter is null || parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + return parameter; } /// - /// Ensures that the specified parameter is not null when is a reference type, or otherwise - /// throws an . PLEASE NOTICE: you should only use this assertion in generic contexts, - /// use by default. + /// Ensures that the string does not contain the specified value, or otherwise throws a . /// - /// The value to be checked for null. + /// The string to be checked. + /// The string that must not be part of . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is a reference type and is null. + /// Thrown when contains . + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeNullReference([ValidatedNotNull, NoEnumeration] this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static string MustNotContain([NotNull][ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (default(T) != null) - return parameter; - if (parameter is null) - Throw.ArgumentNull(parameterName, message); + if (parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) + { + Throw.StringContains(parameter, value, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified parameter is not null when is a reference type, or otherwise - /// throws your custom exception. PLEASE NOTICE: you should only use this assertion in generic contexts, - /// use by default. + /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. /// - /// The value to be checked for null. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is a reference type and is null. + /// The string to be checked. + /// The string that must not be part of . + /// The delegate that creates your custom exception (optional). and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeNullReference([ValidatedNotNull, NoEnumeration] this T parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotContain([NotNull][ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) { - if (default(T) != null) - return parameter; - if (parameter is null) - Throw.CustomException(exceptionFactory); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || parameter.Contains(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; } /// - /// Ensures that can be cast to and returns the cast value, or otherwise throws a . + /// Ensures that the string does not contain the specified value, or otherwise throws a . /// - /// The value to be cast. + /// The string to be checked. + /// The string that must not be part of . + /// One of the enumeration values that specifies the rules for the search. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when cannot be cast to . - /// Thrown when is null. + /// Thrown when contains . + /// Thrown when or is null. + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeOfType([ValidatedNotNull, NoEnumeration] this object? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static string MustNotContain([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNull(parameterName, message)is T castValue) - return castValue; - Throw.InvalidTypeCast(parameter, typeof(T), parameterName, message); - return default; + if (parameter.MustNotBeNull(parameterName, message).IndexOf(value.MustNotBeNull(nameof(value), message), comparisonType) >= 0) + { + Throw.StringContains(parameter, value, comparisonType, parameterName, message); + } + + return parameter; } /// - /// Ensures that can be cast to and returns the cast value, or otherwise throws your custom exception. + /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. /// - /// The value to be cast. - /// The delegate that creates your custom exception. The is passed to this delegate. - /// Your custom exception thrown when cannot be cast to . + /// The string to be checked. + /// The string that must not be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception (optional). , , and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeOfType([ValidatedNotNull, NoEnumeration] this object? parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotContain([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) { - if (parameter is T castValue) - return castValue; - Throw.CustomException(exceptionFactory, parameter); - return default; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || parameter.IndexOf(value, comparisonType) >= 0) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; } /// - /// Checks if the specified value is a valid enum value of its type. This is true when the specified value - /// is one of the constants defined in the enum, or a valid flags combination when the enum type is marked - /// with the . + /// Checks if the specified string is trimmed at the end, i.e. it does not end with + /// white space characters. Inputting an empty string will return true. /// - /// The type of the enum. - /// The enum value to be checked. + /// The string to be checked. + /// + /// The value indicating whether true or false should be returned from this method when the + /// is null. The default value is true. + /// + /// + /// True if the is trimmed at the end, else false. + /// An empty string will result in true. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsValidEnumValue(this T parameter) - where T : Enum => EnumInfo.IsValidEnumValue(parameter); + public static bool IsTrimmedAtEnd(this string? parameter, bool regardNullAsTrimmed = true) => parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtEnd(); /// - /// Ensures that the specified enum value is valid, or otherwise throws an . An enum value - /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type - /// is marked with the . + /// Checks if the specified character span is trimmed at the end, i.e. it does not end with + /// white space characters. Inputting an empty span will return true. /// - /// The type of the enum. - /// The value to be checked. + /// The character span to be checked. + /// + /// True if the is trimmed at the end, else false. + /// An empty span will result in true. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmedAtEnd(this ReadOnlySpan parameter) => parameter.Length == 0 || !parameter[parameter.Length - 1].IsWhiteSpace(); + /// + /// Ensures that the collection has the specified number of items, or otherwise throws an . + /// + /// The collection to be checked. + /// The number of items the collection must have. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is no valid enum value. + /// Thrown when does not have the specified number of items. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustBeValidEnumValue(this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : Enum + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveCount([NotNull][ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where TCollection : class, IEnumerable { - if (!EnumInfo.IsValidEnumValue(parameter)) - Throw.EnumValueNotDefined(parameter, parameterName, message); + if (parameter!.Count(parameterName, message) != count) + { + Throw.InvalidCollectionCount(parameter, count, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified enum value is valid, or otherwise throws your custom exception. An enum value - /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type - /// is marked with the . + /// Ensures that the collection has the specified number of items, or otherwise throws your custom exception. /// - /// The type of the enum. - /// The value to be checked. - /// The delegate that creates your custom exception. The is passed to this delegate. - /// Your custom exception thrown when is no valid enum value, or when is no enum type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static T MustBeValidEnumValue(this T parameter, Func exceptionFactory) - where T : Enum + /// The collection to be checked. + /// The number of items the collection must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified number of items, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveCount([NotNull][ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (!EnumInfo.IsValidEnumValue(parameter)) - Throw.CustomException(exceptionFactory, parameter); + if (parameter is null || parameter.Count() != count) + { + Throw.CustomException(exceptionFactory, parameter, count); + } + return parameter; } /// - /// Checks if the specified GUID is an empty one. - /// - /// The GUID to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsEmpty(this Guid parameter) => parameter == Guid.Empty; - /// - /// Ensures that the specified GUID is not empty, or otherwise throws an . + /// Ensures that the collection is not null or empty, or otherwise throws an . /// - /// The GUID to be checked. + /// The collection to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is an empty GUID. + /// Thrown when has no items. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Guid MustNotBeEmpty(this Guid parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotBeNullOrEmpty([NotNull][ValidatedNotNull] this TCollection? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where TCollection : class, IEnumerable { - if (parameter == Guid.Empty) - Throw.EmptyGuid(parameterName, message); + if (parameter.Count(parameterName, message) == 0) + { + Throw.EmptyCollection(parameterName, message); + } + return parameter; } /// - /// Ensures that the specified GUID is not empty, or otherwise throws your custom exception. + /// Ensures that the collection is not null or empty, or otherwise throws your custom exception. /// - /// The GUID to be checked. + /// The collection to be checked. /// The delegate that creates your custom exception. - /// Your custom exception thrown when is an empty GUID. + /// Thrown when has no items, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static Guid MustNotBeEmpty(this Guid parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustNotBeNullOrEmpty([NotNull][ValidatedNotNull] this TCollection? parameter, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (parameter == Guid.Empty) - Throw.CustomException(exceptionFactory); + if (parameter is null || parameter.Count() == 0) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Checks if the specified is true and throws an in this case. + /// Ensures that the specified string is not null or empty, or otherwise throws an or . /// - /// The condition to be checked. The exception is thrown when it is true. - /// The message that will be passed to the (optional). - /// Thrown when is true. + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is an empty string. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvalidOperation(bool condition, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotBeNullOrEmpty([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (condition) - Throw.InvalidOperation(message); + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + + if (parameter.Length == 0) + { + Throw.EmptyString(parameterName, message); + } + + return parameter; } /// - /// Checks if the specified is true and throws an in this case. + /// Ensures that the specified string is not null or empty, or otherwise throws your custom exception. /// - /// The condition to be checked. The exception is thrown when it is true. - /// The message that will be passed to the . - /// Thrown when is true. + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is an empty string or null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvalidState(bool condition, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static string MustNotBeNullOrEmpty([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { - if (condition) - Throw.InvalidState(message); + if (parameter.IsNullOrEmpty()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; } /// - /// Checks if the specified is true and throws an in this case. + /// Ensures that the URI has one of the specified schemes, or otherwise throws an . /// - /// The condition to be checked. The exception is thrown when it is true. + /// The URI to be checked. + /// One of these strings must be equal to the scheme of the URI. /// The name of the parameter (optional). - /// The message that will be passed to the (optional). - /// Thrown when is true. + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the scheme is not equal to one of the specified schemes. + /// Thrown when is relative and thus has no scheme. + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvalidArgument(bool condition, string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; schemes:null => halt")] + public static Uri MustHaveOneSchemeOf([NotNull][ValidatedNotNull] this Uri? parameter, IEnumerable schemes, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (condition) - Throw.Argument(parameterName, message); - } + // ReSharper disable PossibleMultipleEnumeration + parameter.MustBeAbsoluteUri(parameterName, message); + if (schemes is ICollection collection) + { + if (!collection.Contains(parameter.Scheme)) + { + Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); + } - /// - /// Checks if the specified is true and throws your custom exception in this case. - /// - /// The condition to be checked. The exception is thrown when it is true. - /// The delegate that creates your custom exception. - /// Your custom exception thrown when is true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static void InvalidArgument(bool condition, Func exceptionFactory) - { - if (condition) - Throw.CustomException(exceptionFactory); + return parameter; + } + + if (!schemes.MustNotBeNull(nameof(schemes), message).Contains(parameter.Scheme)) + { + Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); + } + + return parameter; + // ReSharper restore PossibleMultipleEnumeration } /// - /// Checks if the specified is true and throws your custom exception in this case. + /// Ensures that the URI has one of the specified schemes, or otherwise throws your custom exception. /// - /// The condition to be checked. The exception is thrown when it is true. - /// The value that is checked in the . This value is passed to the . - /// The delegate that creates your custom exception. The is passed to this delegate. - /// Your custom exception thrown when is true. + /// The URI to be checked. + /// One of these strings must be equal to the scheme of the URI. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the scheme is not equal to one of the specified schemes, or when is a relative URI, or when is null. + /// Thrown when is null. + /// The type of the collection containing the schemes. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static void InvalidArgument(bool condition, T parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveOneSchemeOf([NotNull][ValidatedNotNull] this Uri? parameter, TCollection schemes, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (condition) - Throw.CustomException(exceptionFactory, parameter); + if (parameter is null || !parameter.IsAbsoluteUri) + { + Throw.CustomException(exceptionFactory, parameter, schemes); + } + + if (schemes is ICollection collection) + { + if (!collection.Contains(parameter.Scheme)) + { + Throw.CustomException(exceptionFactory, parameter, schemes); + } + + return parameter; + } + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (schemes is null || !schemes.Contains(parameter.Scheme)) + { + Throw.CustomException(exceptionFactory, parameter, schemes!); + } + + return parameter; } /// - /// Ensures that the specified nullable has a value and returns it, or otherwise throws a . + /// Ensures that the specified is not less than the given value, or otherwise throws an . /// - /// The nullable to be checked. + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has no value. + /// Thrown when the specified is less than . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustHaveValue([NoEnumeration] this T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : struct + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeGreaterThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (!parameter.HasValue) - Throw.NullableHasNoValue(parameterName, message); - return parameter.Value; + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) + { + Throw.MustBeGreaterThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Ensures that the specified nullable has a value and returns it, or otherwise throws your custom exception. + /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. /// - /// The nullable to be checked. - /// The delegate that creates your custom exception. - /// Thrown when has no value. + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static T MustHaveValue([NoEnumeration] this T? parameter, Func exceptionFactory) - where T : struct + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeGreaterThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable { - if (!parameter.HasValue) - Throw.CustomException(exceptionFactory); - return parameter.Value; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) < 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; } /// - /// Checks if and point to the same object. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - // ReSharper disable StringLiteralTypo - [ContractAnnotation("parameter:notNull => true, other:notnull; parameter:notNull => false, other:canbenull; other:notnull => true, parameter:notnull; other:notnull => false, parameter:canbenull")] - // ReSharper restore StringLiteralTypo - public static bool IsSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other) - where T : class => ReferenceEquals(parameter, other); - /// - /// Ensures that and do not point to the same object instance, or otherwise - /// throws a . + /// Ensures that the collection has at most the specified number of items, or otherwise throws an . /// - /// The first reference to be checked. - /// The second reference to be checked. + /// The collection to be checked. + /// The number of items the collection should have at most. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when both and point to the same object. + /// Thrown when does not contain at most the specified number of items. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : class + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMaximumCount([NotNull][ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where TCollection : class, IEnumerable { - if (ReferenceEquals(parameter, other)) - Throw.SameObjectReference(parameter, parameterName, message); + if (parameter.Count(parameterName, message) > count) + { + Throw.InvalidMaximumCollectionCount(parameter, count, parameterName, message); + } + return parameter; } /// - /// Ensures that and do not point to the same object instance, or otherwise - /// throws your custom exception. + /// Ensures that the collection has at most the specified number of items, or otherwise throws your custom exception. /// - /// The first reference to be checked. - /// The second reference to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Thrown when both and point to the same object. + /// The collection to be checked. + /// The number of items the collection should have at most. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain at most the specified number of items, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, T? other, Func exceptionFactory) - where T : class + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMaximumCount([NotNull][ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (ReferenceEquals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter); + if (parameter is null || parameter.Count() > count) + { + Throw.CustomException(exceptionFactory, parameter, count); + } + return parameter; } /// - /// Ensures that is equal to using the default equality comparer, or otherwise throws a . + /// Checks if the specified character is a digit. /// - /// The first value to be compared. - /// The other value to be compared. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDigit(this char character) => char.IsDigit(character); + /// + /// Checks if the specified is true and throws an in this case. + /// + /// The condition to be checked. The exception is thrown when it is true. + /// The message that will be passed to the . + /// Thrown when is true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidState(bool condition, string? message = null) + { + if (condition) + { + Throw.InvalidState(message); + } + } + + /// + /// Checks if the specified GUID is an empty one. + /// + /// The GUID to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmpty(this Guid parameter) => parameter == Guid.Empty; + /// + /// Ensures that the string is a valid email address using the default email regular expression + /// defined in , or otherwise throws an . + /// + /// The email address that will be validated. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are not equal. + /// Thrown when is no valid email address. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustBe(this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeEmailAddress([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (!EqualityComparer.Default.Equals(parameter, other)) - Throw.ValuesNotEqual(parameter, other, parameterName, message); + if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress()) + { + Throw.InvalidEmailAddress(parameter, parameterName, message); + } + return parameter; } /// - /// Ensures that is equal to using the default equality comparer, or otherwise throws your custom exception. + /// Ensures that the string is a valid email address using the default email regular expression + /// defined in , or otherwise throws your custom exception. /// - /// The first value to be compared. - /// The other value to be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when and are not equal. + /// The email address that will be validated. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or no valid email address. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustBe(this T parameter, T other, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeEmailAddress([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { - if (!EqualityComparer.Default.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other); + if (!parameter.IsEmailAddress()) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that is equal to using the specified equality comparer, or otherwise throws a . + /// Ensures that the string is a valid email address using the provided regular expression, + /// or otherwise throws an . /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. + /// The email address that will be validated. + /// The regular expression that determines if the input string is a valid email. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are not equal. - /// Thrown when is null. + /// Thrown when is no valid email address. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustBe(this T parameter, T other, IEqualityComparer equalityComparer, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] + public static string MustBeEmailAddress([NotNull][ValidatedNotNull] this string? parameter, Regex emailAddressPattern, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (!equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) - Throw.ValuesNotEqual(parameter, other, parameterName, message); + if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress(emailAddressPattern)) + { + Throw.InvalidEmailAddress(parameter, parameterName, message); + } + return parameter; } /// - /// Ensures that is equal to using the specified equality comparer, or otherwise throws your custom exception. + /// Ensures that the string is a valid email address using the provided regular expression, + /// or otherwise throws your custom exception. /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// Your custom exception thrown when and are not equal, or when is null. + /// The email address that will be validated. + /// The regular expression that determines if the input string is a valid email. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or no valid email address. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustBe(this T parameter, T other, IEqualityComparer equalityComparer, Func, Exception> exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] + public static string MustBeEmailAddress([NotNull][ValidatedNotNull] this string? parameter, Regex emailAddressPattern, Func exceptionFactory) { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (equalityComparer is null || !equalityComparer.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); + if (emailAddressPattern is null || !parameter.IsEmailAddress(emailAddressPattern)) + { + Throw.CustomException(exceptionFactory, parameter, emailAddressPattern!); + } + return parameter; } /// - /// Ensures that is not equal to using the default equality comparer, or otherwise throws a . + /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . /// - /// The first value to be compared. - /// The other value to be compared. + /// The string to be checked. + /// The other string that must not contain . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are equal. + /// Thrown when contains . + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustNotBe(this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (EqualityComparer.Default.Equals(parameter, other)) - Throw.ValuesEqual(parameter, other, parameterName, message); + if (value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) + { + Throw.Substring(parameter, value, parameterName, message); + } + return parameter; } /// - /// Ensures that is not equal to using the default equality comparer, or otherwise throws your custom exception. + /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. /// - /// The first value to be compared. - /// The other value to be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when and are equal. + /// The string to be checked. + /// The other string that must not contain . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T MustNotBe(this T parameter, T other, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) { - if (EqualityComparer.Default.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || value.Contains(parameter)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; } /// - /// Ensures that is not equal to using the specified equality comparer, or otherwise throws a . + /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. + /// The string to be checked. + /// The other string that must not contain . + /// One of the enumeration values that specifies the rules for the search. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when and are equal. - /// Thrown when is null. + /// Thrown when contains . + /// Thrown when or is null. + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustNotBe(this T parameter, T other, IEqualityComparer equalityComparer, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) - Throw.ValuesEqual(parameter, other, parameterName, message); + value.MustNotBeNull(nameof(value), message); + parameter.MustNotBeNull(parameterName, message); + if (value.IndexOf(parameter, comparisonType) != -1) + { + Throw.Substring(parameter, value, comparisonType, parameterName, message); + } + return parameter; } /// - /// Ensures that is not equal to using the specified equality comparer, or otherwise throws your custom exception. + /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. /// - /// The first value to be compared. - /// The other value to be compared. - /// The equality comparer used for comparing the two values. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// Your custom exception thrown when and are equal, or when is null. + /// The string to be checked. + /// The other string that must not contain . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when contains , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("equalityComparer:null => halt")] - public static T MustNotBe(this T parameter, T other, IEqualityComparer equalityComparer, Func, Exception> exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustNotBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (equalityComparer is null || equalityComparer.Equals(parameter, other)) - Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); + if (parameter is null || value is null || value.IndexOf(parameter, comparisonType) != -1) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; } /// - /// Checks if the specified value is approximately the same as the other value, using the given tolerance. + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - /// The first value to be compared. - /// The second value to be compared. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if and are equal or if their absolute difference - /// is smaller than the given , otherwise false. - /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is not + /// less than 0.0001. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this double value, double other, double tolerance) => Math.Abs(value - other) < tolerance; + public static double MustBeApproximately(this double parameter, double other, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) => parameter.MustBeApproximately(other, 0.0001, parameterName, message); /// - /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001. + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - /// The first value to be compared. - /// The second value to be compared. - /// - /// True if and are equal or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is not + /// less than 0.0001. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this double value, double other) => Math.Abs(value - other) < 0.0001; + public static double MustBeApproximately(this double parameter, double other, Func exceptionFactory) + { + if (!parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + /// - /// Checks if the specified value is approximately the same as the other value, using the given tolerance. + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws an . /// - /// The first value to compare. - /// The second value to compare. + /// The value to be checked. + /// The value that should be approximately equal to. /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if and are equal or if their absolute difference - /// is smaller than the given , otherwise false. - /// + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is not + /// less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this float value, float other, float tolerance) => Math.Abs(value - other) < tolerance; + public static double MustBeApproximately(this double parameter, double other, double tolerance, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + /// - /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001f. - /// - /// The first value to be compared. - /// The second value to be compared. - /// - /// True if and are equal or if their absolute difference - /// is smaller than 0.0001f, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsApproximately(this float value, float other) => Math.Abs(value - other) < 0.0001f; - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. - /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is greater than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this double value, double other, double tolerance) => value > other || value.IsApproximately(other, tolerance); - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is greater than or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this double value, double other) => value > other || value.IsApproximately(other); - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws your custom exception. /// - /// The first value to compare. - /// The second value to compare. + /// The value to be checked. + /// The value that should be approximately equal to. /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is greater than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this float value, float other, float tolerance) => value > other || value.IsApproximately(other, tolerance); - /// - /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001f. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is greater than or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// Your custom exception thrown when the absolute difference between and + /// is not less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsGreaterThanOrApproximately(this float value, float other) => value > other || value.IsApproximately(other); + public static double MustBeApproximately(this double parameter, double other, double tolerance, Func exceptionFactory) + { + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + /// - /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . /// - /// The first value to compare. - /// The second value to compare. - /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is less than or if their absolute difference - /// is smaller than the given , otherwise false. - /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when the absolute difference between and is + /// not less than 0.0001f. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this double value, double other, double tolerance) => value < other || value.IsApproximately(other, tolerance); + public static float MustBeApproximately(this float parameter, float other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustBeApproximately(other, 0.0001f, parameterName, message); /// - /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001. + /// Ensures that the specified is approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is less than or if their absolute difference - /// is smaller than 0.0001, otherwise false. - /// + /// The value to be checked. + /// The value that should be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is not + /// less than 0.0001. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this double value, double other) => value < other || value.IsApproximately(other); + public static float MustBeApproximately(this float parameter, float other, Func exceptionFactory) + { + if (!parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + /// - /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws an . /// - /// The first value to compare. - /// The second value to compare. + /// The value to be checked. + /// The value that should be approximately equal to. /// The tolerance indicating how much the two values may differ from each other. - /// - /// True if is less than or if their absolute difference - /// is smaller than the given , otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this float value, float other, float tolerance) => value < other || value.IsApproximately(other, tolerance); - /// - /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001f. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// True if is less than or if their absolute difference - /// is smaller than 0.0001f, otherwise false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLessThanOrApproximately(this float value, float other) => value < other || value.IsApproximately(other); - /* - * ------------------------------------- - * Must Not Be Less Than - * Must Be Greater Than or Equal To - * ------------------------------------- - */ - /// - /// Ensures that the specified is not less than the given value, or otherwise throws an . - /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than . - /// Thrown when is null. + /// + /// Thrown when the absolute difference between and is not + /// less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeLessThan([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + public static float MustBeApproximately(this float parameter, float other, float tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) - Throw.MustNotBeLessThan(parameter, other, parameterName, message); + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. + /// Ensures that the specified is approximately equal to the given + /// value, or otherwise throws your custom exception. /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than , or when is null. + /// The value to be checked. + /// The value that should be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , , and + /// are passed to this delegate. + /// + /// + /// Your custom exception thrown when the absolute difference between and + /// is not less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeLessThan([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + public static float MustBeApproximately(this float parameter, float other, float tolerance, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) < 0) - Throw.CustomException(exceptionFactory, parameter!, other); + if (!parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + return parameter; } /// - /// Ensures that the specified is not less than the given value, or otherwise throws an . + /// Ensures that the string ends with the specified value, or otherwise throws a . /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . + /// The string to be checked. + /// The other string must end with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeGreaterThanOrEqualTo([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + /// Thrown when does not end with . + /// Thrown when or is null. + /// Thrown when is not a valid value. + public static string MustEndWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType = StringComparison.CurrentCulture, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) - Throw.MustBeGreaterThanOrEqualTo(parameter, other, parameterName, message); + if (!parameter.MustNotBeNull(parameterName, message).EndsWith(value, comparisonType)) + { + Throw.StringDoesNotEndWith(parameter, value, comparisonType, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. + /// Ensures that the string ends with the specified value, or otherwise throws a . /// - /// The comparable to be checked. - /// The boundary value that must be less than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than , or when is null. + /// The string to be checked. + /// The other string must end with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not end with , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeGreaterThanOrEqualTo([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustEndWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, [NotNull, ValidatedNotNull] Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) < 0) - Throw.CustomException(exceptionFactory, parameter!, other); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !parameter.EndsWith(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; } - /* - * ------------------------------------- - * Must Be Less Than - * Must Not Be Greater Than or Equal To - * ------------------------------------- - */ /// - /// Ensures that the specified is less than the given value, or otherwise throws an . + /// Ensures that the string ends with the specified value, or otherwise throws a . /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is not less than . - /// Thrown when is null. + /// The string to be checked. + /// The other string must end with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not end with , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeLessThan([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustEndWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType, [NotNull, ValidatedNotNull] Func exceptionFactory) { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) - Throw.MustBeLessThan(parameter, other, parameterName, message); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !parameter.EndsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; } /// - /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. + /// Checks if the string contains the specified value using the given comparison type. /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is not less than , or when is null. + /// The string to be checked. + /// The other string. + /// One of the enumeration values that specifies the rules for the search. + /// True if contains , else false. + /// Thrown when or is null. + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeLessThan([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) >= 0) - Throw.CustomException(exceptionFactory, parameter!, other); - return parameter; - } - + [ContractAnnotation("string:null => halt; value:null => halt")] + public static bool Contains(// ReSharper disable once RedundantNullableFlowAttribute -- Caller might have NRTs turned off + [NotNull][ValidatedNotNull] this string @string, string value, StringComparison comparisonType) => @string.MustNotBeNull(nameof(@string)).IndexOf(value.MustNotBeNull(nameof(value)), comparisonType) >= 0; /// - /// Ensures that the specified is less than the given value, or otherwise throws an . + /// Ensures that and do not point to the same object instance, or otherwise + /// throws a . /// - /// The comparable to be checked. - /// The boundary value that must be greater than . + /// The first reference to be checked. + /// The second reference to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is not less than . - /// Thrown when is null. + /// Thrown when both and point to the same object. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeGreaterThanOrEqualTo([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : class { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) - Throw.MustNotBeGreaterThanOrEqualTo(parameter, other, parameterName, message); + if (ReferenceEquals(parameter, other)) + { + Throw.SameObjectReference(parameter, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. + /// Ensures that and do not point to the same object instance, or otherwise + /// throws your custom exception. /// - /// The comparable to be checked. - /// The boundary value that must be greater than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is not less than , or when is null. + /// The first reference to be checked. + /// The second reference to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Thrown when both and point to the same object. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeGreaterThanOrEqualTo([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + public static T? MustNotBeSameAs([NoEnumeration] this T? parameter, T? other, Func exceptionFactory) + where T : class { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) >= 0) - Throw.CustomException(exceptionFactory, parameter!, other); + if (ReferenceEquals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } - /* - * ------------------------------------- - * Must Be Greater Than - * Must Not Be Less Than or Equal To - * ------------------------------------- - */ /// - /// Ensures that the specified is greater than the given value, or otherwise throws an . + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - /// The comparable to be checked. - /// The boundary value that must be less than . + /// The value to be checked. + /// The value that should not be approximately equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than or equal to . - /// Thrown when is null. + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeGreaterThan([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) - Throw.MustBeGreaterThan(parameter, other, parameterName, message); - return parameter; - } - + public static double MustNotBeApproximately(this double parameter, double other, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) => parameter.MustNotBeApproximately(other, 0.0001, parameterName, message); /// - /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - /// The comparable to be checked. - /// The boundary value that must be less than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than or equal to , or when is null. + /// The value to be checked. + /// The value that should not be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeGreaterThan([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + public static double MustNotBeApproximately(this double parameter, double other, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) <= 0) - Throw.CustomException(exceptionFactory, parameter!, other); + if (parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + return parameter; } /// - /// Ensures that the specified is greater than the given value, or otherwise throws an . + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws an . /// - /// The comparable to be checked. - /// The boundary value that must be less than . + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is less than or equal to . - /// Thrown when is null. + /// + /// Thrown when the absolute difference between and is + /// less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeLessThanOrEqualTo([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + public static double MustNotBeApproximately(this double parameter, double other, double tolerance, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) - Throw.MustNotBeLessThanOrEqualTo(parameter, other, parameterName, message); + if (parameter.IsApproximately(other, tolerance)) + { + Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws your custom exception. /// - /// The comparable to be checked. - /// The boundary value that must be less than . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is less than or equal to , or when is null. + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// Your custom exception thrown when the absolute difference between and + /// is less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeLessThanOrEqualTo([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + public static double MustNotBeApproximately(this double parameter, double other, double tolerance, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) <= 0) - Throw.CustomException(exceptionFactory, parameter!, other); + if (parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + return parameter; } - /* - * ------------------------------------- - * Must Not Be Greater Than - * Must Be Less Than or Equal To - * ------------------------------------- - */ /// - /// Ensures that the specified is not greater than the given value, or otherwise throws an . + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . + /// The value to be checked. + /// The value that should not be approximately equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is greater than . - /// Thrown when is null. + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001f. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeGreaterThan([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable - { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) - Throw.MustNotBeGreaterThan(parameter, other, parameterName, message); - return parameter; - } - + public static float MustNotBeApproximately(this float parameter, float other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustNotBeApproximately(other, 0.0001f, parameterName, message); /// - /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. + /// Ensures that the specified is not approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is greater than , or when is null. + /// The value to be checked. + /// The value that should not be approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when the absolute difference between and is + /// less than 0.0001. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeGreaterThan([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + public static float MustNotBeApproximately(this float parameter, float other, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) > 0) - Throw.CustomException(exceptionFactory, parameter!, other); + if (parameter.IsApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + return parameter; } /// - /// Ensures that the specified is not greater than the given value, or otherwise throws an . + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws an . /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the specified is greater than . - /// Thrown when is null. + /// + /// Thrown when the absolute difference between and is + /// less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeLessThanOrEqualTo([ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + public static float MustNotBeApproximately(this float parameter, float other, float tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) - Throw.MustBeLessThanOrEqualTo(parameter, other, parameterName, message); + if (parameter.IsApproximately(other, tolerance)) + { + Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. + /// Ensures that the specified is not approximately equal to the given + /// value, or otherwise throws your custom exception. /// - /// The comparable to be checked. - /// The boundary value that must be greater than or equal to . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when the specified is greater than , or when is null. + /// The value to be checked. + /// The value that should not be approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , , and + /// are passed to this delegate. + /// + /// + /// Your custom exception thrown when the absolute difference between and + /// is less than . + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeLessThanOrEqualTo([ValidatedNotNull] this T parameter, T other, Func exceptionFactory) - where T : IComparable + public static float MustNotBeApproximately(this float parameter, float other, float tolerance, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || parameter.CompareTo(other) > 0) - Throw.CustomException(exceptionFactory, parameter!, other); + if (parameter.IsApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + return parameter; } - /* - * ------------------------------------- - * Ranges - * ------------------------------------- - */ - /// - /// Checks if the value is within the specified range. - /// - /// The comparable to be checked. - /// The range where must be in-between. - /// True if the parameter is within the specified range, else false. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsIn([ValidatedNotNull] this T parameter, Range range) - where T : IComparable => range.IsValueWithinRange(parameter); /// - /// Checks if the value is not within the specified range. - /// - /// The comparable to be checked. - /// The range where must not be in-between. - /// True if the parameter is not within the specified range, else false. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsNotIn([ValidatedNotNull] this T parameter, Range range) - where T : IComparable => !range.IsValueWithinRange(parameter); - /// - /// Ensures that is within the specified range, or otherwise throws an . + /// Ensures that the specified nullable has a value and returns it, or otherwise throws a . /// - /// The type of the parameter to be checked. - /// The parameter to be checked. - /// The range where must be in-between. + /// The nullable to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not within . - /// Thrown when is null. + /// Thrown when has no value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustBeIn([ValidatedNotNull] this T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + public static T MustHaveValue([NotNull, NoEnumeration] this T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : struct { - if (!range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) - Throw.MustBeInRange(parameter, range, parameterName, message); - return parameter; + if (!parameter.HasValue) + { + Throw.NullableHasNoValue(parameterName, message); + } + + return parameter.Value; } /// - /// Ensures that is within the specified range, or otherwise throws your custom exception. + /// Ensures that the specified nullable has a value and returns it, or otherwise throws your custom exception. /// - /// The parameter to be checked. - /// The range where must be in-between. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is not within , or when is null. + /// The nullable to be checked. + /// The delegate that creates your custom exception. + /// Thrown when has no value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustBeIn([ValidatedNotNull] this T parameter, Range range, Func, Exception> exceptionFactory) - where T : IComparable + [ContractAnnotation("exceptionFactory:null => halt")] + public static T MustHaveValue([NotNull, NoEnumeration] this T? parameter, Func exceptionFactory) + where T : struct { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || !range.IsValueWithinRange(parameter)) - Throw.CustomException(exceptionFactory, parameter!, range); - return parameter; + if (!parameter.HasValue) + { + Throw.CustomException(exceptionFactory); + } + + return parameter.Value; } /// - /// Ensures that is not within the specified range, or otherwise throws an . + /// Ensures that the string is longer than the specified length, or otherwise throws a . /// - /// The type of the parameter to be checked. - /// The parameter to be checked. - /// The range where must not be in-between. + /// The string to be checked. + /// The length that the string must be longer than. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is within . + /// Thrown when has a length shorter than or equal to . /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static T MustNotBeIn([ValidatedNotNull] this T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable + public static string MustBeLongerThan([NotNull][ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) - Throw.MustNotBeInRange(parameter, range, parameterName, message); + if (parameter.MustNotBeNull(parameterName, message).Length <= length) + { + Throw.StringNotLongerThan(parameter, length, parameterName, message); + } + return parameter; } /// - /// Ensures that is not within the specified range, or otherwise throws your custom exception. + /// Ensures that the string is longer than the specified length, or otherwise throws your custom exception. /// - /// The parameter to be checked. - /// The range where must not be in-between. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is within , or when is null. + /// The string to be checked. + /// The length that the string must be longer than. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length shorter than or equal to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static T MustNotBeIn([ValidatedNotNull] this T parameter, Range range, Func, Exception> exceptionFactory) - where T : IComparable + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeLongerThan([NotNull][ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (parameter is null || range.IsValueWithinRange(parameter)) - Throw.CustomException(exceptionFactory, parameter!, range); + if (parameter is null || parameter.Length <= length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the specified uses , or otherwise throws an . + /// Ensures that the span is longer than the specified length, or otherwise throws an . /// - /// The date time to be checked. + /// The span to be checked. + /// The value that the span must be longer than. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not use . + /// Thrown when is shorter than or equal to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime MustBeUtc(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static Span MustBeLongerThan(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Kind != DateTimeKind.Utc) - Throw.MustBeUtcDateTime(parameter, parameterName, message); + ((ReadOnlySpan)parameter).MustBeLongerThan(length, parameterName, message); return parameter; } /// - /// Ensures that the specified uses , or otherwise throws your custom exception. + /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. /// - /// The date time to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when does not use . + /// The span to be checked. + /// The length value that the span must be longer than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than or equal to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static DateTime MustBeUtc(this DateTime parameter, Func exceptionFactory) + public static Span MustBeLongerThan(this Span parameter, int length, SpanExceptionFactory exceptionFactory) { - if (parameter.Kind != DateTimeKind.Utc) - Throw.CustomException(exceptionFactory, parameter); + if (parameter.Length <= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the specified uses , or otherwise throws an . + /// Ensures that the span is longer than the specified length, or otherwise throws an . /// - /// The date time to be checked. + /// The span to be checked. + /// The value that the span must be longer than. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not use . + /// Thrown when is shorter than or equal to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime MustBeLocal(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static ReadOnlySpan MustBeLongerThan(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Kind != DateTimeKind.Local) - Throw.MustBeLocalDateTime(parameter, parameterName, message); + if (parameter.Length <= length) + { + Throw.SpanMustBeLongerThan(parameter, length, parameterName, message); + } + return parameter; } /// - /// Ensures that the specified uses , or otherwise throws your custom exception. + /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. /// - /// The date time to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when does not use . + /// The span to be checked. + /// The length value that the span must be longer than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than or equal to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static DateTime MustBeLocal(this DateTime parameter, Func exceptionFactory) + public static ReadOnlySpan MustBeLongerThan(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) { - if (parameter.Kind != DateTimeKind.Local) - Throw.CustomException(exceptionFactory, parameter); + if (parameter.Length <= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the specified uses , or otherwise throws an . + /// Ensures that the specified object reference is not null, or otherwise throws an . /// - /// The date time to be checked. + /// The object reference to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not use . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime MustBeUnspecified(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeNull([NotNull, ValidatedNotNull, NoEnumeration] this T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : class { - if (parameter.Kind != DateTimeKind.Unspecified) - Throw.MustBeUnspecifiedDateTime(parameter, parameterName, message); + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + return parameter; } /// - /// Ensures that the specified uses , or otherwise throws your custom exception. + /// Ensures that the specified object reference is not null, or otherwise throws your custom exception. /// - /// The date time to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when does not use . + /// The reference to be checked. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("exceptionFactory:null => halt")] - public static DateTime MustBeUnspecified(this DateTime parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeNull([NotNull, ValidatedNotNull, NoEnumeration] this T? parameter, Func exceptionFactory) + where T : class { - if (parameter.Kind != DateTimeKind.Unspecified) - Throw.CustomException(exceptionFactory, parameter); + if (parameter is null) + { + Throw.CustomException(exceptionFactory); + } + return parameter; } /// - /// Ensures that the collection has the specified number of items, or otherwise throws an . + /// Checks if the specified is true and throws an in this case. /// - /// The collection to be checked. - /// The number of items the collection must have. + /// The condition to be checked. The exception is thrown when it is true. /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not have the specified number of items. - /// Thrown when is null. + /// The message that will be passed to the (optional). + /// Thrown when is true. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveCount([ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where TCollection : class, IEnumerable + public static void InvalidArgument(bool condition, string? parameterName = null, string? message = null) { - if (parameter!.Count(parameterName, message) != count) - Throw.InvalidCollectionCount(parameter!, count, parameterName, message); - return parameter!; + if (condition) + { + Throw.Argument(parameterName, message); + } } /// - /// Ensures that the collection has the specified number of items, or otherwise throws your custom exception. + /// Checks if the specified is true and throws your custom exception in this case. /// - /// The collection to be checked. - /// The number of items the collection must have. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not have the specified number of items, or when is null. + /// The condition to be checked. The exception is thrown when it is true. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is true. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveCount([ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) - where TCollection : class, IEnumerable + [ContractAnnotation("exceptionFactory:null => halt")] + public static void InvalidArgument(bool condition, Func exceptionFactory) { - if (parameter is null || parameter.Count() != count) - Throw.CustomException(exceptionFactory, parameter, count); - return parameter; + if (condition) + { + Throw.CustomException(exceptionFactory); + } } /// - /// Checks if the specified collection is null or empty. + /// Checks if the specified is true and throws your custom exception in this case. /// - /// The collection to be checked. - /// True if the collection is null or empty, else false. + /// The condition to be checked. The exception is thrown when it is true. + /// The value that is checked in the . This value is passed to the . + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when is true. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> true, collection:canbenull; => false, collection:notnull")] - public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? collection) => collection is null || collection.Count() == 0; + [ContractAnnotation("exceptionFactory:null => halt")] + public static void InvalidArgument(bool condition, T parameter, Func exceptionFactory) + { + if (condition) + { + Throw.CustomException(exceptionFactory, parameter); + } + } + /// - /// Ensures that the collection is not null or empty, or otherwise throws an . + /// Checks if the specified character is a letter or digit. /// - /// The collection to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLetterOrDigit(this char character) => char.IsLetterOrDigit(character); + /// + /// Checks if the given is equal to the specified or if it implements it. Internally, this + /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// + /// The type to be checked. + /// The type that is equivalent to or the interface type that implements. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrImplements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type otherType) => type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType); + /// + /// Checks if the given is equal to the specified or if it implements it. This overload uses the specified + /// to compare the types. + /// + /// , + /// The type to be checked. + /// The type that is equivalent to or the interface type that implements. + /// The equality comparer used to compare the interface types. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrImplements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type otherType, [NotNull][ValidatedNotNull] IEqualityComparer typeComparer) => typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type.MustNotBeNull(nameof(type)), otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType, typeComparer); + /// + /// Checks if the specified string is trimmed, i.e. it does not start or end with + /// white space characters. Inputting an empty string will return true. When null is passed, + /// you can control the return value with which will + /// return true by default. + /// + /// The string to be checked. + /// + /// The value indicating whether true or false should be returned from this method when the + /// is null. The default value is true. + /// + /// + /// True if the is trimmed, else false. An empty string will result in true. + /// You can control the return value with when the + /// is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmed(this string? parameter, bool regardNullAsTrimmed = true) => parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmed(); + /// + /// Checks if the specified character span is trimmed, i.e. it does not start or end with + /// white space characters. Inputting an empty span will return true. + /// + /// The character span to be checked. + /// True if the is trimmed, else false. An empty span will result in true. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsTrimmed(this ReadOnlySpan parameter) => parameter.Length == 0 || !parameter[0].IsWhiteSpace() && !parameter[parameter.Length - 1].IsWhiteSpace(); + /// + /// Ensures that the string is not null and trimmed, or otherwise throws a . + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has no items. + /// + /// Thrown when is not trimmed, i.e. they start or end with white space characters. + /// Empty strings are regarded as trimmed. + /// /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotBeNullOrEmpty([ValidatedNotNull] this TCollection? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where TCollection : class, IEnumerable + public static string MustBeTrimmed([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Count(parameterName, message) == 0) - Throw.EmptyCollection(parameterName, message); - return parameter!; + if (!parameter.MustNotBeNull(parameterName, message).IsTrimmed()) + { + Throw.NotTrimmed(parameter, parameterName, message); + } + + return parameter; } /// - /// Ensures that the collection is not null or empty, or otherwise throws your custom exception. + /// Ensures that the string is not null and trimmed, or otherwise throws your custom exception. + /// Empty strings are regarded as trimmed. /// - /// The collection to be checked. - /// The delegate that creates your custom exception. - /// Thrown when has no items, or when is null. + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not trimmed. Empty strings are regarded as trimmed. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotBeNullOrEmpty([ValidatedNotNull] this TCollection? parameter, Func exceptionFactory) - where TCollection : class, IEnumerable + public static string MustBeTrimmed([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { - if (parameter is null || parameter.Count() == 0) + if (parameter is null || !parameter.AsSpan().IsTrimmed()) + { Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the collection contains the specified item, or otherwise throws a . + /// Ensures that the string does not start with the specified value, or otherwise throws a . /// - /// The collection to be checked. - /// The item that must be part of the collection. + /// The string to be checked. + /// The other string that must not start with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustContain([ValidatedNotNull] this TCollection? parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where TCollection : class, IEnumerable + /// Thrown when starts with . + /// Thrown when or is null. + public static string MustNotStartWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType = StringComparison.CurrentCulture, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (parameter is ICollection collection) + if (parameter.MustNotBeNull(parameterName, message).StartsWith(value, comparisonType)) { - if (!collection.Contains(item)) - Throw.MissingItem(parameter, item, parameterName, message); - return parameter; + Throw.StringStartsWith(parameter, value, comparisonType, parameterName, message); } - if (!parameter.MustNotBeNull(parameterName, message).Contains(item)) - Throw.MissingItem(parameter!, item, parameterName, message); - return parameter!; + return parameter; } /// - /// Ensures that the collection contains the specified item, or otherwise throws your custom exception. + /// Ensures that the string does not start with the specified value, or otherwise throws your custom exception. /// - /// The collection to be checked. - /// The item that must be part of the collection. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not contain , or when is null. + /// The string to be checked. + /// The other string that must not start with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustContain([ValidatedNotNull] this TCollection? parameter, TItem item, Func exceptionFactory) - where TCollection : class, IEnumerable + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotStartWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, [NotNull, ValidatedNotNull] Func exceptionFactory) { - if (parameter is ICollection collection) + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.StartsWith(value)) { - if (!collection.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); - return parameter; + Throw.CustomException(exceptionFactory, parameter, value!); } - if (parameter is null || !parameter.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); return parameter; } /// - /// Ensures that the collection does not contain the specified item, or otherwise throws an . + /// Ensures that the string does not start with the specified value, or otherwise throws your custom exception. /// - /// The collection to be checked. - /// The item that must not be part of the collection. + /// The string to be checked. + /// The other string that must not start with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotStartWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType, [NotNull, ValidatedNotNull] Func exceptionFactory) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.StartsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; + } + + /// + /// Ensures that the span does not start with the specified value, or otherwise throws a . + /// + /// The span to be checked. + /// The other span that must not start with. + /// One of the enumeration values that specifies the rules for the search. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when is null. + /// Thrown when starts with . + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotContain([ValidatedNotNull] this TCollection? parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where TCollection : class, IEnumerable + public static ReadOnlySpan MustNotStartWith(this ReadOnlySpan parameter, ReadOnlySpan value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter is ICollection collection) + if (parameter.StartsWith(value, comparisonType)) { - if (collection.Contains(item)) - Throw.ExistingItem(parameter, item, parameterName, message); - return parameter; + Throw.StringStartsWith(parameter, value, comparisonType, parameterName, message); } - if (parameter.MustNotBeNull(parameterName, message).Contains(item)) - Throw.ExistingItem(parameter!, item, parameterName, message); - return parameter!; + return parameter; } /// - /// Ensures that the collection does not contain the specified item, or otherwise throws your custom exception. + /// Ensures that the span does not start with the specified value, or otherwise throws your custom exception. /// - /// The collection to be checked. - /// The item that must not be part of the collection. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when contains . + /// The span to be checked. + /// The other span that must not start with. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustNotContain([ValidatedNotNull] this TCollection? parameter, TItem item, Func exceptionFactory) - where TCollection : class, IEnumerable + public static ReadOnlySpan MustNotStartWith(this ReadOnlySpan parameter, ReadOnlySpan value, ReadOnlySpansExceptionFactory exceptionFactory) + where T : IEquatable { - if (parameter is ICollection collection) + if (parameter.StartsWith(value)) { - if (collection.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); - return parameter; + Throw.CustomSpanException(exceptionFactory, parameter, value); } - if (parameter is null || parameter.Contains(item)) - Throw.CustomException(exceptionFactory, parameter, item); return parameter; } /// - /// Checks if the given is one of the specified . + /// Ensures that the span does not start with the specified value, or otherwise throws your custom exception. /// - /// The item to be checked. - /// The collection that might contain the . - /// Thrown when is null. + /// The span to be checked. + /// The other span that must not start with. + /// One of the enumeration values that specifies the rules for the search. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when does not start with , + /// or when is null, + /// or when is null. + /// + /// + /// Thrown when is not a valid value. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static bool IsOneOf(this TItem item, [ValidatedNotNull] IEnumerable items) + public static ReadOnlySpan MustNotStartWith(this ReadOnlySpan parameter, ReadOnlySpan value, StringComparison comparisonType, ReadOnlySpansExceptionFactory exceptionFactory) { - if (items is ICollection collection) - return collection.Contains(item); - if (items is string @string && item is char character) - return @string.IndexOf(character) != -1; - return items.MustNotBeNull(nameof(items)).ContainsViaForeach(item); + if (parameter.StartsWith(value, comparisonType)) + { + Throw.CustomSpanException(exceptionFactory, parameter, value, comparisonType); + } + + return parameter; } /// - /// Ensures that the value is one of the specified items, or otherwise throws a . + /// Ensures that the string is a substring of the specified other string, or otherwise throws a . /// - /// The value to be checked. - /// The items that should contain the value. + /// The string to be checked. + /// The other string that must contain . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not equal to one of the specified . - /// Thrown when is null. + /// Thrown when does not contain . + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static TItem MustBeOneOf(this TItem parameter, [ValidatedNotNull] IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - // ReSharper disable PossibleMultipleEnumeration - if (!parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) - Throw.ValueNotOneOf(parameter, items, parameterName, message); + if (!value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) + { + Throw.NotSubstring(parameter, value, parameterName, message); + } + return parameter; - // ReSharper restore PossibleMultipleEnumeration } /// - /// Ensures that the value is one of the specified items, or otherwise throws your custom exception. + /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. /// - /// The value to be checked. - /// The items that should contain the value. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is not equal to one of the specified , or when is null. + /// The string to be checked. + /// The other string that must contain . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static TItem MustBeOneOf(this TItem parameter, [ValidatedNotNull] TCollection items, Func exceptionFactory) - where TCollection : class, IEnumerable + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (items is null || !parameter.IsOneOf(items)) - Throw.CustomException(exceptionFactory, parameter, items!); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || !value.Contains(parameter)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + return parameter; } /// - /// Ensures that the value is not one of the specified items, or otherwise throws a . + /// Ensures that the string is a substring of the specified other string, or otherwise throws a . /// - /// The value to be checked. - /// The items that must not contain the value. + /// The string to be checked. + /// The other string that must contain . + /// One of the enumeration values that specifies the rules for the search. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is equal to one of the specified . - /// Thrown when is null. + /// Thrown when does not contain . + /// Thrown when or is null. + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static TItem MustNotBeOneOf(this TItem parameter, [ValidatedNotNull] IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - // ReSharper disable PossibleMultipleEnumeration - if (parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) - Throw.ValueIsOneOf(parameter, items, parameterName, message); + value.MustNotBeNull(nameof(value), message); + parameter.MustNotBeNull(parameterName, message); + if (value.IndexOf(parameter, comparisonType) == -1) + { + Throw.NotSubstring(parameter, value, comparisonType, parameterName, message); + } + return parameter; - // ReSharper restore PossibleMultipleEnumeration } /// - /// Ensures that the value is not one of the specified items, or otherwise throws your custom exception. + /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. /// - /// The value to be checked. - /// The items that must not contain the value. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is equal to one of the specified , or when is null. + /// The string to be checked. + /// The other string that must contain . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("items:null => halt")] - public static TItem MustNotBeOneOf(this TItem parameter, [ValidatedNotNull] TCollection items, Func exceptionFactory) - where TCollection : class, IEnumerable + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] + public static string MustBeSubstringOf([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (items is null || parameter.IsOneOf(items)) - Throw.CustomException(exceptionFactory, parameter, items!); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || value.IndexOf(parameter, comparisonType) == -1) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; } /// - /// Ensures that the collection has at least the specified number of items, or otherwise throws an . + /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws an . /// - /// The collection to be checked. - /// The number of items the collection should have at least. + /// The URI to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain at least the specified number of items. + /// Thrown when uses a different scheme than "http" or "https". + /// Thrown when is relative and thus has no scheme. /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMinimumCount([ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where TCollection : class, IEnumerable + public static Uri MustBeHttpOrHttpsUrl([NotNull][ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Count(parameterName, message) < count) - Throw.InvalidMinimumCollectionCount(parameter!, count, parameterName, message); - return parameter!; + if (parameter.MustBeAbsoluteUri(parameterName, message).Scheme.Equals("https") == false && parameter.Scheme.Equals("http") == false) + { + Throw.UriMustHaveOneSchemeOf(parameter, ["https", "http"], parameterName, message); + } + + return parameter; } /// - /// Ensures that the collection has at least the specified number of items, or otherwise throws your custom exception. + /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws your custom exception. /// - /// The collection to be checked. - /// The number of items the collection should have at least. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not contain at least the specified number of items, or when is null. + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than "http" or "https", or when is a relative URI, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMinimumCount([ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) - where TCollection : class, IEnumerable + public static Uri MustBeHttpOrHttpsUrl([NotNull][ValidatedNotNull] this Uri? parameter, Func exceptionFactory) { - if (parameter is null || parameter.Count() < count) - Throw.CustomException(exceptionFactory, parameter, count); + if (parameter.MustBeAbsoluteUri(exceptionFactory).Scheme.Equals("https") == false && parameter.Scheme.Equals("http") == false) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the collection has at most the specified number of items, or otherwise throws an . + /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws a . /// - /// The collection to be checked. - /// The number of items the collection should have at most. + /// The string to be checked. + /// The length that the string must be shorter than or equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain at most the specified number of items. + /// Thrown when has a length greater than . /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMaximumCount([ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where TCollection : class, IEnumerable + public static string MustBeShorterThanOrEqualTo([NotNull][ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Count(parameterName, message) > count) - Throw.InvalidMaximumCollectionCount(parameter!, count, parameterName, message); - return parameter!; + if (parameter.MustNotBeNull(parameterName, message).Length > length) + { + Throw.StringNotShorterThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; } /// - /// Ensures that the collection has at most the specified number of items, or otherwise throws your custom exception. + /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws your custom exception. /// - /// The collection to be checked. - /// The number of items the collection should have at most. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not contain at most the specified number of items, or when is null. + /// The string to be checked. + /// The length that the string must be shorter than or equal to. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length greater than . [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static TCollection MustHaveMaximumCount([ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) - where TCollection : class, IEnumerable + public static string MustBeShorterThanOrEqualTo([NotNull][ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) { - if (parameter is null || parameter.Count() > count) - Throw.CustomException(exceptionFactory, parameter, count); + if (parameter is null || parameter.Length > length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the span has the specified length, or otherwise throws an . + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . /// /// The span to be checked. - /// The length that the span must have. + /// The length value that the span must be shorter than or equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not have the specified length. + /// Thrown when is longer than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustHaveLength(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static Span MustBeShorterThanOrEqualTo(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); + ((ReadOnlySpan)parameter).MustBeShorterThanOrEqualTo(length, parameterName, message); return parameter; } /// - /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. /// /// The span to be checked. - /// The length that the span must have. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not have the specified length. + /// The length value that the span must be shorter than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustHaveLength(this Span parameter, int length, SpanExceptionFactory exceptionFactory) + public static Span MustBeShorterThanOrEqualTo(this Span parameter, int length, SpanExceptionFactory exceptionFactory) { - if (parameter.Length != length) + if (parameter.Length > length) + { Throw.CustomSpanException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . /// /// The span to be checked. - /// The length that the span must have. + /// The length value that the span must be shorter than or equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Your custom exception thrown when does not have the specified length. + /// Thrown when is longer than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static ReadOnlySpan MustBeShorterThanOrEqualTo(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length != length) - Throw.InvalidSpanLength(parameter, length, parameterName, message); + if (parameter.Length > length) + { + Throw.SpanMustBeShorterThanOrEqualTo(parameter, length, parameterName, message); + } + return parameter; } /// - /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. /// /// The span to be checked. - /// The length that the span must have. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when does not have the specified length. + /// The length value that the span must be shorter than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) + public static ReadOnlySpan MustBeShorterThanOrEqualTo(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) { - if (parameter.Length != length) + if (parameter.Length > length) + { Throw.CustomSpanException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the span is longer than the specified length, or otherwise throws an . + /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws an , an , or a . /// - /// The span to be checked. - /// The value that the span must be longer than. + /// The string to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than or equal to . + /// Thrown when contains only white space. + /// Thrown when is an empty string. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThan(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustNotBeNullOrWhiteSpace([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length <= length) - Throw.SpanMustBeLongerThan(parameter, length, parameterName, message); - return parameter; + parameter.MustNotBeNullOrEmpty(parameterName, message); + foreach (var character in parameter) + { + if (!character.IsWhiteSpace()) + { + return parameter; + } + } + + Throw.WhiteSpaceString(parameter, parameterName, message); + return null; } /// - /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. + /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws your custom exception. /// - /// The span to be checked. - /// The length value that the span must be longer than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than or equal to . + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null, empty, or contains only white space. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThan(this Span parameter, int length, SpanExceptionFactory exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory: null => halt")] + public static string MustNotBeNullOrWhiteSpace([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { - if (parameter.Length <= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); + if (parameter.IsNullOrWhiteSpace()) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the span is longer than the specified length, or otherwise throws an . + /// Checks if the two specified types are equivalent. This is true when both types are equal or + /// when one type is a constructed generic type and the other type is the corresponding generic type definition. /// - /// The span to be checked. - /// The value that the span must be longer than. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than or equal to . + /// The first type to be checked. + /// The other type to be checked. + /// + /// True if both types are null, or if both are equal, or if one type + /// is a constructed generic type and the other one is the corresponding generic type definition, else false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThan(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static bool IsEquivalentTypeTo(this Type? type, Type? other) => ReferenceEquals(type, other) || (type is not null && other is not null && (type == other || (type.IsConstructedGenericType != other.IsConstructedGenericType && CheckTypeEquivalency(type, other)))); + private static bool CheckTypeEquivalency(Type type, Type other) { - if (parameter.Length <= length) - Throw.SpanMustBeLongerThan(parameter, length, parameterName, message); - return parameter; + if (type.IsConstructedGenericType) + { + return type.GetGenericTypeDefinition() == other; + } + + return other.GetGenericTypeDefinition() == type; } /// - /// Ensures that the span is longer than the specified length, or otherwise throws your custom exception. + /// Checks if the value is within the specified range. /// - /// The span to be checked. - /// The length value that the span must be longer than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than or equal to . + /// The comparable to be checked. + /// The range where must be in-between. + /// True if the parameter is within the specified range, else false. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThan(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length <= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); - return parameter; - } - + public static bool IsIn([NotNull][ValidatedNotNull] this T parameter, Range range) + where T : IComparable => range.IsValueWithinRange(parameter); /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . + /// Ensures that the specified is greater than the given value, or otherwise throws an . /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. + /// The comparable to be checked. + /// The boundary value that must be less than . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than . + /// Thrown when the specified is less than or equal to . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThanOrEqualTo(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeLessThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (parameter.Length < length) - Throw.SpanMustBeLongerThanOrEqualTo(parameter, length, parameterName, message); + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) + { + Throw.MustNotBeLessThanOrEqualTo(parameter, other, parameterName, message); + } + return parameter; } /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. + /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than . + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than or equal to , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeLongerThanOrEqualTo(this Span parameter, int length, SpanExceptionFactory exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeLessThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable { - if (parameter.Length < length) - Throw.CustomSpanException(exceptionFactory, parameter, length); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) <= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + return parameter; } /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . + /// Checks if the given type derives from the specified base class or interface type. Internally, this method uses + /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. + /// The type to be checked. + /// The type describing an interface or base class that should derive from or implement. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt")] + public static bool InheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type baseClassOrInterfaceType) => baseClassOrInterfaceType.MustNotBeNull(nameof(baseClassOrInterfaceType)).IsInterface ? type.Implements(baseClassOrInterfaceType) : type.DerivesFrom(baseClassOrInterfaceType); + /// + /// Checks if the given type derives from the specified base class or interface type. This overload uses the specified + /// to compare the types. + /// + /// The type to be checked. + /// The type describing an interface or base class that should derive from or implement. + /// The equality comparer used to compare the types. + /// Thrown when , or , or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt; typeComparer:null => halt")] + public static bool InheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type baseClassOrInterfaceType, [NotNull][ValidatedNotNull] IEqualityComparer typeComparer) => baseClassOrInterfaceType.MustNotBeNull(nameof(baseClassOrInterfaceType)).IsInterface ? type.Implements(baseClassOrInterfaceType, typeComparer) : type.DerivesFrom(baseClassOrInterfaceType, typeComparer); + /// + /// Ensures that the specified uses , or otherwise throws an . + /// + /// The date time to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is shorter than . + /// Thrown when does not use . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThanOrEqualTo(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static DateTime MustBeUnspecified(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length < length) - Throw.SpanMustBeLongerThanOrEqualTo(parameter, length, parameterName, message); + if (parameter.Kind != DateTimeKind.Unspecified) + { + Throw.MustBeUnspecifiedDateTime(parameter, parameterName, message); + } + return parameter; } /// - /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. + /// Ensures that the specified uses , or otherwise throws your custom exception. /// - /// The span to be checked. - /// The value that the span must be longer than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is shorter than . + /// The date time to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when does not use . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeLongerThanOrEqualTo(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) + [ContractAnnotation("exceptionFactory:null => halt")] + public static DateTime MustBeUnspecified(this DateTime parameter, Func exceptionFactory) { - if (parameter.Length < length) - Throw.CustomSpanException(exceptionFactory, parameter, length); + if (parameter.Kind != DateTimeKind.Unspecified) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the span is shorter than the specified length, or otherwise throws an . + /// Checks if the specified string is null, empty, or contains only white space. /// - /// The span to be checked. - /// The length value that the span must be shorter than. + /// The string to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] + public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? @string) => string.IsNullOrWhiteSpace(@string); + /// + /// Ensures that the specified uses , or otherwise throws an . + /// + /// The date time to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than or equal to . + /// Thrown when does not use . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThan(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static DateTime MustBeLocal(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length >= length) - Throw.SpanMustBeShorterThan(parameter, length, parameterName, message); + if (parameter.Kind != DateTimeKind.Local) + { + Throw.MustBeLocalDateTime(parameter, parameterName, message); + } + return parameter; } /// - /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. + /// Ensures that the specified uses , or otherwise throws your custom exception. /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than or equal to . + /// The date time to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when does not use . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThan(this Span parameter, int length, SpanExceptionFactory exceptionFactory) + [ContractAnnotation("exceptionFactory:null => halt")] + public static DateTime MustBeLocal(this DateTime parameter, Func exceptionFactory) { - if (parameter.Length >= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); + if (parameter.Kind != DateTimeKind.Local) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the span is shorter than the specified length, or otherwise throws an . + /// Ensures that the value is one of the specified items, or otherwise throws a . /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The name of the parameter (optional). + /// The value to be checked. + /// The items that should contain the value. + /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than or equal to . + /// Thrown when is not equal to one of the specified . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("items:null => halt")] + public static TItem MustBeOneOf(this TItem parameter, // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests + [NotNull][ValidatedNotNull] IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length >= length) - Throw.SpanMustBeShorterThan(parameter, length, parameterName, message); - return parameter; - } + // ReSharper disable PossibleMultipleEnumeration + if (!parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) + { + Throw.ValueNotOneOf(parameter, items, parameterName, message); + } - /// - /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than or equal to . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length >= length) - Throw.CustomSpanException(exceptionFactory, parameter, length); return parameter; + // ReSharper restore PossibleMultipleEnumeration } /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . + /// Ensures that the value is one of the specified items, or otherwise throws your custom exception. /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than . + /// The value to be checked. + /// The items that should contain the value. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is not equal to one of the specified , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThanOrEqualTo(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("items:null => halt")] + public static TItem MustBeOneOf(this TItem parameter, [NotNull][ValidatedNotNull] TCollection items, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (parameter.Length > length) - Throw.SpanMustBeShorterThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (items is null || !parameter.IsOneOf(items)) + { + Throw.CustomException(exceptionFactory, parameter, items!); + } - /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span MustBeShorterThanOrEqualTo(this Span parameter, int length, SpanExceptionFactory exceptionFactory) - { - if (parameter.Length > length) - Throw.CustomSpanException(exceptionFactory, parameter, length); return parameter; } /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws an . + /// Ensures that is equal to using the default equality comparer, or otherwise throws a . /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. + /// The first value to be compared. + /// The other value to be compared. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is longer than . + /// Thrown when and are not equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThanOrEqualTo(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBe(this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.Length > length) - Throw.SpanMustBeShorterThanOrEqualTo(parameter, length, parameterName, message); - return parameter; - } + if (!EqualityComparer.Default.Equals(parameter, other)) + { + Throw.ValuesNotEqual(parameter, other, parameterName, message); + } - /// - /// Ensures that the span is shorter than or equal to the specified length, or otherwise throws your custom exception. - /// - /// The span to be checked. - /// The length value that the span must be shorter than or equal to. - /// The delegate that creates your custom exception. and are passed to it. - /// Your custom exception thrown when is longer than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan MustBeShorterThanOrEqualTo(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) - { - if (parameter.Length > length) - Throw.CustomSpanException(exceptionFactory, parameter, length); return parameter; } /// - /// Checks if the specified string is null or empty. - /// - /// The string to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] - public static bool IsNullOrEmpty([NotNullWhen(false)] this string? @string) => string.IsNullOrEmpty(@string); - /// - /// Ensures that the specified string is not null or empty, or otherwise throws an or . + /// Ensures that is equal to using the default equality comparer, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is an empty string. - /// Thrown when is null. + /// The first value to be compared. + /// The other value to be compared. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when and are not equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotBeNullOrEmpty([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBe(this T parameter, T other, Func exceptionFactory) { - if (parameter is null) - Throw.ArgumentNull(parameterName, message); - if (parameter.Length == 0) - Throw.EmptyString(parameterName, message); - return parameter; - } + if (!EqualityComparer.Default.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } - /// - /// Ensures that the specified string is not null or empty, or otherwise throws your custom exception. - /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is an empty string or null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] - public static string MustNotBeNullOrEmpty([ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (string.IsNullOrEmpty(parameter)) - Throw.CustomException(exceptionFactory, parameter); -#if NETSTANDARD2_0 - return parameter!; -#else return parameter; -#endif } /// - /// Checks if the specified string is null, empty, or contains only white space. - /// - /// The string to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] - public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? @string) => string.IsNullOrWhiteSpace(@string); - /// - /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws an , an , or a . + /// Ensures that is equal to using the specified equality comparer, or otherwise throws a . /// - /// The string to be checked. + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains only white space. - /// Thrown when is an empty string. - /// Thrown when is null. + /// Thrown when and are not equal. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustBe(this T parameter, T other, IEqualityComparer equalityComparer, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - parameter.MustNotBeNullOrEmpty(parameterName, message); - foreach (var character in parameter!) + if (!equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) { - if (!character.IsWhiteSpace()) - return parameter; + Throw.ValuesNotEqual(parameter, other, parameterName, message); } - Throw.WhiteSpaceString(parameter, parameterName, message); - return null; + return parameter; } /// - /// Ensures that the specified string is not null, empty, or contains only white space, or otherwise throws your custom exception. + /// Ensures that is equal to using the specified equality comparer, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null, empty, or contains only white space. + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when and are not equal, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory: null => halt")] - public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? parameter, Func exceptionFactory) + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustBe(this T parameter, T other, IEqualityComparer equalityComparer, Func, Exception> exceptionFactory) { - if (parameter.IsNullOrWhiteSpace()) - Throw.CustomException(exceptionFactory, parameter); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (equalityComparer is null || !equalityComparer.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); + } + return parameter; } - /// - /// Checks if the specified character is a white space character. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsWhiteSpace(this char character) => char.IsWhiteSpace(character); - /// - /// Checks if the specified character is a letter. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLetter(this char character) => char.IsLetter(character); - /// - /// Checks if the specified character is a letter or digit. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLetterOrDigit(this char character) => char.IsLetterOrDigit(character); - /// - /// Checks if the specified character is a digit. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDigit(this char character) => char.IsDigit(character); /// /// Ensures that the two strings are equal using the specified , or otherwise throws a . /// @@ -1994,7 +2363,10 @@ public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? p public static string? MustBe(this string? parameter, string? other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { if (!string.Equals(parameter, other, comparisonType)) + { Throw.ValuesNotEqual(parameter, other, parameterName, message); + } + return parameter; } @@ -2008,10 +2380,13 @@ public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? p /// Your custom exception thrown when is not equal to . /// Thrown when is not a valid value from the enum. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustBe(this string? parameter, string? other, StringComparison comparisonType, Func exceptionFactory) + public static string? MustBe(this string? parameter, string? other, StringComparison comparisonType, Func exceptionFactory) { if (!string.Equals(parameter, other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other); + { + Throw.CustomException(exceptionFactory, parameter, other, comparisonType); + } + return parameter; } @@ -2024,12 +2399,15 @@ public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? p /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). /// Thrown when is not equal to . - /// Thrown when is not a valid value from the enum. + /// Thrown when is not a valid value from the enum. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string? MustBe(this string? parameter, string? other, StringComparisonType comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { if (!parameter.Equals(other, comparisonType)) + { Throw.ValuesNotEqual(parameter, other, parameterName, message); + } + return parameter; } @@ -2041,86 +2419,34 @@ public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? p /// The enum value specifying how the two strings should be compared. /// The delegate that creates your custom exception. and are passed to this delegate. /// Your custom exception thrown when is not equal to . - /// Thrown when is not a valid value from the enum. - public static string? MustBe(this string? parameter, string? other, StringComparisonType comparisonType, Func exceptionFactory) + /// Thrown when is not a valid value from the enum. + public static string? MustBe(this string? parameter, string? other, StringComparisonType comparisonType, Func exceptionFactory) { if (!parameter.Equals(other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other); + { + Throw.CustomException(exceptionFactory, parameter, other, comparisonType); + } + return parameter; } /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws a . + /// Checks if the specified is true and throws an in this case. /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is equal to . - /// Thrown when is not a valid value from the enum. + /// The condition to be checked. The exception is thrown when it is true. + /// The message that will be passed to the (optional). + /// Thrown when is true. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static void InvalidOperation(bool condition, string? message = null) { - if (string.Equals(parameter, other, comparisonType)) - Throw.ValuesEqual(parameter, other, parameterName, message); - return parameter; + if (condition) + { + Throw.InvalidOperation(message); + } } /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparison comparisonType, Func exceptionFactory) - { - if (string.Equals(parameter, other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other); - return parameter; - } - - /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws a . - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparisonType comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.Equals(other, comparisonType)) - Throw.ValuesEqual(parameter, other, parameterName, message); - return parameter; - } - - /// - /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. - /// - /// The first string to be compared. - /// The second string to be compared. - /// The enum value specifying how the two strings should be compared. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is equal to . - /// Thrown when is not a valid value from the enum. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string? MustNotBe(this string? parameter, string? other, StringComparisonType comparisonType, Func exceptionFactory) - { - if (parameter.Equals(other, comparisonType)) - Throw.CustomException(exceptionFactory, parameter, other); - return parameter; - } - - /// - /// Ensures that the string matches the specified regular expression, or otherwise throws a . + /// Ensures that the string matches the specified regular expression, or otherwise throws a . /// /// The string to be checked. /// The regular expression used for pattern matching. @@ -2130,11 +2456,14 @@ public static string MustNotBeNullOrWhiteSpace([ValidatedNotNull] this string? p /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; regex:null => halt")] - public static string MustMatch([ValidatedNotNull] this string? parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static string MustMatch([NotNull][ValidatedNotNull] this string? parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { if (!regex.MustNotBeNull(nameof(regex), message).IsMatch(parameter.MustNotBeNull(parameterName, message))) - Throw.StringDoesNotMatch(parameter!, regex, parameterName, message); - return parameter!; + { + Throw.StringDoesNotMatch(parameter, regex, parameterName, message); + } + + return parameter; } /// @@ -2149,3236 +2478,4801 @@ public static string MustMatch([ValidatedNotNull] this string? parameter, Regex /// or when is null. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string MustMatch([ValidatedNotNull] this string? parameter, Regex regex, Func exceptionFactory) + public static string MustMatch([NotNull][ValidatedNotNull] this string? parameter, Regex regex, Func exceptionFactory) { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off if (parameter is null || regex is null || !regex.IsMatch(parameter)) + { Throw.CustomException(exceptionFactory, parameter, regex!); - return parameter; - } + } - /// - /// Checks if the specified strings are equal, using the given comparison rules. - /// - /// The first string to compare. - /// The second string to compare. - /// One of the enumeration values that specifies the rules for the comparison. - /// True if the two strings are considered equal, else false. - /// Thrown when is no valid enum value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Equals(this string? @string, string? value, StringComparisonType comparisonType) - { - if ((int)comparisonType < 6) - return string.Equals(@string, value, (StringComparison)comparisonType); - if (comparisonType == StringComparisonType.OrdinalIgnoreWhiteSpace) - return @string.EqualsOrdinalIgnoreWhiteSpace(value); - if (comparisonType == StringComparisonType.OrdinalIgnoreCaseIgnoreWhiteSpace) - return @string.EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(value); - Throw.EnumValueNotDefined(comparisonType, nameof(comparisonType)); - return false; + return parameter; } /// - /// Ensures that the string contains the specified substring, or otherwise throws a . + /// Ensures that can be cast to and returns the cast value, or otherwise throws a . /// - /// The string to be checked. - /// The substring that must be part of . + /// The value to be cast. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. + /// Thrown when cannot be cast to . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([ValidatedNotNull] this string? parameter, string? value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBeOfType([NotNull, ValidatedNotNull, NoEnumeration] this object? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (!parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) - Throw.StringDoesNotContain(parameter!, value!, parameterName, message); - return parameter!; + if (parameter.MustNotBeNull(parameterName, message)is T castValue) + return castValue; + Throw.InvalidTypeCast(parameter, typeof(T), parameterName, message); + return default; } /// - /// Ensures that the string contains the specified value, or otherwise throws your custom exception. + /// Ensures that can be cast to and returns the cast value, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The substring that must be part of . - /// The delegate that creates you custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null. - /// + /// The value to be cast. + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when cannot be cast to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeOfType([NotNull, ValidatedNotNull, NoEnumeration] this object? parameter, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !parameter.Contains(value)) - Throw.CustomException(exceptionFactory, parameter, value!); - return parameter; + if (parameter is T castValue) + return castValue; + Throw.CustomException(exceptionFactory, parameter); + return default; } /// - /// Ensures that the string contains the specified value, or otherwise throws a . + /// Checks if the specified value is a valid enum value of its type. This is true when the specified value + /// is one of the constants defined in the enum, or a valid flags combination when the enum type is marked + /// with the . /// - /// The string to be checked. - /// The substring that must be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. - /// Thrown when is not a valid value. + /// The type of the enum. + /// The enum value to be checked. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).IndexOf(value.MustNotBeNull(nameof(value), message), comparisonType) < 0) - Throw.StringDoesNotContain(parameter!, value, comparisonType, parameterName, message); - return parameter!; - } - + public static bool IsValidEnumValue(this T parameter) + where T : struct, Enum => EnumInfo.IsValidEnumValue(parameter); /// - /// Ensures that the string contains the specified value, or otherwise throws your custom exception. + /// Checks if and point to the same object. /// - /// The string to be checked. - /// The substring that must be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates you custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null, - /// or when is not a valid value from the enum. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustContain([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || parameter.IndexOf(value, comparisonType) < 0) - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - return parameter; - } - + // ReSharper disable StringLiteralTypo + [ContractAnnotation("parameter:notNull => true, other:notnull; parameter:notNull => false, other:canbenull; other:notnull => true, parameter:notnull; other:notnull => false, parameter:canbenull")] + // ReSharper restore StringLiteralTypo + public static bool IsSameAs([NoEnumeration] this T? parameter, [NoEnumeration] T? other) + where T : class => ReferenceEquals(parameter, other); /// - /// Ensures that the string does not contain the specified value, or otherwise throws a . + /// Checks if the specified value is approximately the same as the other value, using the given tolerance. /// - /// The string to be checked. - /// The string that must not be part of . - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. + /// The first value to be compared. + /// The second value to be compared. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if and are equal or if their absolute difference + /// is smaller than the given , otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) - Throw.StringContains(parameter!, value, parameterName, message); - return parameter!; - } - + public static bool IsApproximately(this double value, double other, double tolerance) => Math.Abs(value - other) <= tolerance; /// - /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. + /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001. /// - /// The string to be checked. - /// The string that must not be part of . - /// The delegate that creates your custom exception (optional). and are passed to this - /// - /// Your custom exception thrown when contains , - /// or when is null, - /// or when is null. - /// + /// The first value to be compared. + /// The second value to be compared. + /// + /// True if and are equal or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || parameter.Contains(value)) - Throw.CustomException(exceptionFactory, parameter, value!); - return parameter; - } - + public static bool IsApproximately(this double value, double other) => Math.Abs(value - other) <= 0.0001; /// - /// Ensures that the string does not contain the specified value, or otherwise throws a . + /// Checks if the specified value is approximately the same as the other value, using the given tolerance. /// - /// The string to be checked. - /// The string that must not be part of . - /// One of the enumeration values that specifies the rules for the search. + /// The first value to be compared. + /// The second value to be compared. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if and are equal or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this float value, float other, float tolerance) => Math.Abs(value - other) <= tolerance; + /// + /// Checks if the specified value is approximately the same as the other value, using the default tolerance of 0.0001f. + /// + /// The first value to be compared. + /// The second value to be compared. + /// + /// True if and are equal or if their absolute difference + /// is smaller than 0.0001f, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsApproximately(this float value, float other) => Math.Abs(value - other) <= 0.0001f; + /// + /// Ensures that is within the specified range, or otherwise throws an . + /// + /// The type of the parameter to be checked. + /// The parameter to be checked. + /// The range where must be in-between. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. - /// Thrown when is not a valid value. + /// Thrown when is not within . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBeIn([NotNull][ValidatedNotNull] this T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (parameter.MustNotBeNull(parameterName, message).IndexOf(value.MustNotBeNull(nameof(value), message), comparisonType) >= 0) - Throw.StringContains(parameter!, value, comparisonType, parameterName, message); - return parameter!; + if (!range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) + { + Throw.MustBeInRange(parameter, range, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string does not contain the specified value, or otherwise throws your custom exception. + /// Ensures that is within the specified range, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The string that must not be part of . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception (optional). , , and are passed to this delegate. - /// - /// Your custom exception thrown when contains , - /// or when is null, - /// or when is null, - /// or when is not a valid value of the enum. - /// + /// The parameter to be checked. + /// The range where must be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is not within , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustNotContain([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeIn([NotNull][ValidatedNotNull] this T parameter, Range range, Func, Exception> exceptionFactory) + where T : IComparable { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || parameter.IndexOf(value, comparisonType) >= 0) - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + if (parameter is null || !range.IsValueWithinRange(parameter)) + { + Throw.CustomException(exceptionFactory, parameter!, range); + } + return parameter; } /// - /// Checks if the string contains the specified value using the given comparison type. + /// Checks if the specified string is trimmed at the start, i.e. it does not start with + /// white space characters. Inputting an empty string will return true. /// - /// The string to be checked. - /// The other string. - /// One of the enumeration values that specifies the rules for the search. - /// True if contains , else false. - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("string:null => halt; value:null => halt")] - public static bool Contains([ValidatedNotNull] this string @string, string value, StringComparison comparisonType) => @string.MustNotBeNull(nameof(@string)).IndexOf(value.MustNotBeNull(nameof(value)), comparisonType) >= 0; - /// - /// Checks if the string is a substring of the other string. - /// - /// The string to be checked. - /// The other string. - /// True if is a substring of , else false. - /// Thrown when or is null. + /// The string to be checked. + /// + /// The value indicating whether true or false should be returned from this method when the + /// is null. The default value is true. + /// + /// + /// True if the is trimmed at the start, else false. + /// An empty string will result in true. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("value:null => halt; other:null => halt")] - public static bool IsSubstringOf([ValidatedNotNull] this string value, [ValidatedNotNull] string other) => other.MustNotBeNull(nameof(other)).Contains(value); + public static bool IsTrimmedAtStart(this string? parameter, bool regardNullAsTrimmed = true) => parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtStart(); /// - /// Checks if the string is a substring of the other string. + /// Checks if the specified character span is trimmed at the start, i.e. it does not start with + /// white space characters. Inputting an empty span will return true. /// - /// The string to be checked. - /// The other string. - /// One of the enumeration values that specifies the rules for the search. - /// True if is a substring of , else false. - /// Thrown when or is null. - /// Thrown when is not a valid value. + /// The character span to be checked. + /// + /// True if the is trimmed at the start, else false. + /// An empty span will result in true. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("value:null => halt; other:null => halt")] - public static bool IsSubstringOf(this string value, string other, StringComparison comparisonType) => other.MustNotBeNull(nameof(other)).IndexOf(value, comparisonType) != -1; + public static bool IsTrimmedAtStart(this ReadOnlySpan parameter) => parameter.Length == 0 || !parameter[0].IsWhiteSpace(); /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws a . + /// Ensures that the has the specified scheme, or otherwise throws an . /// - /// The string to be checked. - /// The other string that must contain . + /// The URI to be checked. + /// The scheme that the URI should have. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. + /// Thrown when uses a different scheme than the specified one. + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveScheme([NotNull][ValidatedNotNull] this Uri? parameter, string scheme, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (!value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) - Throw.NotSubstring(parameter!, value, parameterName, message); - return parameter!; + if (string.Equals(parameter.MustBeAbsoluteUri(parameterName, message).Scheme, scheme) == false) + { + Throw.UriMustHaveScheme(parameter, scheme, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. + /// Ensures that the has the specified scheme, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The other string that must contain . - /// The delegate that creates your custom exception. and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null. - /// + /// The URI to be checked. + /// The scheme that the URI should have. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than the specified one, or when is a relative URI, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveScheme([NotNull][ValidatedNotNull] this Uri? parameter, string scheme, Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !value.Contains(parameter)) - Throw.CustomException(exceptionFactory, parameter, value!); + if (string.Equals(parameter.MustBeAbsoluteUri(exceptionFactory).Scheme, scheme) == false) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws a . + /// Ensures that the has the specified scheme, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The other string that must contain . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when does not contain . - /// Thrown when or is null. - /// Thrown when is not a valid value. + /// The URI to be checked. + /// The scheme that the URI should have. + /// The delegate that creates the exception to be thrown. and are passed to this delegate. + /// Your custom exception thrown when uses a different scheme than the specified one, or when is a relative URI, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustHaveScheme([NotNull][ValidatedNotNull] this Uri? parameter, string scheme, Func exceptionFactory) { - if (value.MustNotBeNull(nameof(value), message).IndexOf(parameter.MustNotBeNull(parameterName, message), comparisonType) == -1) - Throw.NotSubstring(parameter!, value, comparisonType, parameterName, message); - return parameter!; + if (parameter is null || !parameter.IsAbsoluteUri || parameter.Scheme.Equals(scheme) == false) + { + Throw.CustomException(exceptionFactory, parameter, scheme); + } + + return parameter; } /// - /// Ensures that the string is a substring of the specified other string, or otherwise throws your custom exception. + /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. /// - /// The string to be checked. - /// The other string that must contain . - /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. , , and are passed to this delegate. - /// - /// Your custom exception thrown when does not contain , - /// or when is null, - /// or when is null, - /// or when is not a valid value of the enum. - /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is less than or if their absolute difference + /// is smaller than the given , otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustBeSubstringOf([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || value.IndexOf(parameter, comparisonType) == -1) - Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); - return parameter; - } - + public static bool IsLessThanOrApproximately(this double value, double other, double tolerance) => value < other || value.IsApproximately(other, tolerance); /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . + /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// True if is less than or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this double value, double other) => value < other || value.IsApproximately(other); + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the given tolerance. + /// + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is less than or if their absolute difference + /// is smaller than the given , otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this float value, float other, float tolerance) => value < other || value.IsApproximately(other, tolerance); + /// + /// Checks if the specified value is less than or approximately the same as the other value, using the default tolerance of 0.0001f. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// True if is less than or if their absolute difference + /// is smaller than 0.0001f, otherwise false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLessThanOrApproximately(this float value, float other) => value < other || value.IsApproximately(other); + /// + /// Ensures that the specified URI has the "http" scheme, or otherwise throws an . + /// + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when uses a different scheme than "http". + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpUrl([NotNull][ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustHaveScheme("http", parameterName, message); + /// + /// Ensures that the specified URI has the "http" scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than "http", or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpUrl([NotNull][ValidatedNotNull] this Uri? parameter, Func exceptionFactory) => parameter.MustHaveScheme("http", exceptionFactory); + /// + /// Ensures that the string starts with the specified value, or otherwise throws a . /// /// The string to be checked. - /// The other string that must not contain . + /// The other string must start with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . + /// Thrown when does not start with . /// Thrown when or is null. + /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([ValidatedNotNull] this string? parameter, string value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static string MustStartWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType = StringComparison.CurrentCulture, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (value.MustNotBeNull(nameof(value), message).Contains(parameter.MustNotBeNull(parameterName, message))) - Throw.Substring(parameter!, value, parameterName, message); - return parameter!; + if (!parameter.MustNotBeNull(parameterName, message).StartsWith(value, comparisonType)) + { + Throw.StringDoesNotStartWith(parameter, value, comparisonType, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. + /// Ensures that the string starts with the specified value, or otherwise throws your custom exception. /// /// The string to be checked. - /// The other string that must not contain . + /// The other string must start with. /// The delegate that creates your custom exception. and are passed to this delegate. /// - /// Your custom exception thrown when contains , + /// Your custom exception thrown when does not start with , /// or when is null, /// or when is null. /// - /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustStartWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, [NotNull, ValidatedNotNull] Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || value.Contains(parameter)) + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !parameter.StartsWith(value)) + { Throw.CustomException(exceptionFactory, parameter, value!); - return parameter; - } + } - /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws a . - /// - /// The string to be checked. - /// The other string that must not contain . - /// One of the enumeration values that specifies the rules for the search. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when contains . - /// Thrown when or is null. - /// Thrown when is not a valid value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (value.MustNotBeNull(nameof(value), message).IndexOf(parameter.MustNotBeNull(parameterName, message), comparisonType) != -1) - Throw.Substring(parameter!, value, comparisonType, parameterName, message); - return parameter!; + return parameter; } /// - /// Ensures that the string is not a substring of the specified other string, or otherwise throws your custom exception. + /// Ensures that the string starts with the specified value, or otherwise throws your custom exception. /// /// The string to be checked. - /// The other string that must not contain . + /// The other string must start with. /// One of the enumeration values that specifies the rules for the search. - /// The delegate that creates your custom exception. and are passed to this delegate. + /// The delegate that creates your custom exception. , , and are passed to this delegate. /// - /// Your custom exception thrown when contains , + /// Your custom exception thrown when does not start with , /// or when is null, /// or when is null. /// /// Thrown when is not a valid value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt")] - public static string MustNotBeSubstringOf([ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustStartWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType, [NotNull, ValidatedNotNull] Func exceptionFactory) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || value.IndexOf(parameter, comparisonType) != -1) + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || !comparisonType.IsValidEnumValue() || !parameter.StartsWith(value, comparisonType)) + { Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + return parameter; } /// - /// Checks if the specified string is an email address using the default email regular expression - /// defined in . + /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. /// - /// The string to be checked if it is an email address. + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is greater than or if their absolute difference + /// is smaller than the given , otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("emailAddress:null => false")] - public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress) => emailAddress != null && RegularExpressions.EmailRegex.IsMatch(emailAddress); + public static bool IsGreaterThanOrApproximately(this double value, double other, double tolerance) => value > other || value.IsApproximately(other, tolerance); /// - /// Checks if the specified string is an email address using the provided regular expression for validation. + /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001. /// - /// The string to be checked. - /// The regular expression that determines whether the input string is an email address. - /// Thrown when is null. + /// The first value to compare. + /// The second value to compare. + /// + /// True if is greater than or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("emailAddress:null => false; emailAddressPattern:null => halt")] - public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress, Regex emailAddressPattern) => emailAddress != null && emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress); + public static bool IsGreaterThanOrApproximately(this double value, double other) => value > other || value.IsApproximately(other); /// - /// Ensures that the string is a valid email address using the default email regular expression - /// defined in , or otherwise throws an . + /// Checks if the specified value is greater than or approximately the same as the other value, using the given tolerance. /// - /// The email address that will be validated. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when is no valid email address. - /// Thrown when is null. + /// The first value to compare. + /// The second value to compare. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// True if is greater than or if their absolute difference + /// is smaller than the given , otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeEmailAddress([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - { - if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress()) - Throw.InvalidEmailAddress(parameter!, parameterName, message); - return parameter!; - } - + public static bool IsGreaterThanOrApproximately(this float value, float other, float tolerance) => value > other || value.IsApproximately(other, tolerance); /// - /// Ensures that the string is a valid email address using the default email regular expression - /// defined in , or otherwise throws your custom exception. + /// Checks if the specified value is greater than or approximately the same as the other value, using the default tolerance of 0.0001f. /// - /// The email address that will be validated. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or no valid email address. + /// The first value to compare. + /// The second value to compare. + /// + /// True if is greater than or if their absolute difference + /// is smaller than 0.0001, otherwise false. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeEmailAddress([ValidatedNotNull] this string? parameter, Func exceptionFactory) - { - if (!parameter.IsEmailAddress()) - Throw.CustomException(exceptionFactory, parameter); - return parameter; - } - + public static bool IsGreaterThanOrApproximately(this float value, float other) => value > other || value.IsApproximately(other); /// - /// Ensures that the string is a valid email address using the provided regular expression, - /// or otherwise throws an . + /// Ensures that is not within the specified range, or otherwise throws an . /// - /// The email address that will be validated. - /// The regular expression that determines if the input string is a valid email. + /// The type of the parameter to be checked. + /// The parameter to be checked. + /// The range where must not be in-between. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is no valid email address. + /// Thrown when is within . /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] - public static string MustBeEmailAddress([ValidatedNotNull] this string? parameter, Regex emailAddressPattern, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeIn([NotNull][ValidatedNotNull] this T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (!parameter.MustNotBeNull(parameterName, message).IsEmailAddress(emailAddressPattern)) - Throw.InvalidEmailAddress(parameter!, parameterName, message); - return parameter!; + if (range.IsValueWithinRange(parameter.MustNotBeNullReference(parameterName, message))) + { + Throw.MustNotBeInRange(parameter, range, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is a valid email address using the provided regular expression, - /// or otherwise throws your custom exception. + /// Ensures that is not within the specified range, or otherwise throws your custom exception. /// - /// The email address that will be validated. - /// The regular expression that determines if the input string is a valid email. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or no valid email address. + /// The parameter to be checked. + /// The range where must not be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is within , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; emailAddressPattern:null => halt")] - public static string MustBeEmailAddress([ValidatedNotNull] this string? parameter, Regex emailAddressPattern, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeIn([NotNull][ValidatedNotNull] this T parameter, Range range, Func, Exception> exceptionFactory) + where T : IComparable { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off - if (emailAddressPattern is null || !parameter.IsEmailAddress(emailAddressPattern)) - Throw.CustomException(exceptionFactory, parameter, emailAddressPattern!); + if (parameter is null || range.IsValueWithinRange(parameter)) + { + Throw.CustomException(exceptionFactory, parameter!, range); + } + return parameter; } /// - /// Ensures that the string is shorter than the specified length, or otherwise throws a . + /// Ensures that the string is longer than or equal to the specified length, or otherwise throws a . /// /// The string to be checked. - /// The length that the string must be shorter than. + /// The length that the string must be longer than or equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length greater than or equal to . + /// Thrown when has a length shorter than . /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThan([ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static string MustBeLongerThanOrEqualTo([NotNull][ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNull(parameterName, message).Length >= length) - Throw.StringNotShorterThan(parameter!, length, parameterName, message); - return parameter!; + if (parameter.MustNotBeNull(parameterName, message).Length < length) + { + Throw.StringNotLongerThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is shorter than the specified length, or otherwise throws your custom exception. + /// Ensures that the string is longer than or equal to the specified length, or otherwise throws your custom exception. /// /// The string to be checked. - /// The length that the string must be shorter than. + /// The length that the string must be longer than or equal to. /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length greater than or equal to . + /// Your custom exception thrown when is null or when it has a length shorter than . [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThan([ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) + public static string MustBeLongerThanOrEqualTo([NotNull][ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) { - if (parameter is null || parameter.Length >= length) + if (parameter is null || parameter.Length < length) + { Throw.CustomException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws a . + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . /// - /// The string to be checked. - /// The length that the string must be shorter than or equal to. + /// The span to be checked. + /// The value that the span must be longer than or equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length greater than . - /// Thrown when is null. + /// Thrown when is shorter than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThanOrEqualTo([ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static Span MustBeLongerThanOrEqualTo(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNull(parameterName, message).Length > length) - Throw.StringNotShorterThanOrEqualTo(parameter!, length, parameterName, message); - return parameter!; + ((ReadOnlySpan)parameter).MustBeLongerThanOrEqualTo(length, parameterName, message); + return parameter; } /// - /// Ensures that the string is shorter than or equal to the specified length, or otherwise throws your custom exception. + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The length that the string must be shorter than or equal to. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length greater than . + /// The span to be checked. + /// The value that the span must be longer than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeShorterThanOrEqualTo([ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) + public static Span MustBeLongerThanOrEqualTo(this Span parameter, int length, SpanExceptionFactory exceptionFactory) { - if (parameter is null || parameter.Length > length) - Throw.CustomException(exceptionFactory, parameter, length); + if (parameter.Length < length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the string has the specified length, or otherwise throws a . + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws an . /// - /// The string to be checked. - /// The asserted length of the string. + /// The span to be checked. + /// The value that the span must be longer than or equal to. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length different than . - /// Thrown when is null. + /// Thrown when is shorter than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLength([ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static ReadOnlySpan MustBeLongerThanOrEqualTo(this ReadOnlySpan parameter, int length, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNull(parameterName, message).Length != length) - Throw.StringLengthNotEqualTo(parameter!, length, parameterName, message); - return parameter!; + if (parameter.Length < length) + { + Throw.SpanMustBeLongerThanOrEqualTo(parameter, length, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string has the specified length, or otherwise throws your custom exception. + /// Ensures that the span is longer than or equal to the specified length, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The asserted length of the string. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length different than . + /// The span to be checked. + /// The value that the span must be longer than or equal to. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is shorter than . [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLength([ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) + public static ReadOnlySpan MustBeLongerThanOrEqualTo(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) { - if (parameter is null || parameter.Length != length) - Throw.CustomException(exceptionFactory, parameter, length); + if (parameter.Length < length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + return parameter; } /// - /// Ensures that the string is longer than the specified length, or otherwise throws a . + /// Ensures that the string is a valid file extension, or otherwise throws an . /// /// The string to be checked. - /// The length that the string must be longer than. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length shorter than or equal to . + /// Thrown when is not a valid file extension. /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThan([ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static string MustBeFileExtension([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNull(parameterName, message).Length <= length) - Throw.StringNotLongerThan(parameter!, length, parameterName, message); - return parameter!; + if (!parameter.MustNotBeNull(parameterName, message).IsFileExtension()) + { + Throw.NotFileExtension(parameter, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is longer than the specified length, or otherwise throws your custom exception. + /// Ensures that the string is a valid file extension, or otherwise throws your custom exception. /// /// The string to be checked. - /// The length that the string must be longer than. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length shorter than or equal to . + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not a valid file extension. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThan([ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) + public static string MustBeFileExtension([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { - if (parameter is null || parameter.Length <= length) - Throw.CustomException(exceptionFactory, parameter, length); + if (parameter is null || !parameter.IsFileExtension()) + { + Throw.CustomException(exceptionFactory, parameter); + } + return parameter; } /// - /// Ensures that the string is longer than or equal to the specified length, or otherwise throws a . + /// Ensures that the character span is a valid file extension, or otherwise throws a . /// - /// The string to be checked. - /// The length that the string must be longer than or equal to. + /// The character span to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when has a length shorter than . - /// Thrown when is null. + /// The original character span. + /// Thrown when is not a valid file extension. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThanOrEqualTo([ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static Span MustBeFileExtension(this Span parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (parameter.MustNotBeNull(parameterName, message).Length < length) - Throw.StringNotLongerThanOrEqualTo(parameter!, length, parameterName, message); - return parameter!; + ((ReadOnlySpan)parameter).MustBeFileExtension(parameterName, message); + return parameter; } /// - /// Ensures that the string is longer than or equal to the specified length, or otherwise throws your custom exception. + /// Ensures that the character span is a valid file extension, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The length that the string must be longer than or equal to. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or when it has a length shorter than . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeLongerThanOrEqualTo([ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) + /// The character span to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// The original character span. + /// Your custom exception thrown when is not a valid file extension. + public static Span MustBeFileExtension(this Span parameter, ReadOnlySpanExceptionFactory exceptionFactory) { - if (parameter is null || parameter.Length < length) - Throw.CustomException(exceptionFactory, parameter, length); + ((ReadOnlySpan)parameter).MustBeFileExtension(exceptionFactory); return parameter; } /// - /// Ensures that the string's length is within the specified range, or otherwise throws a . + /// Ensures that the character memory is a valid file extension, or otherwise throws a . /// - /// The string to be checked. - /// The range where the string's length must be in-between. + /// The character memory to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the length of is not with the specified . - /// Thrown when is null. + /// The original character memory. + /// Thrown when is not a valid file extension. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLengthIn([ValidatedNotNull] this string? parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static Memory MustBeFileExtension(this Memory parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (!range.IsValueWithinRange(parameter.MustNotBeNull(parameterName, message).Length)) - Throw.StringLengthNotInRange(parameter!, range, parameterName, message); - return parameter!; + ((ReadOnlySpan)parameter.Span).MustBeFileExtension(parameterName, message); + return parameter; } /// - /// Ensures that the string's length is within the specified range, or otherwise throws your custom exception. + /// Ensures that the character memory is a valid file extension, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The range where the string's length must be in-between. - /// The delegate that creates your custom exception. and are passed to this delegate. - /// Your custom exception thrown when is null or its length is not within the specified range. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustHaveLengthIn([ValidatedNotNull] this string? parameter, Range range, Func, Exception> exceptionFactory) + /// The character memory to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// The original character memory. + /// Your custom exception thrown when is not a valid file extension. + public static Memory MustBeFileExtension(this Memory parameter, ReadOnlySpanExceptionFactory exceptionFactory) { - if (parameter is null || !range.IsValueWithinRange(parameter.Length)) - Throw.CustomException(exceptionFactory, parameter, range); + ((ReadOnlySpan)parameter.Span).MustBeFileExtension(exceptionFactory); return parameter; } /// - /// Checks if the string is either "\n" or "\r\n". This is done independently of the current value of . - /// - /// The string to be checked. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("=> false, parameter:canbenull; => true, parameter:notnull")] - public static bool IsNewLine([NotNullWhen(true)] this string? parameter) => parameter == "\n" || parameter == "\r\n"; - /// - /// Ensures that the string is either "\n" or "\r\n", or otherwise throws a . This is done independently of the current value of . + /// Ensures that the read-only character memory is a valid file extension, or otherwise throws a . /// - /// The string to be checked. + /// The read-only character memory to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not equal to "\n" or "\r\n". - /// Thrown when is null. + /// The original read-only character memory. + /// Thrown when is not a valid file extension. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeNewLine([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static ReadOnlyMemory MustBeFileExtension(this ReadOnlyMemory parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (!parameter.MustNotBeNull(parameterName, message).IsNewLine()) - Throw.NotNewLine(parameter!, parameterName, message); - return parameter!; + parameter.Span.MustBeFileExtension(parameterName, message); + return parameter; } /// - /// Ensures that the string is either "\n" or "\r\n", or otherwise throws your custom exception. This is done independently of the current value of . + /// Ensures that the read-only character memory is a valid file extension, or otherwise throws your custom exception. /// - /// The string to be checked. + /// The read-only character memory to be checked. /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is not equal to "\n" or "\r\n". - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeNewLine([ValidatedNotNull] this string? parameter, Func exceptionFactory) + /// The original read-only character memory. + /// Your custom exception thrown when is not a valid file extension. + public static ReadOnlyMemory MustBeFileExtension(this ReadOnlyMemory parameter, ReadOnlySpanExceptionFactory exceptionFactory) { - if (!parameter.IsNewLine()) - Throw.CustomException(exceptionFactory, parameter); + parameter.Span.MustBeFileExtension(exceptionFactory); return parameter; } /// - /// Checks if the specified string is trimmed, i.e. it does not start or end with - /// white space characters. Inputting an empty string will return true. When null is passed, - /// you can control the return value with which will - /// return true by default. + /// Ensures that the read-only character span is a valid file extension, or otherwise throws a . /// - /// The string to be checked. - /// - /// The value indicating whether true or false should be returned from this method when the - /// is null. The default value is true. - /// - /// - /// True if the is trimmed, else false. An empty string will result in true. - /// You can control the return value with when the - /// is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmed(this string? parameter, bool regardNullAsTrimmed = true) => parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmed(); - /// - /// Checks if the specified character span is trimmed, i.e. it does not start or end with - /// white space characters. Inputting an empty span will return true. - /// - /// The character span to be checked. - /// True if the is trimmed, else false. An empty span will result in true. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmed(this ReadOnlySpan parameter) => parameter.Length == 0 || !parameter[0].IsWhiteSpace() && !parameter[parameter.Length - 1].IsWhiteSpace(); - /// - /// Ensures that the string is not null and trimmed, or otherwise throws a . - /// Empty strings are regarded as trimmed. - /// - /// The string to be checked. + /// The read-only character span to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// - /// Thrown when is not trimmed, i.e. they start or end with white space characters. - /// Empty strings are regarded as trimmed. - /// - /// Thrown when is null. + /// The original read-only character span. + /// Thrown when is not a valid file extension. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmed([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static ReadOnlySpan MustBeFileExtension(this ReadOnlySpan parameter, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - if (!parameter.MustNotBeNull(parameterName, message).IsTrimmed()) - Throw.NotTrimmed(parameter, parameterName, message); - return parameter!; + if (!parameter.IsFileExtension()) + { + Throw.NotFileExtension(parameter, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is not null and trimmed, or otherwise throws your custom exception. - /// Empty strings are regarded as trimmed. + /// Ensures that the read-only character span is a valid file extension, or otherwise throws your custom exception. /// - /// The string to be checked. + /// The read-only character span to be checked. /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or not trimmed. Empty strings are regarded as trimmed. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmed([ValidatedNotNull] this string? parameter, Func exceptionFactory) + /// The original read-only character span. + /// Your custom exception thrown when is not a valid file extension. + public static ReadOnlySpan MustBeFileExtension(this ReadOnlySpan parameter, ReadOnlySpanExceptionFactory exceptionFactory) { - if (!parameter.IsTrimmed(regardNullAsTrimmed: false)) - Throw.CustomException(exceptionFactory, parameter); - return parameter!; + if (!parameter.IsFileExtension()) + { + Throw.CustomSpanException(exceptionFactory, parameter); + } + + return parameter; } /// - /// Checks if the specified string is trimmed at the start, i.e. it does not start with - /// white space characters. Inputting an empty string will return true. - /// - /// The string to be checked. - /// - /// The value indicating whether true or false should be returned from this method when the - /// is null. The default value is true. - /// - /// - /// True if the is trimmed at the start, else false. - /// An empty string will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtStart(this string? parameter, bool regardNullAsTrimmed = true) => parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtStart(); - /// - /// Checks if the specified character span is trimmed at the start, i.e. it does not start with - /// white space characters. Inputting an empty span will return true. - /// - /// The character span to be checked. - /// - /// True if the is trimmed at the start, else false. - /// An empty span will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtStart(this ReadOnlySpan parameter) => parameter.Length == 0 || !parameter[0].IsWhiteSpace(); - /// - /// Ensures that the string is not null and trimmed at the start, or otherwise throws a . - /// Empty strings are regarded as trimmed. + /// Ensures that is not equal to using the default equality comparer, or otherwise throws a . /// - /// The string to be checked. + /// The first value to be compared. + /// The other value to be compared. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// - /// Thrown when is not trimmed at the start, i.e. they start with white space characters. - /// Empty strings are regarded as trimmed. - /// - /// Thrown when is null. + /// Thrown when and are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtStart([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustNotBe(this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtStart()) - Throw.NotTrimmedAtStart(parameter, parameterName, message); - return parameter!; + if (EqualityComparer.Default.Equals(parameter, other)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is not null and trimmed at the start, or otherwise throws your custom exception. - /// Empty strings are regarded as trimmed. + /// Ensures that is not equal to using the default equality comparer, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or not trimmed at the start. Empty strings are regarded as trimmed. + /// The first value to be compared. + /// The other value to be compared. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when and are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtStart([ValidatedNotNull] this string? parameter, Func exceptionFactory) + public static T MustNotBe(this T parameter, T other, Func exceptionFactory) { - if (!parameter.IsTrimmedAtStart(regardNullAsTrimmed: false)) - Throw.CustomException(exceptionFactory, parameter); - return parameter!; + if (EqualityComparer.Default.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; } /// - /// Checks if the specified string is trimmed at the end, i.e. it does not end with - /// white space characters. Inputting an empty string will return true. - /// - /// The string to be checked. - /// - /// The value indicating whether true or false should be returned from this method when the - /// is null. The default value is true. - /// - /// - /// True if the is trimmed at the start, else false. - /// An empty string will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtEnd(this string? parameter, bool regardNullAsTrimmed = true) => parameter is null ? regardNullAsTrimmed : parameter.AsSpan().IsTrimmedAtEnd(); - /// - /// Checks if the specified character span is trimmed at the end, i.e. it does not end with - /// white space characters. Inputting an empty span will return true. - /// - /// The character span to be checked. - /// - /// True if the is trimmed at the end, else false. - /// An empty span will result in true. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrimmedAtEnd(this ReadOnlySpan parameter) => parameter.Length == 0 || !parameter[parameter.Length - 1].IsWhiteSpace(); - /// - /// Ensures that the string is not null and trimmed at the end, or otherwise throws a . - /// Empty strings are regarded as trimmed. + /// Ensures that is not equal to using the specified equality comparer, or otherwise throws a . /// - /// The string to be checked. + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// - /// Thrown when is not trimmed at the end, i.e. they end with white space characters. - /// Empty strings are regarded as trimmed. - /// - /// Thrown when is null. + /// Thrown when and are equal. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtEnd([ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustNotBe(this T parameter, T other, IEqualityComparer equalityComparer, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtEnd()) - Throw.NotTrimmedAtEnd(parameter, parameterName, message); - return parameter!; + if (equalityComparer.MustNotBeNull(nameof(equalityComparer), message).Equals(parameter, other)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Ensures that the string is not null and trimmed at the end, or otherwise throws your custom exception. - /// Empty strings are regarded as trimmed. + /// Ensures that is not equal to using the specified equality comparer, or otherwise throws your custom exception. /// - /// The string to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is null or not trimmed at the end. Empty strings are regarded as trimmed. + /// The first value to be compared. + /// The other value to be compared. + /// The equality comparer used for comparing the two values. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when and are equal, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static string MustBeTrimmedAtEnd([ValidatedNotNull] this string? parameter, Func exceptionFactory) + [ContractAnnotation("equalityComparer:null => halt")] + public static T MustNotBe(this T parameter, T other, IEqualityComparer equalityComparer, Func, Exception> exceptionFactory) { - if (!parameter.IsTrimmedAtEnd(regardNullAsTrimmed: false)) - Throw.CustomException(exceptionFactory, parameter); - return parameter!; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (equalityComparer is null || equalityComparer.Equals(parameter, other)) + { + Throw.CustomException(exceptionFactory, parameter, other, equalityComparer!); + } + + return parameter; } /// - /// Checks if the two specified types are equivalent. This is true when both types are equal or - /// when one type is a constructed generic type and the other type is the corresponding generic type definition. + /// Ensures that the two strings are not equal using the specified , or otherwise throws a . /// - /// The first type to be checked. - /// The other type to be checked. - /// - /// True if both types are null, or if both are equal, or if one type - /// is a constructed generic type and the other one is the corresponding generic type definition, else false. - /// + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is equal to . + /// Thrown when is not a valid value from the enum. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsEquivalentTypeTo(this Type? type, Type? other) => ReferenceEquals(type, other) || !(type is null) && !(other is null) && (type == other || type.IsConstructedGenericType != other.IsConstructedGenericType && CheckTypeEquivalency(type, other)); - private static bool CheckTypeEquivalency(Type type, Type other) + public static string? MustNotBe(this string? parameter, string? other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (type.IsConstructedGenericType) - return type.GetGenericTypeDefinition() == other; - return other.GetGenericTypeDefinition() == type; + if (string.Equals(parameter, other, comparisonType)) + { + Throw.ValuesEqual(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Checks if the type implements the specified interface type. Internally, this method uses - /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. + /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. /// - /// The type to be checked. - /// The interface type that should implement. - /// Thrown when or is null. - [ContractAnnotation("type:null => halt; interfaceType:null => halt")] - public static bool Implements([ValidatedNotNull] this Type type, [ValidatedNotNull] Type interfaceType) + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when is equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustNotBe(this string? parameter, string? other, StringComparison comparisonType, Func exceptionFactory) { - interfaceType.MustNotBeNull(nameof(interfaceType)); - var implementedInterfaces = type.MustNotBeNull(nameof(type)).GetInterfaces(); - for (var i = 0; i < implementedInterfaces.Length; ++i) + if (string.Equals(parameter, other, comparisonType)) { - if (interfaceType.IsEquivalentTypeTo(implementedInterfaces[i])) - return true; + Throw.CustomException(exceptionFactory, parameter, other); } - return false; + return parameter; } /// - /// Checks if the type implements the specified interface type. This overload uses the specified - /// to compare the interface types. + /// Ensures that the two strings are not equal using the specified , or otherwise throws a . /// - /// The type to be checked. - /// The interface type that should implement. - /// The equality comparer used to compare the interface types. - /// Thrown when , or , or is null. - [ContractAnnotation("type:null => halt; interfaceType:null => halt; typeComparer:null => halt")] - public static bool Implements([ValidatedNotNull] this Type type, [ValidatedNotNull] Type interfaceType, [ValidatedNotNull] IEqualityComparer typeComparer) + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is equal to . + /// Thrown when is not a valid value from the enum. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? MustNotBe(this string? parameter, string? other, StringComparisonType comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - interfaceType.MustNotBeNull(nameof(interfaceType)); - typeComparer.MustNotBeNull(nameof(typeComparer)); - var implementedInterfaces = type.MustNotBeNull(nameof(type)).GetInterfaces(); - for (var i = 0; i < implementedInterfaces.Length; ++i) + if (parameter.Equals(other, comparisonType)) { - if (typeComparer.Equals(implementedInterfaces[i], interfaceType)) - return true; + Throw.ValuesEqual(parameter, other, parameterName, message); } - return false; + return parameter; } /// - /// Checks if the given is equal to the specified or if it implements it. Internally, this - /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The type that is equivalent to or the interface type that implements. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrImplements([ValidatedNotNull] this Type type, [ValidatedNotNull] Type otherType) => type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType); - /// - /// Checks if the given is equal to the specified or if it implements it. This overload uses the specified - /// to compare the types. + /// Ensures that the two strings are not equal using the specified , or otherwise throws your custom exception. /// - /// , - /// The type to be checked. - /// The type that is equivalent to or the interface type that implements. - /// The equality comparer used to compare the interface types. - /// Thrown when or is null. + /// The first string to be compared. + /// The second string to be compared. + /// The enum value specifying how the two strings should be compared. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// Your custom exception thrown when is equal to . + /// Thrown when is not a valid value from the enum. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrImplements([ValidatedNotNull] this Type type, [ValidatedNotNull] Type otherType, [ValidatedNotNull] IEqualityComparer typeComparer) => typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type.MustNotBeNull(nameof(type)), otherType.MustNotBeNull(nameof(otherType))) || type.Implements(otherType, typeComparer); - /// - /// Checks if the specified type derives from the other type. Internally, this method uses - /// by default so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type info to be checked. - /// The base class that should derive from. - /// Thrown when or is null. - [ContractAnnotation("type:null => halt; baseClass:null => halt")] - public static bool DerivesFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type baseClass) + public static string? MustNotBe(this string? parameter, string? other, StringComparisonType comparisonType, Func exceptionFactory) { - baseClass.MustNotBeNull(nameof(baseClass)); - var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; - while (currentBaseType != null) + if (parameter.Equals(other, comparisonType)) { - if (currentBaseType.IsEquivalentTypeTo(baseClass)) - return true; - currentBaseType = currentBaseType.BaseType; + Throw.CustomException(exceptionFactory, parameter, other, comparisonType); } - return false; + return parameter; } /// - /// Checks if the specified type derives from the other type. This overload uses the specified - /// to compare the types. + /// Ensures that the collection has at least the specified number of items, or otherwise throws an . /// - /// The type info to be checked. - /// The base class that should derive from. - /// The equality comparer used to compare the types. - /// Thrown when , or , or is null. - [ContractAnnotation("type:null => halt; baseClass:null => halt; typeComparer:null => halt")] - public static bool DerivesFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type baseClass, [ValidatedNotNull] IEqualityComparer typeComparer) + /// The collection to be checked. + /// The number of items the collection should have at least. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain at least the specified number of items. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMinimumCount([NotNull][ValidatedNotNull] this TCollection? parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where TCollection : class, IEnumerable { - baseClass.MustNotBeNull(nameof(baseClass)); - typeComparer.MustNotBeNull(nameof(typeComparer)); - var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; - while (currentBaseType != null) + if (parameter.Count(parameterName, message) < count) { - if (typeComparer.Equals(currentBaseType, baseClass)) - return true; - currentBaseType = currentBaseType.BaseType; + Throw.InvalidMinimumCollectionCount(parameter, count, parameterName, message); } - return false; + return parameter; } /// - /// Checks if the given is equal to the specified or if it derives from it. Internally, this - /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrDerivesFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type otherType) => type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType); - /// - /// Checks if the given is equal to the specified or if it derives from it. This overload uses the specified - /// to compare the types. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// The equality comparer used to compare the types. - /// Thrown when , or , or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] - public static bool IsOrDerivesFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type otherType, [ValidatedNotNull] IEqualityComparer typeComparer) => typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType, typeComparer); - /// - /// Checks if the given type derives from the specified base class or interface type. Internally, this method uses - /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. - /// - /// The type to be checked. - /// The type describing an interface or base class that should derive from or implement. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt")] - public static bool InheritsFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type baseClassOrInterfaceType) => baseClassOrInterfaceType.MustNotBeNull(nameof(baseClassOrInterfaceType)).IsInterface ? type.Implements(baseClassOrInterfaceType) : type.DerivesFrom(baseClassOrInterfaceType); - /// - /// Checks if the given type derives from the specified base class or interface type. This overload uses the specified - /// to compare the types. - /// - /// The type to be checked. - /// The type describing an interface or base class that should derive from or implement. - /// The equality comparer used to compare the types. - /// Thrown when , or , or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; baseClassOrInterfaceType:null => halt; typeComparer:null => halt")] - public static bool InheritsFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type baseClassOrInterfaceType, [ValidatedNotNull] IEqualityComparer typeComparer) => baseClassOrInterfaceType.MustNotBeNull(nameof(baseClassOrInterfaceType)).IsInterface ? type.Implements(baseClassOrInterfaceType, typeComparer) : type.DerivesFrom(baseClassOrInterfaceType, typeComparer); - /// - /// Checks if the given is equal to the specified or if it derives from it or implements it. - /// Internally, this method uses so that constructed generic types and their corresponding generic type definitions - /// are regarded as equal. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt")] - public static bool IsOrInheritsFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type otherType) => type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType); - /// - /// Checks if the given is equal to the specified or if it derives from it or implements it. - /// This overload uses the specified to compare the types. - /// - /// The type to be checked. - /// The type that is equivalent to or the base class type where derives from. - /// The equality comparer used to compare the types. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] - public static bool IsOrInheritsFrom([ValidatedNotNull] this Type type, [ValidatedNotNull] Type otherType, [ValidatedNotNull] IEqualityComparer typeComparer) => typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType, typeComparer); - /// - /// Checks if the given is a generic type that has open generic parameters, - /// but is no generic type definition. + /// Ensures that the collection has at least the specified number of items, or otherwise throws your custom exception. /// - /// The type to be checked. - /// Thrown when is null. + /// The collection to be checked. + /// The number of items the collection should have at least. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain at least the specified number of items, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("type:null => halt")] - public static bool IsOpenConstructedGenericType([ValidatedNotNull] this Type type) => type.MustNotBeNull(nameof(type)).IsGenericType && type.ContainsGenericParameters && type.IsGenericTypeDefinition == false; + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustHaveMinimumCount([NotNull][ValidatedNotNull] this TCollection? parameter, int count, Func exceptionFactory) + where TCollection : class, IEnumerable + { + if (parameter is null || parameter.Count() < count) + { + Throw.CustomException(exceptionFactory, parameter, count); + } + + return parameter; + } + /// - /// Ensures that the specified URI is an absolute one, or otherwise throws a . + /// Ensures that the specified is not greater than the given value, or otherwise throws an . /// - /// The URI to be checked. + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is not an absolute URI. + /// Thrown when the specified is greater than . /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeAbsoluteUri([ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBeLessThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri == false) - Throw.MustBeAbsoluteUri(parameter!, parameterName, message); - return parameter!; + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) + { + Throw.MustBeLessThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Ensures that the specified URI is an absolute one, or otherwise throws your custom exception. + /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// Your custom exception thrown when is not an absolute URI, or when is null. + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is greater than , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeAbsoluteUri([ValidatedNotNull] this Uri? parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeLessThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable { - if (parameter is null || parameter.IsAbsoluteUri == false) - Throw.CustomException(exceptionFactory, parameter); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) > 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + return parameter; } /// - /// Ensures that the specified URI is a relative one, or otherwise throws an . + /// Ensures that the specified is less than the given value, or otherwise throws an . /// - /// The URI to be checked. + /// The comparable to be checked. + /// The boundary value that must be greater than . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when is an absolute URI. + /// Thrown when the specified is not less than . /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeRelativeUri([ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBeLessThan([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri) - Throw.MustBeRelativeUri(parameter!, parameterName, message); - return parameter!; + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) + { + Throw.MustBeLessThan(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Ensures that the specified URI is a relative one, or otherwise throws your custom exception. + /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. /// - /// The URI to be checked. - /// The delegate that creates your custom exception. is passed to this delegate. - /// Your custom exception thrown when is an absolute URI, or when is null. + /// The comparable to be checked. + /// The boundary value that must be greater than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is not less than , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeRelativeUri([ValidatedNotNull] this Uri? parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeLessThan([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable { - if (parameter is null || parameter.IsAbsoluteUri) - Throw.CustomException(exceptionFactory, parameter); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) >= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + return parameter; } /// - /// Ensures that the has the specified scheme, or otherwise throws an . + /// Ensures that the value is not one of the specified items, or otherwise throws a . /// - /// The URI to be checked. - /// The scheme that the URI should have. + /// The value to be checked. + /// The items that must not contain the value. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than the specified one. - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. + /// Thrown when is equal to one of the specified . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveScheme([ValidatedNotNull] this Uri? parameter, string scheme, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + [ContractAnnotation("items:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute - the attribute has an effect, see Issue72NotNullAttribute tests + public static TItem MustNotBeOneOf(this TItem parameter, [NotNull][ValidatedNotNull] IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (string.Equals(parameter.MustBeAbsoluteUri(parameterName, message).Scheme, scheme) == false) - Throw.UriMustHaveScheme(parameter!, scheme, parameterName, message); - return parameter!; + // ReSharper disable PossibleMultipleEnumeration + if (parameter.IsOneOf(items.MustNotBeNull(nameof(items), message))) + { + Throw.ValueIsOneOf(parameter, items, parameterName, message); + } + + return parameter; + // ReSharper restore PossibleMultipleEnumeration } /// - /// Ensures that the has the specified scheme, or otherwise throws your custom exception. + /// Ensures that the value is not one of the specified items, or otherwise throws your custom exception. /// - /// The URI to be checked. - /// The scheme that the URI should have. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than the specified one, - /// or when is a relative URI, - /// or when is null. - /// + /// The value to be checked. + /// The items that must not contain the value. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is equal to one of the specified , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveScheme([ValidatedNotNull] this Uri? parameter, string scheme, Func exceptionFactory) + [ContractAnnotation("items:null => halt")] + public static TItem MustNotBeOneOf(this TItem parameter, [NotNull][ValidatedNotNull] TCollection items, Func exceptionFactory) + where TCollection : class, IEnumerable { - if (string.Equals(parameter.MustBeAbsoluteUri(exceptionFactory).Scheme, scheme) == false) - Throw.CustomException(exceptionFactory, parameter); - return parameter!; - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (items is null || parameter.IsOneOf(items)) + { + Throw.CustomException(exceptionFactory, parameter, items!); + } - /// - /// Ensures that the has the specified scheme, or otherwise throws your custom exception. - /// - /// The URI to be checked. - /// The scheme that the URI should have. - /// The delegate that creates the exception to be thrown. and are passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than the specified one, - /// or when is a relative URI, - /// or when is null. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveScheme([ValidatedNotNull] this Uri? parameter, string scheme, Func exceptionFactory) - { - if (parameter is null || !parameter.IsAbsoluteUri || parameter.Scheme.Equals(scheme) == false) - Throw.CustomException(exceptionFactory, parameter, scheme); return parameter; } /// - /// Ensures that the specified URI has the "https" scheme, or otherwise throws an . + /// Checks if the specified span is empty or contains only white space characters. /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than "https". - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. + /// The span to be checked. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpsUrl([ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustHaveScheme("https", parameterName, message); + public static bool IsEmptyOrWhiteSpace(this Span span) => ((ReadOnlySpan)span).IsEmptyOrWhiteSpace(); /// - /// Ensures that the specified URI has the "https" scheme, or otherwise throws your custom exception. + /// Checks if the specified span is empty or contains only white space characters. /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than "https", - /// or when is a relative URI, - /// or when is null. - /// + /// The span to be checked. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpsUrl([ValidatedNotNull] this Uri? parameter, Func exceptionFactory) => parameter.MustHaveScheme("https", exceptionFactory); + public static bool IsEmptyOrWhiteSpace(this ReadOnlySpan span) + { + if (span.IsEmpty) + { + return true; + } + + foreach (var character in span) + { + if (!character.IsWhiteSpace()) + { + return false; + } + } + + return true; + } + /// - /// Ensures that the specified URI has the "http" scheme, or otherwise throws an . + /// Checks if the specified memory is empty or contains only white space characters. /// - /// The URI to be checked. - /// The name of the parameter (optional). - /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than "http". - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. + /// The memory to be checked. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpUrl([ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustHaveScheme("http", parameterName, message); + public static bool IsEmptyOrWhiteSpace(this Memory memory) => memory.Span.IsEmptyOrWhiteSpace(); /// - /// Ensures that the specified URI has the "http" scheme, or otherwise throws your custom exception. + /// Checks if the specified memory is empty or contains only white space characters. /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than "http", - /// or when is a relative URI, - /// or when is null. - /// + /// The memory to be checked. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpUrl([ValidatedNotNull] this Uri? parameter, Func exceptionFactory) => parameter.MustHaveScheme("http", exceptionFactory); + public static bool IsEmptyOrWhiteSpace(this ReadOnlyMemory memory) => memory.Span.IsEmptyOrWhiteSpace(); /// - /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws an . + /// Ensures that the specified is less than the given value, or otherwise throws an . /// - /// The URI to be checked. + /// The comparable to be checked. + /// The boundary value that must be greater than . /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when uses a different scheme than "http" or "https". - /// Thrown when is relative and thus has no scheme. - /// Throw when is null. + /// Thrown when the specified is not less than . + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpOrHttpsUrl([ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustNotBeGreaterThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - if (parameter.MustBeAbsoluteUri(parameterName, message).Scheme.Equals("https") == false && parameter!.Scheme.Equals("http") == false) - Throw.UriMustHaveOneSchemeOf(parameter, new[] { "https", "http" }, parameterName, message); - return parameter!; + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) >= 0) + { + Throw.MustNotBeGreaterThanOrEqualTo(parameter, other, parameterName, message); + } + + return parameter; } /// - /// Ensures that the specified URI has the "http" or "https" scheme, or otherwise throws your custom exception. + /// Ensures that the specified is less than the given value, or otherwise throws your custom exception. /// - /// The URI to be checked. - /// The delegate that creates the exception to be thrown. is passed to this delegate. - /// - /// Your custom exception thrown when uses a different scheme than "http" or "https", - /// or when is a relative URI, - /// or when is null. - /// + /// The comparable to be checked. + /// The boundary value that must be greater than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is not less than , or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustBeHttpOrHttpsUrl([ValidatedNotNull] this Uri? parameter, Func exceptionFactory) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeGreaterThanOrEqualTo([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable { - if (parameter.MustBeAbsoluteUri(exceptionFactory).Scheme.Equals("https") == false && parameter!.Scheme.Equals("http") == false) - Throw.CustomException(exceptionFactory, parameter); - return parameter!; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) >= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; } /// - /// Ensures that the URI has one of the specified schemes, or otherwise throws an . + /// Ensures that the specified enum value is valid, or otherwise throws an . An enum value + /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type + /// is marked with the . /// - /// The URI to be checked. - /// One of these strings must be equal to the scheme of the URI. + /// The type of the enum. + /// The value to be checked. /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). - /// Thrown when the scheme is not equal to one of the specified schemes. - /// Thrown when is relative and thus has no scheme. - /// Throw when or is null. + /// Thrown when is no valid enum value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; schemes:null => halt")] - public static Uri MustHaveOneSchemeOf([ValidatedNotNull] this Uri? parameter, IEnumerable schemes, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + public static T MustBeValidEnumValue(this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : struct, Enum { - // ReSharper disable PossibleMultipleEnumeration - parameter.MustBeAbsoluteUri(parameterName, message); - if (schemes is ICollection collection) + if (!EnumInfo.IsValidEnumValue(parameter)) { - if (!collection.Contains(parameter!.Scheme)) - Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); - return parameter; + Throw.EnumValueNotDefined(parameter, parameterName, message); } - if (!schemes.MustNotBeNull(nameof(schemes), message).Contains(parameter!.Scheme)) - Throw.UriMustHaveOneSchemeOf(parameter, schemes, parameterName, message); return parameter; - // ReSharper restore PossibleMultipleEnumeration } /// - /// Ensures that the URI has one of the specified schemes, or otherwise throws your custom exception. + /// Ensures that the specified enum value is valid, or otherwise throws your custom exception. An enum value + /// is valid when the specified value is one of the constants defined in the enum, or a valid flags combination when the enum type + /// is marked with the . /// - /// The URI to be checked. - /// One of these strings must be equal to the scheme of the URI. - /// The delegate that creates your custom exception. - /// - /// Your custom exception thrown when the scheme is not equal to one of the specified schemes, - /// or when is a relative URI, - /// or when is null. - /// - /// Throw when is null. + /// The type of the enum. + /// The value to be checked. + /// The delegate that creates your custom exception. The is passed to this delegate. + /// Your custom exception thrown when is no valid enum value, or when is no enum type. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] - public static Uri MustHaveOneSchemeOf([ValidatedNotNull] this Uri? parameter, TCollection schemes, Func exceptionFactory) - where TCollection : class, IEnumerable + [ContractAnnotation("exceptionFactory:null => halt")] + public static T MustBeValidEnumValue(this T parameter, Func exceptionFactory) + where T : struct, Enum { - if (parameter is null || !parameter.IsAbsoluteUri) - Throw.CustomException(exceptionFactory, parameter, schemes); - if (schemes is ICollection collection) + if (!EnumInfo.IsValidEnumValue(parameter)) { - if (!collection.Contains(parameter.Scheme)) - Throw.CustomException(exceptionFactory, parameter, schemes); - return parameter; + Throw.CustomException(exceptionFactory, parameter); } - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - caller might have NRTs turned off - if (schemes is null || !schemes.Contains(parameter.Scheme)) - Throw.CustomException(exceptionFactory, parameter, schemes!); return parameter; } - } - /// - /// Provides meta-information about enum values and the flag bitmask if the enum is marked with the . - /// Can be used to validate that an enum value is valid. - /// - /// The type of the enum. - internal static class EnumInfo - where T : Enum - { - // ReSharper disable StaticMemberInGenericType - /// - /// Gets the value indicating whether the enum type is marked with the flags attribute. - /// - public static readonly bool IsFlagsEnum = typeof(T).GetCustomAttribute(Types.FlagsAttributeType) != null; - /// - /// Gets the flags pattern when is true. If the enum is not a flags enum, then 0UL is returned. - /// - public static readonly ulong FlagsPattern; - private static readonly int EnumSize = Unsafe.SizeOf(); - private static readonly T[] EnumConstantsArray; - /// - /// Gets the underlying type that is used for the enum ( for default enums). - /// - public static readonly Type UnderlyingType; /// - /// Gets the values of the enum as a read-only collection. + /// Ensures that the specified GUID is not empty, or otherwise throws an . /// - public static ReadOnlyMemory EnumConstants { get; } - - static EnumInfo() + /// The GUID to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is an empty GUID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid MustNotBeEmpty(this Guid parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - EnumConstantsArray = (T[])Enum.GetValues(typeof(T)); - var fields = typeof(T).GetFields(); - UnderlyingType = fields[0].FieldType; - EnumConstants = new ReadOnlyMemory(EnumConstantsArray); - if (!IsFlagsEnum) - return; - for (var i = 0; i < EnumConstantsArray.Length; ++i) + if (parameter == Guid.Empty) { - var convertedValue = ConvertToUInt64(EnumConstantsArray[i]); - FlagsPattern |= convertedValue; + Throw.EmptyGuid(parameterName, message); } + + return parameter; } + /// + /// Ensures that the specified GUID is not empty, or otherwise throws your custom exception. + /// + /// The GUID to be checked. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is an empty GUID. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsValidFlagsValue(T enumValue) + [ContractAnnotation("exceptionFactory:null => halt")] + public static Guid MustNotBeEmpty(this Guid parameter, Func exceptionFactory) { - var convertedValue = ConvertToUInt64(enumValue); - return (FlagsPattern & convertedValue) == convertedValue; - } + if (parameter == Guid.Empty) + { + Throw.CustomException(exceptionFactory); + } - private static bool IsValidValue(T parameter) - { - var comparer = EqualityComparer.Default; - for (var i = 0; i < EnumConstantsArray.Length; ++i) - if (comparer.Equals(EnumConstantsArray[i], parameter)) - return true; - return false; + return parameter; } /// - /// Checks if the specified enum value is valid. This is true if either the enum is a standard enum and the enum value corresponds - /// to one of the enum constant values or if the enum type is marked with the and the given value - /// is a valid combination of bits for this type. + /// Checks if the specified strings are equal, using the given comparison rules. /// - /// The enum value to be checked. - /// True if either the enum value is + /// The first string to compare. + /// The second string to compare. + /// One of the enumeration values that specifies the rules for the comparison. + /// True if the two strings are considered equal, else false. + /// Thrown when is no valid enum value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsValidEnumValue(T enumValue) => IsFlagsEnum ? IsValidFlagsValue(enumValue) : IsValidValue(enumValue); - private static ulong ConvertToUInt64(T value) + public static bool Equals(this string? @string, string? value, StringComparisonType comparisonType) { - switch (EnumSize) + if ((int)comparisonType < 6) { - case 1: - return Unsafe.As(ref value); - case 2: - return Unsafe.As(ref value); - case 4: - return Unsafe.As(ref value); - case 8: - return Unsafe.As(ref value); + return string.Equals(@string, value, (StringComparison)comparisonType); + } + + switch (comparisonType) + { + case StringComparisonType.OrdinalIgnoreWhiteSpace: + return @string.EqualsOrdinalIgnoreWhiteSpace(value); + case StringComparisonType.OrdinalIgnoreCaseIgnoreWhiteSpace: + return @string.EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(value); default: - ThrowUnknownEnumSize(); - return default; + Throw.EnumValueNotDefined(comparisonType, nameof(comparisonType)); + return false; } } - private static void ThrowUnknownEnumSize() + /// + /// Ensures that the specified is not greater than the given value, or otherwise throws an . + /// + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is greater than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable { - throw new InvalidOperationException($"The enum type \"{typeof(T)}\" has an unknown size of {EnumSize}. This means that the underlying enum type is not one of the supported ones."); + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) > 0) + Throw.MustNotBeGreaterThan(parameter, other, parameterName, message); + return parameter; } - } - /// - /// Represents an that uses - /// to compare types. This check works like the normal type equality comparison, but when two - /// generic types are compared, they are regarded as equal when one of them is a constructed generic type - /// and the other one is the corresponding generic type definition. - /// - internal sealed class EquivalentTypeComparer : IEqualityComparer - { /// - /// Gets a singleton instance of the equality comparer. + /// Ensures that the specified is not greater than the given value, or otherwise throws your custom exception. /// - public static readonly EquivalentTypeComparer Instance = new EquivalentTypeComparer(); + /// The comparable to be checked. + /// The boundary value that must be greater than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is greater than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeGreaterThan([NotNull, ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) > 0) + Throw.CustomException(exceptionFactory, parameter!, other); + return parameter; + } + /// - /// Checks if the two types are equivalent (using ). - /// This check works like the normal type equality comparison, but when two - /// generic types are compared, they are regarded as equal when one of them is a constructed generic type - /// and the other one is the corresponding generic type definition. + /// Checks if the given is equal to the specified or if it derives from it or implements it. + /// Internally, this method uses so that constructed generic types and their corresponding generic type definitions + /// are regarded as equal. /// - /// The first type. - /// The second type. + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Type? x, Type? y) => x.IsEquivalentTypeTo(y); + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrInheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type otherType) => type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType); /// - /// Returns the hash code of the given type. When the specified type is a constructed generic type, - /// the hash code of the generic type definition is returned instead. + /// Checks if the given is equal to the specified or if it derives from it or implements it. + /// This overload uses the specified to compare the types. /// - /// The type whose hash code is requested. + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// The equality comparer used to compare the types. + /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetHashCode(Type type) => // ReSharper disable once ConditionIsAlwaysTrueOrFalse - type is null ? 0 : type.IsConstructedGenericType ? type.GetGenericTypeDefinition().GetHashCode() : type.GetHashCode(); - } - - /// - /// Represents an that compares strings using the - /// ordinal sort rules, ignoring the case and the white space characters. - /// - internal sealed class OrdinalIgnoreCaseIgnoreWhiteSpaceComparer : IEqualityComparer - { + [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] + public static bool IsOrInheritsFrom( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type otherType, [NotNull][ValidatedNotNull] IEqualityComparer typeComparer) => typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || type.InheritsFrom(otherType, typeComparer); /// - /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the case and - /// the white space of the provided strings. + /// Checks if the given is equal to the specified or if it derives from it. Internally, this + /// method uses so that constructed generic types and their corresponding generic type definitions are regarded as equal. /// - /// Thrown when or are null. - public bool Equals(string? x, string? y) + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt")] + public static bool IsOrDerivesFrom([NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type otherType) => type.IsEquivalentTypeTo(otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType); + /// + /// Checks if the given is equal to the specified or if it derives from it. This overload uses the specified + /// to compare the types. + /// + /// The type to be checked. + /// The type that is equivalent to or the base class type where derives from. + /// The equality comparer used to compare the types. + /// Thrown when , or , or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("type:null => halt; otherType:null => halt; typeComparer:null => halt")] + public static bool IsOrDerivesFrom([NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type otherType, [NotNull][ValidatedNotNull] IEqualityComparer typeComparer) => typeComparer.MustNotBeNull(nameof(typeComparer)).Equals(type, otherType.MustNotBeNull(nameof(otherType))) || type.DerivesFrom(otherType, typeComparer); + /// + /// Ensures that the string is not null and trimmed at the start, or otherwise throws a . + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not trimmed at the start, i.e. they start with white space characters. + /// Empty strings are regarded as trimmed. + /// + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtStart([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - x.MustNotBeNull(nameof(x)); - y.MustNotBeNull(nameof(y)); - return x.EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(y); + if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtStart()) + { + Throw.NotTrimmedAtStart(parameter, parameterName, message); + } + + return parameter; } /// - /// Gets the hash code for the specified string. The hash code is created only from the non-white space characters - /// which are interpreted as case-insensitive. + /// Ensures that the string is not null and trimmed at the start, or otherwise throws your custom exception. + /// Empty strings are regarded as trimmed. /// - /// Thrown when is null. - public int GetHashCode(string @string) + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not trimmed at the start. Empty strings are regarded as trimmed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtStart([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { - @string.MustNotBeNull(nameof(@string)); - var hashBuilder = MultiplyAddHashBuilder.Create(); - foreach (var character in @string) + if (parameter is null || !parameter.AsSpan().IsTrimmedAtStart()) { - if (!character.IsWhiteSpace()) - hashBuilder.CombineIntoHash(char.ToLowerInvariant(character)); + Throw.CustomException(exceptionFactory, parameter); } - return hashBuilder.BuildHash(); + return parameter; } - } - /// - /// Represents an that compares strings using the - /// ordinal sort rules and ignoring the white space characters. - /// - internal sealed class OrdinalIgnoreWhiteSpaceComparer : IEqualityComparer - { /// - /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the white space - /// of the provided strings. + /// Checks if the type implements the specified interface type. Internally, this method uses + /// so that constructed generic types and their corresponding generic type definitions are regarded as equal. /// - /// Thrown when or are null. - public bool Equals(string? x, string? y) + /// The type to be checked. + /// The interface type that should implement. + /// Thrown when or is null. + [ContractAnnotation("type:null => halt; interfaceType:null => halt")] + public static bool Implements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type interfaceType) { - x.MustNotBeNull(nameof(x)); - y.MustNotBeNull(nameof(y)); - return x.EqualsOrdinalIgnoreWhiteSpace(y); + type.MustNotBeNull(); + interfaceType.MustNotBeNull(); + var implementedInterfaces = type.GetInterfaces(); + for (var i = 0; i < implementedInterfaces.Length; ++i) + { + if (interfaceType.IsEquivalentTypeTo(implementedInterfaces[i])) + { + return true; + } + } + + return false; } /// - /// Gets the hash code for the specified string. The hash code is created only from the non-white space characters. + /// Checks if the type implements the specified interface type. This overload uses the specified + /// to compare the interface types. /// - /// Thrown when is null. - public int GetHashCode(string @string) + /// The type to be checked. + /// The interface type that should implement. + /// The equality comparer used to compare the interface types. + /// Thrown when , or , or is null. + [ContractAnnotation("type:null => halt; interfaceType:null => halt; typeComparer:null => halt")] + public static bool Implements( +#if NET8_0 + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + [NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type interfaceType, [NotNull][ValidatedNotNull] IEqualityComparer typeComparer) { - @string.MustNotBeNull(nameof(@string)); - var hashCodeBuilder = MultiplyAddHashBuilder.Create(); - foreach (var character in @string) + type.MustNotBeNull(); + interfaceType.MustNotBeNull(); + typeComparer.MustNotBeNull(); + var implementedInterfaces = type.GetInterfaces(); + for (var i = 0; i < implementedInterfaces.Length; ++i) { - if (!character.IsWhiteSpace()) - hashCodeBuilder.CombineIntoHash(character); + if (typeComparer.Equals(implementedInterfaces[i], interfaceType)) + { + return true; + } } - return hashCodeBuilder.BuildHash(); + return false; } - } - /// - /// Defines a range that can be used to check if a specified is in between it or not. - /// - /// The type that the range should be applied to. - internal readonly struct Range : IEquatable> where T : IComparable - { - /// - /// Gets the lower boundary of the range. - /// - public readonly T From; - /// - /// Gets the upper boundary of the range. - /// - public readonly T To; - /// - /// Gets the value indicating whether the From value is included in the range. - /// - public readonly bool IsFromInclusive; /// - /// Gets the value indicating whether the To value is included in the range. + /// Ensures that the specified URI is a relative one, or otherwise throws an . /// - public readonly bool IsToInclusive; - private readonly int _expectedLowerBoundaryResult; - private readonly int _expectedUpperBoundaryResult; + /// The URI to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is an absolute URI. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeRelativeUri([NotNull][ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (parameter.MustNotBeNull(parameterName, message).IsAbsoluteUri) + { + Throw.MustBeRelativeUri(parameter, parameterName, message); + } + + return parameter; + } + /// - /// Creates a new instance of . + /// Ensures that the specified URI is a relative one, or otherwise throws your custom exception. /// - /// The lower boundary of the range. - /// The upper boundary of the range. - /// The value indicating whether is part of the range. - /// The value indicating whether is part of the range. - /// Thrown when is less than . + /// The URI to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is an absolute URI, or when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Range(T from, T to, bool isFromInclusive = true, bool isToInclusive = true) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeRelativeUri([NotNull][ValidatedNotNull] this Uri? parameter, Func exceptionFactory) { - From = from.MustNotBeNullReference(nameof(from)); - To = to.MustNotBeLessThan(from, nameof(to)); - IsFromInclusive = isFromInclusive; - IsToInclusive = isToInclusive; - _expectedLowerBoundaryResult = isFromInclusive ? 0 : 1; - _expectedUpperBoundaryResult = isToInclusive ? 0 : -1; + if (parameter is null || parameter.IsAbsoluteUri) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; } /// - /// Checks if the specified is within range. + /// Ensures that the specified uses , or otherwise throws an . /// - /// The value to be checked. - /// True if value is within range, otherwise false. + /// The date time to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not use . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsValueWithinRange(T value) => value.MustNotBeNullReference(nameof(value)).CompareTo(From) >= _expectedLowerBoundaryResult && value.CompareTo(To) <= _expectedUpperBoundaryResult; + public static DateTime MustBeUtc(this DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (parameter.Kind != DateTimeKind.Utc) + { + Throw.MustBeUtcDateTime(parameter, parameterName, message); + } + + return parameter; + } + /// - /// Use this method to create a range in a fluent style using method chaining. - /// Defines the lower boundary as an inclusive value. + /// Ensures that the specified uses , or otherwise throws your custom exception. /// - /// The value that indicates the inclusive lower boundary of the resulting range. - /// A value you can use to fluently define the upper boundary of a new range. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RangeFromInfo FromInclusive(T value) => new RangeFromInfo(value, true); - /// - /// Use this method to create a range in a fluent style using method chaining. - /// Defines the lower boundary as an exclusive value. - /// - /// The value that indicates the exclusive lower boundary of the resulting range. - /// A value you can use to fluently define the upper boundary of a new range. + /// The date time to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when does not use . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RangeFromInfo FromExclusive(T value) => new RangeFromInfo(value, false); - /// - /// The nested can be used to fluently create a . - /// - public readonly struct RangeFromInfo + [ContractAnnotation("exceptionFactory:null => halt")] + public static DateTime MustBeUtc(this DateTime parameter, Func exceptionFactory) { - private readonly T _from; - private readonly bool _isFromInclusive; - /// - /// Creates a new RangeFromInfo. - /// - /// The lower boundary of the range. - /// The value indicating whether is part of the range. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RangeFromInfo(T from, bool isFromInclusive) + if (parameter.Kind != DateTimeKind.Utc) { - _from = from; - _isFromInclusive = isFromInclusive; + Throw.CustomException(exceptionFactory, parameter); } - /// - /// Use this method to create a range in a fluent style using method chaining. - /// Defines the upper boundary as an exclusive value. - /// - /// The value that indicates the exclusive upper boundary of the resulting range. - /// A new range with the specified upper and lower boundaries. - /// - /// Thrown when is less than the lower boundary value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Range ToExclusive(T value) => new Range(_from, value, _isFromInclusive, false); - /// - /// Use this method to create a range in a fluent style using method chaining. - /// Defines the upper boundary as an inclusive value. - /// - /// The value that indicates the inclusive upper boundary of the resulting range. - /// A new range with the specified upper and lower boundaries. - /// - /// Thrown when is less than the lower boundary value. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Range ToInclusive(T value) => new Range(_from, value, _isFromInclusive); + return parameter; } - /// - public override string ToString() => $"Range from {CreateRangeDescriptionText()}"; - /// - /// Returns either "inclusive" or "exclusive", depending whether is true or false. - /// - public string LowerBoundaryText {[MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetBoundaryText(IsFromInclusive); } /// - /// Returns either "inclusive" or "exclusive", depending whether is true or false. + /// Checks if the specified character is a white space character. /// - public string UpperBoundaryText {[MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetBoundaryText(IsToInclusive); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhiteSpace(this char character) => char.IsWhiteSpace(character); /// - /// Returns a text description of this range with the following pattern: From (inclusive | exclusive) to To (inclusive | exclusive). + /// Ensures that the string is either "\n" or "\r\n", or otherwise throws a . This is done independently of the current value of . /// - public string CreateRangeDescriptionText(string fromToConnectionWord = "to") => From + " (" + LowerBoundaryText + ") " + fromToConnectionWord + ' ' + To + " (" + UpperBoundaryText + ")"; + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is not equal to "\n" or "\r\n". + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetBoundaryText(bool isInclusive) => isInclusive ? "inclusive" : "exclusive"; - /// - public bool Equals(Range other) + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeNewLine([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - if (IsFromInclusive != other.IsFromInclusive || IsToInclusive != other.IsToInclusive) - return false; - var comparer = EqualityComparer.Default; - return comparer.Equals(From, other.From) && comparer.Equals(To, other.To); - } + if (!parameter.MustNotBeNull(parameterName, message).IsNewLine()) + { + Throw.NotNewLine(parameter, parameterName, message); + } - /// - public override bool Equals(object? other) - { - if (other is null) - return false; - return other is Range range && Equals(range); + return parameter; } - /// - public override int GetHashCode() => MultiplyAddHash.CreateHashCode(From, To, IsFromInclusive, IsToInclusive); - /// - /// Checks if two ranges are equal. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Range first, Range second) => first.Equals(second); /// - /// Checks if two ranges are not equal. + /// Ensures that the string is either "\n" or "\r\n", or otherwise throws your custom exception. This is done independently of the current value of . /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is not equal to "\n" or "\r\n". [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Range first, Range second) => first.Equals(second) == false; - } + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeNewLine([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) + { + if (!parameter.IsNewLine()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; + } - /// - /// Provides methods to simplify the creation of instances. - /// - internal static class Range - { /// - /// Use this method to create a range in a fluent style using method chaining. - /// Defines the lower boundary as an inclusive value. + /// Checks if the specified string represents a valid file extension. /// - /// The value that indicates the inclusive lower boundary of the resulting range. - /// A value you can use to fluently define the upper boundary of a new range. + /// + /// The string to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the string is a valid file extension, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range.RangeFromInfo FromInclusive(T value) - where T : IComparable => new(value, true); + public static bool IsFileExtension([NotNullWhen(true)] this string? value) => value != null && IsFileExtension(value.AsSpan()); /// - /// Use this method to create a range in a fluent style using method chaining. - /// Defines the lower boundary as an exclusive value. + /// Checks if the specified character span represents a valid file extension. /// - /// The value that indicates the exclusive lower boundary of the resulting range. - /// A value you can use to fluently define the upper boundary of a new range. + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. + /// + /// True if the span is a valid file extension, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range.RangeFromInfo FromExclusive(T value) - where T : IComparable => new(value, false); + public static bool IsFileExtension(this Span value) => IsFileExtension((ReadOnlySpan)value); /// - /// Creates a range for the specified enumerable that encompasses all valid indexes. + /// Checks if the specified character memory represents a valid file extension. /// - /// - /// The count of this enumerable will be used to create the index range. Please ensure that this enumerable - /// is actually a collection, not a lazy enumerable. + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. /// - /// Thrown when is null. + /// True if the span is a valid file extension, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range For(IEnumerable enumerable) => new(0, enumerable.Count(), isFromInclusive: true, isToInclusive: false); + public static bool IsFileExtension(this ReadOnlyMemory value) => IsFileExtension(value.Span); /// - /// Creates a range for the specified enumerable that encompasses all valid indexes. + /// Checks if the specified character memory represents a valid file extension. /// - /// - /// The count of this enumerable will be used to create the index range. Please ensure that this enumerable - /// is actually a collection, not a lazy enumerable. + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. /// - /// Thrown when is null. - public static Range For(IEnumerable enumerable) => new(0, enumerable.GetCount(), isFromInclusive: true, isToInclusive: false); + /// True if the span is a valid file extension, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFileExtension(this Memory value) => IsFileExtension(value.Span); /// - /// Creates a range for the specified span that encompasses all valid indexes. + /// Checks if the specified character span represents a valid file extension. /// - /// - /// The length of the span is used to create a valid index range. + /// + /// The character span to be checked. It must start with a period (.) and can only contain letters, digits, + /// and additional periods. /// + /// True if the span is a valid file extension, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range For(ReadOnlySpan span) => new(0, span.Length, isFromInclusive: true, isToInclusive: false); + public static bool IsFileExtension(this ReadOnlySpan value) + { + // ReSharper disable once UseIndexFromEndExpression -- cannot use index from end expression in .NET Standard 2.0 + if (value.Length <= 1 || value[0] != '.' || value[value.Length - 1] == '.') + { + return false; + } + + var hasAlphanumeric = false; + for (var i = 1; i < value.Length; i++) + { + var character = value[i]; + if (character.IsLetterOrDigit()) + { + hasAlphanumeric = true; + } + else if (character != '.') + { + return false; + } + } + + return hasAlphanumeric; + } + /// - /// Creates a range for the specified span that encompasses all valid indexes. + /// Checks if the string is either "\n" or "\r\n". This is done independently of the current value of . /// - /// - /// The length of the span is used to create a valid index range. - /// + /// The string to be checked. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range For(Span span) => new(0, span.Length, isFromInclusive: true, isToInclusive: false); + [ContractAnnotation("=> false, parameter:canbenull; => true, parameter:notnull")] + public static bool IsNewLine([NotNullWhen(true)] this string? parameter) => parameter == "\n" || parameter == "\r\n"; /// - /// Creates a range for the specified memory that encompasses all valid indexes. + /// Checks if the given is a generic type that has open generic parameters, + /// but is no generic type definition. /// - /// - /// The length of the memory is used to create a valid index range. - /// + /// The type to be checked. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range For(Memory memory) => new(0, memory.Length, isFromInclusive: true, isToInclusive: false); + [ContractAnnotation("type:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static bool IsOpenConstructedGenericType([NotNull][ValidatedNotNull] this Type type) => type.MustNotBeNull(nameof(type)).IsGenericType && type.ContainsGenericParameters && type.IsGenericTypeDefinition == false; /// - /// Creates a range for the specified memory that encompasses all valid indexes. + /// Checks if the specified string is an email address using the default email regular expression + /// defined in . /// - /// - /// The length of the memory is used to create a valid index range. - /// + /// The string to be checked if it is an email address. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range For(ReadOnlyMemory memory) => new(0, memory.Length, isFromInclusive: true, isToInclusive: false); + [ContractAnnotation("emailAddress:null => false")] + public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress) => emailAddress != null && RegularExpressions.EmailRegex.IsMatch(emailAddress); /// - /// Creates a range for the specified memory that encompasses all valid indexes. + /// Checks if the specified string is an email address using the provided regular expression for validation. /// - /// - /// The count of the segment is used to create a valid index range. - /// + /// The string to be checked. + /// The regular expression that determines whether the input string is an email address. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Range For(ArraySegment segment) => new(0, segment.Count, isFromInclusive: true, isToInclusive: false); - } - - /// - /// Provides regular expressions that are used in string assertions. - /// - internal static class RegularExpressions - { + [ContractAnnotation("emailAddress:null => false; emailAddressPattern:null => halt")] + public static bool IsEmailAddress([NotNullWhen(true)] this string? emailAddress, Regex emailAddressPattern) => emailAddress != null && emailAddressPattern.MustNotBeNull(nameof(emailAddressPattern)).IsMatch(emailAddress); /// - /// Gets the default regular expression for email validation. - /// This pattern is based on https://www.rhyous.com/2010/06/15/csharp-email-regular-expression/ and - /// was modified to satisfy all tests of https://blogs.msdn.microsoft.com/testing123/2009/02/06/email-address-test-cases/. + /// Checks if the specified character is a letter. /// - public static readonly Regex EmailRegex = new Regex(@"^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*@((((\w+\-?)+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$", RegexOptions.CultureInvariant | RegexOptions.ECMAScript); - } - - /// - /// Represents a delegate that receives a span and a value as parameters and that produces an exception. - /// - internal delegate Exception SpanExceptionFactory(Span span, T value); - /// - /// Represents a delegate that receives a read-only span and a value as parameters and that produces an exception. - /// - internal delegate Exception ReadOnlySpanExceptionFactory(ReadOnlySpan span, T value); - /// - /// Specifies the culture, case , and sort rules when comparing strings. - /// - /// - /// This enum is en extension of , adding - /// capabilities to ignore white space when making string equality comparisons. - /// See the when - /// you want to compare in such a way. - /// - internal enum StringComparisonType - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLetter(this char character) => char.IsLetter(character); /// - /// Compare strings using culture-sensitive sort rules and the current culture. + /// Checks if the string is a substring of the other string. /// - CurrentCulture = 0, + /// The string to be checked. + /// The other string. + /// True if is a substring of , else false. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("value:null => halt; other:null => halt")] + // ReSharper disable RedundantNullableFlowAttribute + public static bool IsSubstringOf([NotNull][ValidatedNotNull] this string value, [NotNull][ValidatedNotNull] string other) => other.MustNotBeNull(nameof(other)).Contains(value); + // ReSharper restore RedundantNullableFlowAttribute /// - /// Compare strings using culture-sensitive sort rules, the current culture, and - /// ignoring the case of the strings being compared. + /// Checks if the string is a substring of the other string. /// - CurrentCultureIgnoreCase = 1, + /// The string to be checked. + /// The other string. + /// One of the enumeration values that specifies the rules for the search. + /// True if is a substring of , else false. + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("value:null => halt; other:null => halt")] + // ReSharper disable RedundantNullableFlowAttribute + public static bool IsSubstringOf([NotNull][ValidatedNotNull] this string value, [NotNull][ValidatedNotNull] string other, StringComparison comparisonType) => other.MustNotBeNull(nameof(other)).IndexOf(value, comparisonType) != -1; /// - /// Compare strings using culture-sensitive sort rules and the invariant culture. + /// Ensures that the string's length is within the specified range, or otherwise throws a . /// - InvariantCulture = 2, - /// - /// Compare strings using culture-sensitive sort rules, the invariant culture, and - /// ignoring the case of the strings being compared. - /// - InvariantCultureIgnoreCase = 3, - /// - /// Compare strings using ordinal sort rules. - /// - Ordinal = 4, - /// - /// Compare strings using ordinal sort rules and ignoring the case of the strings - /// being compared. - /// - OrdinalIgnoreCase = 5, - /// - /// Compare strings using ordinal sort rules and ignoring the white space characters - /// of the strings being compared. - /// - OrdinalIgnoreWhiteSpace = 6, - /// - /// Compare strings using ordinal sort rules, ignoring the case and ignoring the - /// white space characters of the strings being compared. - /// - OrdinalIgnoreCaseIgnoreWhiteSpace = 7 - } + /// The string to be checked. + /// The range where the string's length must be in-between. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the length of is not with the specified . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLengthIn([NotNull][ValidatedNotNull] this string? parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (!range.IsValueWithinRange(parameter.MustNotBeNull(parameterName, message).Length)) + { + Throw.StringLengthNotInRange(parameter, range, parameterName, message); + } + + return parameter; + } - /// - /// This class caches instances to avoid use of the typeof operator. - /// - internal abstract class Types - { /// - /// Gets the type. + /// Ensures that the string's length is within the specified range, or otherwise throws your custom exception. /// - public static readonly Type FlagsAttributeType = typeof(FlagsAttribute); - } + /// The string to be checked. + /// The range where the string's length must be in-between. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or its length is not within the specified range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLengthIn([NotNull][ValidatedNotNull] this string? parameter, Range range, Func, Exception> exceptionFactory) + { + if (parameter is null || !range.IsValueWithinRange(parameter.Length)) + { + Throw.CustomException(exceptionFactory, parameter, range); + } - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class ValidatedNotNullAttribute : Attribute - { - } -} + return parameter; + } -namespace Light.GuardClauses.Exceptions -{ - /// - /// This exception indicates that an URI is absolute instead of relative. - /// - [Serializable] - internal class AbsoluteUriException : UriException - { /// - /// Creates a new instance of . + /// Ensures that the specified parameter is not the default value, or otherwise throws an + /// for reference types, or an for value types. /// + /// The value to be checked. /// The name of the parameter (optional). - /// The message of the exception (optional). - public AbsoluteUriException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is a reference type and null. + /// Thrown when is a value type and the default value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeDefault([NotNull, ValidatedNotNull] this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (default(T)is null) + { + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } - /// - protected AbsoluteUriException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; + } + + if (EqualityComparer.Default.Equals(parameter, default !)) + { + Throw.ArgumentDefault(parameterName, message); + } + +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + + return parameter; +#pragma warning restore CS8777 } - } - /// - /// This exception indicates that a value of a value type is the default value. - /// - [Serializable] - internal class ArgumentDefaultException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the specified parameter is not the default value, or otherwise throws your custom exception. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public ArgumentDefaultException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The value to be checked. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is the default value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeDefault([NotNull, ValidatedNotNull] this T parameter, Func exceptionFactory) { - } + if (default(T)is null) + { + if (parameter is null) + { + Throw.CustomException(exceptionFactory); + } - /// - protected ArgumentDefaultException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; + } + + if (EqualityComparer.Default.Equals(parameter, default !)) + { + Throw.CustomException(exceptionFactory); + } + +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + + return parameter; +#pragma warning restore CS8777 } - } - /// - /// This exception indicates that the state of a collection is invalid. - /// - [Serializable] - internal class CollectionException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the string is shorter than the specified length, or otherwise throws a . /// + /// The string to be checked. + /// The length that the string must be shorter than. /// The name of the parameter (optional). - /// The message of the exception (optional). - public CollectionException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length greater than or equal to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeShorterThan([NotNull][ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (parameter.MustNotBeNull(parameterName, message).Length >= length) + { + Throw.StringNotShorterThan(parameter, length, parameterName, message); + } - /// - protected CollectionException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that a collection has no items. - /// - [Serializable] - internal class EmptyCollectionException : InvalidCollectionCountException - { /// - /// Creates a new instance of . + /// Ensures that the string is shorter than the specified length, or otherwise throws your custom exception. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public EmptyCollectionException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The string to be checked. + /// The length that the string must be shorter than. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length greater than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeShorterThan([NotNull][ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) { - } + if (parameter is null || parameter.Length >= length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } - /// - protected EmptyCollectionException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that that a GUID is empty. - /// - [Serializable] - internal class EmptyGuidException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the span is shorter than the specified length, or otherwise throws an . /// + /// The span to be checked. + /// The length value that the span must be shorter than. /// The name of the parameter (optional). - /// The message of the exception (optional). - public EmptyGuidException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeShorterThan(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { + ((ReadOnlySpan)parameter).MustBeShorterThan(length, parameterName, message); + return parameter; } - /// - protected EmptyGuidException(SerializationInfo info, StreamingContext context) : base(info, context) + /// + /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length value that the span must be shorter than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustBeShorterThan(this Span parameter, int length, SpanExceptionFactory exceptionFactory) { + if (parameter.Length >= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; } - } - /// - /// This exception indicates that a string is empty. - /// - [Serializable] - internal class EmptyStringException : StringException - { /// - /// Creates a new instance of . + /// Ensures that the span is shorter than the specified length, or otherwise throws an . /// + /// The span to be checked. + /// The length value that the span must be shorter than. /// The name of the parameter (optional). - /// The message of the exception (optional). - public EmptyStringException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (parameter.Length >= length) + { + Throw.SpanMustBeShorterThan(parameter, length, parameterName, message); + } - /// - protected EmptyStringException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that a value is not defined in the corresponding enum type. - /// - [Serializable] - internal class EnumValueNotDefinedException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the span is shorter than the specified length, or otherwise throws your custom exception. /// - /// The name of the parameter. - /// The message of the exception. - public EnumValueNotDefinedException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The span to be checked. + /// The length value that the span must be shorter than. + /// The delegate that creates your custom exception. and are passed to it. + /// Your custom exception thrown when is longer than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustBeShorterThan(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) { - } + if (parameter.Length >= length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } - /// - protected EnumValueNotDefinedException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that a collection contains an item that must not be part of it. - /// - [Serializable] - internal class ExistingItemException : CollectionException - { /// - /// Creates a new instance of . + /// Checks if the specified collection is null or empty. + /// + /// The collection to be checked. + /// True if the collection is null or empty, else false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> true, collection:canbenull; => false, collection:notnull")] + public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? collection) => collection is null || collection.Count() == 0; + /// + /// Checks if the specified string is null or empty. + /// + /// The string to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("=> false, string:notnull; => true, string:canbenull")] + public static bool IsNullOrEmpty([NotNullWhen(false)] this string? @string) => string.IsNullOrEmpty(@string); + /// + /// Ensures that the specified URI has the "https" scheme, or otherwise throws an . /// + /// The URI to be checked. /// The name of the parameter (optional). - /// The message of the exception (optional). - public ExistingItemException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when uses a different scheme than "https". + /// Thrown when is relative and thus has no scheme. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpsUrl([NotNull][ValidatedNotNull] this Uri? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustHaveScheme("https", parameterName, message); + /// + /// Ensures that the specified URI has the "https" scheme, or otherwise throws your custom exception. + /// + /// The URI to be checked. + /// The delegate that creates the exception to be thrown. is passed to this delegate. + /// Your custom exception thrown when uses a different scheme than "https", or when is a relative URI, or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static Uri MustBeHttpsUrl([NotNull][ValidatedNotNull] this Uri? parameter, Func exceptionFactory) => parameter.MustHaveScheme("https", exceptionFactory); + /// + /// Ensures that the string is not null and trimmed at the end, or otherwise throws a . + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not trimmed at the end, i.e. they end with white space characters. + /// Empty strings are regarded as trimmed. + /// + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtEnd([NotNull][ValidatedNotNull] this string? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { + if (!parameter.MustNotBeNull(parameterName, message).IsTrimmedAtEnd()) + { + Throw.NotTrimmedAtEnd(parameter, parameterName, message); + } + + return parameter; } - /// - protected ExistingItemException(SerializationInfo info, StreamingContext context) : base(info, context) + /// + /// Ensures that the string is not null and trimmed at the end, or otherwise throws your custom exception. + /// Empty strings are regarded as trimmed. + /// + /// The string to be checked. + /// The delegate that creates your custom exception. is passed to this delegate. + /// Your custom exception thrown when is null or not trimmed at the end. Empty strings are regarded as trimmed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustBeTrimmedAtEnd([NotNull][ValidatedNotNull] this string? parameter, Func exceptionFactory) { + if (parameter is null || !parameter.AsSpan().IsTrimmedAtEnd()) + { + Throw.CustomException(exceptionFactory, parameter); + } + + return parameter; } - } - /// - /// This exception indicates that a collection has an invalid number of items. - /// - [Serializable] - internal class InvalidCollectionCountException : CollectionException - { /// - /// Creates a new instance of . + /// Ensures that the specified parameter is not null when is a reference type, or otherwise + /// throws an . PLEASE NOTICE: you should only use this assertion in generic contexts, + /// use by default. /// + /// The value to be checked for null. /// The name of the parameter (optional). - /// The message of the exception (optional). - public InvalidCollectionCountException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when is a reference type and is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeNullReference([NotNull, ValidatedNotNull, NoEnumeration] this T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (default(T) != null) + { + // If we end up here, parameter cannot be null +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - /// - protected InvalidCollectionCountException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; +#pragma warning restore CS8777 + } + + if (parameter is null) + { + Throw.ArgumentNull(parameterName, message); + } + + return parameter; } - } - /// - /// This exception indicates that configuration data is invalid. - /// - [Serializable] - internal class InvalidConfigurationException : Exception - { /// - /// Initializes a new instance of . + /// Ensures that the specified parameter is not null when is a reference type, or otherwise + /// throws your custom exception. PLEASE NOTICE: you should only use this assertion in generic contexts, + /// use by default. /// - /// The message of the exception (optional). - /// The exception that is the cause of this one (optional). - public InvalidConfigurationException(string? message = null, Exception? innerException = null) : base(message, innerException) + /// The value to be checked for null. + /// The delegate that creates your custom exception. + /// Your custom exception thrown when is a reference type and is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeNullReference([NotNull, ValidatedNotNull, NoEnumeration] this T parameter, Func exceptionFactory) { - } + if (default(T) != null) + { + // If we end up here, parameter cannot be null +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - /// - protected InvalidConfigurationException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; +#pragma warning restore CS8777 + } + + if (parameter is null) + { + Throw.CustomException(exceptionFactory); + } + + return parameter; } - } - /// - /// This exception indicates that a value is invalid. - /// - [Serializable] - internal class InvalidDateTimeException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the string does not end with the specified value, or otherwise throws a . /// + /// The string to be checked. + /// The other string must not end with. + /// One of the enumeration values that specifies the rules for the search (optional). The default value is . /// The name of the parameter (optional). - /// The message of the exception (optional). - public InvalidDateTimeException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when ends with . + /// Thrown when or is null. + /// Thrown when is not a valid value. + public static string MustNotEndWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType = StringComparison.CurrentCulture, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (parameter.MustNotBeNull(parameterName, message).EndsWith(value, comparisonType)) + { + Throw.StringEndsWith(parameter, value, comparisonType, parameterName, message); + } - /// - protected InvalidDateTimeException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that an Email address is invalid. - /// - [Serializable] - internal class InvalidEmailAddressException : StringException - { /// - /// Creates a new instance of . + /// Ensures that the string does not end with the specified value, or otherwise throws your custom exception. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public InvalidEmailAddressException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The string to be checked. + /// The other string must not end with. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when ends with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotEndWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, [NotNull, ValidatedNotNull] Func exceptionFactory) { - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.EndsWith(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } - /// - protected InvalidEmailAddressException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that the data is in invalid state. - /// - [Serializable] - internal class InvalidStateException : Exception - { /// - /// Creates a new instance of . + /// Ensures that the string does not end with the specified value, or otherwise throws your custom exception. /// - /// The message of the exception (optional). - /// The exception that is the cause of this one (optional). - public InvalidStateException(string? message = null, Exception? innerException = null) : base(message, innerException) + /// The string to be checked. + /// The other string must not end with. + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates your custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when ends with , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; value:null => halt; exceptionFactory:null => halt")] + public static string MustNotEndWith([NotNull, ValidatedNotNull] this string? parameter, [NotNull, ValidatedNotNull] string value, StringComparison comparisonType, [NotNull, ValidatedNotNull] Func exceptionFactory) { - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- caller might have NRTs turned off + if (parameter is null || value is null || parameter.EndsWith(value, comparisonType)) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } - /// - protected InvalidStateException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that an URI has an invalid scheme. - /// - [Serializable] - internal class InvalidUriSchemeException : UriException - { /// - /// Creates a new instance of . + /// Ensures that the string has the specified length, or otherwise throws a . /// + /// The string to be checked. + /// The asserted length of the string. /// The name of the parameter (optional). - /// The message of the exception (optional). - public InvalidUriSchemeException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has a length other than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLength([NotNull][ValidatedNotNull] this string? parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (parameter.MustNotBeNull(parameterName, message).Length != length) + { + Throw.StringLengthNotEqualTo(parameter, length, parameterName, message); + } - /// - protected InvalidUriSchemeException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that an item is not present in a collection. - /// - [Serializable] - internal class MissingItemException : CollectionException - { /// - /// Creates a new instance of . + /// Ensures that the string has the specified length, or otherwise throws your custom exception. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public MissingItemException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The string to be checked. + /// The asserted length of the string. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when is null or when it has a length other than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustHaveLength([NotNull][ValidatedNotNull] this string? parameter, int length, Func exceptionFactory) { - } + if (parameter is null || parameter.Length != length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } - /// - protected MissingItemException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that a has no value. - /// - [Serializable] - internal class NullableHasNoValueException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the span has the specified length, or otherwise throws an . /// + /// The span to be checked. + /// The length that the span must have. /// The name of the parameter (optional). - /// The message of the exception (optional). - public NullableHasNoValueException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustHaveLength(this Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { + ((ReadOnlySpan)parameter).MustHaveLength(length, parameterName, message); + return parameter; } - /// - protected NullableHasNoValueException(SerializationInfo info, StreamingContext context) : base(info, context) + /// + /// Ensures that the span has the specified length, or otherwise throws your custom exception. + /// + /// The span to be checked. + /// The length that the span must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span MustHaveLength(this Span parameter, int length, SpanExceptionFactory exceptionFactory) { + if (parameter.Length != length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } + + return parameter; } - } - /// - /// This exception indicates that an URI is relative instead of absolute. - /// - [Serializable] - internal class RelativeUriException : UriException - { /// - /// Creates a new instance of . + /// Ensures that the span has the specified length, or otherwise throws your custom exception. /// + /// The span to be checked. + /// The length that the span must have. /// The name of the parameter (optional). - /// The message of the exception (optional). - public RelativeUriException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) { - } + if (parameter.Length != length) + { + Throw.InvalidSpanLength(parameter, length, parameterName, message); + } - /// - protected RelativeUriException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that two references point to the same object. - /// - [Serializable] - internal class SameObjectReferenceException : ArgumentException - { /// - /// Creates a new instance of . + /// Ensures that the span has the specified length, or otherwise throws your custom exception. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public SameObjectReferenceException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The span to be checked. + /// The length that the span must have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not have the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan MustHaveLength(this ReadOnlySpan parameter, int length, ReadOnlySpanExceptionFactory exceptionFactory) { - } + if (parameter.Length != length) + { + Throw.CustomSpanException(exceptionFactory, parameter, length); + } - /// - protected SameObjectReferenceException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that a string is not matching a regular expression. - /// - [Serializable] - internal class StringDoesNotMatchException : StringException - { /// - /// Creates a new instance of . + /// Checks if the specified type derives from the other type. Internally, this method uses + /// by default so that constructed generic types and their corresponding generic type definitions are regarded as equal. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public StringDoesNotMatchException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The type info to be checked. + /// The base class that should derive from. + /// Thrown when or is null. + [ContractAnnotation("type:null => halt; baseClass:null => halt")] + public static bool DerivesFrom([NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type baseClass) { - } + baseClass.MustNotBeNull(nameof(baseClass)); + var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; + while (currentBaseType != null) + { + if (currentBaseType.IsEquivalentTypeTo(baseClass)) + { + return true; + } - /// - protected StringDoesNotMatchException(SerializationInfo info, StreamingContext context) : base(info, context) - { + currentBaseType = currentBaseType.BaseType; + } + + return false; } - } - /// - /// This exception indicates that a string is in an invalid state. - /// - [Serializable] - internal class StringException : ArgumentException - { /// - /// Creates a new instance of . + /// Checks if the specified type derives from the other type. This overload uses the specified + /// to compare the types. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public StringException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The type info to be checked. + /// The base class that should derive from. + /// The equality comparer used to compare the types. + /// Thrown when , or , or is null. + [ContractAnnotation("type:null => halt; baseClass:null => halt; typeComparer:null => halt")] + public static bool DerivesFrom([NotNull][ValidatedNotNull] this Type type, [NotNull][ValidatedNotNull] Type baseClass, [NotNull][ValidatedNotNull] IEqualityComparer typeComparer) { - } + baseClass.MustNotBeNull(nameof(baseClass)); + typeComparer.MustNotBeNull(nameof(typeComparer)); + var currentBaseType = type.MustNotBeNull(nameof(type)).BaseType; + while (currentBaseType != null) + { + if (typeComparer.Equals(currentBaseType, baseClass)) + { + return true; + } - /// - protected StringException(SerializationInfo info, StreamingContext context) : base(info, context) - { + currentBaseType = currentBaseType.BaseType; + } + + return false; } - } - /// - /// This exception indicates that a string has an invalid length. - /// - [Serializable] - internal class StringLengthException : StringException - { /// - /// Creates a new instance of . + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. /// The name of the parameter (optional). - /// The message of the exception (optional). - public StringLengthException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately(this double parameter, double other, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) => parameter.MustBeGreaterThanOrApproximately(other, 0.0001, parameterName, message); + /// + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . + /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately(this double parameter, double other, Func exceptionFactory) { - } + if (!parameter.IsGreaterThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } - /// - protected StringLengthException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// This exception indicates that a string is in an invalid state. - /// - [Serializable] - internal class SubstringException : StringException - { /// - /// Creates a new instance of . + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws an . /// + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. /// The name of the parameter (optional). - /// The message of the exception (optional). - public SubstringException(string? parameterName = null, string? message = null) : base(parameterName, message) + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately(this double parameter, double other, double tolerance, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) { - } + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.MustBeGreaterThanOrApproximately(parameter, other, tolerance, parameterName, message); + } - /// - protected SubstringException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return parameter; } - } - /// - /// Provides static factory methods that throw default exceptions. - /// - internal static class Throw - { - /// - /// Throws the default , using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ArgumentNull(string? parameterName = null, string? message = null) => throw new ArgumentNullException(parameterName, message ?? $"{parameterName ?? "The value"} must not be null."); /// - /// Throws the default indicating that a value is the default value of its type, using the optional parameter name and message. + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ArgumentDefault(string? parameterName = null, string? message = null) => throw new ArgumentDefaultException(parameterName, message ?? $"{parameterName ?? "The value"} must not be the default value."); - /// - /// Throws the default indicating that a reference cannot be downcast, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidTypeCast(object? parameter, Type targetType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new TypeCastException(parameterName, message ?? $"{parameterName ?? "The value"} {parameter.ToStringOrNull()} cannot be cast to \"{targetType}\"."); + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeGreaterThanOrApproximately(this double parameter, double other, double tolerance, Func exceptionFactory) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + /// - /// Throws the default indicating that a value is not one of the constants defined in an enum, using the optional parameter name and message. + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EnumValueNotDefined(T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : Enum => throw new EnumValueNotDefinedException(parameterName, message ?? $"{parameterName ?? "The value"} \"{parameter}\" must be one of the defined constants of enum \"{parameter.GetType()}\", but it actually is not."); + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately(this float parameter, float other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustBeGreaterThanOrApproximately(other, 0.0001f, parameterName, message); /// - /// Throws the default indicating that a GUID is empty, using the optional parameter name and message. + /// Ensures that the specified is greater than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EmptyGuid(string? parameterName = null, string? message = null) => throw new EmptyGuidException(parameterName, message ?? $"{parameterName ?? "The value"} must be a valid GUID, but it actually is an empty one."); + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately(this float parameter, float other, Func exceptionFactory) + { + if (!parameter.IsGreaterThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + /// - /// Throws an using the optional message. + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws an . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidOperation(string? message = null) => throw new InvalidOperationException(message); + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately(this float parameter, float other, float tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.MustBeGreaterThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + /// - /// Throws an using the optional message. + /// Ensures that the specified is greater than or approximately equal to the given + /// value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidState(string? message = null) => throw new InvalidStateException(message); + /// The value to be checked. + /// The value that should be greater than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not greater than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeGreaterThanOrApproximately(this float parameter, float other, float tolerance, Func exceptionFactory) + { + if (!parameter.IsGreaterThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + /// - /// Throws an using the optional parameter name and message. + /// Ensures that the specified is greater than the given value, or otherwise throws an . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void Argument(string? parameterName = null, string? message = null) => throw new ArgumentException(message ?? $"{parameterName ?? "The value"} is invalid.", parameterName); + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is less than or equal to . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustBeGreaterThan([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) <= 0) + { + Throw.MustBeGreaterThan(parameter, other, parameterName, message); + } + + return parameter; + } + /// - /// Throws an using the optional message. + /// Ensures that the specified is greater than the given value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidEmailAddress(string parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidEmailAddressException(parameterName, message ?? $"{parameterName ?? "The string"} must be a valid email address, but it actually is \"{parameter}\"."); + /// The comparable to be checked. + /// The boundary value that must be less than . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than or equal to , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustBeGreaterThan([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) <= 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } + /// - /// Throws the default indicating that a has no value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NullableHasNoValue(string? parameterName = null, string? message = null) => throw new NullableHasNoValueException(parameterName, message ?? $"{parameterName ?? "The nullable"} must have a value, but it actually is null."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately(this double parameter, double other, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) => parameter.MustBeLessThanOrApproximately(other, 0.0001, parameterName, message); /// - /// Throws the default indicating that a comparable value must not be less than the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeLessThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be less than {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately(this double parameter, double other, Func exceptionFactory) + { + if (!parameter.IsLessThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + /// - /// Throws the default indicating that a comparable value must be less than the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws an . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeLessThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately(this double parameter, double other, double tolerance, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.MustBeLessThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + /// - /// Throws the default indicating that a comparable value must not be less than or equal to the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeLessThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be less than or equal to {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double MustBeLessThanOrApproximately(this double parameter, double other, double tolerance, Func exceptionFactory) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + /// - /// Throws the default indicating that a comparable value must not be greater than or equal to the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001f, or otherwise throws an + /// . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeGreaterThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be greater than or equal to {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately(this float parameter, float other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => parameter.MustBeLessThanOrApproximately(other, 0.0001f, parameterName, message); /// - /// Throws the default indicating that a comparable value must be greater than or equal to the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, using the default tolerance of 0.0001, or otherwise throws an + /// . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeGreaterThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than or equal to {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// + /// The delegate that creates your custom exception. and + /// are passed to this delegate. + /// + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately(this float parameter, float other, Func exceptionFactory) + { + if (!parameter.IsLessThanOrApproximately(other)) + { + Throw.CustomException(exceptionFactory, parameter, other); + } + + return parameter; + } + /// - /// Throws the default indicating that a comparable value must be greater than the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws an . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeGreaterThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// + /// Thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately(this float parameter, float other, float tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.MustBeLessThanOrApproximately(parameter, other, tolerance, parameterName, message); + } + + return parameter; + } + /// - /// Throws the default indicating that a comparable value must not be greater than the given boundary value, using the optional parameter name and message. + /// Ensures that the specified is less than or approximately equal to the given + /// value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeGreaterThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be greater than {boundary}, but it actually is {parameter}."); + /// The value to be checked. + /// The value that should be less than or approximately equal to. + /// The tolerance indicating how much the two values may differ from each other. + /// + /// The delegate that creates your custom exception. , + /// , and are passed to this delegate. + /// + /// + /// Your custom exception thrown when is not less than or approximately equal to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MustBeLessThanOrApproximately(this float parameter, float other, float tolerance, Func exceptionFactory) + { + if (!parameter.IsLessThanOrApproximately(other, tolerance)) + { + Throw.CustomException(exceptionFactory, parameter, other, tolerance); + } + + return parameter; + } + /// - /// Throws the default indicating that a comparable value must be less than or equal to the given boundary value, using the optional parameter name and message. + /// Checks if the value is not within the specified range. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeLessThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than or equal to {boundary}, but it actually is {parameter}."); + /// The comparable to be checked. + /// The range where must not be in-between. + /// True if the parameter is not within the specified range, else false. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNotIn([NotNull][ValidatedNotNull] this T parameter, Range range) + where T : IComparable => !range.IsValueWithinRange(parameter); /// - /// Throws the default indicating that a value is not within a specified range, using the optional parameter name and message. + /// Ensures that the collection contains the specified item, or otherwise throws a . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeInRange(T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}."); + /// The collection to be checked. + /// The item that must be part of the collection. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustContain([NotNull][ValidatedNotNull] this TCollection? parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where TCollection : class, IEnumerable + { + if (parameter is ICollection collection) + { + if (!collection.Contains(item)) + { + Throw.MissingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + if (!parameter.MustNotBeNull(parameterName, message).Contains(item)) + { + Throw.MissingItem(parameter, item, parameterName, message); + } + + return parameter; + } + /// - /// Throws the default indicating that a value is within a specified range, using the optional parameter name and message. + /// Ensures that the collection contains the specified item, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustNotBeInRange(T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}."); + /// The collection to be checked. + /// The item that must be part of the collection. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when does not contain , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static TCollection MustContain([NotNull][ValidatedNotNull] this TCollection? parameter, TItem item, Func exceptionFactory) + where TCollection : class, IEnumerable + { + if (parameter is ICollection collection) + { + if (!collection.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + + if (parameter is null || !parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } + /// - /// Throws the default indicating that two references point to the same object, using the optional parameter name and message. + /// Ensures that the string contains the specified substring, or otherwise throws a . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SameObjectReference(T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) - where T : class => throw new SameObjectReferenceException(parameterName, message ?? $"{parameterName ?? "The reference"} must not point to object \"{parameter}\", but it actually does."); + /// The string to be checked. + /// The substring that must be part of . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain([NotNull][ValidatedNotNull] this string? parameter, string? value, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (!parameter.MustNotBeNull(parameterName, message).Contains(value.MustNotBeNull(nameof(value), message))) + { + Throw.StringDoesNotContain(parameter, value, parameterName, message); + } + + return parameter; + } + /// - /// Throws the default indicating that a string is empty, using the optional parameter name and message. + /// Ensures that the string contains the specified value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EmptyString(string? parameterName = null, string? message = null) => throw new EmptyStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be an empty string, but it actually is."); + /// The string to be checked. + /// The substring that must be part of . + /// The delegate that creates you custom exception. and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain([NotNull][ValidatedNotNull] this string? parameter, string value, Func exceptionFactory) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || !parameter.Contains(value)) + { + Throw.CustomException(exceptionFactory, parameter, value!); + } + + return parameter; + } + /// - /// Throws the default indicating that a string contains only white space, using the optional parameter name and message. + /// Ensures that the string contains the specified value, or otherwise throws a . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void WhiteSpaceString(string parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new WhiteSpaceStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain only white space, but it actually is \"{parameter}\"."); + /// The string to be checked. + /// The substring that must be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when does not contain . + /// Thrown when or is null. + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + { + if (parameter.MustNotBeNull(parameterName, message).IndexOf(value.MustNotBeNull(nameof(value), message), comparisonType) < 0) + { + Throw.StringDoesNotContain(parameter, value, comparisonType, parameterName, message); + } + + return parameter; + } + /// - /// Throws the default indicating that a string does not match a regular expression, using the optional parameter name and message. + /// Ensures that the string contains the specified value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotMatch(string parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringDoesNotMatchException(parameterName, message ?? $"{parameterName ?? "The string"} must match the regular expression \"{regex}\", but it actually is \"{parameter}\"."); + /// The string to be checked. + /// The substring that must be part of . + /// One of the enumeration values that specifies the rules for the search. + /// The delegate that creates you custom exception. , , and are passed to this delegate. + /// + /// Your custom exception thrown when does not contain , + /// or when is null, + /// or when is null. + /// + /// Thrown when is not a valid value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static string MustContain([NotNull][ValidatedNotNull] this string? parameter, string value, StringComparison comparisonType, Func exceptionFactory) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || value is null || parameter.IndexOf(value, comparisonType) < 0) + { + Throw.CustomException(exceptionFactory, parameter, value!, comparisonType); + } + + return parameter; + } + /// - /// Throws the default indicating that a string does not contain another string as a substring, using the optional parameter name and message. + /// Ensures that the specified is not less than the given value, or otherwise throws an . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotContain(string parameter, string substring, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when the specified is less than . + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull")] + public static T MustNotBeLessThan([NotNull][ValidatedNotNull] this T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable + { + if (parameter.MustNotBeNullReference(parameterName, message).CompareTo(other) < 0) + { + Throw.MustNotBeLessThan(parameter, other, parameterName, message); + } + + return parameter; + } + /// - /// Throws the default indicating that a string does not contain another string as a substring, using the optional parameter name and message. + /// Ensures that the specified is not less than the given value, or otherwise throws your custom exception. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringDoesNotContain(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()} ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// The comparable to be checked. + /// The boundary value that must be less than or equal to . + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when the specified is less than , or when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("parameter:null => halt; parameter:notnull => notnull; exceptionFactory:null => halt")] + public static T MustNotBeLessThan([NotNull][ValidatedNotNull] this T parameter, T other, Func exceptionFactory) + where T : IComparable + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - caller might have NRTs turned off + if (parameter is null || parameter.CompareTo(other) < 0) + { + Throw.CustomException(exceptionFactory, parameter!, other); + } + + return parameter; + } + } + + /// + /// Represents an that uses + /// to compare types. This check works like the normal type equality comparison, but when two + /// generic types are compared, they are regarded as equal when one of them is a constructed generic type + /// and the other one is the corresponding generic type definition. + /// + internal sealed class EquivalentTypeComparer : IEqualityComparer + { /// - /// Throws the default indicating that a string does contain another string as a substring, using the optional parameter name and message. + /// Gets a singleton instance of the equality comparer. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringContains(string parameter, string substring, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring, but it actually is {parameter.ToStringOrNull()}."); + public static readonly EquivalentTypeComparer Instance = new(); /// - /// Throws the default indicating that a string does contain another string as a substring, using the optional parameter name and message. + /// Checks if the two types are equivalent (using ). + /// This check works like the normal type equality comparison, but when two + /// generic types are compared, they are regarded as equal when one of them is a constructed generic type + /// and the other one is the corresponding generic type definition. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringContains(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// The first type. + /// The second type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Type? x, Type? y) => x.IsEquivalentTypeTo(y); /// - /// Throws the default indicating that a string is not a substring of another one, using the optional parameter name and message. + /// Returns the hash code of the given type. When the specified type is a constructed generic type, + /// the hash code of the generic type definition is returned instead. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotSubstring(string parameter, string other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}."); + /// The type whose hash code is requested. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetHashCode(Type type) => // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + type is null ? 0 : type.IsConstructedGenericType ? type.GetGenericTypeDefinition().GetHashCode() : type.GetHashCode(); + } + + /// + /// Defines a range that can be used to check if a specified is in between it or not. + /// + /// The type that the range should be applied to. + internal readonly struct Range : IEquatable> where T : IComparable + { /// - /// Throws the default indicating that a string is not a substring of another one, using the optional parameter name and message. + /// Gets the lower boundary of the range. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotSubstring(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + public readonly T From; /// - /// Throws the default indicating that a string is a substring of another one, using the optional parameter name and message. + /// Gets the upper boundary of the range. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void Substring(string parameter, string other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}."); + public readonly T To; /// - /// Throws the default indicating that a string is a substring of another one, using the optional parameter name and message. + /// Gets the value indicating whether the From value is included in the range. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void Substring(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + public readonly bool IsFromInclusive; /// - /// Throws the default indicating that a string is not shorter than the given length, using the optional parameter name an message. + /// Gets the value indicating whether the To value is included in the range. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotShorterThan(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be shorter than {length}, but it actually has length {parameter.Length}."); + public readonly bool IsToInclusive; + private readonly int _expectedLowerBoundaryResult; + private readonly int _expectedUpperBoundaryResult; /// - /// Throws the default indicating that a string is not shorter or equal to the given length, using the optional parameter name an message. + /// Creates a new instance of . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotShorterThanOrEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be shorter or equal to {length}, but it actually has length {parameter.Length}."); + /// The lower boundary of the range. + /// The upper boundary of the range. + /// The value indicating whether is part of the range. + /// The value indicating whether is part of the range. + /// Thrown when is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Range(T from, T to, bool isFromInclusive = true, bool isToInclusive = true) + { + From = from.MustNotBeNullReference(nameof(from)); + To = to.MustNotBeLessThan(from, nameof(to)); + IsFromInclusive = isFromInclusive; + IsToInclusive = isToInclusive; + _expectedLowerBoundaryResult = isFromInclusive ? 0 : 1; + _expectedUpperBoundaryResult = isToInclusive ? 0 : -1; + } + /// - /// Throws the default indicating that a string has a different length than the specified one, using the optional parameter name an message. + /// Checks if the specified is within range. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringLengthNotEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must have length {length}, but it actually has length {parameter.Length}."); + /// The value to be checked. + /// True if value is within range, otherwise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsValueWithinRange(T value) => value.MustNotBeNullReference(nameof(value)).CompareTo(From) >= _expectedLowerBoundaryResult && value.CompareTo(To) <= _expectedUpperBoundaryResult; /// - /// Throws the default indicating that a string is not longer than the given length, using the optional parameter name an message. + /// Use this method to create a range in a fluent style using method chaining. + /// Defines the lower boundary as an inclusive value. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotLongerThan(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be longer than {length}, but it actually has length {parameter.Length}."); + /// The value that indicates the inclusive lower boundary of the resulting range. + /// A value you can use to fluently define the upper boundary of a new range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RangeFromInfo FromInclusive(T value) => new(value, true); /// - /// Throws the default indicating that a string is not longer or equal to the given length, using the optional parameter name an message. + /// Use this method to create a range in a fluent style using method chaining. + /// Defines the lower boundary as an exclusive value. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringNotLongerThanOrEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); + /// The value that indicates the exclusive lower boundary of the resulting range. + /// A value you can use to fluently define the upper boundary of a new range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RangeFromInfo FromExclusive(T value) => new(value, false); /// - /// Throws the default indicating that a string's length is not in between the given range, using the optional parameter name an message. + /// The nested can be used to fluently create a . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void StringLengthNotInRange(string parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}."); + public readonly struct RangeFromInfo + { + private readonly T _from; + private readonly bool _isFromInclusive; + /// + /// Creates a new RangeFromInfo. + /// + /// The lower boundary of the range. + /// The value indicating whether is part of the range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RangeFromInfo(T from, bool isFromInclusive) + { + _from = from; + _isFromInclusive = isFromInclusive; + } + + /// + /// Use this method to create a range in a fluent style using method chaining. + /// Defines the upper boundary as an exclusive value. + /// + /// The value that indicates the exclusive upper boundary of the resulting range. + /// A new range with the specified upper and lower boundaries. + /// + /// Thrown when is less than the lower boundary value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Range ToExclusive(T value) => new(_from, value, _isFromInclusive, false); + /// + /// Use this method to create a range in a fluent style using method chaining. + /// Defines the upper boundary as an inclusive value. + /// + /// The value that indicates the inclusive upper boundary of the resulting range. + /// A new range with the specified upper and lower boundaries. + /// + /// Thrown when is less than the lower boundary value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Range ToInclusive(T value) => new(_from, value, _isFromInclusive); + } + + /// + public override string ToString() => $"Range from {CreateRangeDescriptionText()}"; /// - /// Throws the default indicating that a string is not equal to "\n" or "\r\n". + /// Returns either "inclusive" or "exclusive", depending on whether is true or false. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotNewLine(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be either \"\\n\" or \"\\r\\n\", but it actually is {parameter.ToStringOrNull()}."); + public string LowerBoundaryText {[MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetBoundaryText(IsFromInclusive); } /// - /// Throws the default indicating that a string is not trimmed. + /// Returns either "inclusive" or "exclusive", depending on whether is true or false. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotTrimmed(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed, but it actually is {parameter.ToStringOrNull()}."); + public string UpperBoundaryText {[MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetBoundaryText(IsToInclusive); } + /// - /// Throws the default indicating that a string is not trimmed at the start. + /// Returns a text description of this range with the following pattern: From (inclusive | exclusive) to To (inclusive | exclusive). /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotTrimmedAtStart(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed at the start, but it actually is {parameter.ToStringOrNull()}."); + public string CreateRangeDescriptionText(string fromToConnectionWord = "to") => From + " (" + LowerBoundaryText + ") " + fromToConnectionWord + ' ' + To + " (" + UpperBoundaryText + ")"; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetBoundaryText(bool isInclusive) => isInclusive ? "inclusive" : "exclusive"; + /// + public bool Equals(Range other) + { + if (IsFromInclusive != other.IsFromInclusive || IsToInclusive != other.IsToInclusive) + { + return false; + } + + var comparer = EqualityComparer.Default; + return comparer.Equals(From, other.From) && comparer.Equals(To, other.To); + } + + /// + public override bool Equals(object? other) + { + if (other is null) + { + return false; + } + + return other is Range range && Equals(range); + } + + /// + public override int GetHashCode() => MultiplyAddHash.CreateHashCode(From, To, IsFromInclusive, IsToInclusive); /// - /// Throws the default indicating that a string is not trimmed at the end. + /// Checks if two ranges are equal. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void NotTrimmedAtEnd(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed at the end, but it actually is {parameter.ToStringOrNull()}."); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Range first, Range second) => first.Equals(second); /// - /// Throws the default indicating that two values are not equal, using the optional parameter name and message. + /// Checks if two ranges are not equal. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValuesNotEqual(T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValuesNotEqualException(parameterName, message ?? $"{parameterName ?? "The value"} must be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Range first, Range second) => first.Equals(second) == false; + } + + /// + /// Provides methods to simplify the creation of instances. + /// + internal static class Range + { /// - /// Throws the default indicating that two values are equal, using the optional parameter name and message. + /// Use this method to create a range in a fluent style using method chaining. + /// Defines the lower boundary as an inclusive value. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValuesEqual(T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValuesEqualException(parameterName, message ?? $"{parameterName ?? "The value"} must not be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); + /// The value that indicates the inclusive lower boundary of the resulting range. + /// A value you can use to fluently define the upper boundary of a new range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range.RangeFromInfo FromInclusive(T value) + where T : IComparable => new(value, true); /// - /// Throws the default indicating that a collection has an invalid number of items, using the optional parameter name and message. + /// Use this method to create a range in a fluent style using method chaining. + /// Defines the lower boundary as an exclusive value. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have count {count}, but it actually has count {parameter.Count()}."); + /// The value that indicates the exclusive lower boundary of the resulting range. + /// A value you can use to fluently define the upper boundary of a new range. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range.RangeFromInfo FromExclusive(T value) + where T : IComparable => new(value, false); /// - /// Throws the default indicating that a span has an invalid length, using the optional parameter name and message. + /// Creates a range with both boundaries inclusive. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidSpanLength(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must have length {length}, but it actually has length {parameter.Length}."); + /// The lower boundary of the range. + /// The upper boundary of the range. + /// A new range with both boundaries inclusive. + /// Thrown when is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range InclusiveBetween(T from, T to) + where T : IComparable => new(from, to); /// - /// Throws the default indicating that a span has an invalid length, using the optional parameter name and message. + /// Creates a range with both boundaries exclusive. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidSpanLength(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The read-only span"} must have length {length}, but it actually has length {parameter.Length}."); + /// The lower boundary of the range. + /// The upper boundary of the range. + /// A new range with both boundaries exclusive. + /// Thrown when is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range ExclusiveBetween(T from, T to) + where T : IComparable => new(from, to, false, false); /// - /// Throws the default indicating that a collection has less than a minimum number of items, using the optional parameter name and message. + /// Creates a range for the specified enumerable that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidMinimumCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have at least count {count}, but it actually has count {parameter.Count()}."); + /// + /// The count of this enumerable will be used to create the index range. Please ensure that this enumerable + /// is actually a collection, not a lazy enumerable. + /// + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range For(IEnumerable enumerable) => new(0, enumerable.Count(), true, false); /// - /// Throws the default indicating that a span is not longer than the specified length. + /// Creates a range for the specified enumerable that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThan(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than {length}, but it actually has length {parameter.Length}."); + /// + /// The count of this enumerable will be used to create the index range. Please ensure that this enumerable + /// is actually a collection, not a lazy enumerable. + /// + /// Thrown when is null. + public static Range For(IEnumerable enumerable) => new(0, enumerable.GetCount(), true, false); /// - /// Throws the default indicating that a span is not longer than the specified length. + /// Creates a range for the specified span that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThan(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than {length}, but it actually has length {parameter.Length}."); + /// + /// The length of the span is used to create a valid index range. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range For(ReadOnlySpan span) => new(0, span.Length, true, false); /// - /// Throws the default indicating that a span is not longer than and not equal to the specified length. + /// Creates a range for the specified span that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThanOrEqualTo(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// The length of the span is used to create a valid index range. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range For(Span span) => new(0, span.Length, true, false); /// - /// Throws the default indicating that a span is not longer than and not equal to the specified length. + /// Creates a range for the specified memory that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeLongerThanOrEqualTo(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// The length of the memory is used to create a valid index range. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range For(Memory memory) => new(0, memory.Length, true, false); /// - /// Throws the default indicating that a span is not shorter than the specified length. + /// Creates a range for the specified memory that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThan(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than {length}, but it actually has length {parameter.Length}."); + /// + /// The length of the memory is used to create a valid index range. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range For(ReadOnlyMemory memory) => new(0, memory.Length, true, false); /// - /// Throws the default indicating that a span is not shorter than the specified length. + /// Creates a range for the specified memory that encompasses all valid indexes. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThanOrEqualTo(in Span parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// The count of the segment is used to create a valid index range. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Range For(ArraySegment segment) => new(0, segment.Count, true, false); + } + + /// + /// Provides regular expressions that are used in string assertions. + /// +#if NET8_0 +public static partial class RegularExpressions +#else + internal static class RegularExpressions +#endif + { /// - /// Throws the default indicating that a span is not shorter than the specified length. + /// Gets the string that represents the . /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThanOrEqualTo(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than or equal to {length}, but it actually has length {parameter.Length}."); + // This is an AI-generated regex. I don't have any clue and I find it way to complex to ever understand it. + public const string EmailRegexText = @"^(?:(?:""(?:(?:[^""\\]|\\.)*)""|[\p{L}\p{N}!#$%&'*+\-/=?^_`{|}~-]+(?:\.[\p{L}\p{N}!#$%&'*+\-/=?^_`{|}~-]+)*)@(?:(?:[A-Za-z0-9](?:[A-Za-z0-9\-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}|(?:\[(?:IPv6:[0-9A-Fa-f:.]+)\])|(?:25[0-5]|2[0-4]\d|[01]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)){3}))$"; /// - /// Throws the default indicating that a span is not shorter than the specified length. + /// Gets the default regular expression for email validation. + /// This pattern is based on https://www.rhyous.com/2010/06/15/csharp-email-regular-expression/ and + /// was modified to satisfy all tests of https://blogs.msdn.microsoft.com/testing123/2009/02/06/email-address-test-cases/. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void SpanMustBeShorterThan(in ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than {length}, but it actually has length {parameter.Length}."); - /// - /// Throws the default indicating that a collection has more than a maximum number of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void InvalidMaximumCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have at most count {count}, but it actually has count {parameter.Count()}."); - /// - /// Throws the default indicating that a collection has no items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void EmptyCollection(string? parameterName = null, string? message = null) => throw new EmptyCollectionException(parameterName, message ?? $"{parameterName ?? "The collection"} must not be an empty collection, but it actually is."); - /// - /// Throws the default indicating that a collection is not containing the specified item, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MissingItem(IEnumerable parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new MissingItemException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The collection"} must contain {item.ToStringOrNull()}, but it actually does not.").AppendCollectionContent(parameter).ToString()); - /// - /// Throws the default indicating that a collection contains the specified item that should not be part of it, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ExistingItem(IEnumerable parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ExistingItemException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The collection"} must not contain {item.ToStringOrNull()}, but it actually does.").AppendCollectionContent(parameter).ToString()); - /// - /// Throws the default indicating that a value is not one of a specified collection of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValueNotOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValueIsNotOneOfException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The value"} must be one of the following items").AppendItemsWithNewLine(items).AppendLine($"but it actually is {parameter.ToStringOrNull()}.").ToString()); - /// - /// Throws the default indicating that a value is one of a specified collection of items, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void ValueIsOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValueIsOneOfException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The value"} must not be one of the following items").AppendItemsWithNewLine(items).AppendLine($"but it actually is {parameter.ToStringOrNull()}.").ToString()); - /// - /// Throws the default indicating that a URI is relative instead of absolute, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeAbsoluteUri(Uri parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new RelativeUriException(parameterName, message ?? $"{parameterName ?? "The URI"} must be an absolute URI, but it actually is \"{parameter}\"."); - /// - /// Throws the default indicating that a URI is absolute instead of relative, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeRelativeUri(Uri parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new AbsoluteUriException(parameterName, message ?? $"{parameterName ?? "The URI"} must be a relative URI, but it actually is \"{parameter}\"."); - /// - /// Throws the default indicating that a URI has an unexpected scheme, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void UriMustHaveScheme(Uri parameter, string scheme, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidUriSchemeException(parameterName, message ?? $"{parameterName ?? "The URI"} must use the scheme \"{scheme}\", but it actually is \"{parameter}\"."); - /// - /// Throws the default indicating that a URI does not use one of a set of expected schemes, using the optional parameter name and message. - /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void UriMustHaveOneSchemeOf(Uri parameter, IEnumerable schemes, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidUriSchemeException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The URI"} must use one of the following schemes").AppendItemsWithNewLine(schemes).AppendLine($"but it actually is \"{parameter}\".").ToString()); + public static readonly Regex EmailRegex = +#if NET8_0 + GenerateEmailRegex(); + + [GeneratedRegex(EmailRegexText, RegexOptions.ECMAScript | RegexOptions.CultureInvariant)] + private static partial Regex GenerateEmailRegex(); +#else + new(EmailRegexText, RegexOptions.ECMAScript | RegexOptions.CultureInvariant | RegexOptions.Compiled); +#endif + } + + /// + /// This class caches instances to avoid use of the typeof operator. + /// + internal abstract class Types + { /// - /// Throws the default indicating that a date time is not using , using the optional parameter name and message. + /// Gets the type. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeUtcDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Utc}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); + public static readonly Type FlagsAttributeType = typeof(FlagsAttribute); + } + + /// + /// Specifies the culture, case , and sort rules when comparing strings. + /// + /// + /// This enum is en extension of , adding + /// capabilities to ignore white space when making string equality comparisons. + /// See the when + /// you want to compare in such a way. + /// + internal enum StringComparisonType + { /// - /// Throws the default indicating that a date time is not using , using the optional parameter name and message. + /// Compare strings using culture-sensitive sort rules and the current culture. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeLocalDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Local}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); + CurrentCulture = 0, /// - /// Throws the default indicating that a date time is not using , using the optional parameter name and message. + /// Compare strings using culture-sensitive sort rules, the current culture, and + /// ignoring the case of the strings being compared. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void MustBeUnspecifiedDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Unspecified}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); + CurrentCultureIgnoreCase = 1, /// - /// Throws the exception that is returned by . + /// Compare strings using culture-sensitive sort rules and the invariant culture. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(); + InvariantCulture = 2, /// - /// Throws the exception that is returned by . is passed to . + /// Compare strings using culture-sensitive sort rules, the invariant culture, and + /// ignoring the case of the strings being compared. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory, T parameter) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(parameter); + InvariantCultureIgnoreCase = 3, /// - /// Throws the exception that is returned by . and are passed to . + /// Compare strings using ordinal sort rules. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory, T1 first, T2 second) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second); + Ordinal = 4, /// - /// Throws the exception that is returned by . , , and are passed to . + /// Compare strings using ordinal sort rules and ignoring the case of the strings + /// being compared. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomException(Func exceptionFactory, T1 first, T2 second, T3 third) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second, third); + OrdinalIgnoreCase = 5, /// - /// Throws the exception that is returned by . and are passed to . + /// Compare strings using ordinal sort rules and ignoring the white space characters + /// of the strings being compared. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomSpanException(SpanExceptionFactory exceptionFactory, in Span span, T value) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span, value); + OrdinalIgnoreWhiteSpace = 6, /// - /// Throws the exception that is returned by . and are passed to . + /// Compare strings using ordinal sort rules, ignoring the case and ignoring the + /// white space characters of the strings being compared. /// - [ContractAnnotation("=> halt")] - [DoesNotReturn] - public static void CustomSpanException(ReadOnlySpanExceptionFactory exceptionFactory, in ReadOnlySpan span, T value) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span, value); + OrdinalIgnoreCaseIgnoreWhiteSpace = 7, } /// - /// This exception indicates that a value cannot be cast to another type. + /// Represents an that compares strings using the + /// ordinal sort rules, ignoring the case and the white space characters. /// - [Serializable] - internal class TypeCastException : ArgumentException + internal sealed class OrdinalIgnoreCaseIgnoreWhiteSpaceComparer : IEqualityComparer { /// - /// Creates a new instance of . + /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the case and + /// the white space of the provided strings. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public TypeCastException(string? parameterName = null, string? message = null) : base(message, parameterName) - { - } - - /// - protected TypeCastException(SerializationInfo info, StreamingContext context) : base(info, context) + /// Thrown when or are null. + public bool Equals(string? x, string? y) { + x.MustNotBeNull(nameof(x)); + y.MustNotBeNull(nameof(y)); + return x.EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(y); } - } - /// - /// This exception indicates that an URI is invalid. - /// - [Serializable] - internal class UriException : ArgumentException - { /// - /// Creates a new instance of . + /// Gets the hash code for the specified string. The hash code is created only from the non-white space characters + /// which are interpreted as case-insensitive. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public UriException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// Thrown when is null. + public int GetHashCode(string @string) { - } + @string.MustNotBeNull(nameof(@string)); + var hashBuilder = MultiplyAddHashBuilder.Create(); + foreach (var character in @string) + { + if (!character.IsWhiteSpace()) + { + hashBuilder.CombineIntoHash(char.ToLowerInvariant(character)); + } + } - /// - protected UriException(SerializationInfo info, StreamingContext context) : base(info, context) - { + return hashBuilder.BuildHash(); } } /// - /// This exception indicates that an item is not part of a collection. + /// Provides meta-information about enum values and the flag bitmask if the enum is marked with the . + /// Can be used to validate that an enum value is valid. /// - [Serializable] - internal class ValueIsNotOneOfException : ArgumentException + /// The type of the enum. + internal static class EnumInfo + where T : struct, Enum { + // ReSharper disable StaticMemberInGenericType /// - /// Creates a new instance of . + /// Gets the value indicating whether the enum type is marked with the flags attribute. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public ValueIsNotOneOfException(string? parameterName = null, string? message = null) : base(message, parameterName) - { - } + public static readonly bool IsFlagsEnum = typeof(T).GetCustomAttribute(Types.FlagsAttributeType) != null; + /// + /// Gets the flags pattern when is true. If the enum is not a flags enum, then 0UL is returned. + /// + public static readonly ulong FlagsPattern; + private static readonly int EnumSize = Unsafe.SizeOf(); + private static readonly T[] EnumConstantsArray; + /// + /// Gets the values of the enum as a read-only collection. + /// + public static ReadOnlyMemory EnumConstants { get; } - /// - protected ValueIsNotOneOfException(SerializationInfo info, StreamingContext context) : base(info, context) + static EnumInfo() { +#if NET8_0 + EnumConstantsArray = Enum.GetValues(); +#else + EnumConstantsArray = (T[])Enum.GetValues(typeof(T)); +#endif + EnumConstants = new ReadOnlyMemory(EnumConstantsArray); + if (!IsFlagsEnum) + { + return; + } + + for (var i = 0; i < EnumConstantsArray.Length; ++i) + { + var convertedValue = ConvertToUInt64(EnumConstantsArray[i]); + FlagsPattern |= convertedValue; + } } - } - /// - /// This exception indicates that an item is part of a collection. - /// - [Serializable] - internal class ValueIsOneOfException : ArgumentException - { - /// - /// Creates a new instance of . - /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public ValueIsOneOfException(string? parameterName = null, string? message = null) : base(message, parameterName) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidFlagsValue(T enumValue) { + var convertedValue = ConvertToUInt64(enumValue); + return (FlagsPattern & convertedValue) == convertedValue; } - /// - protected ValueIsOneOfException(SerializationInfo info, StreamingContext context) : base(info, context) + private static bool IsValidValue(T parameter) { + var comparer = EqualityComparer.Default; + for (var i = 0; i < EnumConstantsArray.Length; ++i) + { + if (comparer.Equals(EnumConstantsArray[i], parameter)) + { + return true; + } + } + + return false; } - } - /// - /// This exception indicates that two values are equal. - /// - [Serializable] - internal class ValuesEqualException : ArgumentException - { /// - /// Creates a new instance of . + /// Checks if the specified enum value is valid. This is true if either the enum is a standard enum and the enum value corresponds + /// to one of the enum constant values or if the enum type is marked with the and the given value + /// is a valid combination of bits for this type. /// - /// The name of the parameter (optional). - /// The message of the exception (optional). - public ValuesEqualException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The enum value to be checked. + /// True if either the enum value is + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsValidEnumValue(T enumValue) => IsFlagsEnum ? IsValidFlagsValue(enumValue) : IsValidValue(enumValue); + private static ulong ConvertToUInt64(T value) + { + switch (EnumSize) + { + case 1: + return Unsafe.As(ref value); + case 2: + return Unsafe.As(ref value); + case 4: + return Unsafe.As(ref value); + case 8: + return Unsafe.As(ref value); + default: + ThrowUnknownEnumSize(); + return 0UL; + } + } + + private static void ThrowUnknownEnumSize() => throw new InvalidOperationException($"The enum type \"{typeof(T)}\" has an unknown size of {EnumSize}. This means that the underlying enum type is not one of the supported ones."); + } + + /// + /// Represents an that compares strings using the + /// ordinal sort rules and ignoring the white space characters. + /// + internal sealed class OrdinalIgnoreWhiteSpaceComparer : IEqualityComparer + { + /// + /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the white space + /// of the provided strings. + /// + /// Thrown when or are null. + public bool Equals(string? x, string? y) + { + x.MustNotBeNull(nameof(x)); + y.MustNotBeNull(nameof(y)); + return x.EqualsOrdinalIgnoreWhiteSpace(y); + } + + /// + /// Gets the hash code for the specified string. The hash code is created only from the non-white space characters. + /// + /// Thrown when is null. + public int GetHashCode(string @string) + { + @string.MustNotBeNull(nameof(@string)); + var hashCodeBuilder = MultiplyAddHashBuilder.Create(); + foreach (var character in @string) + { + if (!character.IsWhiteSpace()) + { + hashCodeBuilder.CombineIntoHash(character); + } + } + + return hashCodeBuilder.BuildHash(); + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class ValidatedNotNullAttribute : Attribute + { + } +} + +namespace Light.GuardClauses.Exceptions +{ + /// + /// This exception indicates that a value is not defined in the corresponding enum type. + /// + [Serializable] + internal class EnumValueNotDefinedException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter. + /// The message of the exception. + public EnumValueNotDefinedException(string? parameterName = null, string? message = null) : base(message, parameterName) { } +#if !NET8_0 /// - protected ValuesEqualException(SerializationInfo info, StreamingContext context) : base(info, context) + protected EnumValueNotDefinedException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif } /// - /// This exception indicates that two values are not equal. + /// This exception indicates that configuration data is invalid. /// [Serializable] - internal class ValuesNotEqualException : ArgumentException + internal class InvalidConfigurationException : Exception { /// - /// Creates a new instance of . + /// Initializes a new instance of . /// - /// The name of the parameter (optional). /// The message of the exception (optional). - public ValuesNotEqualException(string? parameterName = null, string? message = null) : base(message, parameterName) + /// The exception that is the cause of this one (optional). + public InvalidConfigurationException(string? message = null, Exception? innerException = null) : base(message, innerException) { } +#if !NET8_0 /// - protected ValuesNotEqualException(SerializationInfo info, StreamingContext context) : base(info, context) + protected InvalidConfigurationException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif } /// - /// This exception indicates that a string contains only white space. + /// This exception indicates that an URI is relative instead of absolute. /// [Serializable] - internal class WhiteSpaceStringException : StringException + internal class RelativeUriException : UriException { /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// The name of the parameter (optional). /// The message of the exception (optional). - public WhiteSpaceStringException(string? parameterName = null, string? message = null) : base(parameterName, message) + public RelativeUriException(string? parameterName = null, string? message = null) : base(parameterName, message) { } +#if !NET8_0 /// - protected WhiteSpaceStringException(SerializationInfo info, StreamingContext context) : base(info, context) + protected RelativeUriException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif } -} -namespace Light.GuardClauses.FrameworkExtensions -{ /// - /// Provides extension methods for the interface. + /// This exception indicates that a collection contains an item that must not be part of it. /// - internal static class EnumerableExtensions + [Serializable] + internal class ExistingItemException : CollectionException { /// - /// Tries to cast the specified enumerable to an , or - /// creates a new containing the enumerable items. - /// - /// The item type of the enumerable. - /// The enumerable to be transformed. - /// The list containing the items of the enumerable. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("source:null => halt; source:notnull => notnull")] - public static IList AsList([ValidatedNotNull] this IEnumerable source) => source as IList ?? source.ToList(); - /// - /// Tries to cast the specified enumerable to an , or - /// creates a new collection containing the enumerable items by calling the specified delegate. - /// - /// The item type of the collection. - /// The enumerable that will be converted to . - /// The delegate that creates the collection containing the specified items. - /// The cast enumerable, or a new collection containing the enumerable items. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("source:null => halt; source:notnull => notnull; createCollection:null => halt")] - public static IList AsList([ValidatedNotNull] this IEnumerable source, Func, IList> createCollection) => source as IList ?? createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); - /// - /// Tries to downcast the specified enumerable to an array, or creates a new array with the specified items. - /// - /// The item type of the collection. - /// The enumerable that will be converted to an array. - /// The cast array, or a new array containing the enumerable items. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("source:null => halt; source:notnull => notnull")] - public static T[] AsArray([ValidatedNotNull] this IEnumerable source) => source as T[] ?? source.ToArray(); - /// - /// Performs the action on each item of the specified enumerable. If the enumerable contains items that are null, this - /// method can either throw an exception or ignore the value (your delegate will not be called in this case). + /// Creates a new instance of . /// - /// The item type of the enumerable. - /// The collection containing the items that will be passed to the action. - /// The action that executes for each item of the collection. - /// The value indicating whether this method should throw a when any of the items is null (optional). Defaults to true. - /// Thrown when or is null. - /// Thrown when contains a value that is null and is set to true. - public static IEnumerable ForEach([ValidatedNotNull] this IEnumerable enumerable, Action action, bool throwWhenItemIsNull = true) + /// The name of the parameter (optional). + /// The message of the exception (optional). + public ExistingItemException(string? parameterName = null, string? message = null) : base(parameterName, message) { - // ReSharper disable PossibleMultipleEnumeration - action.MustNotBeNull(nameof(action)); - var i = 0; - if (enumerable is IList list) - { - for (; i < list.Count; i++) - { - var item = list[i]; - if (item is null) - { - if (throwWhenItemIsNull) - throw new CollectionException(nameof(enumerable), $"The collection contains null at index {i}."); - continue; - } - - action(item); - } - } - else - { - foreach (var item in enumerable.MustNotBeNull(nameof(enumerable))) - { - if (item is null) - { - if (throwWhenItemIsNull) - throw new CollectionException(nameof(enumerable), $"The collection contains null at index {i}."); - ++i; - continue; - } - - action(item); - ++i; - } - } + } - return enumerable; - // ReSharper restore PossibleMultipleEnumeration +#if !NET8_0 + /// + protected ExistingItemException(SerializationInfo info, StreamingContext context) : base(info, context) + { } +#endif + } + /// + /// This exception indicates that an item is not present in a collection. + /// + [Serializable] + internal class MissingItemException : CollectionException + { /// - /// Tries to cast the specified enumerable as an , or - /// creates a new containing the enumerable items. - /// - /// The item type of the enumerable. - /// The enumerable to be transformed. - /// The list containing the items of the enumerable. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("source:null => halt; source:notnull => notnull")] - public static IReadOnlyList AsReadOnlyList([ValidatedNotNull] this IEnumerable source) => source as IReadOnlyList ?? source.ToList(); - /// - /// Tries to cast the specified enumerable as an , or - /// creates a new collection containing the enumerable items by calling the specified delegate. - /// - /// The item type of the collection. - /// The enumerable that will be converted to . - /// The delegate that creates the collection containing the specified items. - /// The cast enumerable, or a new collection containing the enumerable items. - /// Thrown when or is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("source:null => halt; source:notnull => notnull; createCollection:null => halt")] - public static IReadOnlyList AsReadOnlyList([ValidatedNotNull] this IEnumerable source, Func, IReadOnlyList> createCollection) => source as IReadOnlyList ?? createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); - /// - /// Gets the count of the specified enumerable. + /// Creates a new instance of . /// - /// The enumerable whose count should be determined. - /// Thrown when is null. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("enumerable:null => halt")] - public static int Count([ValidatedNotNull] this IEnumerable enumerable) + /// The name of the parameter (optional). + /// The message of the exception (optional). + public MissingItemException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected MissingItemException(SerializationInfo info, StreamingContext context) : base(info, context) { - if (enumerable is ICollection collection) - return collection.Count; - if (enumerable is string @string) - return @string.Length; - return DetermineCountViaEnumerating(enumerable); } +#endif + } + /// + /// This exception indicates that an Email address is invalid. + /// + [Serializable] + internal class InvalidEmailAddressException : StringException + { /// - /// Gets the count of the specified enumerable. + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public InvalidEmailAddressException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected InvalidEmailAddressException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that two values are equal. + /// + [Serializable] + internal class ValuesEqualException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public ValuesEqualException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected ValuesEqualException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a string has an invalid length. + /// + [Serializable] + internal class StringLengthException : StringException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public StringLengthException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected StringLengthException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a string is in an invalid state. + /// + [Serializable] + internal class SubstringException : StringException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public SubstringException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected SubstringException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a collection has no items. + /// + [Serializable] + internal class EmptyCollectionException : InvalidCollectionCountException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public EmptyCollectionException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected EmptyCollectionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a value of a value type is the default value. + /// + [Serializable] + internal class ArgumentDefaultException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public ArgumentDefaultException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected ArgumentDefaultException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a collection has an invalid number of items. + /// + [Serializable] + internal class InvalidCollectionCountException : CollectionException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public InvalidCollectionCountException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected InvalidCollectionCountException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that the state of a collection is invalid. + /// + [Serializable] + internal class CollectionException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public CollectionException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected CollectionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a string is in an invalid state. + /// + [Serializable] + internal class StringException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public StringException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected StringException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a has no value. + /// + [Serializable] + internal class NullableHasNoValueException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public NullableHasNoValueException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected NullableHasNoValueException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that two references point to the same object. + /// + [Serializable] + internal class SameObjectReferenceException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public SameObjectReferenceException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected SameObjectReferenceException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that an URI has an invalid scheme. + /// + [Serializable] + internal class InvalidUriSchemeException : UriException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public InvalidUriSchemeException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected InvalidUriSchemeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that an URI is absolute instead of relative. + /// + [Serializable] + internal class AbsoluteUriException : UriException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public AbsoluteUriException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected AbsoluteUriException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that that a GUID is empty. + /// + [Serializable] + internal class EmptyGuidException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public EmptyGuidException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected EmptyGuidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that an URI is invalid. + /// + [Serializable] + internal class UriException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public UriException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected UriException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that two values are not equal. + /// + [Serializable] + internal class ValuesNotEqualException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public ValuesNotEqualException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected ValuesNotEqualException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a string contains only white space. + /// + [Serializable] + internal class WhiteSpaceStringException : StringException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public WhiteSpaceStringException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected WhiteSpaceStringException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that an item is part of a collection. + /// + [Serializable] + internal class ValueIsOneOfException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public ValueIsOneOfException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected ValueIsOneOfException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a string is not matching a regular expression. + /// + [Serializable] + internal class StringDoesNotMatchException : StringException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public StringDoesNotMatchException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected StringDoesNotMatchException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a value is invalid. + /// + [Serializable] + internal class InvalidDateTimeException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public InvalidDateTimeException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected InvalidDateTimeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a string is empty. + /// + [Serializable] + internal class EmptyStringException : StringException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public EmptyStringException(string? parameterName = null, string? message = null) : base(parameterName, message) + { + } + +#if !NET8_0 + /// + protected EmptyStringException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that an item is not part of a collection. + /// + [Serializable] + internal class ValueIsNotOneOfException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public ValueIsNotOneOfException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected ValueIsNotOneOfException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that a value cannot be cast to another type. + /// + [Serializable] + internal class TypeCastException : ArgumentException + { + /// + /// Creates a new instance of . + /// + /// The name of the parameter (optional). + /// The message of the exception (optional). + public TypeCastException(string? parameterName = null, string? message = null) : base(message, parameterName) + { + } + +#if !NET8_0 + /// + protected TypeCastException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } + + /// + /// This exception indicates that the data is in invalid state. + /// + [Serializable] + internal class InvalidStateException : Exception + { + /// + /// Creates a new instance of . + /// + /// The message of the exception (optional). + /// The exception that is the cause of this one (optional). + public InvalidStateException(string? message = null, Exception? innerException = null) : base(message, innerException) + { + } + +#if !NET8_0 + /// + protected InvalidStateException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +#endif + } +} + +namespace Light.GuardClauses.ExceptionFactory +{ + /// + /// Provides static factory methods that throw default exceptions. + /// + // ReSharper disable once RedundantTypeDeclarationBody - requried for the Source Code Transformation + internal static class Throw + { + /// + /// Throws the default , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ArgumentNull(string? parameterName = null, string? message = null) => throw new ArgumentNullException(parameterName, message ?? $"{parameterName ?? "The value"} must not be null."); + /// + /// Throws the default indicating that a string is empty, using the optional + /// parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EmptyString(string? parameterName = null, string? message = null) => throw new EmptyStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be an empty string, but it actually is."); + /// + /// Throws the default indicating that a comparable value must not be + /// less than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeLessThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be less than or equal to {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a collection contains the specified item + /// that should not be part of it, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ExistingItem(IEnumerable parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ExistingItemException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The collection"} must not contain {item.ToStringOrNull()}, but it actually does.").AppendCollectionContent(parameter).ToString()); + /// + /// Throws the default indicating that a comparable value must be greater + /// than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeGreaterThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a string is not trimmed at the end. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotTrimmedAtEnd(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed at the end, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a value must be approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeApproximately(T parameter, T other, T tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}."); + /// + /// Throws the exception that is returned by . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(); + /// + /// Throws the exception that is returned by . is + /// passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory, T parameter) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(parameter); + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory, T1 first, T2 second) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second); + /// + /// Throws the exception that is returned by . , + /// , and are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomException(Func exceptionFactory, T1 first, T2 second, T3 third) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second, third); + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException(SpanExceptionFactory exceptionFactory, Span span, T value) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory)).Invoke(span, value); + /// + /// Throws the exception that is returned by . is + /// passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException(ReadOnlySpanExceptionFactory exceptionFactory, ReadOnlySpan span) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span); + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException(ReadOnlySpanExceptionFactory exceptionFactory, ReadOnlySpan span, T value) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(span, value); + /// + /// Throws the exception that is returned by . and + /// are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException(ReadOnlySpansExceptionFactory exceptionFactory, ReadOnlySpan first, ReadOnlySpan second) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second); + /// + /// Throws the exception that is returned by . , + /// , and are passed to . + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void CustomSpanException(ReadOnlySpansExceptionFactory exceptionFactory, ReadOnlySpan first, ReadOnlySpan second, T third) => throw exceptionFactory.MustNotBeNull(nameof(exceptionFactory))(first, second, third); + /// + /// Throws the default indicating that a value is not within a specified + /// range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeInRange(T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a value is within a specified + /// range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeInRange(T parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be between {range.CreateRangeDescriptionText("and")}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a comparable value must not be + /// less than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeLessThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be less than {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a comparable value must be less + /// than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLessThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a comparable value must be greater + /// than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeGreaterThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than or equal to {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a date time is not using + /// , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeUtcDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Utc}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); + /// + /// Throws the default indicating that a date time is not using + /// , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLocalDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Local}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); + /// + /// Throws the default indicating that a date time is not using + /// , using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeUnspecifiedDateTime(DateTime parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidDateTimeException(parameterName, message ?? $"{parameterName ?? "The date time"} must use kind \"{DateTimeKind.Unspecified}\", but it actually uses \"{parameter.Kind}\" and is \"{parameter:O}\"."); + /// + /// Throws the default indicating that a string is not equal to "\n" or "\r\n". + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotNewLine(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be either \"\\n\" or \"\\r\\n\", but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string is not shorter than the given + /// length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotShorterThan(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be shorter than {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a string is not shorter or equal to the + /// given length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotShorterThanOrEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be shorter or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a string has a different length than the + /// specified one, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringLengthNotEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must have length {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a string is not longer than the given + /// length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotLongerThan(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be longer than {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a string is not longer than or equal to + /// the given length, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringNotLongerThanOrEqualTo(string parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a string's length is not in within the + /// given range, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringLengthNotInRange(string parameter, Range range, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringLengthException(parameterName, message ?? $"{parameterName ?? "The string"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a string is not a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotSubstring(string parameter, string other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string is not a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotSubstring(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string is a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void Substring(string parameter, string other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be a substring of \"{other}\", but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string is a substring of another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void Substring(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not be a substring of \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a span has an invalid length, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidSpanLength(ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The read-only span"} must have length {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a span is not longer than the + /// specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeLongerThan(ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a span is not longer than and + /// not equal to the specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeLongerThanOrEqualTo(ReadOnlySpan parameter, int length, [CallerArgumentExpression(nameof(parameter))] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be longer than or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a span is not shorter than the + /// specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeShorterThan(ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a span is not shorter than the + /// specified length. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SpanMustBeShorterThanOrEqualTo(ReadOnlySpan parameter, int length, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The span"} must be shorter than or equal to {length}, but it actually has length {parameter.Length}."); + /// + /// Throws the default indicating that a collection has less than a + /// minimum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMinimumCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have at least count {count}, but it actually has count {parameter.Count()}."); + /// + /// Throws the default indicating that a string contains only white space, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void WhiteSpaceString(string parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new WhiteSpaceStringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain only white space, but it actually is \"{parameter}\"."); + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidState(string? message = null) => throw new InvalidStateException(message); + /// + /// Throws the default indicating that a URI is relative instead of absolute, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeAbsoluteUri(Uri parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new RelativeUriException(parameterName, message ?? $"{parameterName ?? "The URI"} must be an absolute URI, but it actually is \"{parameter}\"."); + /// + /// Throws the default indicating that a URI is absolute instead of relative, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeRelativeUri(Uri parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new AbsoluteUriException(parameterName, message ?? $"{parameterName ?? "The URI"} must be a relative URI, but it actually is \"{parameter}\"."); + /// + /// Throws the default indicating that a URI has an unexpected scheme, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void UriMustHaveScheme(Uri parameter, string scheme, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidUriSchemeException(parameterName, message ?? $"{parameterName ?? "The URI"} must use the scheme \"{scheme}\", but it actually is \"{parameter}\"."); + /// + /// Throws the default indicating that a URI does not use one of a set of + /// expected schemes, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void UriMustHaveOneSchemeOf(Uri parameter, IEnumerable schemes, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidUriSchemeException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The URI"} must use one of the following schemes").AppendItemsWithNewLine(schemes).AppendLine($"but it actually is \"{parameter}\".").ToString()); + /// + /// Throws the default indicating that a collection has no items, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EmptyCollection(string? parameterName = null, string? message = null) => throw new EmptyCollectionException(parameterName, message ?? $"{parameterName ?? "The collection"} must not be an empty collection, but it actually is."); + /// + /// Throws the default indicating that a string is not trimmed at the start. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotTrimmedAtStart(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed at the start, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidOperation(string? message = null) => throw new InvalidOperationException(message); + /// + /// Throws the default indicating that a comparable value must be less + /// than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLessThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than or equal to {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that two values are not equal, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValuesNotEqual(T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValuesNotEqualException(parameterName, message ?? $"{parameterName ?? "The value"} must be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that two values are equal, using the optional + /// parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValuesEqual(T parameter, T other, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValuesEqualException(parameterName, message ?? $"{parameterName ?? "The value"} must not be equal to {other.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a value is not one of the + /// constants defined in an enum, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EnumValueNotDefined(T parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : Enum => throw new EnumValueNotDefinedException(parameterName, message ?? $"{parameterName ?? "The value"} \"{parameter}\" must be one of the defined constants of enum \"{parameter.GetType()}\", but it actually is not."); + /// + /// Throws the default indicating that a value must be less than or approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeLessThanOrApproximately(T parameter, T other, T tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be less than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a value is the default value of its + /// type, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ArgumentDefault(string? parameterName = null, string? message = null) => throw new ArgumentDefaultException(parameterName, message ?? $"{parameterName ?? "The value"} must not be the default value."); + /// + /// Throws an using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void Argument(string? parameterName = null, string? message = null) => throw new ArgumentException(message ?? $"{parameterName ?? "The value"} is invalid.", parameterName); + /// + /// Throws the default indicating that a collection is not containing the + /// specified item, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MissingItem(IEnumerable parameter, TItem item, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new MissingItemException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The collection"} must contain {item.ToStringOrNull()}, but it actually does not.").AppendCollectionContent(parameter).ToString()); + /// + /// Throws the default indicating that a value is one of a specified collection + /// of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValueIsOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValueIsOneOfException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The value"} must not be one of the following items").AppendItemsWithNewLine(items).AppendLine($"but it actually is {parameter.ToStringOrNull()}.").ToString()); + /// + /// Throws the default indicating that two references point to the same + /// object, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void SameObjectReference(T? parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : class => throw new SameObjectReferenceException(parameterName, message ?? $"{parameterName ?? "The reference"} must not point to object \"{parameter}\", but it actually does."); + /// + /// Throws the default indicating that a GUID is empty, using the optional + /// parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void EmptyGuid(string? parameterName = null, string? message = null) => throw new EmptyGuidException(parameterName, message ?? $"{parameterName ?? "The value"} must be a valid GUID, but it actually is an empty one."); + /// + /// Throws the default indicating that a string is not trimmed. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotTrimmed(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be trimmed, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a comparable value must not be + /// greater than or equal to the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeGreaterThanOrEqualTo(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be greater than or equal to {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a has + /// no value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NullableHasNoValue(string? parameterName = null, string? message = null) => throw new NullableHasNoValueException(parameterName, message ?? $"{parameterName ?? "The nullable"} must have a value, but it actually is null."); + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidEmailAddress(string parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidEmailAddressException(parameterName, message ?? $"{parameterName ?? "The string"} must be a valid email address, but it actually is \"{parameter}\"."); + /// + /// Throws an using the optional message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidEmailAddress(ReadOnlySpan parameter, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidEmailAddressException(parameterName, message ?? $"{parameterName ?? "The string"} must be a valid email address, but it actually is \"{parameter.ToString()}\"."); + /// + /// Throws the default indicating that a collection has more than a + /// maximum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMaximumCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have at most count {count}, but it actually has count {parameter.Count()}."); + /// + /// Throws the default indicating that a string is not a valid file extension. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotFileExtension(string? parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a valid file extension, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string is not a valid file extension. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void NotFileExtension(ReadOnlySpan parameter, string? parameterName, string? message) => throw new StringException(parameterName, message ?? $"{parameterName ?? "The string"} must be a valid file extension, but it actually is {parameter.ToString()}."); + /// + /// Throws the default indicating that a value must be greater than or approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustBeGreaterThanOrApproximately(T parameter, T other, T tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must be greater than or approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a string does not end with another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotEndWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must end with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a reference cannot be downcast, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidTypeCast(object? parameter, Type targetType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new TypeCastException(parameterName, message ?? $"{parameterName ?? "The value"} {parameter.ToStringOrNull()} cannot be cast to \"{targetType}\"."); + /// + /// Throws the default indicating that a collection has an invalid + /// number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidCollectionCount(IEnumerable parameter, int count, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new InvalidCollectionCountException(parameterName, message ?? $"{parameterName ?? "The collection"} must have count {count}, but it actually has count {parameter.Count()}."); + /// + /// Throws the default indicating that a string ends with another one, using the + /// optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringEndsWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not end with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string does not match a regular + /// expression, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotMatch(string parameter, Regex regex, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new StringDoesNotMatchException(parameterName, message ?? $"{parameterName ?? "The string"} must match the regular expression \"{regex}\", but it actually is \"{parameter}\"."); + /// + /// Throws the default indicating that a value is not one of a specified + /// collection of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void ValueNotOneOf(TItem parameter, IEnumerable items, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ValueIsNotOneOfException(parameterName, message ?? new StringBuilder().AppendLine($"{parameterName ?? "The value"} must be one of the following items").AppendItemsWithNewLine(items).AppendLine($"but it actually is {parameter.ToStringOrNull()}.").ToString()); + /// + /// Throws the default indicating that a value must not be approximately + /// equal to another value within a specified tolerance, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeApproximately(T parameter, T other, T tolerance, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a string does start with another one, using + /// the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringStartsWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not start with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string does start with another one, using + /// the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringStartsWith(ReadOnlySpan parameter, ReadOnlySpan other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not start with \"{other.ToString()}\" ({comparisonType}), but it actually is {parameter.ToString()}."); + /// + /// Throws the default indicating that a comparable value must not be + /// greater than the given boundary value, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void MustNotBeGreaterThan(T parameter, T boundary, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) + where T : IComparable => throw new ArgumentOutOfRangeException(parameterName, message ?? $"{parameterName ?? "The value"} must not be greater than {boundary}, but it actually is {parameter}."); + /// + /// Throws the default indicating that a string does not contain another string as + /// a substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotContain(string parameter, string substring, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()}, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string does not contain another string as + /// a substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotContain(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must contain {substring.ToStringOrNull()} ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string does contain another string as a + /// substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringContains(string parameter, string substring, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring, but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string does contain another string as a + /// substring, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringContains(string parameter, string substring, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must not contain {substring.ToStringOrNull()} as a substring ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + /// + /// Throws the default indicating that a string does not start with another one, + /// using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void StringDoesNotStartWith(string parameter, string other, StringComparison comparisonType, [CallerArgumentExpression("parameter")] string? parameterName = null, string? message = null) => throw new SubstringException(parameterName, message ?? $"{parameterName ?? "The string"} must start with \"{other}\" ({comparisonType}), but it actually is {parameter.ToStringOrNull()}."); + } + + /// + /// Represents a delegate that receives a span and a value as parameters and that produces an exception. + /// + internal delegate Exception SpanExceptionFactory(Span span, T value); + /// + /// Represents a delegate that receives a read-only span and produces an exception. + /// + internal delegate Exception ReadOnlySpanExceptionFactory(ReadOnlySpan span); + /// + /// Represents a delegate that receives a read-only span and a value as parameters and that produces an exception. + /// + internal delegate Exception ReadOnlySpanExceptionFactory(ReadOnlySpan span, T value); + /// + /// Represents a delegate that receives two spans and produces an exception. + /// + internal delegate Exception ReadOnlySpansExceptionFactory(ReadOnlySpan span1, ReadOnlySpan span2); + /// + /// Represents a delegate that receives two spans and a value as parameters and that produces an exception. + /// + internal delegate Exception ReadOnlySpansExceptionFactory(ReadOnlySpan span1, ReadOnlySpan span2, T value); +} + +namespace Light.GuardClauses.FrameworkExtensions +{ + /// + /// Represents a builder for the algorithm that does not allocate. + /// Should only be used in cases where the overload for sixteen values is not enough or a dedicated + /// initial hash must be provided (e.g. for test reasons). + /// Instantiate the builder with the method. You have to instantiate a new builder + /// for each hash code that you want to calculate. + /// + internal struct MultiplyAddHashBuilder + { + private int _hash; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MultiplyAddHashBuilder(int initialHash) => _hash = initialHash; + /// + /// Combines the given value into the hash using the method. /// - /// The enumerable whose count should be determined. - /// The name of the parameter that is passed to the (optional). - /// The message that is passed to the (optional). - /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("enumerable:null => halt")] - public static int Count([ValidatedNotNull] this IEnumerable? enumerable, string? parameterName, string? message) + public MultiplyAddHashBuilder CombineIntoHash(T value) { - if (enumerable is ICollection collection) - return collection.Count; - if (enumerable is string @string) - return @string.Length; - return DetermineCountViaEnumerating(enumerable, parameterName, message); + MultiplyAddHash.CombineIntoHash(ref _hash, value); + return this; } /// - /// Gets the count of the specified enumerable. + /// Returns the calculated hash code. /// - /// The enumerable whose count should be determined. - /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("enumerable:null => halt")] - public static int GetCount(this IEnumerable enumerable) - { - if (enumerable is ICollection collection) - return collection.Count; - if (enumerable is string @string) - return @string.Length; - if (TryGetCollectionOfTCount(enumerable, out var count)) - return count; - return DetermineCountViaEnumerating(enumerable); - } - + public int BuildHash() => _hash; /// - /// Gets the count of the specified enumerable. + /// Initializes a new instance of with the specified initial hash. /// - /// The enumerable whose count should be determined. - /// The name of the parameter that is passed to the (optional). - /// The message that is passed to the (optional). - /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("enumerable:null => halt")] - public static int GetCount(this IEnumerable enumerable, string? parameterName, string? message = null) - { - if (enumerable is ICollection collection) - return collection.Count; - if (enumerable is string @string) - return @string.Length; - if (TryGetCollectionOfTCount(enumerable, out var count)) - return count; - return DetermineCountViaEnumerating(enumerable, parameterName, message); - } - - private static bool TryGetCollectionOfTCount([NoEnumeration] this IEnumerable enumerable, out int count) - { - if (enumerable is ICollection collectionOfT) - { - count = collectionOfT.Count; - return true; - } - - if (enumerable is IReadOnlyCollection readOnlyCollection) - { - count = readOnlyCollection.Count; - return true; - } - - count = 0; - return false; - } - - private static int DetermineCountViaEnumerating(IEnumerable? enumerable) - { - var count = 0; - var enumerator = enumerable.MustNotBeNull(nameof(enumerable)).GetEnumerator(); - while (enumerator.MoveNext()) - count++; - return count; - } - - private static int DetermineCountViaEnumerating(IEnumerable? enumerable, string? parameterName, string? message) - { - var count = 0; - var enumerator = enumerable.MustNotBeNull(parameterName, message).GetEnumerator(); - while (enumerator.MoveNext()) - count++; - return count; - } - - internal static bool ContainsViaForeach(this IEnumerable items, TItem item) - { - var equalityComparer = EqualityComparer.Default; - foreach (var i in items) - { - if (equalityComparer.Equals(i, item)) - return true; - } - - return false; - } + public static MultiplyAddHashBuilder Create(int initialHash = MultiplyAddHash.FirstPrime) => new(initialHash); } /// @@ -5387,7 +7281,7 @@ internal static bool ContainsViaForeach(this IEnumerable items, TI internal static class ExpressionExtensions { /// - /// Extracts the from a expression of the shape "object => object.Property". + /// Extracts the from an expression of the shape "object => object.Property". /// /// The object type. /// The type of the property. @@ -5396,17 +7290,21 @@ internal static class ExpressionExtensions /// /// Throw when the is not of the shape "object => object.Property". /// - public static PropertyInfo ExtractProperty([ValidatedNotNull] this Expression> expression) + public static PropertyInfo ExtractProperty(// ReSharper disable once RedundantNullableFlowAttribute - NotNull is not redundant, see Issue72NotNullAttributeTests + [NotNull][ValidatedNotNull] this Expression> expression) { expression.MustNotBeNull(nameof(expression)); var memberExpression = expression.Body as MemberExpression; if (!(memberExpression?.Member is PropertyInfo propertyInfo)) + { throw new ArgumentException("The specified expression is not valid. Please use an expression like the following one: o => o.Property", nameof(expression)); + } + return propertyInfo; } /// - /// Extracts the from a expression of the shape "object => object.Field". + /// Extracts the from an expression of the shape "object => object.Field". /// /// The object type. /// The type of the field. @@ -5415,12 +7313,16 @@ public static PropertyInfo ExtractProperty([ValidatedNotNull] this /// /// Throw when the is not of the shape "object => object.Field". /// - public static FieldInfo ExtractField([ValidatedNotNull] this Expression> expression) + public static FieldInfo ExtractField(// ReSharper disable once RedundantNullableFlowAttribute - NotNull is not redundant, see Issue72NotNullAttributeTests + [NotNull][ValidatedNotNull] this Expression> expression) { expression.MustNotBeNull(nameof(expression)); var memberExpression = expression.Body as MemberExpression; if (!(memberExpression?.Member is FieldInfo fieldInfo)) + { throw new ArgumentException("The specified expression is not valid. Please use an expression like the following one: o => o.Field", nameof(expression)); + } + return fieldInfo; } } @@ -5706,71 +7608,333 @@ public static int CreateHashCode - /// Creates a hash code from the sixteen specified values. + /// Creates a hash code from the sixteen specified values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12, T13 value13, T14 value14, T15 value15, T16 value16) + { + var hash = FirstPrime; + CombineIntoHash(ref hash, value1); + CombineIntoHash(ref hash, value2); + CombineIntoHash(ref hash, value3); + CombineIntoHash(ref hash, value4); + CombineIntoHash(ref hash, value5); + CombineIntoHash(ref hash, value6); + CombineIntoHash(ref hash, value7); + CombineIntoHash(ref hash, value8); + CombineIntoHash(ref hash, value9); + CombineIntoHash(ref hash, value10); + CombineIntoHash(ref hash, value11); + CombineIntoHash(ref hash, value12); + CombineIntoHash(ref hash, value13); + CombineIntoHash(ref hash, value14); + CombineIntoHash(ref hash, value15); + CombineIntoHash(ref hash, value16); + return hash; + } + + /// + /// Mutates the given hash with the specified value using the following statement: + /// hash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0);. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CombineIntoHash(ref int hash, T value) => hash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0); + } + + /// + /// Provides extension methods for the interface. + /// + internal static class EnumerableExtensions + { + /// + /// Tries to cast the specified enumerable to an , or + /// creates a new containing the enumerable items. + /// + /// The item type of the enumerable. + /// The enumerable to be transformed. + /// The list containing the items of the enumerable. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("source:null => halt; source:notnull => notnull")] + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static IList AsList([NotNull][ValidatedNotNull] this IEnumerable source) => source as IList ?? source.ToList(); + /// + /// Tries to cast the specified enumerable to an , or + /// creates a new collection containing the enumerable items by calling the specified delegate. + /// + /// The item type of the collection. + /// The enumerable that will be converted to . + /// The delegate that creates the collection containing the specified items. + /// The cast enumerable, or a new collection containing the enumerable items. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("source:null => halt; source:notnull => notnull; createCollection:null => halt")] + public static IList AsList(// ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + [NotNull][ValidatedNotNull] this IEnumerable source, Func, IList> createCollection) => source as IList ?? createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); + /// + /// Tries to downcast the specified enumerable to an array, or creates a new array with the specified items. + /// + /// The item type of the collection. + /// The enumerable that will be converted to an array. + /// The cast array, or a new array containing the enumerable items. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("source:null => halt; source:notnull => notnull")] + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static T[] AsArray([NotNull][ValidatedNotNull] this IEnumerable source) => source as T[] ?? source.ToArray(); + /// + /// Performs the action on each item of the specified enumerable. If the enumerable contains items that are null, this + /// method can either throw an exception or ignore the value (your delegate will not be called in this case). + /// + /// The item type of the enumerable. + /// The collection containing the items that will be passed to the action. + /// The action that executes for each item of the collection. + /// The value indicating whether this method should throw a when any of the items is null (optional). Defaults to true. + /// Thrown when or is null. + /// Thrown when contains a value that is null and is set to true. + public static IEnumerable ForEach(// ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + [NotNull][ValidatedNotNull] this IEnumerable enumerable, Action action, bool throwWhenItemIsNull = true) + { + // ReSharper disable PossibleMultipleEnumeration + action.MustNotBeNull(nameof(action)); + var i = 0; + if (enumerable is IList list) + { + for (; i < list.Count; i++) + { + var item = list[i]; + if (item is null) + { + if (throwWhenItemIsNull) + { + throw new CollectionException(nameof(enumerable), $"The collection contains null at index {i}."); + } + + continue; + } + + action(item); + } + } + else + { + foreach (var item in enumerable.MustNotBeNull(nameof(enumerable))) + { + if (item is null) + { + if (throwWhenItemIsNull) + { + throw new CollectionException(nameof(enumerable), $"The collection contains null at index {i}."); + } + + ++i; + continue; + } + + action(item); + ++i; + } + } + + return enumerable; + // ReSharper restore PossibleMultipleEnumeration + } + + /// + /// Tries to cast the specified enumerable as an , or + /// creates a new containing the enumerable items. + /// + /// The item type of the enumerable. + /// The enumerable to be transformed. + /// The list containing the items of the enumerable. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("source:null => halt; source:notnull => notnull")] + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static IReadOnlyList AsReadOnlyList([NotNull][ValidatedNotNull] this IEnumerable source) => source as IReadOnlyList ?? source.ToList(); + /// + /// Tries to cast the specified enumerable as an , or + /// creates a new collection containing the enumerable items by calling the specified delegate. + /// + /// The item type of the collection. + /// The enumerable that will be converted to . + /// The delegate that creates the collection containing the specified items. + /// The cast enumerable, or a new collection containing the enumerable items. + /// Thrown when or is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("source:null => halt; source:notnull => notnull; createCollection:null => halt")] + // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static IReadOnlyList AsReadOnlyList([NotNull][ValidatedNotNull] this IEnumerable source, [NotNull][ValidatedNotNull] Func, IReadOnlyList> createCollection) => source as IReadOnlyList ?? createCollection.MustNotBeNull(nameof(createCollection))(source.MustNotBeNull(nameof(source))); + // ReSharper restore RedundantNullableFlowAttribute + /// + /// Gets the count of the specified enumerable. + /// + /// The enumerable whose count should be determined. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("enumerable:null => halt")] + // ReSharper disable once RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static int Count([NotNull][ValidatedNotNull] this IEnumerable enumerable) + { + if (enumerable is ICollection collection) + { + return collection.Count; + } + + if (enumerable is string @string) + { + return @string.Length; + } + + return DetermineCountViaEnumerating(enumerable); + } + + /// + /// Gets the count of the specified enumerable. /// + /// The enumerable whose count should be determined. + /// The name of the parameter that is passed to the (optional). + /// The message that is passed to the (optional). + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CreateHashCode(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8, T9 value9, T10 value10, T11 value11, T12 value12, T13 value13, T14 value14, T15 value15, T16 value16) + [ContractAnnotation("enumerable:null => halt")] + public static int Count([NotNull][ValidatedNotNull] this IEnumerable? enumerable, string? parameterName, string? message) { - var hash = FirstPrime; - CombineIntoHash(ref hash, value1); - CombineIntoHash(ref hash, value2); - CombineIntoHash(ref hash, value3); - CombineIntoHash(ref hash, value4); - CombineIntoHash(ref hash, value5); - CombineIntoHash(ref hash, value6); - CombineIntoHash(ref hash, value7); - CombineIntoHash(ref hash, value8); - CombineIntoHash(ref hash, value9); - CombineIntoHash(ref hash, value10); - CombineIntoHash(ref hash, value11); - CombineIntoHash(ref hash, value12); - CombineIntoHash(ref hash, value13); - CombineIntoHash(ref hash, value14); - CombineIntoHash(ref hash, value15); - CombineIntoHash(ref hash, value16); - return hash; + if (enumerable is ICollection collection) + { + return collection.Count; + } + + if (enumerable is string @string) + { + return @string.Length; + } + + return DetermineCountViaEnumerating(enumerable, parameterName, message); } /// - /// Mutates the given hash with the specified value using the following statement: - /// hash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0);. + /// Gets the count of the specified enumerable. /// + /// The enumerable whose count should be determined. + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CombineIntoHash(ref int hash, T value) => hash = unchecked(hash * SecondPrime + value?.GetHashCode() ?? 0); - } + [ContractAnnotation("enumerable:null => halt")] + // ReSharper disable RedundantNullableFlowAttribute -- NotNull has an effect, see Issue72NotNullAttributeTests + public static int GetCount([NotNull][ValidatedNotNull] this IEnumerable enumerable) + { + if (enumerable is ICollection collection) + { + return collection.Count; + } + + if (enumerable is string @string) + { + return @string.Length; + } + + if (TryGetCollectionOfTCount(enumerable, out var count)) + { + return count; + } + + return DetermineCountViaEnumerating(enumerable); + } - /// - /// Represents a builder for the algorithm that does not allocate. - /// Should only be used in cases where the overload for sixteen values is not enough or a dedicated - /// initial hash must be provided (e.g. for test reasons). - /// Instantiate the builder with the method. You have to instantiate a new builder - /// for each hash code that you want to calculate. - /// - internal struct MultiplyAddHashBuilder - { - private int _hash; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MultiplyAddHashBuilder(int initialHash) => _hash = initialHash; /// - /// Combines the given value into the hash using the method. + /// Gets the count of the specified enumerable. /// + /// The enumerable whose count should be determined. + /// The name of the parameter that is passed to the (optional). + /// The message that is passed to the (optional). + /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public MultiplyAddHashBuilder CombineIntoHash(T value) + [ContractAnnotation("enumerable:null => halt")] + public static int GetCount([NotNull][ValidatedNotNull] this IEnumerable enumerable, string? parameterName, string? message = null) { - MultiplyAddHash.CombineIntoHash(ref _hash, value); - return this; + if (enumerable is ICollection collection) + { + return collection.Count; + } + + if (enumerable is string @string) + { + return @string.Length; + } + + if (TryGetCollectionOfTCount(enumerable, out var count)) + { + return count; + } + + return DetermineCountViaEnumerating(enumerable, parameterName, message); } - /// - /// Returns the calculated hash code. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int BuildHash() => _hash; - /// - /// Initializes a new instance of with the specified initial hash. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MultiplyAddHashBuilder Create(int initialHash = MultiplyAddHash.FirstPrime) => new MultiplyAddHashBuilder(initialHash); + private static bool TryGetCollectionOfTCount([NoEnumeration] this IEnumerable enumerable, out int count) + { + if (enumerable is ICollection collectionOfT) + { + count = collectionOfT.Count; + return true; + } + + if (enumerable is IReadOnlyCollection readOnlyCollection) + { + count = readOnlyCollection.Count; + return true; + } + + count = 0; + return false; + } + + private static int DetermineCountViaEnumerating(IEnumerable? enumerable) + { + var count = 0; + var enumerator = enumerable.MustNotBeNull(nameof(enumerable)).GetEnumerator(); + while (enumerator.MoveNext()) + { + count++; + } + + if (enumerator is IDisposable disposable) + { + disposable.Dispose(); + } + + return count; + } + + private static int DetermineCountViaEnumerating([NotNull] IEnumerable? enumerable, string? parameterName, string? message) + { + var count = 0; + var enumerator = enumerable.MustNotBeNull(parameterName, message).GetEnumerator(); + while (enumerator.MoveNext()) + { + count++; + } + + if (enumerator is IDisposable disposable) + { + disposable.Dispose(); + } + + return count; + } + + internal static bool ContainsViaForeach(this IEnumerable items, TItem item) + { + var equalityComparer = EqualityComparer.Default; + foreach (var i in items) + { + if (equalityComparer.Equals(i, item)) + { + return true; + } + } + + return false; + } } /// @@ -5785,33 +7949,69 @@ internal static class TextExtensions /// /// Gets the list of types that will not be surrounded by quotation marks in error messages. /// - public static readonly ReadOnlyCollection UnquotedTypes = new ReadOnlyCollection(new[] { typeof(int), typeof(long), typeof(short), typeof(sbyte), typeof(uint), typeof(ulong), typeof(ushort), typeof(byte), typeof(bool), typeof(double), typeof(decimal), typeof(float) }); + public static readonly ReadOnlyCollection UnquotedTypes = new([typeof(int), typeof(long), typeof(short), typeof(sbyte), typeof(uint), typeof(ulong), typeof(ushort), typeof(byte), typeof(bool), typeof(double), typeof(decimal), typeof(float), ]); private static bool IsUnquotedType() { if (typeof(T) == typeof(int)) + { return true; + } + if (typeof(T) == typeof(long)) + { return true; + } + if (typeof(T) == typeof(short)) + { return true; + } + if (typeof(T) == typeof(sbyte)) + { return true; + } + if (typeof(T) == typeof(uint)) + { return true; + } + if (typeof(T) == typeof(ulong)) + { return true; + } + if (typeof(T) == typeof(ushort)) + { return true; + } + if (typeof(T) == typeof(byte)) + { return true; + } + if (typeof(T) == typeof(bool)) + { return true; + } + if (typeof(T) == typeof(double)) + { return true; + } + if (typeof(T) == typeof(decimal)) + { return true; + } + if (typeof(T) == typeof(float)) + { return true; + } + return false; } @@ -5830,12 +8030,15 @@ private static bool IsUnquotedType() /// /// The value whose string representation is requested. [ContractAnnotation("value:null => halt; value:notnull => notnull")] - public static string? ToStringRepresentation([ValidatedNotNull] this T value) + public static string? ToStringRepresentation([NotNull][ValidatedNotNull] this T value) { value.MustNotBeNullReference(nameof(value)); - var content = value!.ToString(); + var content = value.ToString(); if (IsUnquotedType() || content.IsNullOrEmpty()) + { return content; + } + // ReSharper disable UseIndexFromEndExpression -- not possible in netstandard2.0 if (content.Length <= 126) { @@ -5864,7 +8067,9 @@ private static bool IsUnquotedType() /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; items:null => halt; stringBuilder:notnull => notnull")] - public static StringBuilder AppendCollectionContent([ValidatedNotNull] this StringBuilder stringBuilder, [ValidatedNotNull] IEnumerable items, string headerLine = "Content of the collection:", bool finishWithNewLine = true) => stringBuilder.MustNotBeNull(nameof(stringBuilder)).AppendLine(headerLine).AppendItemsWithNewLine(items, finishWithNewLine: finishWithNewLine); + // ReSharper disable RedundantNullableFlowAttribute + public static StringBuilder AppendCollectionContent([NotNull][ValidatedNotNull] this StringBuilder stringBuilder, [NotNull][ValidatedNotNull] IEnumerable items, string headerLine = "Content of the collection:", bool finishWithNewLine = true) => stringBuilder.MustNotBeNull(nameof(stringBuilder)).AppendLine(headerLine).AppendItemsWithNewLine(items, finishWithNewLine: finishWithNewLine); + // ReSharper restore RedundantNullableFlowAttribute /// /// Appends the string representations of the specified items to the string builder. /// @@ -5874,21 +8079,31 @@ private static bool IsUnquotedType() /// The text that is appended to the string builder when is empty. Defaults to "empty collection". /// Thrown when or is null. [ContractAnnotation("stringBuilder:null => halt; items:null => halt; stringBuilder:notnull => notnull")] - public static StringBuilder AppendItems([ValidatedNotNull] this StringBuilder stringBuilder, [ValidatedNotNull] IEnumerable items, string itemSeparator = ", ", string emptyCollectionText = "empty collection") + // ReSharper disable RedundantNullableFlowAttribute + public static StringBuilder AppendItems([NotNull][ValidatedNotNull] this StringBuilder stringBuilder, [NotNull][ValidatedNotNull] IEnumerable items, string itemSeparator = ", ", string emptyCollectionText = "empty collection") + // ReSharper restore RedundantNullableFlowAttribute { stringBuilder.MustNotBeNull(nameof(stringBuilder)); var list = items.MustNotBeNull(nameof(items)).AsList(); var currentIndex = 0; var itemsCount = list.Count; if (itemsCount == 0) + { return stringBuilder.Append(emptyCollectionText); + } + while (true) { stringBuilder.Append(list[currentIndex].ToStringOrNull()); if (currentIndex < itemsCount - 1) + { stringBuilder.Append(itemSeparator); + } else + { return stringBuilder; + } + ++currentIndex; } } @@ -5903,20 +8118,26 @@ public static StringBuilder AppendItems([ValidatedNotNull] this StringBuilder /// Thrown when or is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; items:null => halt; stringBuilder:notnull => notnull")] - public static StringBuilder AppendItemsWithNewLine([ValidatedNotNull] this StringBuilder stringBuilder, [ValidatedNotNull] IEnumerable items, string emptyCollectionText = "empty collection", bool finishWithNewLine = true) => stringBuilder.AppendItems(items, DefaultNewLineSeparator, emptyCollectionText).AppendLineIf(finishWithNewLine); + // ReSharper disable RedundantNullableFlowAttribute + public static StringBuilder AppendItemsWithNewLine([NotNull][ValidatedNotNull] this StringBuilder stringBuilder, [NotNull][ValidatedNotNull] IEnumerable items, string emptyCollectionText = "empty collection", bool finishWithNewLine = true) => stringBuilder.AppendItems(items, DefaultNewLineSeparator, emptyCollectionText).AppendLineIf(finishWithNewLine); + // ReSharper restore RedundantNullableFlowAttribute /// /// Appends the value to the specified string builder if the condition is true. /// /// The string builder where will be appended to. - /// The boolean value indicating whether the append will be performed or not. + /// The boolean value indicating whether the append operation will be performed or not. /// The value to be appended to the string builder. /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; stringBuilder:notnull => notnull")] - public static StringBuilder AppendIf([ValidatedNotNull] this StringBuilder stringBuilder, bool condition, string value) + public static StringBuilder AppendIf(// ReSharper disable once RedundantNullableFlowAttribute + [NotNull][ValidatedNotNull] this StringBuilder stringBuilder, bool condition, string value) { if (condition) + { stringBuilder.MustNotBeNull(nameof(stringBuilder)).Append(value); + } + return stringBuilder; } @@ -5924,15 +8145,19 @@ public static StringBuilder AppendIf([ValidatedNotNull] this StringBuilder strin /// Appends the value followed by a new line separator to the specified string builder if the condition is true. /// /// The string builder where will be appended to. - /// The boolean value indicating whether the append will be performed or not. + /// The boolean value indicating whether the append operation will be performed or not. /// The value to be appended to the string builder (optional). This value defaults to an empty string. /// Thrown when is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("stringBuilder:null => halt; stringBuilder:notnull => notnull")] - public static StringBuilder AppendLineIf([ValidatedNotNull] this StringBuilder stringBuilder, bool condition, string value = "") + public static StringBuilder AppendLineIf(// ReSharper disable once RedundantNullableFlowAttribute + [NotNull][ValidatedNotNull] this StringBuilder stringBuilder, bool condition, string value = "") { if (condition) + { stringBuilder.MustNotBeNull(nameof(stringBuilder)).AppendLine(value); + } + return stringBuilder; } @@ -5941,7 +8166,9 @@ public static StringBuilder AppendLineIf([ValidatedNotNull] this StringBuilder s /// specified . /// /// Thrown when any parameter is null. - public static StringBuilder AppendExceptionMessages([ValidatedNotNull] this StringBuilder stringBuilder, [ValidatedNotNull] Exception exception) + // ReSharper disable RedundantNullableFlowAttribute + public static StringBuilder AppendExceptionMessages([NotNull][ValidatedNotNull] this StringBuilder stringBuilder, [NotNull][ValidatedNotNull] Exception exception) + // ReSharper restore RedundantNullableFlowAttribute { stringBuilder.MustNotBeNull(nameof(stringBuilder)); exception.MustNotBeNull(nameof(exception)); @@ -5950,7 +8177,10 @@ public static StringBuilder AppendExceptionMessages([ValidatedNotNull] this Stri // ReSharper disable once PossibleNullReferenceException stringBuilder.AppendLine(exception.Message); if (exception.InnerException is null) + { return stringBuilder; + } + stringBuilder.AppendLine(); exception = exception.InnerException; } @@ -5961,7 +8191,8 @@ public static StringBuilder AppendExceptionMessages([ValidatedNotNull] this Stri /// a single string. /// /// Thrown when is null. - public static string GetAllExceptionMessages([ValidatedNotNull] this Exception exception) => new StringBuilder().AppendExceptionMessages(exception).ToString(); + // ReSharper disable once RedundantNullableFlowAttribute + public static string GetAllExceptionMessages([NotNull][ValidatedNotNull] this Exception exception) => new StringBuilder().AppendExceptionMessages(exception).ToString(); /// /// Checks if the two strings are equal using ordinal sorting rules as well as ignoring the white space /// of the provided strings. @@ -5969,23 +8200,34 @@ public static StringBuilder AppendExceptionMessages([ValidatedNotNull] this Stri public static bool EqualsOrdinalIgnoreWhiteSpace(this string? x, string? y) { if (ReferenceEquals(x, y)) + { return true; + } + if (x is null || y is null) + { return false; + } + if (x.Length == 0) + { return y.Length == 0; + } + var indexX = 0; var indexY = 0; bool wasXSuccessful; bool wasYSuccessful; // This condition of the while loop actually has to use the single '&' operator because // y.TryAdvanceToNextNonWhiteSpaceCharacter must be called even though it already returned - // false on x. Otherwise the 'wasXSuccessful == wasYSuccessful' comparison would not return + // false on x. Otherwise, the 'wasXSuccessful == wasYSuccessful' comparison would not return // the desired result. while ((wasXSuccessful = x.TryAdvanceToNextNonWhiteSpaceCharacter(ref indexX)) & (wasYSuccessful = y.TryAdvanceToNextNonWhiteSpaceCharacter(ref indexY))) { if (x[indexX++] != y[indexY++]) + { return false; + } } return wasXSuccessful == wasYSuccessful; @@ -5998,23 +8240,34 @@ public static bool EqualsOrdinalIgnoreWhiteSpace(this string? x, string? y) public static bool EqualsOrdinalIgnoreCaseIgnoreWhiteSpace(this string? x, string? y) { if (ReferenceEquals(x, y)) + { return true; + } + if (x is null || y is null) + { return false; + } + if (x.Length == 0) + { return y.Length == 0; + } + var indexX = 0; var indexY = 0; bool wasXSuccessful; bool wasYSuccessful; // This condition of the while loop actually has to use the single '&' operator because // y.TryAdvanceToNextNonWhiteSpaceCharacter must be called even though it already returned - // false on x. Otherwise the 'wasXSuccessful == wasYSuccessful' comparison would not return + // false on x. Otherwise, the 'wasXSuccessful == wasYSuccessful' comparison would not return // the desired result. while ((wasXSuccessful = x.TryAdvanceToNextNonWhiteSpaceCharacter(ref indexX)) & (wasYSuccessful = y.TryAdvanceToNextNonWhiteSpaceCharacter(ref indexY))) { if (char.ToLowerInvariant(x[indexX++]) != char.ToLowerInvariant(y[indexY++])) + { return false; + } } return wasXSuccessful == wasYSuccessful; @@ -6025,7 +8278,10 @@ private static bool TryAdvanceToNextNonWhiteSpaceCharacter(this string @string, while (currentIndex < @string.Length) { if (!char.IsWhiteSpace(@string[currentIndex])) + { return true; + } + ++currentIndex; } diff --git a/README.md b/README.md index dc3ed066..64c403f2 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ **A lightweight .NET library for expressive Guard Clauses.** [![License](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/blob/master/LICENSE) -[![NuGet](https://img.shields.io/badge/NuGet-12.0.0-blue.svg?style=for-the-badge)](https://www.nuget.org/packages/Light.GuardClauses/) -[![Source Code](https://img.shields.io/badge/Source%20Code-12.0.0-blue.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/blob/master/Light.GuardClauses.SingleFile.cs) +[![NuGet](https://img.shields.io/badge/NuGet-13.0.0-blue.svg?style=for-the-badge)](https://www.nuget.org/packages/Light.GuardClauses/) +[![Source Code](https://img.shields.io/badge/Source%20Code-13.0.0-blue.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/blob/master/Light.GuardClauses.SingleFile.cs) [![Documentation](https://img.shields.io/badge/Docs-Wiki-yellowgreen.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/wiki) [![Documentation](https://img.shields.io/badge/Docs-Changelog-yellowgreen.svg?style=for-the-badge)](https://github.com/feO2x/Light.GuardClauses/releases) @@ -85,6 +85,8 @@ In addition to assertions that throw exceptions (all these start with `Must`), * - `string.IsNullOrWhitespace()` - `collection.IsNullOrEmpty()` - `enum.IsValidEnumValue()` +- `string.IsFileExtension()` +- `span.IsEmptyOrWhiteSpace()` You can use these in your branching logic to easily check if an assertion is true or false. @@ -94,9 +96,9 @@ Every assertion is well-documented - explore them using IntelliSense or check ou Since version 4, **Light.GuardClauses** is optimized for performance (measured in .NET 4.8 and .NET 6). With the incredible help of [@redknightlois](https://github.com/redknightlois) and the awesome tool [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet), most assertions are as fast as your imperative code would be. -**Light.GuardClauses** has support for [.NET analyzers / FxCopAnalyzers](https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview) with the `ValidatedNotNullAttribute`. Analyzers will know when an assertion validated that a parameters is not null and consequently, CA1062 will not be raised. +**Light.GuardClauses** has support for [.NET analyzers / FxCopAnalyzers](https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview) with the `ValidatedNotNullAttribute` and the `NotNullAttribute`. Analyzers will know when an assertion validated that a parameters is not null and consequently, CA1062 will not be raised. -Furthermore, **Light.GuardClauses** has support for ReSharper since version 4.x. Via [Contract Annotations](https://www.jetbrains.com/help/resharper/Contract_Annotations.html), R# knows when assertions do not return a null value and thus removes squiggly lines indicating a possible `NullReferenceException`. Since version 10.1.0, Light.GuardClauses also annotates assertions that do not iterate over an `IEnumerable` with ReSharper's `NoEnumerationAttribute` - ReSharper will then not indicate a "Possible multiple enumeration" (thanks to [cdonnellytx](https://github.com/cdonnellytx) for this contribution). +Furthermore, **Light.GuardClauses** has support for ReSharper since version 4.x. Via [Contract Annotations](https://www.jetbrains.com/help/resharper/Contract_Annotations.html), ReSharper knows when assertions do not return a null value and thus removes squiggly lines indicating a possible `NullReferenceException`. Since version 10.1.0, Light.GuardClauses also annotates assertions that do not iterate over an `IEnumerable` with ReSharper's `NoEnumerationAttribute` - ReSharper will then not indicate a "Possible multiple enumeration" (thanks to [cdonnellytx](https://github.com/cdonnellytx) for this contribution). Since version 11, Light.GuardClauses supports Native AOT. @@ -106,7 +108,7 @@ And, of course, the functional correctness of **Light.GuardClauses** is covered ## Supported Platforms -**Light.GuardClauses** is built against [.NET Standard 2.0 and 2.1](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), thus it can be used in frameworks like .NET 6, .NET 7, .NET Framework 4.6.1 or newer, Unity, Mono, or UWP. +**Light.GuardClauses** is built against [.NET 8, .NET Standard 2.0 and 2.1](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), thus it can be used with a large variaty of target frameworks like .NET 5 or newer .NET Framework 4.6.1 or newer, Unity, Mono, or UWP. ## How to Install @@ -114,7 +116,7 @@ Light.GuardClauses is available as a [NuGet package](https://www.nuget.org/packa - **dotnet CLI**: `dotnet add package Light.GuardClauses` - **Visual Studio Package Manager Console**: `Install-Package Light.GuardClauses` -- **Package Reference in csproj**: `` +- **Package Reference in csproj**: `` Also, you can incorporate Light.GuardClauses as a **single source file** where the API is changed to `internal`. This is especially interesting for framework / library developers that do not want to have a dependency on the Light.GuardClauses DLL. You can grab the default .NET Standard 2.0 version in [Light.GuardClauses.SingleFile.cs](https://github.com/feO2x/Light.GuardClauses/blob/master/Light.GuardClauses.SingleFile.cs) or you can use the [Light.GuardClauses.SourceCodeTransformation](https://github.com/feO2x/Light.GuardClauses/tree/master/Code/Light.GuardClauses.SourceCodeTransformation) project to create your custom file. You can learn more about it [here](https://github.com/feO2x/Light.GuardClauses/wiki/Including-Light.GuardClauses-as-source-code).