diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/AvailableValuesAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/AvailableValuesAttribute.cs
new file mode 100644
index 0000000000..b13fe203c8
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/AvailableValuesAttribute.cs
@@ -0,0 +1,37 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Checks if value is in list of available values.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class AvailableValuesAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public AvailableValuesAttribute(params object[] values)
+ {
+ Values = values;
+ }
+
+ ///
+ /// Gets the array of possible values.
+ ///
+ public object[] Values { get; }
+
+ ///
+ public bool Check(object value) => Values.Contains(value);
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/CustomValidatorAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/CustomValidatorAttribute.cs
new file mode 100644
index 0000000000..76c33cab55
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/CustomValidatorAttribute.cs
@@ -0,0 +1,37 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Check a value with custom function.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class CustomValidatorAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public CustomValidatorAttribute(Func customFunction)
+ {
+ CustomFunction = customFunction;
+ }
+
+ ///
+ /// Gets the custom check function.
+ ///
+ public Func CustomFunction { get; }
+
+ ///
+ public bool Check(object value) => CustomFunction(value);
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/GreaterOrEqualAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/GreaterOrEqualAttribute.cs
new file mode 100644
index 0000000000..5e905971ec
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/GreaterOrEqualAttribute.cs
@@ -0,0 +1,40 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Checks if value greater or equal.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class GreaterOrEqualAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public GreaterOrEqualAttribute(IComparable value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Gets the minimum value.
+ ///
+ public IComparable Value { get; }
+
+ ///
+ public bool Check(object value)
+ {
+ return Value.CompareTo(value) >= 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/GreaterThanAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/GreaterThanAttribute.cs
new file mode 100644
index 0000000000..91e4ea4106
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/GreaterThanAttribute.cs
@@ -0,0 +1,40 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Interfaces;
+
+ ///
+ /// Check if value is greater.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class GreaterThanAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public GreaterThanAttribute(IComparable value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Gets the minimum value.
+ ///
+ public IComparable Value { get; }
+
+ ///
+ public bool Check(object value)
+ {
+ return Value.CompareTo(value) >= 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/LessOrEqualAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/LessOrEqualAttribute.cs
new file mode 100644
index 0000000000..cc11854e5a
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/LessOrEqualAttribute.cs
@@ -0,0 +1,40 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Checks if value less or equal.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class LessOrEqualAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public LessOrEqualAttribute(IComparable value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Gets the minimum value.
+ ///
+ public IComparable Value { get; }
+
+ ///
+ public bool Check(object value)
+ {
+ return Value.CompareTo(value) <= 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/LessThanAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/LessThanAttribute.cs
new file mode 100644
index 0000000000..36f13b2f15
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/LessThanAttribute.cs
@@ -0,0 +1,40 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Checks if value is less.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class LessThanAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public LessThanAttribute(IComparable value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Gets the minimum value.
+ ///
+ public IComparable Value { get; }
+
+ ///
+ public bool Check(object value)
+ {
+ return Value.CompareTo(value) <= -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/NonNegativeAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/NonNegativeAttribute.cs
new file mode 100644
index 0000000000..f0475c8e8e
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/NonNegativeAttribute.cs
@@ -0,0 +1,26 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ ///
+ /// Checks if value is 0 or greater.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class NonNegativeAttribute : GreaterOrEqualAttribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NonNegativeAttribute()
+ : base(0)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/NonPositiveAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/NonPositiveAttribute.cs
new file mode 100644
index 0000000000..1bed2a8987
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/NonPositiveAttribute.cs
@@ -0,0 +1,26 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ ///
+ /// Check if value is 0 or less.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class NonPositiveAttribute : LessOrEqualAttribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NonPositiveAttribute()
+ : base(0)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Features/Attributes/Validators/RangeAttribute.cs b/EXILED/Exiled.API/Features/Attributes/Validators/RangeAttribute.cs
new file mode 100644
index 0000000000..7c5287f4ff
--- /dev/null
+++ b/EXILED/Exiled.API/Features/Attributes/Validators/RangeAttribute.cs
@@ -0,0 +1,57 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Features.Attributes.Validators
+{
+ using System;
+
+ using Exiled.API.Interfaces;
+
+ ///
+ /// Check if an is inside a specific range.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class RangeAttribute : Attribute, IValidator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ public RangeAttribute(IComparable min, IComparable max, bool inclusive = false)
+ {
+ Min = min;
+ Max = max;
+ Inclusive = inclusive;
+ }
+
+ ///
+ /// Gets the minimum value.
+ ///
+ public IComparable Max { get; }
+
+ ///
+ /// Gets the maximum value.
+ ///
+ public IComparable Min { get; }
+
+ ///
+ /// Gets a value indicating whether check is inclusive.
+ ///
+ public bool Inclusive { get; }
+
+ ///
+ public bool Check(object value)
+ {
+ int minResult = Inclusive ? 0 : -1;
+ int maxResult = Inclusive ? 0 : 1;
+
+ return Max.CompareTo(value) <= minResult && Min.CompareTo(value) >= maxResult;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.API/Interfaces/IValidator.cs b/EXILED/Exiled.API/Interfaces/IValidator.cs
new file mode 100644
index 0000000000..c07a044341
--- /dev/null
+++ b/EXILED/Exiled.API/Interfaces/IValidator.cs
@@ -0,0 +1,22 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.API.Interfaces
+{
+ ///
+ /// Interface for all validations attributes.
+ ///
+ public interface IValidator
+ {
+ ///
+ /// Checks if is satisfying this attributes condition.
+ ///
+ /// Value to check.
+ /// Whether the value has passed check.
+ public bool Check(object value);
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Loader/Config.cs b/EXILED/Exiled.Loader/Config.cs
index 6e07979ef2..35ea91aa36 100644
--- a/EXILED/Exiled.Loader/Config.cs
+++ b/EXILED/Exiled.Loader/Config.cs
@@ -88,5 +88,11 @@ public sealed class Config : IConfig
///
[Description("Indicates whether Exiled should auto-update itself as soon as a new release is available.")]
public bool EnableAutoUpdates { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether config validator should check all properties inside config values' types.
+ ///
+ [Description("Indicating whether config validator should check all properties inside config values' types.")]
+ public bool EnableDeepValidation { get; set; } = true;
}
}
\ No newline at end of file
diff --git a/EXILED/Exiled.Loader/ConfigManager.cs b/EXILED/Exiled.Loader/ConfigManager.cs
index e925517be9..339c7e57a1 100644
--- a/EXILED/Exiled.Loader/ConfigManager.cs
+++ b/EXILED/Exiled.Loader/ConfigManager.cs
@@ -5,6 +5,8 @@
//
// -----------------------------------------------------------------------
+using System.Reflection;
+
namespace Exiled.Loader
{
using System;
@@ -65,6 +67,58 @@ public static SortedDictionary LoadSorted(string rawConfigs)
}
}
+ ///
+ /// Validates plugin config.
+ ///
+ /// Plugin which config is validated.
+ /// Validated config.
+ /// Config after validation is passed.
+ public static IConfig ValidateConfig(this IPlugin plugin, IConfig config)
+ {
+ int validated = 0;
+ foreach (PropertyInfo propertyInfo in config.GetType().GetProperties().Where(x => x.GetMethod != null && x.SetMethod != null))
+ plugin.ValidateType(config, propertyInfo, ref validated);
+
+ Log.Info($"Config has successfully passed {validated} validations!");
+ return config;
+ }
+
+ ///
+ /// Performs a validation for property and all its properties in 's type.
+ ///
+ /// Plugin which config is validated.
+ /// Validated config.
+ /// Property which will be validated.
+ /// Amount of successfully passed validations.
+ public static void ValidateType(this IPlugin plugin, IConfig config, PropertyInfo propertyInfo, ref int validated)
+ {
+ foreach (Attribute attribute in propertyInfo.GetCustomAttributes())
+ {
+ if (attribute is not IValidator validator)
+ continue;
+
+ object value = propertyInfo.GetValue(config, null);
+ object defaultValue = propertyInfo.GetValue(plugin.Config, null);
+ if (!validator.Check(value))
+ {
+ Log.Error($"Value {value} in config ({propertyInfo.Name.ToSnakeCase()}) has failed validation for attribute {attribute.GetType().Name}. Default value ({defaultValue}) will be used instead.");
+ propertyInfo.SetValue(config, defaultValue);
+ continue;
+ }
+
+ validated++;
+ }
+
+ if (!LoaderPlugin.Config.EnableDeepValidation)
+ return;
+
+ if (!(propertyInfo.PropertyType.Namespace?.Contains("System") ?? false))
+ {
+ foreach (PropertyInfo property in propertyInfo.PropertyType.GetProperties().Where(x => x.GetMethod != null && x.SetMethod != null))
+ plugin.ValidateType(config, property, ref validated);
+ }
+ }
+
///
/// Loads the config of a plugin using the distribution.
///
@@ -99,7 +153,7 @@ public static IConfig LoadDefaultConfig(this IPlugin plugin, Dictionary
try
{
string rawConfigString = Loader.Serializer.Serialize(rawDeserializedConfig);
- config = (IConfig)Loader.Deserializer.Deserialize(rawConfigString, plugin.Config.GetType());
+ config = ValidateConfig(plugin, (IConfig)Loader.Deserializer.Deserialize(rawConfigString, plugin.Config.GetType()));
plugin.Config.CopyProperties(config);
}
catch (YamlException yamlException)
@@ -128,7 +182,7 @@ public static IConfig LoadSeparatedConfig(this IPlugin plugin)
try
{
- config = (IConfig)Loader.Deserializer.Deserialize(File.ReadAllText(plugin.ConfigPath), plugin.Config.GetType());
+ config = ValidateConfig(plugin, (IConfig)Loader.Deserializer.Deserialize(File.ReadAllText(plugin.ConfigPath), plugin.Config.GetType()));
plugin.Config.CopyProperties(config);
}
catch (YamlException yamlException)