diff --git a/GFramework.Game/Config/YamlConfigSchemaValidator.cs b/GFramework.Game/Config/YamlConfigSchemaValidator.cs index 09153c57..a53fccab 100644 --- a/GFramework.Game/Config/YamlConfigSchemaValidator.cs +++ b/GFramework.Game/Config/YamlConfigSchemaValidator.cs @@ -913,7 +913,21 @@ internal static class YamlConfigSchemaValidator { case YamlConfigSchemaPropertyType.Integer: case YamlConfigSchemaPropertyType.Number: - var numericValue = double.Parse(normalizedValue, CultureInfo.InvariantCulture); + if (!double.TryParse( + normalizedValue, + NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, + out var numericValue)) + { + throw ConfigLoadExceptionFactory.Create( + ConfigLoadFailureKind.UnexpectedFailure, + tableName, + $"Property '{displayPath}' in config file '{yamlPath}' could not be normalized into a comparable numeric value.", + yamlPath: yamlPath, + schemaPath: schemaNode.SchemaPathHint, + displayPath: GetDiagnosticPath(displayPath), + rawValue: rawValue); + } if (constraints.Minimum.HasValue && numericValue < constraints.Minimum.Value) { @@ -975,6 +989,16 @@ internal static class YamlConfigSchemaValidator } return; + + default: + throw ConfigLoadExceptionFactory.Create( + ConfigLoadFailureKind.UnexpectedFailure, + tableName, + $"Property '{displayPath}' in config file '{yamlPath}' resolved unsupported constraint host type '{schemaNode.NodeType}'.", + yamlPath: yamlPath, + schemaPath: schemaNode.SchemaPathHint, + displayPath: GetDiagnosticPath(displayPath), + rawValue: schemaNode.NodeType.ToString()); } } @@ -1153,16 +1177,29 @@ internal static class YamlConfigSchemaValidator 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 + YamlConfigSchemaPropertyType.Integer when long.TryParse( + value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var integerValue) => + integerValue.ToString(CultureInfo.InvariantCulture), + YamlConfigSchemaPropertyType.Number when double.TryParse( + value, + NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, + out var numberValue) => + numberValue.ToString(CultureInfo.InvariantCulture), + YamlConfigSchemaPropertyType.Boolean when bool.TryParse(value, out var booleanValue) => + booleanValue.ToString().ToLowerInvariant(), + YamlConfigSchemaPropertyType.Integer => + throw new InvalidOperationException($"Value '{value}' cannot be normalized as integer."), + YamlConfigSchemaPropertyType.Number => + throw new InvalidOperationException($"Value '{value}' cannot be normalized as number."), + YamlConfigSchemaPropertyType.Boolean => + throw new InvalidOperationException($"Value '{value}' cannot be normalized as boolean."), + _ => + throw new InvalidOperationException( + $"Schema node type '{expectedType}' cannot be normalized as a scalar value.") }; } diff --git a/tools/gframework-config-tool/src/configValidation.js b/tools/gframework-config-tool/src/configValidation.js index de70e832..31467f78 100644 --- a/tools/gframework-config-tool/src/configValidation.js +++ b/tools/gframework-config-tool/src/configValidation.js @@ -505,10 +505,18 @@ function parseSchemaNode(rawNode, displayPath) { title: metadata.title, description: metadata.description, defaultValue: metadata.defaultValue, - minimum: metadata.minimum, - maximum: metadata.maximum, - minLength: metadata.minLength, - maxLength: metadata.maxLength, + minimum: type === "integer" || type === "number" + ? metadata.minimum + : undefined, + maximum: type === "integer" || type === "number" + ? metadata.maximum + : undefined, + minLength: type === "string" + ? metadata.minLength + : undefined, + maxLength: type === "string" + ? metadata.maxLength + : undefined, enumValues: normalizeSchemaEnumValues(value.enum), refTable: metadata.refTable }; @@ -587,7 +595,12 @@ function validateNode(schemaNode, yamlNode, displayPath, diagnostics, localizer) } const scalarValue = unquoteScalar(yamlNode.value); - if (typeof schemaNode.minimum === "number" && Number(scalarValue) < schemaNode.minimum) { + const supportsNumericConstraints = schemaNode.type === "integer" || schemaNode.type === "number"; + const supportsLengthConstraints = schemaNode.type === "string"; + + if (supportsNumericConstraints && + typeof schemaNode.minimum === "number" && + Number(scalarValue) < schemaNode.minimum) { diagnostics.push({ severity: "error", message: localizeValidationMessage(ValidationMessageKeys.minimumViolation, localizer, { @@ -597,7 +610,9 @@ function validateNode(schemaNode, yamlNode, displayPath, diagnostics, localizer) }); } - if (typeof schemaNode.maximum === "number" && Number(scalarValue) > schemaNode.maximum) { + if (supportsNumericConstraints && + typeof schemaNode.maximum === "number" && + Number(scalarValue) > schemaNode.maximum) { diagnostics.push({ severity: "error", message: localizeValidationMessage(ValidationMessageKeys.maximumViolation, localizer, { @@ -607,7 +622,9 @@ function validateNode(schemaNode, yamlNode, displayPath, diagnostics, localizer) }); } - if (typeof schemaNode.minLength === "number" && scalarValue.length < schemaNode.minLength) { + if (supportsLengthConstraints && + typeof schemaNode.minLength === "number" && + scalarValue.length < schemaNode.minLength) { diagnostics.push({ severity: "error", message: localizeValidationMessage(ValidationMessageKeys.minLengthViolation, localizer, { @@ -617,7 +634,9 @@ function validateNode(schemaNode, yamlNode, displayPath, diagnostics, localizer) }); } - if (typeof schemaNode.maxLength === "number" && scalarValue.length > schemaNode.maxLength) { + if (supportsLengthConstraints && + typeof schemaNode.maxLength === "number" && + scalarValue.length > schemaNode.maxLength) { diagnostics.push({ severity: "error", message: localizeValidationMessage(ValidationMessageKeys.maxLengthViolation, localizer, { diff --git a/tools/gframework-config-tool/test/configValidation.test.js b/tools/gframework-config-tool/test/configValidation.test.js index cfac2fb4..a88becb6 100644 --- a/tools/gframework-config-tool/test/configValidation.test.js +++ b/tools/gframework-config-tool/test/configValidation.test.js @@ -266,6 +266,28 @@ test("parseSchemaContent should capture scalar range and length metadata", () => assert.equal(schema.properties.tags.items.maxLength, 6); }); +test("parseSchemaContent should ignore mismatched constraint metadata on unsupported scalar types", () => { + const schema = parseSchemaContent(` + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "minimum": 1, + "minLength": 3 + } + } + } + `); + const yaml = parseTopLevelYaml(` +enabled: true +`); + + assert.equal(schema.properties.enabled.minimum, undefined); + assert.equal(schema.properties.enabled.minLength, undefined); + assert.deepEqual(validateParsedConfig(schema, yaml), []); +}); + test("validateParsedConfig should localize diagnostics when Chinese UI is requested", () => { const schema = parseSchemaContent(` {