mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-13 06:04:30 +08:00
refactor(game): 拆分对象 schema 关键字校验方法
- 重构 dependentRequired 与 dependentSchemas 的单项解析流程 - 重构 allOf 与条件 schema 的分支解析流程 - 优化 object-focused 内联 schema 的 properties 与 required 校验拆分
This commit is contained in:
parent
e1c1eb1123
commit
1395b84439
@ -104,67 +104,12 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
var dependentRequired = new Dictionary<string, IReadOnlyList<string>>(StringComparer.Ordinal);
|
var dependentRequired = new Dictionary<string, IReadOnlyList<string>>(StringComparer.Ordinal);
|
||||||
foreach (var dependency in dependentRequiredElement.EnumerateObject())
|
foreach (var dependency in dependentRequiredElement.EnumerateObject())
|
||||||
{
|
{
|
||||||
if (!properties.ContainsKey(dependency.Name))
|
var dependencyTargets = ParseDependentRequiredConstraint(
|
||||||
{
|
tableName,
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
schemaPath,
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
propertyPath,
|
||||||
tableName,
|
dependency,
|
||||||
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' declares 'dependentRequired' for undeclared property '{dependency.Name}'.",
|
properties);
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dependency.Value.ValueKind != JsonValueKind.Array)
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'dependentRequired' as an array of sibling property names.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
var dependencyTargets = new List<string>();
|
|
||||||
var seenDependencyTargets = new HashSet<string>(StringComparer.Ordinal);
|
|
||||||
foreach (var dependencyTarget in dependency.Value.EnumerateArray())
|
|
||||||
{
|
|
||||||
if (dependencyTarget.ValueKind != JsonValueKind.String)
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'dependentRequired' entries as strings.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
var dependencyTargetName = dependencyTarget.GetString();
|
|
||||||
if (string.IsNullOrWhiteSpace(dependencyTargetName))
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' cannot declare blank 'dependentRequired' entries.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!properties.ContainsKey(dependencyTargetName))
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' declares 'dependentRequired' target '{dependencyTargetName}' that is not declared in the same object schema.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seenDependencyTargets.Add(dependencyTargetName))
|
|
||||||
{
|
|
||||||
dependencyTargets.Add(dependencyTargetName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dependencyTargets.Count > 0)
|
if (dependencyTargets.Count > 0)
|
||||||
{
|
{
|
||||||
dependentRequired[dependency.Name] = dependencyTargets;
|
dependentRequired[dependency.Name] = dependencyTargets;
|
||||||
@ -176,6 +121,116 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
: dependentRequired;
|
: dependentRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析单个 <c>dependentRequired</c> 触发字段的依赖目标列表。
|
||||||
|
/// 触发字段和目标字段必须都来自父对象已声明属性;重复目标会被去重以保持运行时约束稳定。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">对象字段路径。</param>
|
||||||
|
/// <param name="dependency">当前触发字段声明。</param>
|
||||||
|
/// <param name="properties">当前对象已声明的属性集合。</param>
|
||||||
|
/// <returns>去重后的依赖目标列表。</returns>
|
||||||
|
private static IReadOnlyList<string> ParseDependentRequiredConstraint(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
JsonProperty dependency,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
if (!properties.ContainsKey(dependency.Name))
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' declares 'dependentRequired' for undeclared property '{dependency.Name}'.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dependency.Value.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'dependentRequired' as an array of sibling property names.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dependencyTargets = new List<string>();
|
||||||
|
var seenDependencyTargets = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var dependencyTarget in dependency.Value.EnumerateArray())
|
||||||
|
{
|
||||||
|
var dependencyTargetName = ParseDependentRequiredTargetName(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
propertyPath,
|
||||||
|
dependency.Name,
|
||||||
|
dependencyTarget,
|
||||||
|
properties);
|
||||||
|
if (seenDependencyTargets.Add(dependencyTargetName))
|
||||||
|
{
|
||||||
|
dependencyTargets.Add(dependencyTargetName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencyTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取并校验 <c>dependentRequired</c> 的单个目标字段名。
|
||||||
|
/// 目标必须是非空字符串并已在同一个对象 schema 中声明,避免依赖关系指向不可满足字段。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">对象字段路径。</param>
|
||||||
|
/// <param name="dependencyName">当前触发字段名称。</param>
|
||||||
|
/// <param name="dependencyTarget">当前依赖目标节点。</param>
|
||||||
|
/// <param name="properties">当前对象已声明的属性集合。</param>
|
||||||
|
/// <returns>已校验的依赖目标字段名。</returns>
|
||||||
|
private static string ParseDependentRequiredTargetName(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
string dependencyName,
|
||||||
|
JsonElement dependencyTarget,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
if (dependencyTarget.ValueKind != JsonValueKind.String)
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"Property '{dependencyName}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'dependentRequired' entries as strings.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dependencyTargetName = dependencyTarget.GetString();
|
||||||
|
if (string.IsNullOrWhiteSpace(dependencyTargetName))
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"Property '{dependencyName}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' cannot declare blank 'dependentRequired' entries.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.ContainsKey(dependencyTargetName))
|
||||||
|
{
|
||||||
|
return dependencyTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' declares 'dependentRequired' target '{dependencyTargetName}' that is not declared in the same object schema.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析对象节点声明的 <c>dependentSchemas</c> 条件 schema。
|
/// 解析对象节点声明的 <c>dependentSchemas</c> 条件 schema。
|
||||||
/// 当前实现把它作为“当触发字段出现时,当前对象还必须额外满足一段内联 schema”来解释,
|
/// 当前实现把它作为“当触发字段出现时,当前对象还必须额外满足一段内联 schema”来解释,
|
||||||
@ -212,43 +267,12 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
var dependentSchemas = new Dictionary<string, YamlConfigSchemaNode>(StringComparer.Ordinal);
|
var dependentSchemas = new Dictionary<string, YamlConfigSchemaNode>(StringComparer.Ordinal);
|
||||||
foreach (var dependency in dependentSchemasElement.EnumerateObject())
|
foreach (var dependency in dependentSchemasElement.EnumerateObject())
|
||||||
{
|
{
|
||||||
if (!properties.ContainsKey(dependency.Name))
|
dependentSchemas[dependency.Name] = ParseDependentSchemaConstraint(
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' declares 'dependentSchemas' for undeclared property '{dependency.Name}'.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dependency.Value.ValueKind != JsonValueKind.Object)
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'dependentSchemas' as an object-valued schema.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
var dependencySchemaPath = BuildNestedSchemaPath(propertyPath, $"dependentSchemas:{dependency.Name}");
|
|
||||||
var dependencySchemaNode = ParseNode(
|
|
||||||
tableName,
|
tableName,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
dependencySchemaPath,
|
propertyPath,
|
||||||
dependency.Value);
|
dependency,
|
||||||
if (dependencySchemaNode.NodeType != YamlConfigSchemaPropertyType.Object)
|
properties);
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare an object-typed 'dependentSchemas' schema.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(dependencySchemaPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
dependentSchemas[dependency.Name] = dependencySchemaNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dependentSchemas.Count == 0
|
return dependentSchemas.Count == 0
|
||||||
@ -256,6 +280,62 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
: dependentSchemas;
|
: dependentSchemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析单个 <c>dependentSchemas</c> 触发字段关联的 object-typed schema。
|
||||||
|
/// 触发字段必须属于当前对象,关联 schema 继续通过通用节点解析流程获得完整约束模型。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">对象字段路径。</param>
|
||||||
|
/// <param name="dependency">当前触发字段声明。</param>
|
||||||
|
/// <param name="properties">当前对象已声明的属性集合。</param>
|
||||||
|
/// <returns>解析后的 object-typed 条件 schema。</returns>
|
||||||
|
private static YamlConfigSchemaNode ParseDependentSchemaConstraint(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
JsonProperty dependency,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
if (!properties.ContainsKey(dependency.Name))
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' declares 'dependentSchemas' for undeclared property '{dependency.Name}'.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dependency.Value.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'dependentSchemas' as an object-valued schema.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dependencySchemaPath = BuildNestedSchemaPath(propertyPath, $"dependentSchemas:{dependency.Name}");
|
||||||
|
var dependencySchemaNode = ParseNode(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
dependencySchemaPath,
|
||||||
|
dependency.Value);
|
||||||
|
if (dependencySchemaNode.NodeType == YamlConfigSchemaPropertyType.Object)
|
||||||
|
{
|
||||||
|
return dependencySchemaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"Property '{dependency.Name}' in {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare an object-typed 'dependentSchemas' schema.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(dependencySchemaPath));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析对象节点声明的 <c>allOf</c> 组合约束。
|
/// 解析对象节点声明的 <c>allOf</c> 组合约束。
|
||||||
/// 当前实现仅接受 object-typed 内联 schema,并把每个条目当成 focused constraint block
|
/// 当前实现仅接受 object-typed 内联 schema,并把每个条目当成 focused constraint block
|
||||||
@ -293,40 +373,13 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
var allOfIndex = 0;
|
var allOfIndex = 0;
|
||||||
foreach (var allOfSchemaElement in allOfElement.EnumerateArray())
|
foreach (var allOfSchemaElement in allOfElement.EnumerateArray())
|
||||||
{
|
{
|
||||||
if (allOfSchemaElement.ValueKind != JsonValueKind.Object)
|
var allOfSchemaNode = ParseAllOfSchemaConstraint(
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare 'allOf' entries as object-valued schemas.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
var allOfSchemaPath = BuildNestedSchemaPath(propertyPath, $"allOf[{allOfIndex.ToString(CultureInfo.InvariantCulture)}]");
|
|
||||||
ValidateInlineObjectSchemaTargetsAgainstParentObject(
|
|
||||||
tableName,
|
tableName,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
propertyPath,
|
propertyPath,
|
||||||
allOfSchemaPath,
|
|
||||||
$"Entry #{(allOfIndex + 1).ToString(CultureInfo.InvariantCulture)} in 'allOf'",
|
|
||||||
allOfSchemaElement,
|
allOfSchemaElement,
|
||||||
|
allOfIndex,
|
||||||
properties);
|
properties);
|
||||||
var allOfSchemaNode = ParseNode(
|
|
||||||
tableName,
|
|
||||||
schemaPath,
|
|
||||||
allOfSchemaPath,
|
|
||||||
allOfSchemaElement);
|
|
||||||
if (allOfSchemaNode.NodeType != YamlConfigSchemaPropertyType.Object)
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"Entry #{(allOfIndex + 1).ToString(CultureInfo.InvariantCulture)} in 'allOf' for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare an object-typed schema.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(allOfSchemaPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
allOfSchemas.Add(allOfSchemaNode);
|
allOfSchemas.Add(allOfSchemaNode);
|
||||||
allOfIndex++;
|
allOfIndex++;
|
||||||
}
|
}
|
||||||
@ -336,6 +389,64 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
: allOfSchemas;
|
: allOfSchemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 <c>allOf</c> 中的单个 object-focused schema 条目。
|
||||||
|
/// 每个条目只允许约束父对象已声明的字段,并且必须保持 object-typed 语义。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">父对象路径。</param>
|
||||||
|
/// <param name="allOfSchemaElement">当前 allOf 条目。</param>
|
||||||
|
/// <param name="allOfIndex">当前 allOf 条目的零基索引。</param>
|
||||||
|
/// <param name="properties">父对象已声明的属性集合。</param>
|
||||||
|
/// <returns>解析后的 object-typed schema。</returns>
|
||||||
|
private static YamlConfigSchemaNode ParseAllOfSchemaConstraint(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
JsonElement allOfSchemaElement,
|
||||||
|
int allOfIndex,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
if (allOfSchemaElement.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare 'allOf' entries as object-valued schemas.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
var allOfEntryLabel = $"Entry #{(allOfIndex + 1).ToString(CultureInfo.InvariantCulture)} in 'allOf'";
|
||||||
|
var allOfSchemaPath = BuildNestedSchemaPath(propertyPath, $"allOf[{allOfIndex.ToString(CultureInfo.InvariantCulture)}]");
|
||||||
|
ValidateInlineObjectSchemaTargetsAgainstParentObject(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
propertyPath,
|
||||||
|
allOfSchemaPath,
|
||||||
|
allOfEntryLabel,
|
||||||
|
allOfSchemaElement,
|
||||||
|
properties);
|
||||||
|
|
||||||
|
var allOfSchemaNode = ParseNode(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
allOfSchemaPath,
|
||||||
|
allOfSchemaElement);
|
||||||
|
if (allOfSchemaNode.NodeType == YamlConfigSchemaPropertyType.Object)
|
||||||
|
{
|
||||||
|
return allOfSchemaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{allOfEntryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare an object-typed schema.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(allOfSchemaPath));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析对象节点声明的 object-focused <c>if</c> / <c>then</c> / <c>else</c> 条件约束。
|
/// 解析对象节点声明的 object-focused <c>if</c> / <c>then</c> / <c>else</c> 条件约束。
|
||||||
/// 当前共享子集要求三段内联 schema 都保持 object-typed focused block 语义,
|
/// 当前共享子集要求三段内联 schema 都保持 object-typed focused block 语义,
|
||||||
@ -362,25 +473,7 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasIf)
|
ValidateConditionalSchemaKeywordPresence(tableName, schemaPath, propertyPath, hasIf, hasThen, hasElse);
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare 'if' when using 'then' or 'else'.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasThen && !hasElse)
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare at least one of 'then' or 'else' when using 'if'.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(propertyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
var ifSchemaPath = BuildNestedSchemaPath(propertyPath, "if");
|
var ifSchemaPath = BuildNestedSchemaPath(propertyPath, "if");
|
||||||
var ifSchemaNode = ParseConditionalObjectSchema(
|
var ifSchemaNode = ParseConditionalObjectSchema(
|
||||||
@ -391,31 +484,100 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
"if",
|
"if",
|
||||||
ifElement,
|
ifElement,
|
||||||
properties);
|
properties);
|
||||||
|
var thenSchemaNode = ParseOptionalConditionalObjectSchema(
|
||||||
var thenSchemaNode = hasThen
|
tableName,
|
||||||
? ParseConditionalObjectSchema(
|
schemaPath,
|
||||||
tableName,
|
propertyPath,
|
||||||
schemaPath,
|
"then",
|
||||||
propertyPath,
|
hasThen,
|
||||||
BuildNestedSchemaPath(propertyPath, "then"),
|
thenElement,
|
||||||
"then",
|
properties);
|
||||||
thenElement,
|
var elseSchemaNode = ParseOptionalConditionalObjectSchema(
|
||||||
properties)
|
tableName,
|
||||||
: null;
|
schemaPath,
|
||||||
var elseSchemaNode = hasElse
|
propertyPath,
|
||||||
? ParseConditionalObjectSchema(
|
"else",
|
||||||
tableName,
|
hasElse,
|
||||||
schemaPath,
|
elseElement,
|
||||||
propertyPath,
|
properties);
|
||||||
BuildNestedSchemaPath(propertyPath, "else"),
|
|
||||||
"else",
|
|
||||||
elseElement,
|
|
||||||
properties)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return new YamlConfigConditionalSchemas(ifSchemaNode, thenSchemaNode, elseSchemaNode);
|
return new YamlConfigConditionalSchemas(ifSchemaNode, thenSchemaNode, elseSchemaNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验 object-focused 条件关键字的组合关系。
|
||||||
|
/// <c>then</c> 与 <c>else</c> 只能跟随 <c>if</c>,而单独的 <c>if</c> 没有可执行分支。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">对象字段路径。</param>
|
||||||
|
/// <param name="hasIf">是否声明 if。</param>
|
||||||
|
/// <param name="hasThen">是否声明 then。</param>
|
||||||
|
/// <param name="hasElse">是否声明 else。</param>
|
||||||
|
private static void ValidateConditionalSchemaKeywordPresence(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
bool hasIf,
|
||||||
|
bool hasThen,
|
||||||
|
bool hasElse)
|
||||||
|
{
|
||||||
|
if (!hasIf)
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare 'if' when using 'then' or 'else'.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasThen || hasElse)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{DescribeObjectSchemaTarget(propertyPath)} in schema file '{schemaPath}' must declare at least one of 'then' or 'else' when using 'if'.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(propertyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析可选的 <c>then</c> 或 <c>else</c> 条件分支。
|
||||||
|
/// 未声明的分支保留为空,声明的分支必须通过 object-focused schema 校验。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">对象字段路径。</param>
|
||||||
|
/// <param name="keywordName">条件关键字名称。</param>
|
||||||
|
/// <param name="hasKeyword">是否声明该条件关键字。</param>
|
||||||
|
/// <param name="keywordElement">条件关键字对应的 schema 节点。</param>
|
||||||
|
/// <param name="properties">父对象已声明的属性集合。</param>
|
||||||
|
/// <returns>解析后的条件分支;未声明时返回空。</returns>
|
||||||
|
private static YamlConfigSchemaNode? ParseOptionalConditionalObjectSchema(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
string keywordName,
|
||||||
|
bool hasKeyword,
|
||||||
|
JsonElement keywordElement,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
return hasKeyword
|
||||||
|
? ParseConditionalObjectSchema(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
propertyPath,
|
||||||
|
BuildNestedSchemaPath(propertyPath, keywordName),
|
||||||
|
keywordName,
|
||||||
|
keywordElement,
|
||||||
|
properties)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析单个条件分支的 object-focused 内联 schema。
|
/// 解析单个条件分支的 object-focused 内联 schema。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -494,34 +656,96 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
JsonElement inlineSchemaElement,
|
JsonElement inlineSchemaElement,
|
||||||
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
{
|
{
|
||||||
if (inlineSchemaElement.TryGetProperty("properties", out var inlinePropertiesElement))
|
ValidateInlineObjectSchemaPropertiesAgainstParentObject(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
propertyPath,
|
||||||
|
inlineSchemaPath,
|
||||||
|
entryLabel,
|
||||||
|
inlineSchemaElement,
|
||||||
|
properties);
|
||||||
|
|
||||||
|
ValidateInlineRequiredPropertiesAgainstParentObject(
|
||||||
|
tableName,
|
||||||
|
schemaPath,
|
||||||
|
propertyPath,
|
||||||
|
inlineSchemaPath,
|
||||||
|
entryLabel,
|
||||||
|
inlineSchemaElement,
|
||||||
|
properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验 object-focused 内联 schema 的 <c>properties</c> 只引用父对象字段。
|
||||||
|
/// focused block 不负责声明新字段,所以任何父对象未声明字段都会在 schema 加载时被拒绝。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">父对象路径。</param>
|
||||||
|
/// <param name="inlineSchemaPath">当前内联 schema 路径。</param>
|
||||||
|
/// <param name="entryLabel">用于诊断文本的条目标签。</param>
|
||||||
|
/// <param name="inlineSchemaElement">当前内联 schema。</param>
|
||||||
|
/// <param name="properties">父对象已声明的属性集合。</param>
|
||||||
|
private static void ValidateInlineObjectSchemaPropertiesAgainstParentObject(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
string inlineSchemaPath,
|
||||||
|
string entryLabel,
|
||||||
|
JsonElement inlineSchemaElement,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
if (!inlineSchemaElement.TryGetProperty("properties", out var inlinePropertiesElement))
|
||||||
{
|
{
|
||||||
if (inlinePropertiesElement.ValueKind != JsonValueKind.Object)
|
return;
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'properties' as an object-valued map.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var property in inlinePropertiesElement.EnumerateObject())
|
|
||||||
{
|
|
||||||
if (properties.ContainsKey(property.Name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' declares property '{property.Name}', but that property is not declared in the parent object schema.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inlinePropertiesElement.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'properties' as an object-valued map.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in inlinePropertiesElement.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (properties.ContainsKey(property.Name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' declares property '{property.Name}', but that property is not declared in the parent object schema.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验 object-focused 内联 schema 的 <c>required</c> 只引用父对象字段。
|
||||||
|
/// 该校验在加载期暴露不可满足的条件块,而不是等到运行时才发现无效字段名。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">父对象路径。</param>
|
||||||
|
/// <param name="inlineSchemaPath">当前内联 schema 路径。</param>
|
||||||
|
/// <param name="entryLabel">用于诊断文本的条目标签。</param>
|
||||||
|
/// <param name="inlineSchemaElement">当前内联 schema。</param>
|
||||||
|
/// <param name="properties">父对象已声明的属性集合。</param>
|
||||||
|
private static void ValidateInlineRequiredPropertiesAgainstParentObject(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
string inlineSchemaPath,
|
||||||
|
string entryLabel,
|
||||||
|
JsonElement inlineSchemaElement,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
if (!inlineSchemaElement.TryGetProperty("required", out var inlineRequiredElement))
|
if (!inlineSchemaElement.TryGetProperty("required", out var inlineRequiredElement))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -539,39 +763,69 @@ internal static partial class YamlConfigSchemaValidator
|
|||||||
|
|
||||||
foreach (var requiredProperty in inlineRequiredElement.EnumerateArray())
|
foreach (var requiredProperty in inlineRequiredElement.EnumerateArray())
|
||||||
{
|
{
|
||||||
if (requiredProperty.ValueKind != JsonValueKind.String)
|
ValidateInlineRequiredPropertyAgainstParentObject(
|
||||||
{
|
tableName,
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
schemaPath,
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
propertyPath,
|
||||||
tableName,
|
inlineSchemaPath,
|
||||||
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'required' entries as property-name strings.",
|
entryLabel,
|
||||||
schemaPath: schemaPath,
|
requiredProperty,
|
||||||
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
properties);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var requiredPropertyName = requiredProperty.GetString();
|
|
||||||
if (string.IsNullOrWhiteSpace(requiredPropertyName))
|
|
||||||
{
|
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
|
||||||
tableName,
|
|
||||||
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' cannot declare blank property names in 'required'.",
|
|
||||||
schemaPath: schemaPath,
|
|
||||||
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (properties.ContainsKey(requiredPropertyName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验 object-focused 内联 schema 的单个 <c>required</c> 字段名。
|
||||||
|
/// 字段名必须是非空字符串并且属于父对象声明范围,保持条件块与父对象形状一致。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">所属配置表名称。</param>
|
||||||
|
/// <param name="schemaPath">Schema 文件路径。</param>
|
||||||
|
/// <param name="propertyPath">父对象路径。</param>
|
||||||
|
/// <param name="inlineSchemaPath">当前内联 schema 路径。</param>
|
||||||
|
/// <param name="entryLabel">用于诊断文本的条目标签。</param>
|
||||||
|
/// <param name="requiredProperty">当前 required 条目。</param>
|
||||||
|
/// <param name="properties">父对象已声明的属性集合。</param>
|
||||||
|
private static void ValidateInlineRequiredPropertyAgainstParentObject(
|
||||||
|
string tableName,
|
||||||
|
string schemaPath,
|
||||||
|
string propertyPath,
|
||||||
|
string inlineSchemaPath,
|
||||||
|
string entryLabel,
|
||||||
|
JsonElement requiredProperty,
|
||||||
|
IReadOnlyDictionary<string, YamlConfigSchemaNode> properties)
|
||||||
|
{
|
||||||
|
if (requiredProperty.ValueKind != JsonValueKind.String)
|
||||||
|
{
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
ConfigLoadFailureKind.SchemaUnsupported,
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
tableName,
|
tableName,
|
||||||
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' requires property '{requiredPropertyName}', but that property is not declared in the parent object schema.",
|
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' must declare 'required' entries as property-name strings.",
|
||||||
schemaPath: schemaPath,
|
schemaPath: schemaPath,
|
||||||
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var requiredPropertyName = requiredProperty.GetString();
|
||||||
|
if (string.IsNullOrWhiteSpace(requiredPropertyName))
|
||||||
|
{
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' cannot declare blank property names in 'required'.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.ContainsKey(requiredPropertyName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
ConfigLoadFailureKind.SchemaUnsupported,
|
||||||
|
tableName,
|
||||||
|
$"{entryLabel} for {DescribeObjectSchemaTargetInClause(propertyPath)} of schema file '{schemaPath}' requires property '{requiredPropertyName}', but that property is not declared in the parent object schema.",
|
||||||
|
schemaPath: schemaPath,
|
||||||
|
displayPath: GetDiagnosticPath(inlineSchemaPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user