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。
This commit is contained in:
1 parent
24d5924cee
commit
464ed2cad3
6 files changed
+718
-156
No files matched your search
@@ -0,0 +1,81 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user