- 引入 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。
82 lines
2.0 KiB
Go
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
|
|
}
|
|
}
|