package resultcontract import ( "encoding/json" "fmt" "regexp" "strings" ) var keyPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`) type Schema struct { Fields []Field `json:"fields"` } type Field struct { Key string `json:"key"` Name string `json:"name"` Type string `json:"type"` Required bool `json:"required"` Options []any `json:"options,omitempty"` Fields []Field `json:"fields,omitempty"` Items *Field `json:"items,omitempty"` Min *float64 `json:"min,omitempty"` Max *float64 `json:"max,omitempty"` Default interface{} `json:"default,omitempty"` } func ParseAndValidate(raw []byte) (Schema, error) { var schema Schema if len(raw) == 0 { return schema, nil } if err := json.Unmarshal(raw, &schema); err != nil { return schema, fmt.Errorf("result_schema 格式不正确") } if err := validateFields(schema.Fields, 0); err != nil { return schema, err } return schema, nil } func ValidateSchema(value map[string]interface{}) error { raw, err := json.Marshal(value) if err != nil { return fmt.Errorf("result_schema 格式不正确") } _, err = ParseAndValidate(raw) return err } // ValidateResult permits nil and an empty object so a run can finish without a business outcome. func ValidateResult(schema Schema, value map[string]interface{}) error { if len(value) == 0 { return nil } return validateObject(schema.Fields, value, "final_result") } func validateFields(fields []Field, depth int) error { if depth > 8 { return fmt.Errorf("result_schema 嵌套层级不能超过 8 层") } seen := map[string]bool{} for _, field := range fields { if !keyPattern.MatchString(field.Key) || seen[field.Key] { return fmt.Errorf("结果字段标识必须唯一且格式正确") } seen[field.Key] = true if field.Name == "" { return fmt.Errorf("结果字段 %s 缺少名称", field.Key) } if !supportedType(field.Type) { return fmt.Errorf("结果字段 %s 使用了不支持的类型 %s", field.Key, field.Type) } if field.Type == "object" { if len(field.Fields) == 0 { return fmt.Errorf("对象字段 %s 必须定义 fields", field.Key) } if err := validateFields(field.Fields, depth+1); err != nil { return err } } if field.Type == "array" && field.Items != nil { item := *field.Items if item.Type == "" { return fmt.Errorf("数组字段 %s 的 items 必须定义类型", field.Key) } if item.Type == "object" { if len(item.Fields) == 0 { return fmt.Errorf("数组字段 %s 的对象项必须定义 fields", field.Key) } if err := validateFields(item.Fields, depth+1); err != nil { return err } } else if !supportedType(item.Type) || item.Type == "array" { return fmt.Errorf("数组字段 %s 的 items 类型不正确", field.Key) } } } return nil } func supportedType(value string) bool { switch value { case "string", "text", "textarea", "number", "integer", "boolean", "select", "multiselect", "date", "array", "object", "any": return true default: return false } } func validateObject(fields []Field, value map[string]interface{}, path string) error { definitions := make(map[string]Field, len(fields)) for _, field := range fields { definitions[field.Key] = field if field.Required { if item, ok := value[field.Key]; !ok || item == nil || item == "" { return fmt.Errorf("%s.%s 为必填字段", path, field.Key) } } } for key, item := range value { field, ok := definitions[key] if !ok { return fmt.Errorf("%s.%s 未在结果格式中定义", path, key) } if err := validateValue(field, item, path+"."+key); err != nil { return err } } return nil } func validateValue(field Field, value interface{}, path string) error { if value == nil { if field.Required { return fmt.Errorf("%s 为必填字段", path) } return nil } switch field.Type { case "string", "text", "textarea", "select", "date": text, ok := value.(string) if !ok { return fmt.Errorf("%s 必须是字符串", path) } if field.Type == "select" && len(field.Options) > 0 && !contains(field.Options, text) { return fmt.Errorf("%s 不在允许选项中", path) } case "number", "integer": number, ok := value.(float64) if !ok || (field.Type == "integer" && number != float64(int64(number))) { return fmt.Errorf("%s 必须是%s", path, map[bool]string{true: "整数", false: "数字"}[field.Type == "integer"]) } if field.Min != nil && number < *field.Min || field.Max != nil && number > *field.Max { return fmt.Errorf("%s 超出允许范围", path) } case "boolean": if _, ok := value.(bool); !ok { return fmt.Errorf("%s 必须是布尔值", path) } case "object": object, ok := value.(map[string]interface{}) if !ok { return fmt.Errorf("%s 必须是对象", path) } return validateObject(field.Fields, object, path) case "array", "multiselect": items, ok := value.([]interface{}) if !ok { return fmt.Errorf("%s 必须是数组", path) } for index, item := range items { if field.Type == "multiselect" && len(field.Options) > 0 && !contains(field.Options, item) { return fmt.Errorf("%s[%d] 不在允许选项中", path, index) } if field.Items != nil { if err := validateValue(*field.Items, item, fmt.Sprintf("%s[%d]", path, index)); err != nil { return err } } } } return nil } func contains(options []any, value interface{}) bool { want := fmt.Sprint(value) for _, option := range options { if strings.EqualFold(fmt.Sprint(option), want) { return true } } return false }