refactor: 模型错误解析改为 ErrorMessageMapping 配置驱动
- parseModelError 按模型配置的 ErrorMessageMapping(schema 树)解析错误响应 - 成功判定: code 提取为空 / code 节点配置 defaultValue 且提取值等于它 → 成功 - 未配置 mapping 不识别错误(纯配置驱动), 删硬编码 ModelErrorResp/ModelError1Resp - DTO 加 errorMessageMapping 字段, update.sql 建 error_message_mapping JSONB 列 - 单测: parse_error_test.go 覆盖扁平/嵌套/数组/纯字符串路径/未配置/坏 JSON
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package service
|
||||
|
||||
import "testing"
|
||||
|
||||
// flatMapping 扁平形态:code 配置 defaultValue=20000000 为成功码
|
||||
func flatMapping() map[string]any {
|
||||
return map[string]any{
|
||||
"code": map[string]any{
|
||||
"type": "number", "value": 0, "label": "", "fieldType": "number", "isForm": false, "required": false,
|
||||
"defaultValue": float64(20000000),
|
||||
},
|
||||
"message": map[string]any{
|
||||
"type": "string", "value": "", "label": "", "fieldType": "string", "isForm": false, "required": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorFlatDefaultValue(t *testing.T) {
|
||||
mapping := flatMapping()
|
||||
// 成功:code == defaultValue
|
||||
code, msg, err := parseModelError([]byte(`{"code":20000000,"message":"ok"}`), mapping)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("success(default): code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
// 成功:code 数字零值(视为空)
|
||||
code, msg, err = parseModelError([]byte(`{"code":0}`), mapping)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("success(zero): code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
// 错误:code != defaultValue
|
||||
code, msg, err = parseModelError([]byte(`{"code":1234,"message":"boom"}`), mapping)
|
||||
if err != nil || code != "1234" || msg != "boom" {
|
||||
t.Fatalf("error: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorNested(t *testing.T) {
|
||||
mapping := map[string]any{
|
||||
"error": map[string]any{
|
||||
"type": "object",
|
||||
"attrs": map[string]any{
|
||||
"code": map[string]any{"type": "string", "label": "", "value": "", "fieldType": "string", "isForm": false, "required": false},
|
||||
"message": map[string]any{"type": "string", "label": "", "value": "", "fieldType": "string", "isForm": false, "required": false},
|
||||
},
|
||||
"label": "", "isForm": false, "required": false, "fieldType": "string",
|
||||
},
|
||||
}
|
||||
// 错误响应
|
||||
code, msg, err := parseModelError([]byte(`{"error":{"code":"rate_limit_exceeded","message":"Too fast"}}`), mapping)
|
||||
if err != nil || code != "rate_limit_exceeded" || msg != "Too fast" {
|
||||
t.Fatalf("error: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
// 成功响应(无 error 字段 → code 提取为空)
|
||||
code, msg, err = parseModelError([]byte(`{"id":"x","choices":[]}`), mapping)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("success: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorArray(t *testing.T) {
|
||||
mapping := map[string]any{
|
||||
"errors": map[string]any{
|
||||
"type": "array",
|
||||
"attrs": []any{map[string]any{
|
||||
"code": map[string]any{"type": "number"},
|
||||
"message": map[string]any{"type": "string"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
code, msg, err := parseModelError([]byte(`{"errors":[{"code":1001,"message":"err-a"}]}`), mapping)
|
||||
if err != nil || code != "1001" || msg != "err-a" {
|
||||
t.Fatalf("array error: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
code, msg, err = parseModelError([]byte(`{"errors":[]}`), mapping)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("array success: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorPlainStringMapping(t *testing.T) {
|
||||
// 纯字符串路径形态:字段值直接是字段路径
|
||||
mapping := map[string]any{"code": "error.code", "message": "error.message"}
|
||||
code, msg, err := parseModelError([]byte(`{"error":{"code":100,"message":"nope"}}`), mapping)
|
||||
if err != nil || code != "100" || msg != "nope" {
|
||||
t.Fatalf("plain mapping: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
code, msg, err = parseModelError([]byte(`{"data":"ok"}`), mapping)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("plain success: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorNoMapping(t *testing.T) {
|
||||
// 未配置 mapping:不识别错误,一律成功(纯配置驱动)
|
||||
code, msg, err := parseModelError([]byte(`{"error":{"code":"x"}}`), nil)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("no mapping: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorBadJSON(t *testing.T) {
|
||||
mapping := map[string]any{"code": map[string]any{"type": "string"}}
|
||||
if _, _, err := parseModelError([]byte(`{invalid`), mapping); err == nil {
|
||||
t.Fatalf("bad json should err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModelErrorOnlyMessage(t *testing.T) {
|
||||
// 只配置 message:以 message 是否为空判定错误
|
||||
mapping := map[string]any{"message": map[string]any{"type": "string"}}
|
||||
code, msg, err := parseModelError([]byte(`{"message":"something went wrong"}`), mapping)
|
||||
if err != nil || code != "" || msg != "something went wrong" {
|
||||
t.Fatalf("only-message error: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
code, msg, err = parseModelError([]byte(`{"data":"ok"}`), mapping)
|
||||
if err != nil || code != "" || msg != "" {
|
||||
t.Fatalf("only-message success: code=%q msg=%q err=%v", code, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectErrorMapping(t *testing.T) {
|
||||
// 扁平 + defaultValue
|
||||
codePath, msgPath, hasDef, def := collectErrorMapping(flatMapping())
|
||||
if codePath != "code" || msgPath != "message" || !hasDef || def != float64(20000000) {
|
||||
t.Fatalf("flat: code=%q msg=%q hasDef=%v def=%v", codePath, msgPath, hasDef, def)
|
||||
}
|
||||
// 嵌套
|
||||
codePath, msgPath, hasDef, def = collectErrorMapping(map[string]any{
|
||||
"error": map[string]any{"type": "object", "attrs": map[string]any{
|
||||
"code": map[string]any{"type": "string"},
|
||||
"message": map[string]any{"type": "string"},
|
||||
}},
|
||||
})
|
||||
if codePath != "error.code" || msgPath != "error.message" || hasDef {
|
||||
t.Fatalf("nested: code=%q msg=%q hasDef=%v", codePath, msgPath, hasDef)
|
||||
}
|
||||
// 数组 → [*]
|
||||
codePath, _, _, _ = collectErrorMapping(map[string]any{
|
||||
"errors": map[string]any{"type": "array", "attrs": []any{
|
||||
map[string]any{"code": map[string]any{"type": "number"}},
|
||||
}},
|
||||
})
|
||||
if codePath != "errors[*].code" {
|
||||
t.Fatalf("array: code=%q", codePath)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user