namespace GFramework.Game.Config; /// /// 提供 YAML 配置文件与 JSON Schema 之间的最小运行时校验能力。 /// 该校验器与当前配置生成器、VS Code 工具支持的 schema 子集保持一致, /// 并通过递归遍历方式覆盖嵌套对象、对象数组、标量数组与深层 enum / 引用约束。 /// internal static class YamlConfigSchemaValidator { /// /// 从磁盘加载并解析一个 JSON Schema 文件。 /// /// Schema 文件路径。 /// 取消令牌。 /// 解析后的 schema 模型。 /// 为空时抛出。 /// 当 schema 文件不存在时抛出。 /// 当 schema 内容不符合当前运行时支持的子集时抛出。 internal static async Task LoadAsync( string schemaPath, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(schemaPath)) { throw new ArgumentException("Schema path cannot be null or whitespace.", nameof(schemaPath)); } if (!File.Exists(schemaPath)) { throw new FileNotFoundException($"Schema file '{schemaPath}' was not found.", schemaPath); } string schemaText; try { schemaText = await File.ReadAllTextAsync(schemaPath, cancellationToken); } catch (Exception exception) { throw new InvalidOperationException($"Failed to read schema file '{schemaPath}'.", exception); } try { using var document = JsonDocument.Parse(schemaText); var root = document.RootElement; var rootNode = ParseNode(schemaPath, "", root, isRoot: true); if (rootNode.NodeType != YamlConfigSchemaPropertyType.Object) { throw new InvalidOperationException( $"Schema file '{schemaPath}' must declare a root object schema."); } var referencedTableNames = new HashSet(StringComparer.Ordinal); CollectReferencedTableNames(rootNode, referencedTableNames); return new YamlConfigSchema(schemaPath, rootNode, referencedTableNames.ToArray()); } catch (JsonException exception) { throw new InvalidOperationException($"Schema file '{schemaPath}' contains invalid JSON.", exception); } } /// /// 使用已解析的 schema 校验 YAML 文本。 /// /// 已解析的 schema 模型。 /// YAML 文件路径,仅用于诊断信息。 /// YAML 文本内容。 /// 当参数为空时抛出。 /// 当 YAML 内容与 schema 不匹配时抛出。 internal static void Validate( YamlConfigSchema schema, string yamlPath, string yamlText) { ValidateAndCollectReferences(schema, yamlPath, yamlText); } /// /// 使用已解析的 schema 校验 YAML 文本,并提取声明过的跨表引用。 /// 该方法让结构校验与引用采集共享同一份 YAML 解析结果,避免加载器重复解析同一文件。 /// /// 已解析的 schema 模型。 /// YAML 文件路径,仅用于诊断信息。 /// YAML 文本内容。 /// 当前 YAML 文件中声明的跨表引用集合。 /// 当参数为空时抛出。 /// 当 YAML 内容与 schema 不匹配时抛出。 internal static IReadOnlyList ValidateAndCollectReferences( YamlConfigSchema schema, string yamlPath, string yamlText) { ArgumentNullException.ThrowIfNull(schema); ArgumentNullException.ThrowIfNull(yamlPath); ArgumentNullException.ThrowIfNull(yamlText); YamlStream yamlStream = new(); try { using var reader = new StringReader(yamlText); yamlStream.Load(reader); } catch (Exception exception) { throw new InvalidOperationException( $"Config file '{yamlPath}' could not be parsed as YAML before schema validation.", exception); } if (yamlStream.Documents.Count != 1) { throw new InvalidOperationException( $"Config file '{yamlPath}' must contain exactly one YAML document."); } var references = new List(); ValidateNode(yamlPath, string.Empty, yamlStream.Documents[0].RootNode, schema.RootNode, references); return references; } /// /// 递归解析 schema 节点,使运行时只保留校验真正需要的最小结构信息。 /// /// Schema 文件路径。 /// 当前节点的逻辑属性路径。 /// Schema JSON 节点。 /// 是否为根节点。 /// 可用于运行时校验的节点模型。 private static YamlConfigSchemaNode ParseNode( string schemaPath, string propertyPath, JsonElement element, bool isRoot = false) { if (!element.TryGetProperty("type", out var typeElement) || typeElement.ValueKind != JsonValueKind.String) { throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' must declare a string 'type'."); } var typeName = typeElement.GetString() ?? string.Empty; var referenceTableName = TryGetReferenceTableName(schemaPath, propertyPath, element); switch (typeName) { case "object": EnsureReferenceKeywordIsSupported(schemaPath, propertyPath, YamlConfigSchemaPropertyType.Object, referenceTableName); return ParseObjectNode(schemaPath, propertyPath, element, isRoot); case "array": return ParseArrayNode(schemaPath, propertyPath, element, referenceTableName); case "integer": return CreateScalarNode(schemaPath, propertyPath, YamlConfigSchemaPropertyType.Integer, element, referenceTableName); case "number": return CreateScalarNode(schemaPath, propertyPath, YamlConfigSchemaPropertyType.Number, element, referenceTableName); case "boolean": return CreateScalarNode(schemaPath, propertyPath, YamlConfigSchemaPropertyType.Boolean, element, referenceTableName); case "string": return CreateScalarNode(schemaPath, propertyPath, YamlConfigSchemaPropertyType.String, element, referenceTableName); default: throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' uses unsupported type '{typeName}'."); } } /// /// 解析对象节点,保留属性字典与必填集合,以便后续递归校验时逐层定位错误。 /// /// Schema 文件路径。 /// 对象属性路径。 /// 对象 schema 节点。 /// 是否为根节点。 /// 对象节点模型。 private static YamlConfigSchemaNode ParseObjectNode( string schemaPath, string propertyPath, JsonElement element, bool isRoot) { if (!element.TryGetProperty("properties", out var propertiesElement) || propertiesElement.ValueKind != JsonValueKind.Object) { var subject = isRoot ? "root schema" : $"object property '{propertyPath}'"; throw new InvalidOperationException( $"The {subject} in schema file '{schemaPath}' must declare an object-valued 'properties' section."); } var requiredProperties = new HashSet(StringComparer.Ordinal); if (element.TryGetProperty("required", out var requiredElement) && requiredElement.ValueKind == JsonValueKind.Array) { foreach (var item in requiredElement.EnumerateArray()) { if (item.ValueKind != JsonValueKind.String) { continue; } var requiredPropertyName = item.GetString(); if (!string.IsNullOrWhiteSpace(requiredPropertyName)) { requiredProperties.Add(requiredPropertyName); } } } var properties = new Dictionary(StringComparer.Ordinal); foreach (var property in propertiesElement.EnumerateObject()) { properties[property.Name] = ParseNode( schemaPath, CombineSchemaPath(propertyPath, property.Name), property.Value); } return new YamlConfigSchemaNode( YamlConfigSchemaPropertyType.Object, properties, requiredProperties, itemNode: null, referenceTableName: null, allowedValues: null, schemaPath); } /// /// 解析数组节点。 /// 当前子集支持标量数组和对象数组,不支持数组嵌套数组。 /// 当数组声明跨表引用时,会把引用语义挂到元素节点上,便于后续逐项校验。 /// /// Schema 文件路径。 /// 数组属性路径。 /// 数组 schema 节点。 /// 声明在数组节点上的目标引用表。 /// 数组节点模型。 private static YamlConfigSchemaNode ParseArrayNode( string schemaPath, string propertyPath, JsonElement element, string? referenceTableName) { if (!element.TryGetProperty("items", out var itemsElement) || itemsElement.ValueKind != JsonValueKind.Object) { throw new InvalidOperationException( $"Array property '{propertyPath}' in schema file '{schemaPath}' must declare an object-valued 'items' schema."); } var itemNode = ParseNode(schemaPath, $"{propertyPath}[]", itemsElement); if (!string.IsNullOrWhiteSpace(referenceTableName)) { if (itemNode.NodeType != YamlConfigSchemaPropertyType.String && itemNode.NodeType != YamlConfigSchemaPropertyType.Integer) { throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' uses 'x-gframework-ref-table', but only string, integer, or arrays of those scalar types can declare cross-table references."); } itemNode = itemNode.WithReferenceTable(referenceTableName); } if (itemNode.NodeType == YamlConfigSchemaPropertyType.Array) { throw new InvalidOperationException( $"Array property '{propertyPath}' in schema file '{schemaPath}' uses unsupported nested array items."); } return new YamlConfigSchemaNode( YamlConfigSchemaPropertyType.Array, properties: null, requiredProperties: null, itemNode, referenceTableName: null, allowedValues: null, schemaPath); } /// /// 创建标量节点,并在解析阶段就完成 enum 与引用约束的兼容性检查。 /// /// Schema 文件路径。 /// 标量属性路径。 /// 标量类型。 /// 标量 schema 节点。 /// 目标引用表名称。 /// 标量节点模型。 private static YamlConfigSchemaNode CreateScalarNode( string schemaPath, string propertyPath, YamlConfigSchemaPropertyType nodeType, JsonElement element, string? referenceTableName) { EnsureReferenceKeywordIsSupported(schemaPath, propertyPath, nodeType, referenceTableName); return new YamlConfigSchemaNode( nodeType, properties: null, requiredProperties: null, itemNode: null, referenceTableName, ParseEnumValues(schemaPath, propertyPath, element, nodeType, "enum"), schemaPath); } /// /// 递归校验 YAML 节点。 /// 每层都带上逻辑字段路径,这样深层对象与数组元素的错误也能直接定位。 /// /// YAML 文件路径。 /// 当前字段路径;根节点时为空。 /// 实际 YAML 节点。 /// 对应的 schema 节点。 /// 已收集的跨表引用。 private static void ValidateNode( string yamlPath, string displayPath, YamlNode node, YamlConfigSchemaNode schemaNode, ICollection references) { switch (schemaNode.NodeType) { case YamlConfigSchemaPropertyType.Object: ValidateObjectNode(yamlPath, displayPath, node, schemaNode, references); return; case YamlConfigSchemaPropertyType.Array: ValidateArrayNode(yamlPath, displayPath, node, schemaNode, references); return; case YamlConfigSchemaPropertyType.Integer: case YamlConfigSchemaPropertyType.Number: case YamlConfigSchemaPropertyType.Boolean: case YamlConfigSchemaPropertyType.String: ValidateScalarNode(yamlPath, displayPath, node, schemaNode, references); return; default: throw new InvalidOperationException( $"Schema node '{displayPath}' uses unsupported runtime node type '{schemaNode.NodeType}'."); } } /// /// 校验对象节点,同时处理重复字段、未知字段和深层必填字段。 /// /// YAML 文件路径。 /// 当前对象的逻辑字段路径。 /// 实际 YAML 节点。 /// 对象 schema 节点。 /// 已收集的跨表引用。 private static void ValidateObjectNode( string yamlPath, string displayPath, YamlNode node, YamlConfigSchemaNode schemaNode, ICollection references) { if (node is not YamlMappingNode mappingNode) { var subject = displayPath.Length == 0 ? "Root object" : $"Property '{displayPath}'"; throw new InvalidOperationException( $"{subject} in config file '{yamlPath}' must be an object."); } var seenProperties = new HashSet(StringComparer.Ordinal); foreach (var entry in mappingNode.Children) { if (entry.Key is not YamlScalarNode keyNode || string.IsNullOrWhiteSpace(keyNode.Value)) { var subject = displayPath.Length == 0 ? "root object" : $"object property '{displayPath}'"; throw new InvalidOperationException( $"Config file '{yamlPath}' contains a non-scalar or empty property name inside {subject}."); } var propertyName = keyNode.Value; var propertyPath = CombineDisplayPath(displayPath, propertyName); if (!seenProperties.Add(propertyName)) { throw new InvalidOperationException( $"Config file '{yamlPath}' contains duplicate property '{propertyPath}'."); } if (schemaNode.Properties is null || !schemaNode.Properties.TryGetValue(propertyName, out var propertySchema)) { throw new InvalidOperationException( $"Config file '{yamlPath}' contains unknown property '{propertyPath}' that is not declared in schema '{schemaNode.SchemaPathHint}'."); } ValidateNode(yamlPath, propertyPath, entry.Value, propertySchema, references); } if (schemaNode.RequiredProperties is null) { return; } foreach (var requiredProperty in schemaNode.RequiredProperties) { if (seenProperties.Contains(requiredProperty)) { continue; } throw new InvalidOperationException( $"Config file '{yamlPath}' is missing required property '{CombineDisplayPath(displayPath, requiredProperty)}' defined by schema '{schemaNode.SchemaPathHint}'."); } } /// /// 校验数组节点,并递归验证每个元素。 /// /// YAML 文件路径。 /// 数组字段路径。 /// 实际 YAML 节点。 /// 数组 schema 节点。 /// 已收集的跨表引用。 private static void ValidateArrayNode( string yamlPath, string displayPath, YamlNode node, YamlConfigSchemaNode schemaNode, ICollection references) { if (node is not YamlSequenceNode sequenceNode) { throw new InvalidOperationException( $"Property '{displayPath}' in config file '{yamlPath}' must be an array."); } if (schemaNode.ItemNode is null) { throw new InvalidOperationException( $"Schema node '{displayPath}' is missing array item information."); } for (var itemIndex = 0; itemIndex < sequenceNode.Children.Count; itemIndex++) { ValidateNode( yamlPath, $"{displayPath}[{itemIndex}]", sequenceNode.Children[itemIndex], schemaNode.ItemNode, references); } } /// /// 校验标量节点,并在值有效时收集跨表引用。 /// /// YAML 文件路径。 /// 标量字段路径。 /// 实际 YAML 节点。 /// 标量 schema 节点。 /// 已收集的跨表引用。 private static void ValidateScalarNode( string yamlPath, string displayPath, YamlNode node, YamlConfigSchemaNode schemaNode, ICollection references) { if (node is not YamlScalarNode scalarNode) { throw new InvalidOperationException( $"Property '{displayPath}' in config file '{yamlPath}' must be a scalar value of type '{GetTypeName(schemaNode.NodeType)}'."); } var value = scalarNode.Value; if (value is null) { throw new InvalidOperationException( $"Property '{displayPath}' in config file '{yamlPath}' cannot be null when schema type is '{GetTypeName(schemaNode.NodeType)}'."); } var tag = scalarNode.Tag.ToString(); var isValid = schemaNode.NodeType switch { YamlConfigSchemaPropertyType.String => IsStringScalar(tag), YamlConfigSchemaPropertyType.Integer => long.TryParse( value, NumberStyles.Integer, CultureInfo.InvariantCulture, out _), YamlConfigSchemaPropertyType.Number => double.TryParse( value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out _), YamlConfigSchemaPropertyType.Boolean => bool.TryParse(value, out _), _ => false }; if (!isValid) { throw new InvalidOperationException( $"Property '{displayPath}' in config file '{yamlPath}' must be of type '{GetTypeName(schemaNode.NodeType)}', but the current YAML scalar value is '{value}'."); } var normalizedValue = NormalizeScalarValue(schemaNode.NodeType, value); if (schemaNode.AllowedValues is { Count: > 0 } && !schemaNode.AllowedValues.Contains(normalizedValue, StringComparer.Ordinal)) { throw new InvalidOperationException( $"Property '{displayPath}' in config file '{yamlPath}' must be one of [{string.Join(", ", schemaNode.AllowedValues)}], but the current YAML scalar value is '{value}'."); } if (schemaNode.ReferenceTableName != null) { references.Add( new YamlConfigReferenceUsage( yamlPath, displayPath, normalizedValue, schemaNode.ReferenceTableName, schemaNode.NodeType)); } } /// /// 解析 enum,并在读取阶段验证枚举值与字段类型的兼容性。 /// /// Schema 文件路径。 /// 字段路径。 /// Schema 节点。 /// 期望的标量类型。 /// 当前读取的关键字名称。 /// 归一化后的枚举值集合;未声明时返回空。 private static IReadOnlyCollection? ParseEnumValues( string schemaPath, string propertyPath, JsonElement element, YamlConfigSchemaPropertyType expectedType, string keywordName) { if (!element.TryGetProperty("enum", out var enumElement)) { return null; } if (enumElement.ValueKind != JsonValueKind.Array) { throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' must declare '{keywordName}' as an array."); } var allowedValues = new List(); foreach (var item in enumElement.EnumerateArray()) { allowedValues.Add(NormalizeEnumValue(schemaPath, propertyPath, keywordName, expectedType, item)); } return allowedValues; } /// /// 解析跨表引用目标表名称。 /// /// Schema 文件路径。 /// 字段路径。 /// Schema 节点。 /// 目标表名称;未声明时返回空。 private static string? TryGetReferenceTableName( string schemaPath, string propertyPath, JsonElement element) { if (!element.TryGetProperty("x-gframework-ref-table", out var referenceTableElement)) { return null; } if (referenceTableElement.ValueKind != JsonValueKind.String) { throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' must declare a string 'x-gframework-ref-table' value."); } var referenceTableName = referenceTableElement.GetString(); if (string.IsNullOrWhiteSpace(referenceTableName)) { throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' must declare a non-empty 'x-gframework-ref-table' value."); } return referenceTableName; } /// /// 验证哪些 schema 类型允许声明跨表引用。 /// /// Schema 文件路径。 /// 字段路径。 /// 字段类型。 /// 目标表名称。 private static void EnsureReferenceKeywordIsSupported( string schemaPath, string propertyPath, YamlConfigSchemaPropertyType propertyType, string? referenceTableName) { if (referenceTableName == null) { return; } if (propertyType == YamlConfigSchemaPropertyType.String || propertyType == YamlConfigSchemaPropertyType.Integer) { return; } throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' uses 'x-gframework-ref-table', but only string, integer, or arrays of those scalar types can declare cross-table references."); } /// /// 递归收集 schema 中声明的目标表名称。 /// /// 当前 schema 节点。 /// 输出集合。 private static void CollectReferencedTableNames( YamlConfigSchemaNode node, ISet referencedTableNames) { if (node.ReferenceTableName != null) { referencedTableNames.Add(node.ReferenceTableName); } if (node.Properties is not null) { foreach (var property in node.Properties.Values) { CollectReferencedTableNames(property, referencedTableNames); } } if (node.ItemNode is not null) { CollectReferencedTableNames(node.ItemNode, referencedTableNames); } } /// /// 将 schema 中的 enum 单值归一化到运行时比较字符串。 /// /// Schema 文件路径。 /// 字段路径。 /// 关键字名称。 /// 期望的标量类型。 /// 当前枚举值节点。 /// 归一化后的字符串值。 private static string NormalizeEnumValue( string schemaPath, string propertyPath, string keywordName, YamlConfigSchemaPropertyType expectedType, JsonElement item) { try { return expectedType switch { YamlConfigSchemaPropertyType.String when item.ValueKind == JsonValueKind.String => item.GetString() ?? string.Empty, YamlConfigSchemaPropertyType.Integer when item.ValueKind == JsonValueKind.Number => item.GetInt64().ToString(CultureInfo.InvariantCulture), YamlConfigSchemaPropertyType.Number when item.ValueKind == JsonValueKind.Number => item.GetDouble().ToString(CultureInfo.InvariantCulture), YamlConfigSchemaPropertyType.Boolean when item.ValueKind == JsonValueKind.True => bool.TrueString.ToLowerInvariant(), YamlConfigSchemaPropertyType.Boolean when item.ValueKind == JsonValueKind.False => bool.FalseString.ToLowerInvariant(), _ => throw new InvalidOperationException() }; } catch { throw new InvalidOperationException( $"Property '{propertyPath}' in schema file '{schemaPath}' contains a '{keywordName}' value that is incompatible with schema type '{GetTypeName(expectedType)}'."); } } /// /// 将 YAML 标量值规范化成运行时比较格式。 /// /// 期望的标量类型。 /// 原始字符串值。 /// 归一化后的字符串。 private static string NormalizeScalarValue(YamlConfigSchemaPropertyType expectedType, string value) { return expectedType switch { YamlConfigSchemaPropertyType.String => value, YamlConfigSchemaPropertyType.Integer => long.Parse( value, NumberStyles.Integer, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture), YamlConfigSchemaPropertyType.Number => double.Parse( value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture), YamlConfigSchemaPropertyType.Boolean => bool.Parse(value).ToString().ToLowerInvariant(), _ => value }; } /// /// 获取 schema 类型的可读名称,用于错误信息。 /// /// Schema 节点类型。 /// 可读类型名。 private static string GetTypeName(YamlConfigSchemaPropertyType type) { return type switch { YamlConfigSchemaPropertyType.Integer => "integer", YamlConfigSchemaPropertyType.Number => "number", YamlConfigSchemaPropertyType.Boolean => "boolean", YamlConfigSchemaPropertyType.String => "string", YamlConfigSchemaPropertyType.Array => "array", YamlConfigSchemaPropertyType.Object => "object", _ => type.ToString() }; } /// /// 组合 schema 中的逻辑路径,便于诊断时指出深层字段。 /// /// 父级路径。 /// 当前属性名。 /// 组合后的路径。 private static string CombineSchemaPath(string parentPath, string propertyName) { return parentPath == "" ? propertyName : $"{parentPath}.{propertyName}"; } /// /// 组合 YAML 诊断展示路径。 /// /// 父级路径。 /// 当前属性名。 /// 组合后的路径。 private static string CombineDisplayPath(string parentPath, string propertyName) { return string.IsNullOrWhiteSpace(parentPath) ? propertyName : $"{parentPath}.{propertyName}"; } /// /// 判断当前标量是否应按字符串处理。 /// 这里显式排除 YAML 的数字、布尔和 null 标签,避免未加引号的值被当成字符串混入运行时。 /// /// YAML 标量标签。 /// 是否为字符串标量。 private static bool IsStringScalar(string tag) { if (string.IsNullOrWhiteSpace(tag)) { return true; } return !string.Equals(tag, "tag:yaml.org,2002:int", StringComparison.Ordinal) && !string.Equals(tag, "tag:yaml.org,2002:float", StringComparison.Ordinal) && !string.Equals(tag, "tag:yaml.org,2002:bool", StringComparison.Ordinal) && !string.Equals(tag, "tag:yaml.org,2002:null", StringComparison.Ordinal); } } /// /// 表示已解析并可用于运行时校验的 JSON Schema。 /// 该模型保留根节点与引用依赖集合,避免运行时引入完整 schema 引擎。 /// internal sealed class YamlConfigSchema { /// /// 初始化一个可用于运行时校验的 schema 模型。 /// /// Schema 文件路径。 /// 根节点模型。 /// Schema 声明的目标引用表名称集合。 public YamlConfigSchema( string schemaPath, YamlConfigSchemaNode rootNode, IReadOnlyCollection referencedTableNames) { ArgumentNullException.ThrowIfNull(schemaPath); ArgumentNullException.ThrowIfNull(rootNode); ArgumentNullException.ThrowIfNull(referencedTableNames); SchemaPath = schemaPath; RootNode = rootNode; ReferencedTableNames = referencedTableNames; } /// /// 获取 schema 文件路径。 /// public string SchemaPath { get; } /// /// 获取根节点模型。 /// public YamlConfigSchemaNode RootNode { get; } /// /// 获取 schema 声明的目标引用表名称集合。 /// 该信息用于热重载时推导受影响的依赖表闭包。 /// public IReadOnlyCollection ReferencedTableNames { get; } } /// /// 表示单个 schema 节点的最小运行时描述。 /// 同一个模型同时覆盖对象、数组和标量,便于递归校验逻辑只依赖一种树结构。 /// internal sealed class YamlConfigSchemaNode { /// /// 初始化一个 schema 节点描述。 /// /// 节点类型。 /// 对象属性集合。 /// 对象必填属性集合。 /// 数组元素节点。 /// 目标引用表名称。 /// 标量允许值集合。 /// 用于错误信息的 schema 文件路径提示。 public YamlConfigSchemaNode( YamlConfigSchemaPropertyType nodeType, IReadOnlyDictionary? properties, IReadOnlyCollection? requiredProperties, YamlConfigSchemaNode? itemNode, string? referenceTableName, IReadOnlyCollection? allowedValues, string schemaPathHint) { NodeType = nodeType; Properties = properties; RequiredProperties = requiredProperties; ItemNode = itemNode; ReferenceTableName = referenceTableName; AllowedValues = allowedValues; SchemaPathHint = schemaPathHint; } /// /// 获取节点类型。 /// public YamlConfigSchemaPropertyType NodeType { get; } /// /// 获取对象属性集合;非对象节点时返回空。 /// public IReadOnlyDictionary? Properties { get; } /// /// 获取对象必填属性集合;非对象节点时返回空。 /// public IReadOnlyCollection? RequiredProperties { get; } /// /// 获取数组元素节点;非数组节点时返回空。 /// public YamlConfigSchemaNode? ItemNode { get; } /// /// 获取目标引用表名称;未声明跨表引用时返回空。 /// public string? ReferenceTableName { get; } /// /// 获取标量允许值集合;未声明 enum 时返回空。 /// public IReadOnlyCollection? AllowedValues { get; } /// /// 获取用于诊断显示的 schema 路径提示。 /// 当前节点本身不记录独立路径,因此对象校验会回退到所属根 schema 路径。 /// public string SchemaPathHint { get; } /// /// 基于当前节点复制一个只替换引用表名称的新节点。 /// 该方法用于把数组级别的 ref-table 语义挂接到元素节点上。 /// /// 新的目标引用表名称。 /// 复制后的节点。 public YamlConfigSchemaNode WithReferenceTable(string referenceTableName) { return new YamlConfigSchemaNode( NodeType, Properties, RequiredProperties, ItemNode, referenceTableName, AllowedValues, SchemaPathHint); } } /// /// 表示单个 YAML 文件中提取出的跨表引用。 /// 该模型保留源文件、字段路径和目标表等诊断信息,以便加载器在批量校验失败时给出可定位的错误。 /// internal sealed class YamlConfigReferenceUsage { /// /// 初始化一个跨表引用使用记录。 /// /// 源 YAML 文件路径。 /// 声明引用的字段路径。 /// YAML 中的原始标量值。 /// 目标配置表名称。 /// 引用值的 schema 标量类型。 public YamlConfigReferenceUsage( string yamlPath, string propertyPath, string rawValue, string referencedTableName, YamlConfigSchemaPropertyType valueType) { ArgumentNullException.ThrowIfNull(yamlPath); ArgumentNullException.ThrowIfNull(propertyPath); ArgumentNullException.ThrowIfNull(rawValue); ArgumentNullException.ThrowIfNull(referencedTableName); YamlPath = yamlPath; PropertyPath = propertyPath; RawValue = rawValue; ReferencedTableName = referencedTableName; ValueType = valueType; } /// /// 获取源 YAML 文件路径。 /// public string YamlPath { get; } /// /// 获取声明引用的字段路径。 /// public string PropertyPath { get; } /// /// 获取 YAML 中的原始标量值。 /// public string RawValue { get; } /// /// 获取目标配置表名称。 /// public string ReferencedTableName { get; } /// /// 获取引用值的 schema 标量类型。 /// public YamlConfigSchemaPropertyType ValueType { get; } /// /// 获取便于诊断显示的字段路径。 /// public string DisplayPath => PropertyPath; } /// /// 表示当前运行时 schema 校验器支持的属性类型。 /// internal enum YamlConfigSchemaPropertyType { /// /// 对象类型。 /// Object, /// /// 整数类型。 /// Integer, /// /// 数值类型。 /// Number, /// /// 布尔类型。 /// Boolean, /// /// 字符串类型。 /// String, /// /// 数组类型。 /// Array }