A:添加测试项目
This commit is contained in:
1 parent
25c4a663a5
commit
859814514b
12 files changed
+562
-3
No files matched your search
@@ -0,0 +1,183 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Sharp.Config.Serialization;
|
||||
using Tomlyn.Extensions.Configuration;
|
||||
|
||||
namespace Sharp.Config;
|
||||
|
||||
public sealed class ConfigManager
|
||||
{
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
public ConfigOptions Options { get; }
|
||||
|
||||
private ConfigManager(IConfigurationRoot configuration, ConfigOptions options)
|
||||
{
|
||||
Configuration = configuration;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public static ConfigManager Build(ConfigOptions options)
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.BasePath))
|
||||
{
|
||||
builder.SetBasePath(options.BasePath!);
|
||||
}
|
||||
|
||||
if (options.FileProvider is not null)
|
||||
{
|
||||
builder.SetFileProvider(options.FileProvider);
|
||||
}
|
||||
|
||||
foreach (var src in options.Sources)
|
||||
{
|
||||
switch (src.Format)
|
||||
{
|
||||
case ConfigFormat.Json:
|
||||
builder.AddJsonFile(src.Path, src.Optional, options.ReloadOnChange);
|
||||
break;
|
||||
case ConfigFormat.Ini:
|
||||
builder.AddIniFile(src.Path, src.Optional, options.ReloadOnChange);
|
||||
break;
|
||||
case ConfigFormat.Xml:
|
||||
builder.AddXmlFile(src.Path, src.Optional, options.ReloadOnChange);
|
||||
break;
|
||||
case ConfigFormat.Toml:
|
||||
builder.AddToml(src.Path, src.Optional, options.ReloadOnChange);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported format: {src.Format}");
|
||||
}
|
||||
}
|
||||
|
||||
if (options.IncludeEnvironmentVariables)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(options.EnvironmentPrefix))
|
||||
{
|
||||
builder.AddEnvironmentVariables(options.EnvironmentPrefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddEnvironmentVariables();
|
||||
}
|
||||
}
|
||||
|
||||
if (options.ConfigureBuilderHook is not null)
|
||||
{
|
||||
builder = (ConfigurationBuilder)options.ConfigureBuilderHook(builder);
|
||||
}
|
||||
|
||||
var configuration = builder.Build();
|
||||
return new ConfigManager(configuration, options);
|
||||
}
|
||||
|
||||
// Strongly-typed binding helpers
|
||||
public T Get<T>(string? section = null, bool validate = false)
|
||||
where T : new()
|
||||
{
|
||||
var target = new T();
|
||||
if (string.IsNullOrWhiteSpace(section))
|
||||
{
|
||||
Configuration.Bind(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
Configuration.GetSection(section).Bind(target);
|
||||
}
|
||||
|
||||
if (validate)
|
||||
{
|
||||
ValidateObject(target, throwOnError: true);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public bool TryGet<T>(out T instance, string? section = null, bool validate = false) where T : new()
|
||||
{
|
||||
instance = Get<T>(section, validate: false);
|
||||
if (!validate) return true;
|
||||
var results = ValidateObject(instance, throwOnError: false);
|
||||
return results.Count == 0;
|
||||
}
|
||||
|
||||
public IList<ValidationResult> ValidateObject(object instance, bool throwOnError)
|
||||
{
|
||||
var context = new ValidationContext(instance);
|
||||
var results = new List<ValidationResult>();
|
||||
// validate top-level
|
||||
Validator.TryValidateObject(instance, context, results, validateAllProperties: true);
|
||||
|
||||
// validate nested properties with DataAnnotations recursively
|
||||
foreach (var prop in instance.GetType().GetProperties())
|
||||
{
|
||||
var value = prop.GetValue(instance);
|
||||
if (value is null) continue;
|
||||
if (IsSimple(prop.PropertyType)) continue;
|
||||
var nestedContext = new ValidationContext(value);
|
||||
Validator.TryValidateObject(value, nestedContext, results, validateAllProperties: true);
|
||||
}
|
||||
|
||||
if (throwOnError && results.Count > 0)
|
||||
{
|
||||
throw new ConfigurationValidationException(results);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static bool IsSimple(Type t)
|
||||
{
|
||||
t = Nullable.GetUnderlyingType(t) ?? t;
|
||||
return t.IsPrimitive || t.IsEnum || t == typeof(string) || t == typeof(decimal) || t == typeof(DateTime) || t == typeof(Guid) || t == typeof(DateTimeOffset) || t == typeof(TimeSpan);
|
||||
}
|
||||
|
||||
// Save APIs
|
||||
public void Save<T>(T instance, string path, ConfigFormat format, bool indented = true, Encoding? encoding = null)
|
||||
{
|
||||
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(path)) ?? ".");
|
||||
var serializer = SerializerFactory.Get(format);
|
||||
serializer.SerializeToFile(path, instance!, indented, encoding);
|
||||
}
|
||||
|
||||
// Read file to typed object directly (bypassing IConfiguration)
|
||||
public T? LoadFromFile<T>(string path, ConfigFormat format)
|
||||
{
|
||||
var serializer = SerializerFactory.Get(format);
|
||||
return serializer.DeserializeFromFile<T>(path);
|
||||
}
|
||||
|
||||
// Reload notifications
|
||||
public IDisposable OnReload(Action callback)
|
||||
{
|
||||
IChangeToken token = Configuration.GetReloadToken();
|
||||
return ChangeToken.OnChange(() => token, callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SerializerFactory
|
||||
{
|
||||
public static IConfigSerializer Get(ConfigFormat format) => format switch
|
||||
{
|
||||
ConfigFormat.Json => new JsonConfigSerializer(),
|
||||
ConfigFormat.Xml => new XmlConfigSerializer(),
|
||||
ConfigFormat.Ini => new IniConfigSerializer(),
|
||||
ConfigFormat.Toml => new TomlConfigSerializer(),
|
||||
_ => throw new NotSupportedException($"Unsupported format: {format}")
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class ConfigurationValidationException : Exception
|
||||
{
|
||||
public IReadOnlyList<ValidationResult> Errors { get; }
|
||||
public ConfigurationValidationException(IReadOnlyList<ValidationResult> errors)
|
||||
: base($"Configuration validation failed: {string.Join("; ", errors.Select(e => e.ErrorMessage))}")
|
||||
{
|
||||
Errors = errors;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user