-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added Protobuf and JSON struct validator
* Added Protobuf and JSON struct validator through reflection based on their tags * Forked from 'github.com/pixel-plaza-dev/uru-databases-2-go-service-common'
- Loading branch information
1 parent
421e35c
commit a6655f9
Showing
10 changed files
with
638 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package field | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var ( | ||
InvalidBirthdateError = errors.New("invalid birthdate") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package mail | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var ( | ||
InvalidMailAddressError = errors.New("invalid mail address") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package mail | ||
|
||
import ( | ||
"net/mail" | ||
) | ||
|
||
// ValidMailAddress checks if the mail address is valid | ||
func ValidMailAddress(address string) (string, error) { | ||
// Check if the mail address is empty | ||
if address == "" { | ||
return "", InvalidMailAddressError | ||
} | ||
|
||
// Check if the mail address is valid | ||
addr, err := mail.ParseAddress(address) | ||
if err != nil { | ||
return "", InvalidMailAddressError | ||
} | ||
|
||
return addr.Address, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/ralvarezdev/go-validator | ||
|
||
go 1.23.4 | ||
|
||
require github.com/ralvarezdev/go-flags v0.2.0 // indirect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
github.com/ralvarezdev/go-flags v0.1.0 h1:WG6eMbS2yL+UYtI3X8R/HbisrfEgzLiMKMWqXanwyys= | ||
github.com/ralvarezdev/go-flags v0.1.0/go.mod h1:pYw9H7NJ07Y5asZDC/EI5bpBLR0kdL2ISsh6X5ws+3s= | ||
github.com/ralvarezdev/go-flags v0.2.0/go.mod h1:pYw9H7NJ07Y5asZDC/EI5bpBLR0kdL2ISsh6X5ws+3s= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package mapper | ||
|
||
var ( | ||
MissingProtobufTagError = "missing protobuf tag: %s" | ||
MissingProtobufTagNameError = "missing protobuf tag name: %s" | ||
DuplicateProtobufTagNameError = "duplicate protobuf tag name: %s" | ||
MissingJSONTagError = "missing json tag: %s" | ||
EmptyJSONTagError = "empty json tag: %s" | ||
DuplicateJSONTagNameError = "duplicate json tag name: %s" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
package mapper | ||
|
||
import ( | ||
"fmt" | ||
goflagsmode "github.com/ralvarezdev/go-flags/mode" | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
// Protobuf fields generated by the protoc compiler | ||
const ( | ||
State = "state" | ||
SizeCache = "sizeCache" | ||
UnknownFields = "unknownFields" | ||
ProtobufTag = "protobuf" | ||
ProtobufOneOf = "oneof" | ||
ProtobufNamePrefix = "name=" | ||
JSONTag = "json" | ||
JSONOmitempty = "omitempty" | ||
) | ||
|
||
// Mapper is a map of fields to validate from a struct | ||
type Mapper struct { | ||
Fields map[string]string // Key is the field name and value is the name used in the validation error | ||
NestedMappers map[string]*Mapper | ||
} | ||
|
||
// CreateProtobufMapper creates the fields to validate from a Protobuf compiled struct | ||
func CreateProtobufMapper(structInstance interface{}, mode *goflagsmode.Flag) ( | ||
*Mapper, | ||
error, | ||
) { | ||
// Reflection of data | ||
typeReflection := reflect.TypeOf(structInstance) | ||
|
||
// If data is a pointer, dereference it | ||
if typeReflection.Kind() == reflect.Ptr { | ||
typeReflection = typeReflection.Elem() | ||
} | ||
|
||
// Initialize the map fields and the map of nested mappers | ||
rootMapper := make(map[string]string) | ||
rootNestedMappers := make(map[string]*Mapper) | ||
|
||
// Reflection of the type of data | ||
var protobufTag string | ||
var protobufName string | ||
for i := 0; i < typeReflection.NumField(); i++ { | ||
// Get the field type through reflection | ||
field := typeReflection.Field(i) | ||
|
||
// Check if the field is a protoc generated field | ||
if field.Name == State || field.Name == SizeCache || field.Name == UnknownFields { | ||
continue | ||
} | ||
|
||
// Print field on debug mode | ||
fieldType := field.Type | ||
if mode != nil && mode.IsDebug() { | ||
fmt.Printf("field '%v' type: %v\n", field.Name, fieldType) | ||
} | ||
|
||
// Check if the field is a pointer | ||
if fieldType.Kind() != reflect.Ptr { | ||
// Get the Protobuf tag of the field | ||
protobufTag = field.Tag.Get(ProtobufTag) | ||
if protobufTag == "" { | ||
return nil, fmt.Errorf(MissingProtobufTagError, field.Name) | ||
} | ||
} else { | ||
fieldType = fieldType.Elem() | ||
|
||
// Check if the element type is not a struct, which would mean that it is an optional scalar type | ||
if fieldType.Kind() != reflect.Struct { | ||
continue | ||
} | ||
|
||
// Get the Protobuf tag of the field | ||
protobufTag = field.Tag.Get(ProtobufTag) | ||
if protobufTag == "" { | ||
return nil, fmt.Errorf(MissingProtobufTagError, field.Name) | ||
} | ||
|
||
// Check the tag to determine if it contains 'oneof', which means it is an optional struct field | ||
if ok := strings.Contains(protobufTag, ProtobufOneOf); ok { | ||
continue | ||
} | ||
|
||
// Create a new Mapper for the nested struct field | ||
fieldNestedMapper, err := CreateProtobufMapper( | ||
reflect.New(fieldType).Interface(), | ||
mode, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Add the nested fields to the map | ||
rootNestedMappers[field.Name] = fieldNestedMapper | ||
} | ||
|
||
// Get the field name from the Protobuf tag | ||
tagParts := strings.Split(protobufTag, ",") | ||
protobufName = "" | ||
for _, part := range tagParts { | ||
if strings.HasPrefix(part, ProtobufNamePrefix) { | ||
protobufName = strings.TrimPrefix(part, ProtobufNamePrefix) | ||
break | ||
} | ||
} | ||
|
||
// Check if the field name is empty | ||
if protobufName == "" { | ||
return nil, fmt.Errorf(MissingProtobufTagNameError, field.Name) | ||
} | ||
|
||
// Check if the field name has already been assigned | ||
if _, ok := rootMapper[protobufName]; ok { | ||
return nil, fmt.Errorf(DuplicateProtobufTagNameError, protobufName) | ||
} | ||
|
||
// Add the field to the map | ||
rootMapper[field.Name] = protobufName | ||
} | ||
|
||
return &Mapper{ | ||
Fields: rootMapper, | ||
NestedMappers: rootNestedMappers, | ||
}, nil | ||
} | ||
|
||
// CreateJSONMapper creates the fields to validate from a JSON struct | ||
func CreateJSONMapper(structInstance interface{}, mode *goflagsmode.Flag) ( | ||
*Mapper, | ||
error, | ||
) { | ||
// Reflection of data | ||
typeReflection := reflect.TypeOf(structInstance) | ||
|
||
// If data is a pointer, dereference it | ||
if typeReflection.Kind() == reflect.Ptr { | ||
typeReflection = typeReflection.Elem() | ||
} | ||
|
||
// Initialize the map fields and the map of nested mappers | ||
rootMapper := make(map[string]string) | ||
rootNestedMappers := make(map[string]*Mapper) | ||
|
||
// Reflection of the type of data | ||
var jsonTag string | ||
var jsonName string | ||
for i := 0; i < typeReflection.NumField(); i++ { | ||
// Get the field type through reflection | ||
field := typeReflection.Field(i) | ||
|
||
// Print field on debug mode | ||
fieldType := field.Type | ||
if mode != nil && mode.IsDebug() { | ||
fmt.Printf("field '%v' type: %v\n", field.Name, fieldType) | ||
} | ||
|
||
// Get the JSON tag of the field | ||
jsonTag = field.Tag.Get(JSONTag) | ||
if jsonTag == "" { | ||
return nil, fmt.Errorf(MissingJSONTagError, field.Name) | ||
} | ||
|
||
// Check if the JSON tag is unassigned | ||
if jsonTag == "-" { | ||
continue | ||
} | ||
|
||
// Check the tag to determine if it contains 'omitempty', which means it is an optional field | ||
if ok := strings.Contains(jsonTag, JSONOmitempty); ok { | ||
continue | ||
} | ||
|
||
// Check if the field is a pointer | ||
if fieldType.Kind() == reflect.Ptr { | ||
fieldType = fieldType.Elem() | ||
} | ||
|
||
// Check if the element type is a struct | ||
if fieldType.Kind() == reflect.Struct { | ||
// Create a new Mapper for the nested struct field | ||
fieldNestedMapper, err := CreateJSONMapper( | ||
reflect.New(fieldType).Interface(), | ||
mode, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Add the nested fields to the map | ||
rootNestedMappers[field.Name] = fieldNestedMapper | ||
} | ||
|
||
// Get the field name from the JSON tag | ||
tagParts := strings.Split(jsonTag, ",") | ||
if len(tagParts) == 0 { | ||
return nil, fmt.Errorf(EmptyJSONTagError, field.Name) | ||
} | ||
jsonName = tagParts[0] | ||
|
||
// Check if the field name has already been assigned | ||
if _, ok := rootMapper[jsonName]; ok { | ||
return nil, fmt.Errorf(DuplicateJSONTagNameError, jsonName) | ||
} | ||
|
||
// Add the field to the map | ||
rootMapper[field.Name] = jsonName | ||
} | ||
|
||
return &Mapper{ | ||
Fields: rootMapper, | ||
NestedMappers: rootNestedMappers, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package validations | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var ( | ||
NilDataError = errors.New("data cannot be nil") | ||
NilMapperError = errors.New("mapper cannot be nil") | ||
FieldNotFoundError = errors.New("field not found") | ||
) |
Oops, something went wrong.