Skip to content

Commit

Permalink
HW9 is completed
Browse files Browse the repository at this point in the history
Signed-off-by: Pavel Pogodaev <[email protected]>
  • Loading branch information
Pavel Pogodaev committed Nov 4, 2024
1 parent 411b524 commit e1d4150
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 10 deletions.
Empty file removed hw09_struct_validator/.sync
Empty file.
8 changes: 8 additions & 0 deletions hw09_struct_validator/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/fixme_my_friend/hw09_struct_validator

go 1.22

require github.com/stretchr/testify v1.9.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions hw09_struct_validator/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
197 changes: 195 additions & 2 deletions hw09_struct_validator/validator.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,210 @@
package hw09structvalidator

import (
"fmt"
"log"
"reflect"
"regexp"
"strconv"
"strings"
)

type ValidationError struct {
Field string
Err error
}

type ValidationErrors []ValidationError

var hashType = map[reflect.Kind]bool{
reflect.Int: true,
reflect.Int8: true,
reflect.Int16: true,
reflect.Int32: true,
reflect.Int64: true,
reflect.Uint: true,
reflect.Uint8: true,
reflect.Uint16: true,
reflect.Uint32: true,
reflect.Uint64: true,
}

func (v ValidationErrors) Error() string {
panic("implement me")
errSlc := make([]string, 0, len(v))
for _, msg := range v {
errSlc = append(errSlc, msg.Err.Error())
}

return strings.Join(errSlc, "; ")
}

func Validate(v interface{}) error {
// Place your code here.
var (
dataType = reflect.TypeOf(v)
valueType = reflect.ValueOf(v)
errSlice ValidationErrors
)

if dataType.Kind() != reflect.Struct {
log.Fatalf("invalid type: expected struct, got %v", dataType.Kind())
}

for i := 0; i < dataType.NumField(); i++ {
if alias, ok := dataType.Field(i).Tag.Lookup("validate"); ok {
if len(alias) == 0 {
continue
}

ValidateField(
&errSlice,
strings.Fields(alias),
dataType.Field(i).Name,
valueType.FieldByName(dataType.Field(i).Name),
)
}
}
if len(errSlice) != 0 {
return errSlice
}
return nil
}

func ValidateField(errSlice *ValidationErrors, tags []string, fieldName string, value reflect.Value) {
tagData := strings.Split(tags[0], "|")
valid := func(tag string, value reflect.Value) {
validateField(
errSlice,
tag,
fieldName,
value,
)
}

for _, tag := range tagData {
if value.Kind() == reflect.Slice {
if typeEl := value.Type().Elem(); hashType[typeEl.Kind()] {
valid(tag, value)
} else {
for j := 0; j < value.Len(); j++ {
valid(tag, value.Index(j))
}
}
} else {
valid(tag, value)
}
}
}

func validateField(errSlice *ValidationErrors, tag string, fieldName string, value reflect.Value) {
switch strings.Split(tag, ":")[0] {
case "regexp":
validateRegexp(errSlice, fieldName, tag, value)
case "len":
validateLength(errSlice, fieldName, tag, value)
case "in":
validateIn(errSlice, fieldName, tag, value)
case "max":
validateMax(errSlice, fieldName, tag, value)
case "min":
validateMin(errSlice, fieldName, tag, value)
}
}

func validateMin(errSlice *ValidationErrors, fieldName, tag string, value reflect.Value) {
extremum, err := strconv.Atoi(strings.Split(tag, ":")[1])
if err != nil {
log.Fatalf("can't parse value: %v", err)
}

if extremum <= int(value.Int()) {
log.Printf("validation field %v success", fieldName)
} else {
*errSlice = append(
*errSlice,
ValidationError{
Field: fieldName,
Err: fmt.Errorf("validation error: field '%s'", fieldName),
},
)
}
}

func validateMax(errSlice *ValidationErrors, fieldName, tag string, value reflect.Value) {
extremum, err := strconv.Atoi(strings.Split(tag, ":")[1])
if err != nil {
log.Fatalf("parsing int error: %v", err)
}

if extremum >= int(value.Int()) {
log.Printf("validation field %v success", fieldName)
} else {
*errSlice = append(
*errSlice,
ValidationError{
Field: fieldName,
Err: fmt.Errorf("validation error: field '%s'", fieldName),
},
)
}
}

