package service import ( "encoding/json" "reflect" modelUtils "model-gateway/service/utils" "github.com/gogf/gf/v2/util/gconv" ) // parseModelError 按模型配置的 ErrorMessageMapping 解析错误响应,返回错误码与错误消息(无错误均返回空串)。 // ErrorMessageMapping 为 schema 树(与请求模板同格式:type/value/label/isForm/required/fieldType/defaultValue/attrs), // 解析时剔除包装字段取 code/message 的字段路径(复用 normalizeSchemaPath 归一 attrs/数组段), // 经 GetByPath 从响应体提取。成功判定: // - code 提取值为空(nil/空串/数字零)→ 成功 // - code 节点配置了 defaultValue 且提取值等于它 → 成功 // - 否则 → 错误(返回 code + message) // // 未配置 ErrorMessageMapping → 不识别错误,一律返回成功(纯配置驱动)。 // 解析失败返回 err,由调用方决定重试/终态。 func parseModelError(body []byte, mapping map[string]any) (code, msg string, err error) { if len(mapping) == 0 { return "", "", nil } var respObj map[string]any if err = json.Unmarshal(body, &respObj); err != nil { return "", "", err } codePath, msgPath, hasCodeDefault, codeDefault := collectErrorMapping(mapping) codeVal := modelUtils.GetByPathValue(respObj, codePath) msgVal := modelUtils.GetByPathValue(respObj, msgPath) if codePath != "" { if isEmptyErrorCode(codeVal) { return "", "", nil } if hasCodeDefault && sameErrorValue(codeVal, codeDefault) { return "", "", nil } return gconv.String(codeVal), gconv.String(msgVal), nil } // 未配置 code 路径:以 message 是否为空判定错误 if !isEmptyErrorCode(msgVal) { return "", gconv.String(msgVal), nil } return "", "", nil } // collectErrorMapping 遍历 ErrorMessageMapping schema 树,提取 code/message 的字段路径与 code 节点的 defaultValue。 // 支持 schema 节点(含 type/attrs 等包装)与纯字符串路径两种形态;数组段经 normalizeSchemaPath 归一到 [*]。 // 返回路径为已归一字段路径;未配置返回空串。 func collectErrorMapping(mapping map[string]any) (codePath, msgPath string, hasCodeDefault bool, codeDefault any) { var walk func(node map[string]any, prefix string) walk = func(node map[string]any, prefix string) { for key, val := range node { if isErrorMetaKey(key) { continue } m, ok := val.(map[string]any) if !ok { // 纯值形态:字段值直接是字段路径字符串 if s, ok := val.(string); ok { path := normalizeSchemaPath(joinErrorFieldPath(prefix, s)) if key == "code" && codePath == "" { codePath = path } else if key == "message" && msgPath == "" { msgPath = path } } continue } nodeType, _ := m["type"].(string) hasDef := false var def any if d, has := m["defaultValue"]; has && d != nil { hasDef, def = true, d } switch { case nodeType == "object": if attrs, ok := m["attrs"].(map[string]any); ok { walk(attrs, joinErrorFieldPath(prefix, key)) } else if attrs, ok := m["attrs"].([]any); ok && len(attrs) > 0 { if elm, ok := attrs[0].(map[string]any); ok { walkErrorArrayElement(elm, joinErrorArrayPath(prefix, key), walk) } } else { walk(m, joinErrorFieldPath(prefix, key)) } case nodeType == "array": walkErrorArrayContainer(m, joinErrorArrayPath(prefix, key), walk) case nodeType == "": // 无 type 键:纯容器(字段直接作为键),递归下钻 walk(m, joinErrorFieldPath(prefix, key)) default: // 标量叶子字段 path := normalizeSchemaPath(joinErrorFieldPath(prefix, key)) if key == "code" && codePath == "" { codePath, hasCodeDefault, codeDefault = path, hasDef, def } else if key == "message" && msgPath == "" { msgPath = path } } } } walk(mapping, "") return } // walkErrorArrayContainer 数组节点:子字段容器依次尝试 attrs([]any)/enumValues/value([]any) func walkErrorArrayContainer(node map[string]any, prefix string, walk func(map[string]any, string)) { for _, container := range []string{"attrs", "enumValues", "value"} { items, ok := node[container].([]any) if !ok || len(items) == 0 { continue } if elm, ok := items[0].(map[string]any); ok { walkErrorArrayElement(elm, prefix, walk) } return } } // walkErrorArrayElement 数组元素:字段在其 attrs 下(对象元素)或直接作为键(纯元素) func walkErrorArrayElement(elm map[string]any, prefix string, walk func(map[string]any, string)) { if attrs, ok := elm["attrs"].(map[string]any); ok { walk(attrs, prefix) return } walk(elm, prefix) } // isErrorMetaKey 判断是否为 schema 节点元数据字段(非业务字段,剔除) func isErrorMetaKey(key string) bool { switch key { case "type", "value", "label", "isForm", "required", "fieldType", "defaultValue", "description": return true } return false } // joinErrorFieldPath 拼接字段路径(数组段 [0] 由 normalizeSchemaPath 归一为 [*]) func joinErrorFieldPath(prefix, key string) string { if prefix == "" { return key } return prefix + "." + key } // joinErrorArrayPath 数组字段路径:元素下标 [0] 由 normalizeSchemaPath 归一为 [*] func joinErrorArrayPath(prefix, key string) string { if prefix == "" { return key + "[0]" } return prefix + "." + key + "[0]" } // isEmptyErrorCode 判断错误码是否为空(空=无错误):nil / 空串 / 布尔 false / 数字零值 func isEmptyErrorCode(v any) bool { if v == nil { return true } switch t := v.(type) { case string: return t == "" case bool: return !t } if isNumericType(v) { return gconv.Float64(v) == 0 } return false } // sameErrorValue 判断提取值是否等于配置的 defaultValue(数值/字符串跨类型兼容) func sameErrorValue(a, b any) bool { if isNumericType(a) && isNumericType(b) { return gconv.Float64(a) == gconv.Float64(b) } return gconv.String(a) == gconv.String(b) } // isNumericType 判断是否为数值类型 func isNumericType(v any) bool { if v == nil { return false } switch reflect.TypeOf(v).Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: return true } return false }