mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
feat(config): 添加配置验证和YAML解析功能
- 实现了配置模式解析器,支持递归对象/数组/标量树结构 - 添加了可编辑字段收集功能,支持批量编辑标量和数组类型 - 实现了YAML解析器,支持嵌套对象、标量数组和对象数组 - 添加了YAML注释提取功能,将注释映射到逻辑字段路径 - 实现了基于模式的示例YAML配置生成功能 - 添加了扩展端验证诊断功能,支持中英文错误消息 - 实现了表单更新应用功能,支持标量、数组和对象数组更新 - 添加了批处理数组值解析和模式枚举值标准化功能 - 实现了YAML标量格式化和引号移除功能 - 添加了完整的模式节点验证,支持数值约束、长度限制和模式匹配 - 实现了多语言验证消息本地化功能 - 添加了YAML标记化和块解析功能 - 实现了唯一性检查和比较键构建功能
This commit is contained in:
parent
c693337ebf
commit
51de7f1102
@ -370,6 +370,16 @@ function normalizeSchemaNumber(value) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize one strictly positive finite schema number.
|
||||
*
|
||||
* @param {unknown} value Raw schema value.
|
||||
* @returns {number | undefined} Normalized positive number.
|
||||
*/
|
||||
function normalizeSchemaPositiveNumber(value) {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize one non-negative integer schema value for length constraints.
|
||||
*
|
||||
@ -380,6 +390,16 @@ function normalizeSchemaNonNegativeInteger(value) {
|
||||
return Number.isInteger(value) && value >= 0 ? value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize one boolean schema flag.
|
||||
*
|
||||
* @param {unknown} value Raw schema value.
|
||||
* @returns {boolean | undefined} Normalized boolean.
|
||||
*/
|
||||
function normalizeSchemaBoolean(value) {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize one schema pattern string when the regular expression can be
|
||||
* compiled by the local tooling runtime.
|
||||
@ -454,6 +474,25 @@ function matchesSchemaPattern(scalarValue, pattern, displayPath) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether one numeric scalar satisfies a multipleOf constraint.
|
||||
*
|
||||
* @param {string} scalarValue YAML scalar value.
|
||||
* @param {number | undefined} multipleOf Schema multipleOf value.
|
||||
* @returns {boolean} True when compatible or the constraint is absent.
|
||||
*/
|
||||
function matchesSchemaMultipleOf(scalarValue, multipleOf) {
|
||||
if (typeof multipleOf !== "number") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const numericValue = Number(scalarValue);
|
||||
const quotient = numericValue / multipleOf;
|
||||
const nearestInteger = Math.round(quotient);
|
||||
const tolerance = 1e-9 * Math.max(1, Math.abs(quotient));
|
||||
return Math.abs(quotient - nearestInteger) <= tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a scalar value for YAML output.
|
||||
*
|
||||
@ -505,11 +544,13 @@ function parseSchemaNode(rawNode, displayPath) {
|
||||
exclusiveMinimum: normalizeSchemaNumber(value.exclusiveMinimum),
|
||||
maximum: normalizeSchemaNumber(value.maximum),
|
||||
exclusiveMaximum: normalizeSchemaNumber(value.exclusiveMaximum),
|
||||
multipleOf: normalizeSchemaPositiveNumber(value.multipleOf),
|
||||
minLength: normalizeSchemaNonNegativeInteger(value.minLength),
|
||||
maxLength: normalizeSchemaNonNegativeInteger(value.maxLength),
|
||||
pattern: normalizeSchemaPattern(value.pattern, displayPath),
|
||||
minItems: normalizeSchemaNonNegativeInteger(value.minItems),
|
||||
maxItems: normalizeSchemaNonNegativeInteger(value.maxItems),
|
||||
uniqueItems: normalizeSchemaBoolean(value.uniqueItems),
|
||||
refTable: typeof value["x-gframework-ref-table"] === "string"
|
||||
? value["x-gframework-ref-table"]
|
||||
: undefined
|
||||
@ -545,6 +586,7 @@ function parseSchemaNode(rawNode, displayPath) {
|
||||
defaultValue: metadata.defaultValue,
|
||||
minItems: metadata.minItems,
|
||||
maxItems: metadata.maxItems,
|
||||
uniqueItems: metadata.uniqueItems === true,
|
||||
refTable: metadata.refTable,
|
||||
items: itemNode
|
||||
};
|
||||
@ -568,6 +610,9 @@ function parseSchemaNode(rawNode, displayPath) {
|
||||
exclusiveMaximum: type === "integer" || type === "number"
|
||||
? metadata.exclusiveMaximum
|
||||
: undefined,
|
||||
multipleOf: type === "integer" || type === "number"
|
||||
? metadata.multipleOf
|
||||
: undefined,
|
||||
minLength: type === "string"
|
||||
? metadata.minLength
|
||||
: undefined,
|
||||
@ -638,6 +683,26 @@ function validateNode(schemaNode, yamlNode, displayPath, diagnostics, localizer)
|
||||
diagnostics,
|
||||
localizer);
|
||||
}
|
||||
|
||||
if (schemaNode.uniqueItems === true) {
|
||||
const seenItems = new Map();
|
||||
for (let index = 0; index < yamlNode.items.length; index += 1) {
|
||||
const comparableValue = buildComparableNodeValue(schemaNode.items, yamlNode.items[index]);
|
||||
if (seenItems.has(comparableValue)) {
|
||||
diagnostics.push({
|
||||
severity: "error",
|
||||
message: localizeValidationMessage(ValidationMessageKeys.uniqueItemsViolation, localizer, {
|
||||
displayPath: joinArrayIndexPath(displayPath, index),
|
||||
duplicatePath: joinArrayIndexPath(displayPath, seenItems.get(comparableValue))
|
||||
})
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
seenItems.set(comparableValue, index);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -729,6 +794,17 @@ function validateNode(schemaNode, yamlNode, displayPath, diagnostics, localizer)
|
||||
});
|
||||
}
|
||||
|
||||
if (supportsNumericConstraints &&
|
||||
!matchesSchemaMultipleOf(scalarValue, schemaNode.multipleOf)) {
|
||||
diagnostics.push({
|
||||
severity: "error",
|
||||
message: localizeValidationMessage(ValidationMessageKeys.multipleOfViolation, localizer, {
|
||||
displayPath,
|
||||
value: String(schemaNode.multipleOf)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (supportsLengthConstraints &&
|
||||
typeof schemaNode.minLength === "number" &&
|
||||
scalarValue.length < schemaNode.minLength) {
|
||||
@ -824,6 +900,51 @@ function validateObjectNode(schemaNode, yamlNode, displayPath, diagnostics, loca
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build one schema-aware comparable key for uniqueItems checks.
|
||||
*
|
||||
* @param {SchemaNode} schemaNode Schema node.
|
||||
* @param {YamlNode | undefined} yamlNode YAML node.
|
||||
* @returns {string} Comparable key.
|
||||
*/
|
||||
function buildComparableNodeValue(schemaNode, yamlNode) {
|
||||
if (!yamlNode) {
|
||||
return "missing";
|
||||
}
|
||||
|
||||
if (schemaNode.type === "object") {
|
||||
if (yamlNode.kind !== "object") {
|
||||
return yamlNode.kind;
|
||||
}
|
||||
|
||||
return Object.keys(schemaNode.properties)
|
||||
.filter((key) => yamlNode.map.has(key))
|
||||
.sort((left, right) => left.localeCompare(right))
|
||||
.map((key) => `${key.length}:${key}=${buildComparableNodeValue(schemaNode.properties[key], yamlNode.map.get(key))}`)
|
||||
.join("|");
|
||||
}
|
||||
|
||||
if (schemaNode.type === "array") {
|
||||
if (yamlNode.kind !== "array") {
|
||||
return yamlNode.kind;
|
||||
}
|
||||
|
||||
return `[${yamlNode.items.map((item) => buildComparableNodeValue(schemaNode.items, item)).join(",")}]`;
|
||||
}
|
||||
|
||||
if (yamlNode.kind !== "scalar") {
|
||||
return yamlNode.kind;
|
||||
}
|
||||
|
||||
const scalarValue = unquoteScalar(yamlNode.value);
|
||||
const normalizedScalar = schemaNode.type === "integer" || schemaNode.type === "number"
|
||||
? String(Number(scalarValue))
|
||||
: schemaNode.type === "boolean"
|
||||
? String(/^true$/iu.test(scalarValue))
|
||||
: scalarValue;
|
||||
return `${schemaNode.type}:${normalizedScalar}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format one validation message in either English or Simplified Chinese.
|
||||
*
|
||||
@ -859,12 +980,16 @@ function localizeValidationMessage(key, localizer, params) {
|
||||
return `属性“${params.displayPath}”长度必须不超过 ${params.value} 个字符。`;
|
||||
case ValidationMessageKeys.minimumViolation:
|
||||
return `属性“${params.displayPath}”必须大于或等于 ${params.value}。`;
|
||||
case ValidationMessageKeys.multipleOfViolation:
|
||||
return `属性“${params.displayPath}”必须是 ${params.value} 的整数倍。`;
|
||||
case ValidationMessageKeys.minItemsViolation:
|
||||
return `属性“${params.displayPath}”至少需要包含 ${params.value} 个元素。`;
|
||||
case ValidationMessageKeys.minLengthViolation:
|
||||
return `属性“${params.displayPath}”长度必须至少为 ${params.value} 个字符。`;
|
||||
case ValidationMessageKeys.patternViolation:
|
||||
return `属性“${params.displayPath}”必须匹配正则模式“${params.value}”。`;
|
||||
case ValidationMessageKeys.uniqueItemsViolation:
|
||||
return `属性“${params.displayPath}”与更早的数组元素 ${params.duplicatePath} 重复;该数组要求元素唯一。`;
|
||||
case ValidationMessageKeys.expectedObject:
|
||||
return params.subject;
|
||||
case ValidationMessageKeys.missingRequired:
|
||||
@ -897,12 +1022,16 @@ function localizeValidationMessage(key, localizer, params) {
|
||||
return `Property '${params.displayPath}' must be at most ${params.value} characters long.`;
|
||||
case ValidationMessageKeys.minimumViolation:
|
||||
return `Property '${params.displayPath}' must be greater than or equal to ${params.value}.`;
|
||||
case ValidationMessageKeys.multipleOfViolation:
|
||||
return `Property '${params.displayPath}' must be a multiple of ${params.value}.`;
|
||||
case ValidationMessageKeys.minItemsViolation:
|
||||
return `Property '${params.displayPath}' must contain at least ${params.value} items.`;
|
||||
case ValidationMessageKeys.minLengthViolation:
|
||||
return `Property '${params.displayPath}' must be at least ${params.value} characters long.`;
|
||||
case ValidationMessageKeys.patternViolation:
|
||||
return `Property '${params.displayPath}' must match pattern '${params.value}'.`;
|
||||
case ValidationMessageKeys.uniqueItemsViolation:
|
||||
return `Property '${params.displayPath}' duplicates earlier array item '${params.duplicatePath}', but uniqueItems is required.`;
|
||||
case ValidationMessageKeys.expectedObject:
|
||||
return params.subject;
|
||||
case ValidationMessageKeys.missingRequired:
|
||||
@ -1558,6 +1687,7 @@ module.exports = {
|
||||
* defaultValue?: string,
|
||||
* minItems?: number,
|
||||
* maxItems?: number,
|
||||
* uniqueItems?: boolean,
|
||||
* refTable?: string,
|
||||
* items: SchemaNode
|
||||
* } | {
|
||||
@ -1570,6 +1700,7 @@ module.exports = {
|
||||
* exclusiveMinimum?: number,
|
||||
* maximum?: number,
|
||||
* exclusiveMaximum?: number,
|
||||
* multipleOf?: number,
|
||||
* minLength?: number,
|
||||
* maxLength?: number,
|
||||
* pattern?: string,
|
||||
|
||||
@ -1574,7 +1574,7 @@ function getScalarArrayValue(yamlNode) {
|
||||
/**
|
||||
* Render human-facing metadata hints for one schema field.
|
||||
*
|
||||
* @param {{description?: string, defaultValue?: string, minimum?: number, exclusiveMinimum?: number, maximum?: number, exclusiveMaximum?: number, minLength?: number, maxLength?: number, pattern?: string, minItems?: number, maxItems?: number, enumValues?: string[], items?: {enumValues?: string[], minimum?: number, exclusiveMinimum?: number, maximum?: number, exclusiveMaximum?: number, minLength?: number, maxLength?: number, pattern?: string}, refTable?: string}} propertySchema Property schema metadata.
|
||||
* @param {{description?: string, defaultValue?: string, minimum?: number, exclusiveMinimum?: number, maximum?: number, exclusiveMaximum?: number, multipleOf?: number, minLength?: number, maxLength?: number, pattern?: string, minItems?: number, maxItems?: number, uniqueItems?: boolean, enumValues?: string[], items?: {enumValues?: string[], minimum?: number, exclusiveMinimum?: number, maximum?: number, exclusiveMaximum?: number, multipleOf?: number, minLength?: number, maxLength?: number, pattern?: string}, refTable?: string}} propertySchema Property schema metadata.
|
||||
* @param {boolean} isArrayField Whether the field is an array.
|
||||
* @returns {string} HTML fragment.
|
||||
*/
|
||||
@ -1614,6 +1614,10 @@ function renderFieldHint(propertySchema, isArrayField) {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.exclusiveMaximum", {value: propertySchema.exclusiveMaximum})));
|
||||
}
|
||||
|
||||
if (!isArrayField && typeof propertySchema.multipleOf === "number") {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.multipleOf", {value: propertySchema.multipleOf})));
|
||||
}
|
||||
|
||||
if (!isArrayField && typeof propertySchema.minLength === "number") {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.minLength", {value: propertySchema.minLength})));
|
||||
}
|
||||
@ -1634,6 +1638,10 @@ function renderFieldHint(propertySchema, isArrayField) {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.maxItems", {value: propertySchema.maxItems})));
|
||||
}
|
||||
|
||||
if (isArrayField && propertySchema.uniqueItems === true) {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.uniqueItems")));
|
||||
}
|
||||
|
||||
if (isArrayField && propertySchema.items && typeof propertySchema.items.minimum === "number") {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.itemMinimum", {value: propertySchema.items.minimum})));
|
||||
}
|
||||
@ -1650,6 +1658,10 @@ function renderFieldHint(propertySchema, isArrayField) {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.itemExclusiveMaximum", {value: propertySchema.items.exclusiveMaximum})));
|
||||
}
|
||||
|
||||
if (isArrayField && propertySchema.items && typeof propertySchema.items.multipleOf === "number") {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.itemMultipleOf", {value: propertySchema.items.multipleOf})));
|
||||
}
|
||||
|
||||
if (isArrayField && propertySchema.items && typeof propertySchema.items.minLength === "number") {
|
||||
hints.push(escapeHtml(localizer.t("webview.hint.itemMinLength", {value: propertySchema.items.minLength})));
|
||||
}
|
||||
|
||||
@ -109,15 +109,18 @@ const enMessages = {
|
||||
"webview.hint.exclusiveMinimum": "Exclusive minimum: {value}",
|
||||
"webview.hint.maximum": "Maximum: {value}",
|
||||
"webview.hint.exclusiveMaximum": "Exclusive maximum: {value}",
|
||||
"webview.hint.multipleOf": "Multiple of: {value}",
|
||||
"webview.hint.minLength": "Min length: {value}",
|
||||
"webview.hint.maxLength": "Max length: {value}",
|
||||
"webview.hint.pattern": "Pattern: {value}",
|
||||
"webview.hint.minItems": "Min items: {value}",
|
||||
"webview.hint.maxItems": "Max items: {value}",
|
||||
"webview.hint.uniqueItems": "Items must be unique",
|
||||
"webview.hint.itemMinimum": "Item minimum: {value}",
|
||||
"webview.hint.itemExclusiveMinimum": "Item exclusive minimum: {value}",
|
||||
"webview.hint.itemMaximum": "Item maximum: {value}",
|
||||
"webview.hint.itemExclusiveMaximum": "Item exclusive maximum: {value}",
|
||||
"webview.hint.itemMultipleOf": "Item multiple of: {value}",
|
||||
"webview.hint.itemMinLength": "Item min length: {value}",
|
||||
"webview.hint.itemMaxLength": "Item max length: {value}",
|
||||
"webview.hint.itemPattern": "Item pattern: {value}",
|
||||
@ -132,9 +135,11 @@ const enMessages = {
|
||||
[ValidationMessageKeys.maxItemsViolation]: "Property '{displayPath}' must contain at most {value} items.",
|
||||
[ValidationMessageKeys.maxLengthViolation]: "Property '{displayPath}' must be at most {value} characters long.",
|
||||
[ValidationMessageKeys.minimumViolation]: "Property '{displayPath}' must be greater than or equal to {value}.",
|
||||
[ValidationMessageKeys.multipleOfViolation]: "Property '{displayPath}' must be a multiple of {value}.",
|
||||
[ValidationMessageKeys.minItemsViolation]: "Property '{displayPath}' must contain at least {value} items.",
|
||||
[ValidationMessageKeys.minLengthViolation]: "Property '{displayPath}' must be at least {value} characters long.",
|
||||
[ValidationMessageKeys.patternViolation]: "Property '{displayPath}' must match pattern '{value}'.",
|
||||
[ValidationMessageKeys.uniqueItemsViolation]: "Property '{displayPath}' duplicates earlier array item '{duplicatePath}', but uniqueItems is required.",
|
||||
[ValidationMessageKeys.enumMismatch]: "Property '{displayPath}' must be one of: {values}.",
|
||||
[ValidationMessageKeys.expectedArray]: "Property '{displayPath}' is expected to be an array.",
|
||||
[ValidationMessageKeys.expectedObject]: "{subject} is expected to be an object.",
|
||||
@ -208,15 +213,18 @@ const zhCnMessages = {
|
||||
"webview.hint.exclusiveMinimum": "开区间最小值:{value}",
|
||||
"webview.hint.maximum": "最大值:{value}",
|
||||
"webview.hint.exclusiveMaximum": "开区间最大值:{value}",
|
||||
"webview.hint.multipleOf": "倍数约束:{value}",
|
||||
"webview.hint.minLength": "最小长度:{value}",
|
||||
"webview.hint.maxLength": "最大长度:{value}",
|
||||
"webview.hint.pattern": "正则模式:{value}",
|
||||
"webview.hint.minItems": "最少元素数:{value}",
|
||||
"webview.hint.maxItems": "最多元素数:{value}",
|
||||
"webview.hint.uniqueItems": "元素必须唯一",
|
||||
"webview.hint.itemMinimum": "元素最小值:{value}",
|
||||
"webview.hint.itemExclusiveMinimum": "元素开区间最小值:{value}",
|
||||
"webview.hint.itemMaximum": "元素最大值:{value}",
|
||||
"webview.hint.itemExclusiveMaximum": "元素开区间最大值:{value}",
|
||||
"webview.hint.itemMultipleOf": "元素倍数约束:{value}",
|
||||
"webview.hint.itemMinLength": "元素最小长度:{value}",
|
||||
"webview.hint.itemMaxLength": "元素最大长度:{value}",
|
||||
"webview.hint.itemPattern": "元素正则模式:{value}",
|
||||
@ -231,9 +239,11 @@ const zhCnMessages = {
|
||||
[ValidationMessageKeys.maxItemsViolation]: "属性“{displayPath}”最多只能包含 {value} 个元素。",
|
||||
[ValidationMessageKeys.maxLengthViolation]: "属性“{displayPath}”长度必须不超过 {value} 个字符。",
|
||||
[ValidationMessageKeys.minimumViolation]: "属性“{displayPath}”必须大于或等于 {value}。",
|
||||
[ValidationMessageKeys.multipleOfViolation]: "属性“{displayPath}”必须是 {value} 的整数倍。",
|
||||
[ValidationMessageKeys.minItemsViolation]: "属性“{displayPath}”至少需要包含 {value} 个元素。",
|
||||
[ValidationMessageKeys.minLengthViolation]: "属性“{displayPath}”长度必须至少为 {value} 个字符。",
|
||||
[ValidationMessageKeys.patternViolation]: "属性“{displayPath}”必须匹配正则模式“{value}”。",
|
||||
[ValidationMessageKeys.uniqueItemsViolation]: "属性“{displayPath}”与更早的数组元素“{duplicatePath}”重复;该数组要求元素唯一。",
|
||||
[ValidationMessageKeys.enumMismatch]: "属性“{displayPath}”必须是以下值之一:{values}。",
|
||||
[ValidationMessageKeys.expectedArray]: "属性“{displayPath}”应为数组。",
|
||||
[ValidationMessageKeys.expectedObject]: "{subject}",
|
||||
|
||||
@ -10,10 +10,12 @@ const ValidationMessageKeys = Object.freeze({
|
||||
maxItemsViolation: "validation.maxItemsViolation",
|
||||
maxLengthViolation: "validation.maxLengthViolation",
|
||||
minimumViolation: "validation.minimumViolation",
|
||||
multipleOfViolation: "validation.multipleOfViolation",
|
||||
minItemsViolation: "validation.minItemsViolation",
|
||||
minLengthViolation: "validation.minLengthViolation",
|
||||
missingRequired: "validation.missingRequired",
|
||||
patternViolation: "validation.patternViolation",
|
||||
uniqueItemsViolation: "validation.uniqueItemsViolation",
|
||||
unknownProperty: "validation.unknownProperty"
|
||||
});
|
||||
|
||||
|
||||
@ -308,6 +308,47 @@ tags:
|
||||
assert.match(diagnostics[1].message, /at most 3 items|最多只能包含 3 个元素/u);
|
||||
});
|
||||
|
||||
test("validateParsedConfig should report multipleOf and uniqueItems violations", () => {
|
||||
const schema = parseSchemaContent(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hp": {
|
||||
"type": "integer",
|
||||
"multipleOf": 5
|
||||
},
|
||||
"phases": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"wave": { "type": "integer" },
|
||||
"monsterId": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
const yaml = parseTopLevelYaml(`
|
||||
hp: 12
|
||||
phases:
|
||||
-
|
||||
wave: 1
|
||||
monsterId: slime
|
||||
-
|
||||
monsterId: slime
|
||||
wave: 1
|
||||
`);
|
||||
|
||||
const diagnostics = validateParsedConfig(schema, yaml);
|
||||
|
||||
assert.equal(diagnostics.length, 2);
|
||||
assert.match(diagnostics[0].message, /multiple of 5|5 的整数倍/u);
|
||||
assert.match(diagnostics[1].message, /phases\[1\]|uniqueItems|元素唯一/u);
|
||||
});
|
||||
|
||||
test("parseSchemaContent should capture scalar range and length metadata", () => {
|
||||
const schema = parseSchemaContent(`
|
||||
{
|
||||
@ -378,6 +419,32 @@ test("parseSchemaContent should capture exclusive bounds, pattern, and array ite
|
||||
assert.equal(schema.properties.tags.items.pattern, "^[a-z]+$");
|
||||
});
|
||||
|
||||
test("parseSchemaContent should capture multipleOf and uniqueItems metadata", () => {
|
||||
const schema = parseSchemaContent(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hp": {
|
||||
"type": "integer",
|
||||
"multipleOf": 5
|
||||
},
|
||||
"dropRates": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "number",
|
||||
"multipleOf": 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
assert.equal(schema.properties.hp.multipleOf, 5);
|
||||
assert.equal(schema.properties.dropRates.uniqueItems, true);
|
||||
assert.equal(schema.properties.dropRates.items.multipleOf, 0.5);
|
||||
});
|
||||
|
||||
test("parseSchemaContent should reject invalid pattern declarations instead of dropping them", () => {
|
||||
assert.throws(
|
||||
() => parseSchemaContent(`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user