// Package validator ... // // Description : validator ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2024-04-29 10:51 package validator import ( "bytes" "encoding/json" "fmt" "git.zhangdeman.cn/gateway/validator/define" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/serialize" "git.zhangdeman.cn/zhangdeman/util" "git.zhangdeman.cn/zhangdeman/wrapper" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "reflect" "strings" ) // RunForStruct 验证结构体 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:18 2024/4/29 func RunForStruct(sourceData any, ruleList []*define.FieldRule) (map[string]any, error) { if nil == sourceData { return map[string]any{}, nil } byteData, _ := json.Marshal(sourceData) var ( sourceMapData map[string]any err error ) d := json.NewDecoder(bytes.NewReader(byteData)) d.UseNumber() if err = d.Decode(&sourceMapData); nil != err { return nil, err } if err = Run(sourceMapData, ruleList); nil != err { return nil, err } return sourceMapData, nil } // Run 运行参数验证 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:13 2024/4/29 func Run(sourceData map[string]any, ruleList []*define.FieldRule) error { if nil == sourceData || len(sourceData) == 0 { return nil } byteData, _ := json.Marshal(sourceData) for _, itemRule := range ruleList { if len(itemRule.Path) == 0 { // 未指定验证数据位置 continue } inputFieldVal := gjson.GetBytes(byteData, itemRule.Path) if formatRule, err := validate(byteData, inputFieldVal, itemRule); nil != err { return err } else { if !itemRule.DisableRewrite { // 更新数据 byteData, _ = sjson.SetBytes(byteData, itemRule.Path, formatRule) } } } sourceData = make(map[string]any) d := json.NewDecoder(bytes.NewReader(byteData)) d.UseNumber() if err := d.Decode(&sourceData); nil != err { return err } return nil } // getDataStatus 获取数据状态 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:59 2024/4/29 func getDataStatus(val gjson.Result, dataType string) string { if !val.Exists() { return consts.DataStatusNotFound } switch dataType { case consts.DataTypeString: if len(val.String()) == 0 { return consts.DataStatusIsEmpty } case consts.DataTypeFloat: fallthrough case consts.DataTypeInt: if val.Float() == 0 { return consts.DataStatusIsZero } default: if strings.HasPrefix(dataType, "[]") { // 数组 if len(val.Array()) == 0 { return consts.DataStatusIsEmpty } } else if strings.HasPrefix(dataType, "map") { // 对象 if len(val.Map()) == 0 { return consts.DataStatusIsEmpty } } } return "" } // validate 验证字段 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:32 2024/4/29 func validate(sourceData []byte, val gjson.Result, rule *define.FieldRule) (any, error) { inputVal := val.Value() if !val.Exists() { if rule.IsRequired { return nil, fmt.Errorf("%v : field is required, but not found", rule.Path) } // TODO : 验证有条件必传 inputVal = rule.DefaultValue } else { if inputVal == nil { if !rule.AllowNil { return nil, fmt.Errorf("%v : field value is nil, but not allowed", rule.Path) } inputVal = rule.DefaultValue } } return handleData(inputVal, rule) } // handleData 处理数据 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:43 2024/4/29 func handleData(inputVal any, rule *define.FieldRule) (any, error) { switch rule.Type { case consts.DataTypeAny: // 任意类型 return inputVal, nil case consts.DataTypeFloat: // float数据 return handleFloat(inputVal, rule) case consts.DataTypeInt: // int类型 return handleInt(inputVal, rule) case consts.DataTypeUint: return handleUint(inputVal, rule) case consts.DataTypeString: // 字符串处理 return handleString(inputVal, rule) case consts.DataTypeBool: return handleBool(inputVal, rule) case consts.DataTypeMapStrFloat, consts.DataTypeMapStrBool, consts.DataTypeMapStrInt, consts.DataTypeMapStrUint: // 一律按照 map[string]float64处理 return handleMapStringFloat(inputVal, rule) case consts.DataTypeMapStrAny: // 对象结构 return handleMapStringAny(inputVal, rule) case consts.DataTypeMapStrSlice: // map列表 return handleMapStringSlice(inputVal, rule) case consts.DataTypeMapAnyAny: // 任意类型map return handleMapAnyAny(inputVal, rule) case consts.DataTypeSliceUint, consts.DataTypeSliceUintWithChar: // uint数组处理 } return nil, nil } // handleFloat 处理float数据 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:29 2024/4/29 func handleFloat(inputVal any, rule *define.FieldRule) (float64, error) { var ( err error formatData float64 ) if err = util.ConvertAssign(&formatData, inputVal); nil != err { return 0, err } if !rule.AllowZero { return 0, fmt.Errorf("%v : field type is float, but zero val is not allowed", rule.Path) } if nil == rule.ValueLimit { return formatData, nil } if nil != rule.ValueLimit.Min && formatData < *rule.ValueLimit.Min { return 0, fmt.Errorf("%v : field type is float, min val is %v, real val is %v", rule.Path, *rule.ValueLimit.Min, formatData) } if nil != rule.ValueLimit.Max && formatData > *rule.ValueLimit.Max { return 0, fmt.Errorf("%v : field type is float, max val is %v, real val is %v", rule.Path, *rule.ValueLimit.Max, formatData) } if len(rule.ValueLimit.EnumList) > 0 { if wrapper.ArrayType(rule.ValueLimit.EnumList).Has(formatData) < 0 { return 0, fmt.Errorf("%v : field type is float, real val is %v, is not in enum list", rule.Path, formatData) } } return formatData, nil } // handleInt ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:36 2024/4/29 func handleInt(inputVal any, rule *define.FieldRule) (int64, error) { var ( err error formatData int64 ) if err = util.ConvertAssign(&formatData, inputVal); nil != err { return 0, err } if _, err = handleFloat(formatData, rule); nil != err { return 0, err } return formatData, nil } // handleUint ... // // Author : go_developer@163.com<白茶清欢> // // Date : 21:28 2024/4/30 func handleUint(inputVal any, rule *define.FieldRule) (uint64, error) { var ( err error formatData uint64 ) if err = util.ConvertAssign(&formatData, inputVal); nil != err { return 0, err } if _, err = handleFloat(formatData, rule); nil != err { return 0, err } return formatData, nil } // handleBool... // // Author : go_developer@163.com<白茶清欢> // // Date : 21:26 2024/4/30 func handleBool(inputVal any, rule *define.FieldRule) (bool, error) { var ( err error formatData bool ) if err = util.ConvertAssign(&formatData, inputVal); nil != err { return 0, err } if _, err = handleFloat(formatData, rule); nil != err { return false, err } return formatData, nil } // handleString ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:53 2024/4/29 func handleString(inputVal any, rule *define.FieldRule) (string, error) { var ( err error formatData string ) if err = util.ConvertAssign(&formatData, inputVal); nil != err { return "", err } if nil == rule.ValueLimit { return formatData, nil } if nil != rule.ValueLimit.Min { if float64(len(formatData)) < *rule.ValueLimit.Min { return "", fmt.Errorf("%v : data type is string, min length is %v, real length is %v", rule.Path, *rule.ValueLimit.Min, len(formatData)) } } if nil != rule.ValueLimit.Max { if float64(len(formatData)) >= *rule.ValueLimit.Max { return "", fmt.Errorf("%v : data type is string, max length is %v, real length is %v", rule.Path, *rule.ValueLimit.Max, len(formatData)) } } if len(rule.ValueLimit.EnumList) > 0 { if wrapper.ArrayType(rule.ValueLimit.EnumList).Has(formatData) < 0 { return "", fmt.Errorf("%v : data type is string, not in enum list, real val is %v", rule.Path, formatData) } } return formatData, nil } // handleMapStringFloat ... // // Author : go_developer@163.com<白茶清欢> // // Date : 16:38 2024/4/29 func handleMapStringFloat(inputVal any, rule *define.FieldRule) (map[string]float64, error) { var ( err error res map[string]float64 ) if err = strOrMapConvert(inputVal, &res); nil != err { return nil, err } dataFieldTable := make(map[string]string) for k, _ := range res { dataFieldTable[k] = k } if err = validateMap(dataFieldTable, rule); nil != err { return nil, err } return res, nil } // handleMapStringAny ... // // Author : go_developer@163.com<白茶清欢> // // Date : 17:19 2024/4/29 func handleMapStringAny(inputVal any, rule *define.FieldRule) (map[string]any, error) { var ( err error res map[string]any ) if err = strOrMapConvert(inputVal, &res); nil != err { return nil, err } dataFieldTable := make(map[string]string) for k, _ := range res { dataFieldTable[k] = k } if err = validateMap(dataFieldTable, rule); nil != err { return nil, err } return res, nil } // handleMapStringSlice... // // Author : go_developer@163.com<白茶清欢> // // Date : 17:42 2024/4/29 func handleMapStringSlice(inputVal any, rule *define.FieldRule) (map[string][]any, error) { var ( err error res map[string][]any ) if err = strOrMapConvert(inputVal, &res); nil != err { return nil, err } dataFieldTable := make(map[string]string) for k, _ := range res { dataFieldTable[k] = k } if err = validateMap(dataFieldTable, rule); nil != err { return nil, err } return res, nil } // handleMapAnyAny map[any]any处理 // // Author : go_developer@163.com<白茶清欢> // // Date : 17:33 2024/4/29 func handleMapAnyAny(inputVal any, rule *define.FieldRule) (map[any]any, error) { var ( err error res = map[any]any{} jsonRes gjson.Result ) if inputValStr, ok := inputVal.(string); ok { jsonRes = gjson.Parse(inputValStr) } else { jsonRes = gjson.Parse(serialize.JSON.MarshalForString(inputVal)) } if !jsonRes.IsObject() { return nil, fmt.Errorf("%v : is not a map", rule.Path) } fieldTable := make(map[string]string) jsonRes.ForEach(func(key, value gjson.Result) bool { fieldTable[key.String()] = key.String() res[key.Value()] = value.Value() return true }) if err = validateMap(fieldTable, rule); nil != err { return nil, err } return res, nil } // strOrMapConvert 字符串或map转map // // Author : go_developer@163.com<白茶清欢> // // Date : 17:26 2024/4/29 func strOrMapConvert(inputVal any, receiver any) error { var ( err error ) if inputValStr, ok := inputVal.(string); ok { if err = serialize.JSON.UnmarshalWithNumber([]byte(inputValStr), receiver); nil != err { return err } } else { byteData := serialize.JSON.MarshalForByte(inputVal) if err = serialize.JSON.UnmarshalWithNumber(byteData, receiver); nil != err { return err } } return nil } // validateMap 验证map数据 // // Author : go_developer@163.com<白茶清欢> // // Date : 17:21 2024/4/29 func validateMap(dataFieldTable map[string]string, rule *define.FieldRule) error { if nil == rule.ValueLimit { return nil } if nil != rule.ValueLimit.Min && float64(len(dataFieldTable)) < *rule.ValueLimit.Min { return fmt.Errorf("%v : data type is map, min item cnt is %v, real item cnt is %v", rule.Path, *rule.ValueLimit.Min, len(dataFieldTable)) } if nil != rule.ValueLimit.Max && float64(len(dataFieldTable)) >= *rule.ValueLimit.Max { return fmt.Errorf("%v : data type is map, max item cnt is %v, real item max is %v", rule.Path, *rule.ValueLimit.Max, len(dataFieldTable)) } if nil != rule.ValueLimit.Map { for _, itemField := range rule.ValueLimit.Map.IncludeFieldList { if _, exist := dataFieldTable[itemField]; !exist { return fmt.Errorf("%v : data type is map, %v field is required, but not found", rule.Path, itemField) } } } return nil } // handleSlice 数组处理 // // Author : go_developer@163.com<白茶清欢> // // Date : 21:30 2024/4/30 func handleSlice(inputValue interface{}, rule *define.FieldRule) ([]any, error) { inputValType := reflect.TypeOf(inputValue).Kind() if inputValType != reflect.Slice && inputValType != reflect.String { return nil, fmt.Errorf("%v : data type is expect slice or string, but get %v", rule.Path, inputValType.String()) } if inputValType == reflect.Slice { inputValue = serialize.JSON.MarshalForString(inputValue) // 重置配置 if nil == rule.SliceConfig { rule.SliceConfig = &define.SliceConfig{ Mode: consts.DataSliceModelMarshal, DisableAutoConvert: false, SplitChar: "", } } else { rule.SliceConfig.Mode = consts.DataSliceModelMarshal } } if inputStr, ok := inputValue.(string); ok { return handleSliceString(inputStr, rule) } return nil, fmt.Errorf("%v : data type is expect slice or string, but get %v", rule.Path, inputValType.String()) } // handleSliceString ... // // Author : go_developer@163.com<白茶清欢> // // Date : 22:06 2024/4/30 func handleSliceString(inputStr string, rule *define.FieldRule) ([]any, error) { if nil == rule.SliceConfig { return nil, fmt.Errorf("%v : data type is slice, but get string", rule.Path) } if rule.SliceConfig.Mode == consts.DataSliceModelReal { return nil, fmt.Errorf("%v : data type expect real slice, but get string", rule.Path) } if rule.SliceConfig.Mode == consts.DataSliceModelMarshal { // json序列化之后的 var ( err error res []any ) if err = serialize.JSON.UnmarshalWithNumber([]byte(inputStr), &res); nil != err { return nil, fmt.Errorf("%v : data type expect marshal slice, but can not convert", rule.Path) } return res, nil } if rule.SliceConfig.Mode == consts.DataSliceModelWithSplitChar { // 指定字符串切割 strArr := strings.Split(inputStr, rule.SliceConfig.SplitChar) anyArr := make([]any, 0) for _, item := range strArr { anyArr = append(anyArr, item) } return anyArr, nil } return nil, fmt.Errorf("%v : data type is slice, but rule mode [%v] is not supported", rule.Path, rule.SliceConfig.Mode) }