dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts@v1.0.2/common/validator.go (about) 1 //go:build !no_dto_validator 2 3 // 4 // Copyright (C) 2020-2021 IOTech Ltd 5 // 6 // SPDX-License-Identifier: Apache-2.0 7 8 package common 9 10 import ( 11 "fmt" 12 "reflect" 13 "regexp" 14 "strings" 15 "time" 16 17 "github.com/go-playground/validator/v10" 18 "github.com/google/uuid" 19 20 "dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/errors" 21 ) 22 23 var val *validator.Validate 24 25 const ( 26 dtoDurationTag = "edgex-dto-duration" 27 dtoUuidTag = "edgex-dto-uuid" 28 dtoNoneEmptyStringTag = "edgex-dto-none-empty-string" 29 dtoValueType = "edgex-dto-value-type" 30 dtoRFC3986UnreservedCharTag = "edgex-dto-rfc3986-unreserved-chars" 31 emptyOrDtoRFC3986UnreservedCharTag = "len=0|" + dtoRFC3986UnreservedCharTag 32 dtoInterDatetimeTag = "edgex-dto-interval-datetime" 33 dtoAlphaNumericWithSymbols = "edgex-dto-alphanumeric-with-symbols" 34 ) 35 36 const ( 37 // Per https://tools.ietf.org/html/rfc3986#section-2.3, unreserved characters= ALPHA / DIGIT / "-" / "." / "_" / "~" 38 // Also due to names used in topics for Redis Pub/Sub, "."are not allowed 39 rFC3986UnreservedCharsRegexString = "^[a-zA-Z0-9-_~:;=]+$" 40 intervalDatetimeLayout = "20060102T150405" 41 name = "Name" 42 alphaNumericWithSymbols = "^[a-zA-Z0-9-_~:;=+ ]+$" 43 ) 44 45 var ( 46 rFC3986UnreservedCharsRegex = regexp.MustCompile(rFC3986UnreservedCharsRegexString) 47 alphaNumericWithSymbolsRegex = regexp.MustCompile(alphaNumericWithSymbols) 48 ) 49 50 func init() { 51 val = validator.New() 52 _ = val.RegisterValidation(dtoDurationTag, ValidateDuration) 53 _ = val.RegisterValidation(dtoUuidTag, ValidateDtoUuid) 54 _ = val.RegisterValidation(dtoNoneEmptyStringTag, ValidateDtoNoneEmptyString) 55 _ = val.RegisterValidation(dtoValueType, ValidateValueType) 56 _ = val.RegisterValidation(dtoRFC3986UnreservedCharTag, ValidateDtoRFC3986UnreservedChars) 57 _ = val.RegisterValidation(dtoInterDatetimeTag, ValidateIntervalDatetime) 58 _ = val.RegisterValidation(dtoAlphaNumericWithSymbols, ValidateDtoAlphaNumericWithSymbols) 59 } 60 61 // Validate function will use the validator package to validate the struct annotation 62 func Validate(a interface{}) error { 63 err := val.Struct(a) 64 // translate all error at once 65 if err != nil { 66 errs := err.(validator.ValidationErrors) 67 var errMsg []string 68 for _, e := range errs { 69 errMsg = append(errMsg, getErrorMessage(e)) 70 } 71 return errors.NewCommonEdgeX(errors.KindContractInvalid, strings.Join(errMsg, "; "), nil) 72 } 73 return nil 74 } 75 76 // Internal: generate representative validation error messages 77 func getErrorMessage(e validator.FieldError) string { 78 tag := e.Tag() 79 // StructNamespace returns the namespace for the field error, with the field's actual name. 80 fieldName := e.StructNamespace() 81 fieldValue := e.Param() 82 var msg string 83 switch tag { 84 case "uuid": 85 msg = fmt.Sprintf("%s field needs a uuid", fieldName) 86 case "required": 87 msg = fmt.Sprintf("%s field is required", fieldName) 88 case "required_without": 89 msg = fmt.Sprintf("%s field is required if the %s is not present", fieldName, fieldValue) 90 case "len": 91 msg = fmt.Sprintf("The length of %s field is not %s", fieldName, fieldValue) 92 case "oneof": 93 msg = fmt.Sprintf("%s field should be one of %s", fieldName, fieldValue) 94 case "gt": 95 msg = fmt.Sprintf("%s field should greater than %s", fieldName, fieldValue) 96 case dtoDurationTag: 97 msg = fmt.Sprintf("%s field should follows the ISO 8601 Durations format. Eg,100ms, 24h", fieldName) 98 case dtoUuidTag: 99 msg = fmt.Sprintf("%s field needs a uuid", fieldName) 100 case dtoNoneEmptyStringTag: 101 msg = fmt.Sprintf("%s field should not be empty string", fieldName) 102 case dtoRFC3986UnreservedCharTag, emptyOrDtoRFC3986UnreservedCharTag: 103 msg = fmt.Sprintf("%s field only allows unreserved characters which are ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_~:;=", fieldName) 104 default: 105 msg = fmt.Sprintf("%s field validation failed on the %s tag", fieldName, tag) 106 } 107 return msg 108 } 109 110 // ValidateDuration validate field which should follow the ISO 8601 Durations format 111 func ValidateDuration(fl validator.FieldLevel) bool { 112 _, err := time.ParseDuration(fl.Field().String()) 113 return err == nil 114 } 115 116 // ValidateDtoUuid used to check the UpdateDTO uuid pointer value 117 // Currently, required_without can not correct work with other tag, so write custom tag instead. 118 // Issue can refer to https://github.com/go-playground/validator/issues/624 119 func ValidateDtoUuid(fl validator.FieldLevel) bool { 120 idField := fl.Field() 121 // Skip the validation if the pointer value is nil 122 if isNilPointer(idField) { 123 return true 124 } 125 126 // The Id field should accept the empty string if the Name field is provided 127 nameField := fl.Parent().FieldByName(name) 128 if len(strings.TrimSpace(idField.String())) == 0 && !isNilPointer(nameField) && len(nameField.Elem().String()) > 0 { 129 return true 130 } 131 132 _, err := uuid.Parse(idField.String()) 133 return err == nil 134 } 135 136 // ValidateDtoNoneEmptyString used to check the UpdateDTO name pointer value 137 func ValidateDtoNoneEmptyString(fl validator.FieldLevel) bool { 138 val := fl.Field() 139 // Skip the validation if the pointer value is nil 140 if isNilPointer(val) { 141 return true 142 } 143 // The string value should not be empty 144 if len(strings.TrimSpace(val.String())) > 0 { 145 return true 146 } else { 147 return false 148 } 149 } 150 151 // ValidateValueType checks whether the valueType is valid 152 func ValidateValueType(fl validator.FieldLevel) bool { 153 valueType := fl.Field().String() 154 for _, v := range valueTypes { 155 if strings.EqualFold(valueType, v) { 156 return true 157 } 158 } 159 return false 160 } 161 162 // ValidateDtoRFC3986UnreservedChars used to check if DTO's name pointer value only contains unreserved characters as 163 // defined in https://tools.ietf.org/html/rfc3986#section-2.3 164 func ValidateDtoRFC3986UnreservedChars(fl validator.FieldLevel) bool { 165 val := fl.Field() 166 // Skip the validation if the pointer value is nil 167 if isNilPointer(val) { 168 return true 169 } else { 170 return rFC3986UnreservedCharsRegex.MatchString(val.String()) 171 } 172 } 173 174 // ValidateIntervalDatetime validate Interval's datetime field which should follow the ISO 8601 format YYYYMMDD'T'HHmmss 175 func ValidateIntervalDatetime(fl validator.FieldLevel) bool { 176 _, err := time.Parse(intervalDatetimeLayout, fl.Field().String()) 177 return err == nil 178 } 179 180 func isNilPointer(value reflect.Value) bool { 181 return value.Kind() == reflect.Ptr && value.IsNil() 182 } 183 184 func ValidateDtoAlphaNumericWithSymbols(fl validator.FieldLevel) bool { 185 val := fl.Field() 186 // Skip the validation if the pointer value is nil 187 if isNilPointer(val) { 188 return true 189 } else { 190 return alphaNumericWithSymbolsRegex.MatchString(val.String()) 191 } 192 }