func validateRegexp(errSlice *ValidationErrors, fieldName, tag string, value reflect.Value) {
re := regexp.MustCompile(strings.Split(tag, ":")[1])

if len(re.Find([]byte(value.String()))) != 0 {
log.Printf("validation field %v success", fieldName)
} else {
*errSlice = append(
*errSlice,
ValidationError{
Field: fieldName,
Err: fmt.Errorf("validation error: field '%s'", fieldName),
},
)
}
}

func validateIn(errSlice *ValidationErrors, fieldName, tag string, value reflect.Value) {
var inData string
if value.Kind() == reflect.String {
inData = value.String()
}

if value.Kind() == reflect.Int {
inData = strconv.Itoa(int(value.Int()))
}

pattern := strings.ReplaceAll(strings.Split(tag, ":")[1], ",", "|")
re := regexp.MustCompile(pattern)

if find := re.FindStringSubmatch(inData); len(find) != 0 {
log.Printf("validation field %v success", fieldName)
} else {
*errSlice = append(
*errSlice,
ValidationError{
Field: fieldName,
Err: fmt.Errorf("validation error: field '%s'", fieldName),
},
)
}
}

func validateLength(errSlice *ValidationErrors, fieldName, tag string, value reflect.Value) {
length, err := strconv.Atoi(strings.Split(tag, ":")[1])
if err != nil {
log.Fatalf("parsing int error: %v", err)
}

if value.Len() <= length {
log.Printf("validation field %v success", fieldName)
} else {
*errSlice = append(
*errSlice,
ValidationError{
Field: fieldName,
Err: fmt.Errorf("validation error: field '%s'", fieldName),
},
)
}
}
114 changes: 106 additions & 8 deletions hw09_struct_validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package hw09structvalidator

import (
"encoding/json"
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/require" //nolint:depguard
)

type UserRole string
Expand All @@ -26,7 +29,7 @@ type (

Token struct {
Header []byte
Payload []byte
Payload []byte `validate:"len:5"`
Signature []byte
}

Expand All @@ -37,24 +40,119 @@ type (
)

func TestValidate(t *testing.T) {
tests := []struct {
testsSuccess := []struct {
in interface{}
expectedErr error
}{
{
in: User{
ID: "LNL-b5un6PiLrgv",
Name: "John Smith",
Age: 30,
Email: "[email protected]",
Role: "admin",
Phones: []string{"+347880011"},
},
expectedErr: nil,
},
{
in: App{
Version: "6.2.1",
},
expectedErr: nil,
},
{
in: Response{
Code: 200,
Body: "Success message",
},
expectedErr: nil,
},

{
in: Token{
Header: []byte("Content-Length"),
Payload: []byte("12345"),
Signature: []byte("SHA-256"),
},
expectedErr: nil,
},
}

testsFail := []struct {
in interface{}
expectedErr error
}{
{
// Place your code here.
in: User{
ID: "12345678",
Name: "John",
Age: 30,
Email: "[email protected]",
Role: "admin",
Phones: []string{"1234567890"},
},
expectedErr: ValidationErrors{
ValidationError{
Field: "Email",
Err: fmt.Errorf("validation error: field 'Email'"),
},
},
},
{
in: Token{
Header: []byte("Content-Size"),
Payload: []byte("123456"),
Signature: []byte("md5"),
},
expectedErr: ValidationErrors{
ValidationError{
Field: "Payload",
Err: fmt.Errorf("validation error: field 'Payload'"),
},
},
},
{
in: User{
ID: "98283q3487987329847",
Name: "John Deer",
Age: 15,
Email: "[email protected]",
Role: "admin",
Phones: []string{"986745321"},
},
expectedErr: ValidationErrors{
ValidationError{
Field: "Age",
Err: fmt.Errorf("validation error: field 'Age'"),
},
ValidationError{
Field: "Email",
Err: fmt.Errorf("validation error: field 'Email'"),
},
},
},
// ...
// Place your code here.
}

for i, tt := range tests {
for i, tt := range testsSuccess {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
tt := tt
t.Parallel()
err := Validate(tt.in)
require.Equal(t, err, tt.expectedErr)
})
}

// Place your code here.
_ = tt
for i, tt := range testsFail {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
tt := tt
t.Parallel()
err := Validate(tt.in)
if !errors.As(err, &ValidationErrors{}) {
t.Errorf("unexpected error: got %v, expected %v", err, tt.expectedErr)
} else {
require.Equal(t, tt.expectedErr, err)
}
})
}
}

0 comments on commit e1d4150

Please sign in to comment.