using MessagePack;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Xml.Linq;
using XFEExtension.NetCore.FormatExtension;
namespace XFEExtension.NetCore.AutoConfig;
/// <summary>
/// 配置迁移步骤的上下文。
/// </summary>
public sealed class ProfileMigrationContext
{
internal ProfileMigrationContext(int fromVersion, ProfileOperationMode operationMode, string content, JsonSerializerOptions jsonOptions)
{
FromVersion = fromVersion;
OperationMode = operationMode;
Content = content;
JsonOptions = jsonOptions;
}
/// <summary>迁移前版本。</summary>
public int FromVersion { get; }
/// <summary>迁移后的版本。</summary>
public int ToVersion => FromVersion + 1;
/// <summary>当前存储模式。</summary>
public ProfileOperationMode OperationMode { get; }
/// <summary>当前迁移步骤收到的序列化内容。</summary>
public string Content { get; }
/// <summary>配置实例使用的 JSON 选项。</summary>
public JsonSerializerOptions JsonOptions { get; }
}
/// <summary>
/// MessagePack 配置迁移步骤的上下文。属性值保持 MessagePack 二进制表示,只有显式读取或写入时才会反序列化。
/// </summary>
public sealed class MessagePackProfileMigrationContext
{
private readonly Dictionary<string, byte[]> properties;
private readonly MessagePackSerializerOptions options;
internal MessagePackProfileMigrationContext(int fromVersion, Dictionary<string, byte[]> properties, MessagePackSerializerOptions options)
{
FromVersion = fromVersion;
this.properties = properties;
this.options = options;
}
/// <summary>迁移前版本。</summary>
public int FromVersion { get; }
/// <summary>迁移后的版本。</summary>
public int ToVersion => FromVersion + 1;
/// <summary>当前包含的配置属性名称。</summary>
public IReadOnlyCollection<string> PropertyNames => properties.Keys;
/// <summary>判断二进制配置中是否包含指定属性。</summary>
public bool ContainsProperty(string propertyName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
return properties.ContainsKey(propertyName);
}
/// <summary>按指定类型读取一个属性。</summary>
public T? GetProperty<T>(string propertyName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
if (!properties.TryGetValue(propertyName, out var content))
throw new ProfileMigrationException($"MessagePack 配置中不存在属性“{propertyName}”");
return MessagePackSerializer.Deserialize<T>(content, options);
}
/// <summary>写入或替换一个属性。</summary>
public void SetProperty<T>(string propertyName, T value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
properties[propertyName] = MessagePackSerializer.Serialize(value, options);
}
/// <summary>删除一个属性。</summary>
public bool RemoveProperty(string propertyName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
return properties.Remove(propertyName);
}
/// <summary>重命名一个属性;源属性不存在时不执行操作。</summary>
public void RenameProperty(string oldName, string newName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(oldName);
ArgumentException.ThrowIfNullOrWhiteSpace(newName);
if (!properties.TryGetValue(oldName, out var content))
return;
if (properties.ContainsKey(newName))
throw new ProfileMigrationException($"无法把 MessagePack 字段“{oldName}”重命名为“{newName}”:目标字段已存在");
properties.Remove(oldName);
properties.Add(newName, content);
}
}
/// <summary>
/// 配置迁移注册器。每个步骤负责把版本 N 升级到 N + 1。
/// </summary>
public sealed class ProfileMigrationBuilder
{
private readonly Dictionary<int, List<Func<ProfileMigrationContext, string>>> migrations = [];
private readonly Dictionary<int, List<Action<MessagePackProfileMigrationContext>>> messagePackMigrations = [];
/// <summary>
/// 注册一个结构转换步骤。
/// </summary>
public ProfileMigrationBuilder Transform(int fromVersion, Func<ProfileMigrationContext, string> transform)
{
ArgumentNullException.ThrowIfNull(transform);
if (fromVersion < 0)
throw new ArgumentOutOfRangeException(nameof(fromVersion));
if (!migrations.TryGetValue(fromVersion, out var transforms))
{
transforms = [];
migrations.Add(fromVersion, transforms);
}
transforms.Add(transform);
return this;
}
/// <summary>
/// 注册一个 MessagePack 结构转换步骤。未修改的属性不会被反序列化,适合迁移大型对象配置。
/// </summary>
public ProfileMigrationBuilder TransformMessagePack(int fromVersion, Action<MessagePackProfileMigrationContext> transform)
{
ArgumentNullException.ThrowIfNull(transform);
if (fromVersion < 0)
throw new ArgumentOutOfRangeException(nameof(fromVersion));
if (!messagePackMigrations.TryGetValue(fromVersion, out var transforms))
{
transforms = [];
messagePackMigrations.Add(fromVersion, transforms);
}
transforms.Add(transform);
return this;
}
/// <summary>
/// 注册一个无需修改内容的版本升级步骤。
/// </summary>
public ProfileMigrationBuilder NoOp(int fromVersion)
{
Transform(fromVersion, static context => context.Content);
TransformMessagePack(fromVersion, static _ => { });
return this;
}
/// <summary>
/// 注册跨内置格式的字段重命名步骤。JSON/XML 会同时识别生成的 InstanceXxx 成员名,
/// MessagePack 会直接重命名属性对应的二进制块。
/// </summary>
public ProfileMigrationBuilder RenameProperty(int fromVersion, string oldName, string newName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(oldName);
ArgumentException.ThrowIfNullOrWhiteSpace(newName);
Transform(fromVersion, context => RenameProperty(context, oldName, newName));
TransformMessagePack(fromVersion, context => context.RenameProperty(oldName, newName));
return this;
}
internal ProfileMigrationResult Apply(int storedVersion, int targetVersion, ProfileOperationMode operationMode, string content, JsonSerializerOptions jsonOptions)
{
if (storedVersion < 0)
throw new ProfileMigrationException($"配置版本不能小于零:{storedVersion}");
if (storedVersion > targetVersion)
throw new ProfileMigrationException($"配置文件版本 {storedVersion} 高于当前支持版本 {targetVersion},不支持自动降级");
var currentContent = content;
for (var version = storedVersion; version < targetVersion; version++)
{
if (!migrations.TryGetValue(version, out var transforms) || transforms.Count == 0)
throw new ProfileMigrationException($"缺少从版本 {version} 到版本 {version + 1} 的迁移步骤");
foreach (var transform in transforms)
{
var context = new ProfileMigrationContext(version, operationMode, currentContent, jsonOptions);
currentContent = transform(context) ?? throw new ProfileMigrationException($"版本 {version} 的迁移步骤返回了 null");
}
}
return new ProfileMigrationResult(currentContent, storedVersion != targetVersion);
}
internal MessagePackProfileMigrationResult ApplyMessagePack(
int storedVersion,
int targetVersion,
Dictionary<string, byte[]> properties,
MessagePackSerializerOptions options)
{
if (storedVersion < 0)
throw new ProfileMigrationException($"配置版本不能小于零:{storedVersion}");
if (storedVersion > targetVersion)
throw new ProfileMigrationException($"配置文件版本 {storedVersion} 高于当前支持版本 {targetVersion},不支持自动降级");
var currentProperties = new Dictionary<string, byte[]>(properties, StringComparer.Ordinal);
for (var version = storedVersion; version < targetVersion; version++)
{
if (!messagePackMigrations.TryGetValue(version, out var transforms) || transforms.Count == 0)
throw new ProfileMigrationException($"缺少从版本 {version} 到版本 {version + 1} 的 MessagePack 迁移步骤");
foreach (var transform in transforms)
transform(new MessagePackProfileMigrationContext(version, currentProperties, options));
}
return new MessagePackProfileMigrationResult(currentProperties, storedVersion != targetVersion);
}
private static string RenameProperty(ProfileMigrationContext context, string oldName, string newName) => context.OperationMode switch
{
ProfileOperationMode.XFEDictionary => RenameXfeDictionaryProperty(context.Content, oldName, newName),
ProfileOperationMode.Json => RenameJsonProperty(context.Content, oldName, newName, context.JsonOptions),
ProfileOperationMode.Xml => RenameXmlProperty(context.Content, oldName, newName),
_ => throw new ProfileMigrationException("Custom 存储模式需要通过 Transform 注册自定义字段重命名逻辑")
};
private static string RenameXfeDictionaryProperty(string content, string oldName, string newName)
{
XFEDictionary source = content;
var hasOldName = source.Any(entry => entry.Header == oldName);
if (hasOldName && source.Any(entry => entry.Header == newName))
throw new ProfileMigrationException($"无法把 XFE 字典字段“{oldName}”重命名为“{newName}”:目标字段已存在");
var destination = new XFEDictionary();
foreach (var entry in source)
destination.Add(entry.Header == oldName ? newName : entry.Header, entry.Content);
return destination.ToString();
}
private static string RenameJsonProperty(string content, string oldName, string newName, JsonSerializerOptions jsonOptions)
{
if (JsonNode.Parse(content, documentOptions: CreateDocumentOptions(jsonOptions)) is not JsonObject root)
throw new ProfileMigrationException("JSON 配置根节点必须是对象才能执行字段重命名");
var candidates = new[]
{
(Old: ApplyNamingPolicy(oldName, jsonOptions.PropertyNamingPolicy), New: ApplyNamingPolicy(newName, jsonOptions.PropertyNamingPolicy)),
(Old: ApplyNamingPolicy("Instance" + oldName, jsonOptions.PropertyNamingPolicy), New: ApplyNamingPolicy("Instance" + newName, jsonOptions.PropertyNamingPolicy))
};
foreach (var candidate in candidates)
{
if (!root.TryGetPropertyValue(candidate.Old, out var value))
continue;
if (root.ContainsKey(candidate.New))
throw new ProfileMigrationException($"无法把 JSON 字段“{candidate.Old}”重命名为“{candidate.New}”:目标字段已存在");
root.Remove(candidate.Old);
root[candidate.New] = value;
break;
}
return root.ToJsonString(jsonOptions);
}
private static string RenameXmlProperty(string content, string oldName, string newName)
{
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new ProfileMigrationException("XML 配置缺少根节点");
var oldNames = new[] { oldName, "Instance" + oldName };
var newNames = new[] { newName, "Instance" + newName };
for (var index = 0; index < oldNames.Length; index++)
{
var element = root.Elements().FirstOrDefault(candidate => candidate.Name.LocalName == oldNames[index]);
if (element is null)
continue;
if (root.Elements().Any(candidate => candidate.Name.LocalName == newNames[index]))
throw new ProfileMigrationException($"无法把 XML 字段“{oldNames[index]}”重命名为“{newNames[index]}”:目标字段已存在");
element.Name = element.Name.Namespace + newNames[index];
break;
}
return document.ToString(SaveOptions.DisableFormatting);
}
private static string ApplyNamingPolicy(string name, JsonNamingPolicy? namingPolicy) => namingPolicy?.ConvertName(name) ?? name;
private static JsonDocumentOptions CreateDocumentOptions(JsonSerializerOptions options) => new()
{
AllowTrailingCommas = options.AllowTrailingCommas,
CommentHandling = options.ReadCommentHandling == JsonCommentHandling.Skip ? JsonCommentHandling.Skip : JsonCommentHandling.Disallow,
MaxDepth = options.MaxDepth
};
}
/// <summary>
/// 配置迁移失败。
/// </summary>
public sealed class ProfileMigrationException : Exception
{
/// <summary>创建配置迁移异常。</summary>
public ProfileMigrationException(string message) : base(message) { }
/// <summary>创建带内部异常的配置迁移异常。</summary>
public ProfileMigrationException(string message, Exception innerException) : base(message, innerException) { }
}
internal readonly record struct ProfileMigrationResult(string Content, bool WasMigrated);
internal readonly record struct MessagePackProfileMigrationResult(Dictionary<string, byte[]> Properties, bool WasMigrated);
internal static class ProfileVersionMetadata
{
private const string VersionPropertyName = "$xfeProfileVersion";
private const string XmlVersionAttributeName = "xfeProfileVersion";
public static (int Version, string Content) ReadAndStrip(ProfileOperationMode operationMode, string content, JsonSerializerOptions jsonOptions) => operationMode switch
{
ProfileOperationMode.XFEDictionary => ReadAndStripXfeDictionary(content),
ProfileOperationMode.Json => ReadAndStripJson(content, jsonOptions),
ProfileOperationMode.Xml => ReadAndStripXml(content),
_ => (0, content)
};
public static string Write(ProfileOperationMode operationMode, string content, int version, JsonSerializerOptions jsonOptions)
{
if (version <= 0)
return content;
return operationMode switch
{
ProfileOperationMode.XFEDictionary => WriteXfeDictionary(content, version),
ProfileOperationMode.Json => WriteJson(content, version, jsonOptions),
ProfileOperationMode.Xml => WriteXml(content, version),
_ => content
};
}
private static (int Version, string Content) ReadAndStripXfeDictionary(string content)
{
XFEDictionary source = content;
var destination = new XFEDictionary();
var version = 0;
foreach (var entry in source)
{
if (entry.Header == VersionPropertyName)
{
if (!int.TryParse(entry.Content, out version))
throw new ProfileMigrationException($"无效的配置版本:{entry.Content}");
continue;
}
destination.Add(entry.Header, entry.Content);
}
return version == 0 && !source.Any(entry => entry.Header == VersionPropertyName)
? (0, content)
: (version, destination.ToString());
}
private static string WriteXfeDictionary(string content, int version)
{
XFEDictionary source = content;
var destination = new XFEDictionary();
destination.Add(VersionPropertyName, version.ToString(System.Globalization.CultureInfo.InvariantCulture));
foreach (var entry in source)
if (entry.Header != VersionPropertyName)
destination.Add(entry.Header, entry.Content);
return destination.ToString();
}
private static (int Version, string Content) ReadAndStripJson(string content, JsonSerializerOptions jsonOptions)
{
if (JsonNode.Parse(content, documentOptions: CreateDocumentOptions(jsonOptions)) is not JsonObject root)
throw new ProfileMigrationException("JSON 配置根节点必须是对象");
var version = 0;
if (!root.TryGetPropertyValue(VersionPropertyName, out var versionNode))
return (0, content);
if (versionNode is null || !versionNode.AsValue().TryGetValue<int>(out version))
throw new ProfileMigrationException("JSON 配置版本不是有效整数");
root.Remove(VersionPropertyName);
return (version, root.ToJsonString(jsonOptions));
}
private static string WriteJson(string content, int version, JsonSerializerOptions jsonOptions)
{
if (JsonNode.Parse(content, documentOptions: CreateDocumentOptions(jsonOptions)) is not JsonObject root)
throw new ProfileMigrationException("JSON 配置根节点必须是对象");
root[VersionPropertyName] = version;
return root.ToJsonString(jsonOptions);
}
private static (int Version, string Content) ReadAndStripXml(string content)
{
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new ProfileMigrationException("XML 配置缺少根节点");
var attribute = root.Attribute(XmlVersionAttributeName);
var version = 0;
if (attribute is null)
return (0, content);
if (!int.TryParse(attribute.Value, out version))
throw new ProfileMigrationException($"无效的 XML 配置版本:{attribute.Value}");
attribute.Remove();
return (version, document.ToString(SaveOptions.DisableFormatting));
}
private static string WriteXml(string content, int version)
{
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new ProfileMigrationException("XML 配置缺少根节点");
root.SetAttributeValue(XmlVersionAttributeName, version);
return document.ToString(SaveOptions.DisableFormatting);
}
private static JsonDocumentOptions CreateDocumentOptions(JsonSerializerOptions options) => new()
{
AllowTrailingCommas = options.AllowTrailingCommas,
CommentHandling = options.ReadCommentHandling == JsonCommentHandling.Skip ? JsonCommentHandling.Skip : JsonCommentHandling.Disallow,
MaxDepth = options.MaxDepth
};
}
using MessagePack;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Xml.Linq;
using XFEExtension.NetCore.FormatExtension;
namespace XFEExtension.NetCore.AutoConfig;
/// <summary>
/// 配置迁移步骤的上下文。
/// </summary>
public sealed class ProfileMigrationContext
{
internal ProfileMigrationContext(int fromVersion, ProfileOperationMode operationMode, string content, JsonSerializerOptions jsonOptions)
{
FromVersion = fromVersion;
OperationMode = operationMode;
Content = content;
JsonOptions = jsonOptions;
}
/// <summary>迁移前版本。</summary>
public int FromVersion { get; }
/// <summary>迁移后的版本。</summary>
public int ToVersion => FromVersion + 1;
/// <summary>当前存储模式。</summary>
public ProfileOperationMode OperationMode { get; }
/// <summary>当前迁移步骤收到的序列化内容。</summary>
public string Content { get; }
/// <summary>配置实例使用的 JSON 选项。</summary>
public JsonSerializerOptions JsonOptions { get; }
}
/// <summary>
/// MessagePack 配置迁移步骤的上下文。属性值保持 MessagePack 二进制表示,只有显式读取或写入时才会反序列化。
/// </summary>
public sealed class MessagePackProfileMigrationContext
{
private readonly Dictionary<string, byte[]> properties;
private readonly MessagePackSerializerOptions options;
internal MessagePackProfileMigrationContext(int fromVersion, Dictionary<string, byte[]> properties, MessagePackSerializerOptions options)
{
FromVersion = fromVersion;
this.properties = properties;
this.options = options;
}
/// <summary>迁移前版本。</summary>
public int FromVersion { get; }
/// <summary>迁移后的版本。</summary>
public int ToVersion => FromVersion + 1;
/// <summary>当前包含的配置属性名称。</summary>
public IReadOnlyCollection<string> PropertyNames => properties.Keys;
/// <summary>判断二进制配置中是否包含指定属性。</summary>
public bool ContainsProperty(string propertyName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
return properties.ContainsKey(propertyName);
}
/// <summary>按指定类型读取一个属性。</summary>
public T? GetProperty<T>(string propertyName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
if (!properties.TryGetValue(propertyName, out var content))
throw new ProfileMigrationException($"MessagePack 配置中不存在属性“{propertyName}”");
return MessagePackSerializer.Deserialize<T>(content, options);
}
/// <summary>写入或替换一个属性。</summary>
public void SetProperty<T>(string propertyName, T value)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
properties[propertyName] = MessagePackSerializer.Serialize(value, options);
}
/// <summary>删除一个属性。</summary>
public bool RemoveProperty(string propertyName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(propertyName);
return properties.Remove(propertyName);
}
/// <summary>重命名一个属性;源属性不存在时不执行操作。</summary>
public void RenameProperty(string oldName, string newName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(oldName);
ArgumentException.ThrowIfNullOrWhiteSpace(newName);
if (!properties.TryGetValue(oldName, out var content))
return;
if (properties.ContainsKey(newName))
throw new ProfileMigrationException($"无法把 MessagePack 字段“{oldName}”重命名为“{newName}”:目标字段已存在");
properties.Remove(oldName);
properties.Add(newName, content);
}
}
/// <summary>
/// 配置迁移注册器。每个步骤负责把版本 N 升级到 N + 1。
/// </summary>
public sealed class ProfileMigrationBuilder
{
private readonly Dictionary<int, List<Func<ProfileMigrationContext, string>>> migrations = [];
private readonly Dictionary<int, List<Action<MessagePackProfileMigrationContext>>> messagePackMigrations = [];
/// <summary>
/// 注册一个结构转换步骤。
/// </summary>
public ProfileMigrationBuilder Transform(int fromVersion, Func<ProfileMigrationContext, string> transform)
{
ArgumentNullException.ThrowIfNull(transform);
if (fromVersion < 0)
throw new ArgumentOutOfRangeException(nameof(fromVersion));
if (!migrations.TryGetValue(fromVersion, out var transforms))
{
transforms = [];
migrations.Add(fromVersion, transforms);
}
transforms.Add(transform);
return this;
}
/// <summary>
/// 注册一个 MessagePack 结构转换步骤。未修改的属性不会被反序列化,适合迁移大型对象配置。
/// </summary>
public ProfileMigrationBuilder TransformMessagePack(int fromVersion, Action<MessagePackProfileMigrationContext> transform)
{
ArgumentNullException.ThrowIfNull(transform);
if (fromVersion < 0)
throw new ArgumentOutOfRangeException(nameof(fromVersion));
if (!messagePackMigrations.TryGetValue(fromVersion, out var transforms))
{
transforms = [];
messagePackMigrations.Add(fromVersion, transforms);
}
transforms.Add(transform);
return this;
}
/// <summary>
/// 注册一个无需修改内容的版本升级步骤。
/// </summary>
public ProfileMigrationBuilder NoOp(int fromVersion)
{
Transform(fromVersion, static context => context.Content);
TransformMessagePack(fromVersion, static _ => { });
return this;
}
/// <summary>
/// 注册跨内置格式的字段重命名步骤。JSON/XML 会同时识别生成的 InstanceXxx 成员名,
/// MessagePack 会直接重命名属性对应的二进制块。
/// </summary>
public ProfileMigrationBuilder RenameProperty(int fromVersion, string oldName, string newName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(oldName);
ArgumentException.ThrowIfNullOrWhiteSpace(newName);
Transform(fromVersion, context => RenameProperty(context, oldName, newName));
TransformMessagePack(fromVersion, context => context.RenameProperty(oldName, newName));
return this;
}
internal ProfileMigrationResult Apply(int storedVersion, int targetVersion, ProfileOperationMode operationMode, string content, JsonSerializerOptions jsonOptions)
{
if (storedVersion < 0)
throw new ProfileMigrationException($"配置版本不能小于零:{storedVersion}");
if (storedVersion > targetVersion)
throw new ProfileMigrationException($"配置文件版本 {storedVersion} 高于当前支持版本 {targetVersion},不支持自动降级");
var currentContent = content;
for (var version = storedVersion; version < targetVersion; version++)
{
if (!migrations.TryGetValue(version, out var transforms) || transforms.Count == 0)
throw new ProfileMigrationException($"缺少从版本 {version} 到版本 {version + 1} 的迁移步骤");
foreach (var transform in transforms)
{
var context = new ProfileMigrationContext(version, operationMode, currentContent, jsonOptions);
currentContent = transform(context) ?? throw new ProfileMigrationException($"版本 {version} 的迁移步骤返回了 null");
}
}
return new ProfileMigrationResult(currentContent, storedVersion != targetVersion);
}
internal MessagePackProfileMigrationResult ApplyMessagePack(
int storedVersion,
int targetVersion,
Dictionary<string, byte[]> properties,
MessagePackSerializerOptions options)
{
if (storedVersion < 0)
throw new ProfileMigrationException($"配置版本不能小于零:{storedVersion}");
if (storedVersion > targetVersion)
throw new ProfileMigrationException($"配置文件版本 {storedVersion} 高于当前支持版本 {targetVersion},不支持自动降级");
var currentProperties = new Dictionary<string, byte[]>(properties, StringComparer.Ordinal);
for (var version = storedVersion; version < targetVersion; version++)
{
if (!messagePackMigrations.TryGetValue(version, out var transforms) || transforms.Count == 0)
throw new ProfileMigrationException($"缺少从版本 {version} 到版本 {version + 1} 的 MessagePack 迁移步骤");
foreach (var transform in transforms)
transform(new MessagePackProfileMigrationContext(version, currentProperties, options));
}
return new MessagePackProfileMigrationResult(currentProperties, storedVersion != targetVersion);
}
private static string RenameProperty(ProfileMigrationContext context, string oldName, string newName) => context.OperationMode switch
{
ProfileOperationMode.XFEDictionary => RenameXfeDictionaryProperty(context.Content, oldName, newName),
ProfileOperationMode.Json => RenameJsonProperty(context.Content, oldName, newName, context.JsonOptions),
ProfileOperationMode.Xml => RenameXmlProperty(context.Content, oldName, newName),
_ => throw new ProfileMigrationException("Custom 存储模式需要通过 Transform 注册自定义字段重命名逻辑")
};
private static string RenameXfeDictionaryProperty(string content, string oldName, string newName)
{
XFEDictionary source = content;
var hasOldName = source.Any(entry => entry.Header == oldName);
if (hasOldName && source.Any(entry => entry.Header == newName))
throw new ProfileMigrationException($"无法把 XFE 字典字段“{oldName}”重命名为“{newName}”:目标字段已存在");
var destination = new XFEDictionary();
foreach (var entry in source)
destination.Add(entry.Header == oldName ? newName : entry.Header, entry.Content);
return destination.ToString();
}
private static string RenameJsonProperty(string content, string oldName, string newName, JsonSerializerOptions jsonOptions)
{
if (JsonNode.Parse(content, documentOptions: CreateDocumentOptions(jsonOptions)) is not JsonObject root)
throw new ProfileMigrationException("JSON 配置根节点必须是对象才能执行字段重命名");
var candidates = new[]
{
(Old: ApplyNamingPolicy(oldName, jsonOptions.PropertyNamingPolicy), New: ApplyNamingPolicy(newName, jsonOptions.PropertyNamingPolicy)),
(Old: ApplyNamingPolicy("Instance" + oldName, jsonOptions.PropertyNamingPolicy), New: ApplyNamingPolicy("Instance" + newName, jsonOptions.PropertyNamingPolicy))
};
foreach (var candidate in candidates)
{
if (!root.TryGetPropertyValue(candidate.Old, out var value))
continue;
if (root.ContainsKey(candidate.New))
throw new ProfileMigrationException($"无法把 JSON 字段“{candidate.Old}”重命名为“{candidate.New}”:目标字段已存在");
root.Remove(candidate.Old);
root[candidate.New] = value;
break;
}
return root.ToJsonString(jsonOptions);
}
private static string RenameXmlProperty(string content, string oldName, string newName)
{
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new ProfileMigrationException("XML 配置缺少根节点");
var oldNames = new[] { oldName, "Instance" + oldName };
var newNames = new[] { newName, "Instance" + newName };
for (var index = 0; index < oldNames.Length; index++)
{
var element = root.Elements().FirstOrDefault(candidate => candidate.Name.LocalName == oldNames[index]);
if (element is null)
continue;
if (root.Elements().Any(candidate => candidate.Name.LocalName == newNames[index]))
throw new ProfileMigrationException($"无法把 XML 字段“{oldNames[index]}”重命名为“{newNames[index]}”:目标字段已存在");
element.Name = element.Name.Namespace + newNames[index];
break;
}
return document.ToString(SaveOptions.DisableFormatting);
}
private static string ApplyNamingPolicy(string name, JsonNamingPolicy? namingPolicy) => namingPolicy?.ConvertName(name) ?? name;
private static JsonDocumentOptions CreateDocumentOptions(JsonSerializerOptions options) => new()
{
AllowTrailingCommas = options.AllowTrailingCommas,
CommentHandling = options.ReadCommentHandling == JsonCommentHandling.Skip ? JsonCommentHandling.Skip : JsonCommentHandling.Disallow,
MaxDepth = options.MaxDepth
};
}
/// <summary>
/// 配置迁移失败。
/// </summary>
public sealed class ProfileMigrationException : Exception
{
/// <summary>创建配置迁移异常。</summary>
public ProfileMigrationException(string message) : base(message) { }
/// <summary>创建带内部异常的配置迁移异常。</summary>
public ProfileMigrationException(string message, Exception innerException) : base(message, innerException) { }
}
internal readonly record struct ProfileMigrationResult(string Content, bool WasMigrated);
internal readonly record struct MessagePackProfileMigrationResult(Dictionary<string, byte[]> Properties, bool WasMigrated);
internal static class ProfileVersionMetadata
{
private const string VersionPropertyName = "$xfeProfileVersion";
private const string XmlVersionAttributeName = "xfeProfileVersion";
public static (int Version, string Content) ReadAndStrip(ProfileOperationMode operationMode, string content, JsonSerializerOptions jsonOptions) => operationMode switch
{
ProfileOperationMode.XFEDictionary => ReadAndStripXfeDictionary(content),
ProfileOperationMode.Json => ReadAndStripJson(content, jsonOptions),
ProfileOperationMode.Xml => ReadAndStripXml(content),
_ => (0, content)
};
public static string Write(ProfileOperationMode operationMode, string content, int version, JsonSerializerOptions jsonOptions)
{
if (version <= 0)
return content;
return operationMode switch
{
ProfileOperationMode.XFEDictionary => WriteXfeDictionary(content, version),
ProfileOperationMode.Json => WriteJson(content, version, jsonOptions),
ProfileOperationMode.Xml => WriteXml(content, version),
_ => content
};
}
private static (int Version, string Content) ReadAndStripXfeDictionary(string content)
{
XFEDictionary source = content;
var destination = new XFEDictionary();
var version = 0;
foreach (var entry in source)
{
if (entry.Header == VersionPropertyName)
{
if (!int.TryParse(entry.Content, out version))
throw new ProfileMigrationException($"无效的配置版本:{entry.Content}");
continue;
}
destination.Add(entry.Header, entry.Content);
}
return version == 0 && !source.Any(entry => entry.Header == VersionPropertyName)
? (0, content)
: (version, destination.ToString());
}
private static string WriteXfeDictionary(string content, int version)
{
XFEDictionary source = content;
var destination = new XFEDictionary();
destination.Add(VersionPropertyName, version.ToString(System.Globalization.CultureInfo.InvariantCulture));
foreach (var entry in source)
if (entry.Header != VersionPropertyName)
destination.Add(entry.Header, entry.Content);
return destination.ToString();
}
private static (int Version, string Content) ReadAndStripJson(string content, JsonSerializerOptions jsonOptions)
{
if (JsonNode.Parse(content, documentOptions: CreateDocumentOptions(jsonOptions)) is not JsonObject root)
throw new ProfileMigrationException("JSON 配置根节点必须是对象");
var version = 0;
if (!root.TryGetPropertyValue(VersionPropertyName, out var versionNode))
return (0, content);
if (versionNode is null || !versionNode.AsValue().TryGetValue<int>(out version))
throw new ProfileMigrationException("JSON 配置版本不是有效整数");
root.Remove(VersionPropertyName);
return (version, root.ToJsonString(jsonOptions));
}
private static string WriteJson(string content, int version, JsonSerializerOptions jsonOptions)
{
if (JsonNode.Parse(content, documentOptions: CreateDocumentOptions(jsonOptions)) is not JsonObject root)
throw new ProfileMigrationException("JSON 配置根节点必须是对象");
root[VersionPropertyName] = version;
return root.ToJsonString(jsonOptions);
}
private static (int Version, string Content) ReadAndStripXml(string content)
{
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new ProfileMigrationException("XML 配置缺少根节点");
var attribute = root.Attribute(XmlVersionAttributeName);
var version = 0;
if (attribute is null)
return (0, content);
if (!int.TryParse(attribute.Value, out version))
throw new ProfileMigrationException($"无效的 XML 配置版本:{attribute.Value}");
attribute.Remove();
return (version, document.ToString(SaveOptions.DisableFormatting));
}
private static string WriteXml(string content, int version)
{
var document = XDocument.Parse(content, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new ProfileMigrationException("XML 配置缺少根节点");
root.SetAttributeValue(XmlVersionAttributeName, version);
return document.ToString(SaveOptions.DisableFormatting);
}
private static JsonDocumentOptions CreateDocumentOptions(JsonSerializerOptions options) => new()
{
AllowTrailingCommas = options.AllowTrailingCommas,
CommentHandling = options.ReadCommentHandling == JsonCommentHandling.Skip ? JsonCommentHandling.Skip : JsonCommentHandling.Disallow,
MaxDepth = options.MaxDepth
};
}