diff --git a/src/CatLib.Core.Tests/Support/TestsGuard.cs b/src/CatLib.Core.Tests/Support/TestsGuard.cs new file mode 100644 index 0000000..edc1320 --- /dev/null +++ b/src/CatLib.Core.Tests/Support/TestsGuard.cs @@ -0,0 +1,79 @@ +/* + * This file is part of the CatLib package. + * + * (c) CatLib + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: https://catlib.io/ + */ + +#pragma warning disable CA1031 + +using CatLib.Support; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace CatLib.Tests.Support +{ + [TestClass] + public class TestsGuard + { + [TestMethod] + public void TestRequires() + { + var innerException = new Exception("inner exception"); + + try + { + Guard.Requires(false, "foo", innerException); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.AreEqual("foo", ex.Message); + Assert.AreEqual("inner exception", ex.InnerException.Message); + } + } + + [TestMethod] + public void TestExtend() + { + var innerException = new Exception("inner exception"); + + Guard.Extend((messgae, inner, state) => + { + return new ArgumentNullException("foo", inner); + }); + + try + { + Guard.Requires(false, null, innerException); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.AreEqual("foo", ex.Message); + Assert.AreEqual("inner exception", ex.InnerException.Message); + } + } + + [TestMethod] + public void TestRequireNotBaseException() + { + var innerException = new Exception("inner exception"); + + try + { + Guard.Requires(false, "foo", innerException); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.AreEqual("foo", ex.Message); + Assert.AreEqual("inner exception", ex.InnerException.Message); + } + } + } +} diff --git a/src/CatLib.Core/Container/BindData.cs b/src/CatLib.Core/Container/BindData.cs index 224577c..2e123f6 100644 --- a/src/CatLib.Core/Container/BindData.cs +++ b/src/CatLib.Core/Container/BindData.cs @@ -128,7 +128,7 @@ protected override void ReleaseBind() private void AddClosure(Action closure, ref List> list) { - Guard.NotNull(closure, nameof(closure)); + Guard.Requires(closure != null); lock (Locker) { diff --git a/src/CatLib.Core/Container/Container.cs b/src/CatLib.Core/Container/Container.cs index f3b43bf..8f2d651 100644 --- a/src/CatLib.Core/Container/Container.cs +++ b/src/CatLib.Core/Container/Container.cs @@ -193,9 +193,10 @@ public object this[string service] public void Tag(string tag, params string[] service) { Guard.NotEmptyOrNull(tag, nameof(tag)); - Guard.NotNull(service, nameof(service)); - Guard.CountGreaterZero(service, nameof(service)); - Guard.ElementNotEmptyOrNull(service, nameof(service)); + Guard.Requires(service != null); + Guard.Requires(service.Length > 0); + Guard.Requires( + !Array.Exists(service, (s) => string.IsNullOrEmpty(s))); lock (syncRoot) { @@ -377,7 +378,8 @@ public bool BindIf(string service, Type concrete, bool isStatic, out IBindData b /// public IBindData Bind(string service, Type concrete, bool isStatic) { - Guard.NotNull(concrete, nameof(concrete)); + Guard.Requires(concrete != null); + if (IsUnableType(concrete)) { throw new LogicException($"Bind type [{concrete}] can not built"); @@ -391,7 +393,7 @@ public IBindData Bind(string service, Type concrete, bool isStatic) public IBindData Bind(string service, Func concrete, bool isStatic) { Guard.NotEmptyOrNull(service, nameof(service)); - Guard.NotNull(concrete, nameof(concrete)); + Guard.Requires(concrete != null); GuardServiceName(service); service = FormatService(service); @@ -664,7 +666,8 @@ public bool Release(object mixed) /// public IContainer OnFindType(Func func, int priority = int.MaxValue) { - Guard.NotNull(func, nameof(func)); + Guard.Requires(func != null); + lock (syncRoot) { GuardFlushing(); @@ -698,7 +701,8 @@ public IContainer OnAfterResolving(Action closure) /// public IContainer OnRebound(string service, Action callback) { - Guard.NotNull(callback, nameof(callback)); + Guard.Requires(callback != null); + lock (syncRoot) { GuardFlushing(); @@ -1902,7 +1906,7 @@ private Func MakeParamsMatcher(IParams[] tables) /// The specified list. private void AddClosure(Action closure, List> list) { - Guard.NotNull(closure, nameof(closure)); + Guard.Requires(closure != null); lock (syncRoot) { diff --git a/src/CatLib.Core/Support/Guard.cs b/src/CatLib.Core/Support/Guard.cs index e4ddd62..21b8ce5 100644 --- a/src/CatLib.Core/Support/Guard.cs +++ b/src/CatLib.Core/Support/Guard.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Reflection; namespace CatLib.Support { @@ -20,6 +21,7 @@ namespace CatLib.Support public sealed class Guard { private static Guard that; + private static IDictionary> exceptionFactory; /// /// Gets the singleton instance of the Guard functionality. @@ -43,16 +45,33 @@ public static Guard That /// /// Exception triggered when validation fails. /// The condition of the contract. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference. + /// State will be passed to the registered exception build factory. [System.Diagnostics.DebuggerNonUserCode] - public static void Requires(bool condition) + public static void Requires(bool condition, string message = null, Exception innerException = null, object state = null) where TException : Exception, new() + { + Requires(typeof(TException), condition, message, innerException, state); + } + + /// + /// Verifies a condition and throws an exception if the condition of the contract fails. + /// + /// Exception triggered when validation fails. + /// The condition of the contract. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference. + /// State will be passed to the registered exception build factory. + [System.Diagnostics.DebuggerNonUserCode] + public static void Requires(Type exception, bool condition, string message = null, Exception innerException = null, object state = null) { if (condition) { return; } - throw new TException(); + throw CreateExceptionInstance(exception, message, innerException, state); } /// @@ -60,58 +79,95 @@ public static void Requires(bool condition) /// /// The parameter value. /// The parameter name. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference. [System.Diagnostics.DebuggerNonUserCode] - public static void NotEmptyOrNull(string argumentValue, string argumentName) + public static void NotEmptyOrNull(string argumentValue, string argumentName = null, string message = null, Exception innerException = null) { - if (string.IsNullOrEmpty(argumentValue)) + if (!string.IsNullOrEmpty(argumentValue)) + { + return; + } + + var exception = new ArgumentNullException(argumentName, message); + + if (innerException != null) { - throw new ArgumentNullException(argumentName); + SetField(exception, "_innerException", innerException); } + + throw exception; } /// - /// Verifies the length is greater than 0. + /// Extend an exception generation factory. /// - /// The type of parameter. - /// The parameter value. - /// The parameter name. + /// The type of exception. + /// The exception factory. [System.Diagnostics.DebuggerNonUserCode] - public static void CountGreaterZero(IList argumentValue, string argumentName) + public static void Extend(Func factory) { - if (argumentValue.Count <= 0) - { - throw new ArgumentNullException(argumentName); - } + Extend(typeof(T), factory); } /// - /// Verifies the element not empty or null. + /// Extend an exception generation factory. /// - /// The parameter value. - /// The parameter name. + /// The type of exception. + /// The exception factory. [System.Diagnostics.DebuggerNonUserCode] - public static void ElementNotEmptyOrNull(IList argumentValue, string argumentName) + public static void Extend(Type exception, Func factory) + { + VerfiyExceptionFactory(); + exceptionFactory[exception] = factory; + } + + private static Exception CreateExceptionInstance(Type exceptionType, string message, Exception innerException, object state) { - foreach (var val in argumentValue) + if (!typeof(Exception).IsAssignableFrom(exceptionType)) { - if (string.IsNullOrEmpty(val)) - { - throw new ArgumentNullException(argumentName, $"Argument element can not be {nameof(string.Empty)} or null."); - } + throw new ArgumentException( + $"Type: {exceptionType} must be inherited from: {typeof(Exception)}.", + nameof(exceptionType)); + } + + VerfiyExceptionFactory(); + + if (exceptionFactory.TryGetValue(exceptionType, out Func factory)) + { + return factory(message, innerException, state); + } + + var exception = Activator.CreateInstance(exceptionType); + if (!string.IsNullOrEmpty(message)) + { + SetField(exception, "_message", message); + } + + if (innerException != null) + { + SetField(exception, "_innerException", innerException); } + + return (Exception)exception; } - /// - /// Verifies the parameter not null. - /// - /// The parameter value. - /// The parameter name. - [System.Diagnostics.DebuggerNonUserCode] - public static void NotNull(object argumentValue, string argumentName) + private static void VerfiyExceptionFactory() { - if (argumentValue == null) + if (exceptionFactory == null) + { + exceptionFactory = new Dictionary>(); + } + } + + private static void SetField(object obj, string field, object value) + { + var flag = BindingFlags.Instance | BindingFlags.NonPublic; + var fieldInfo = obj.GetType().GetField(field, flag); + + if (fieldInfo != null) { - throw new ArgumentNullException(argumentName); + fieldInfo.SetValue(obj, value); } } }