Files
Aska 464ed2cad3
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(tool): Top5#2 完整 JSON-Schema 校验 + 类型强转 + 错误路径 + defaults(对齐 OpenClaw validation)
- 引入 github.com/santhosh-tekuri/jsonschema/v5(纯 Go,OpenClaw/MCP SDK 同思路)。
- validate.go 重写:validateArgs 用 jsonschema Compile+Validate 替换手写子集(required单层),
  支持 required/properties/嵌套 object/array items/additionalProperties/allOf/anyOf/oneOf/
  类型联合/enum/min-max/pattern/defaults;编译器缓存 sync.Map;Draft7。
- coerce.go:对齐 OpenClaw coerceWithJsonSchema 类型强转(string→number/integer/boolean/
  array/object,number/bool→string;union 处理;object/array 递归)。
- defaults.go:applyDefaults 缺失属性按 default 填(含嵌套),先填默认再强转再校验。
- 错误路径:ValidationError.InstanceLocation → 带路径报告(/count: expected integer...)。
- 测试:tool_test.go 更新 required 语义 + 补强转/default/错误路径用例。
go build/vet/test 全绿 + windows/darwin 交叉编译 + gofmt。
2026-09-06 17:11:52 +08:00

82 lines
2.0 KiB
Go

package tool
// applyDefaults fills absent properties with their schema "default", recursing
// into objects and arrays so nested defaults are honoured too.
func applyDefaults(schema map[string]any, args map[string]any) {
props, _ := schema["properties"].(map[string]any)
for name, raw := range props {
ps, ok := raw.(map[string]any)
if !ok {
continue
}
if rawDefault, has := ps["default"]; has {
if _, present := args[name]; !present {
args[name] = deepCopy(rawDefault)
}
}
if val, present := args[name]; present {
applyDefaultsToValue(ps, val)
}
}
}
// applyDefaultsToValue recurses into a present argument value to fill nested
// defaults according to the sub-schema.
func applyDefaultsToValue(schema map[string]any, val any) {
switch v := val.(type) {
case map[string]any:
applyDefaults(schema, v)
case []any:
applyDefaultsToArray(schema, v)
}
}
// applyDefaultsToArray fills defaults for array items, handling the schematic,
// tuple-in-items and 2020-12 prefixItems forms.
func applyDefaultsToArray(schema map[string]any, v []any) {
switch items := schema["items"].(type) {
case map[string]any:
for _, item := range v {
applyDefaultsToValue(items, item)
}
case []any:
for i, item := range v {
if i < len(items) {
if is, ok := items[i].(map[string]any); ok {
applyDefaultsToValue(is, item)
}
}
}
}
if prefix, ok := schema["prefixItems"].([]any); ok {
for i, item := range v {
if i < len(prefix) {
if is, ok := prefix[i].(map[string]any); ok {
applyDefaultsToValue(is, item)
}
}
}
}
}
// deepCopy returns a structurally independent copy of a JSON value so defaults
// taken from a compiled schema are not corrupted by later in-place coercion.
func deepCopy(v any) any {
switch vv := v.(type) {
case map[string]any:
out := make(map[string]any, len(vv))
for k, val := range vv {
out[k] = deepCopy(val)
}
return out
case []any:
out := make([]any, len(vv))
for i, item := range vv {
out[i] = deepCopy(item)
}
return out
default:
return vv
}
}