From 54414dab9352a608c8ced947a2395e471efd069c Mon Sep 17 00:00:00 2001 From: Fabian Quist Date: Wed, 14 Feb 2024 21:16:30 +0100 Subject: [PATCH] feat(errors, validator): improve error handling, added validator --- gapi/error.go | 81 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- validator/validator.go | 33 +++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 gapi/error.go create mode 100644 validator/validator.go diff --git a/gapi/error.go b/gapi/error.go new file mode 100644 index 0000000..37d601d --- /dev/null +++ b/gapi/error.go @@ -0,0 +1,81 @@ +package gapi + +import ( + "errors" + "strings" + + "github.com/jackc/pgx/v5/pgconn" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func fieldViolation(field string, err error) *errdetails.BadRequest_FieldViolation { + return &errdetails.BadRequest_FieldViolation{ + Field: field, + Description: err.Error(), + } +} + +func invalidArgumentError(violations []*errdetails.BadRequest_FieldViolation) error { + badRequest := &errdetails.BadRequest{FieldViolations: violations} + statusInvalid := status.New(codes.InvalidArgument, "invalid parameters") + + statusDetails, err := statusInvalid.WithDetails(badRequest) + if err != nil { + return statusInvalid.Err() + } + + return statusDetails.Err() +} + +type MultiError []error + +func (me MultiError) Error() string { + var sb strings.Builder + for _, err := range me { + sb.WriteString(err.Error()) + sb.WriteString("\n") + } + return sb.String() +} + +func handleDatabaseError(err error) error { + var pgErr *pgconn.PgError + var errs MultiError + + if errors.As(err, &pgErr) { + switch pgErr.Code { + case "23505": // unique_violation + errs = append(errs, status.Errorf(codes.AlreadyExists, "unique violation occurred: %v", err)) + case "23503": // foreign_key_violation + errs = append(errs, status.Errorf(codes.FailedPrecondition, "foreign key violation occurred: %v", err)) + case "23502": // not_null_violation + errs = append(errs, status.Errorf(codes.InvalidArgument, "not null violation occurred: %v", err)) + case "23514": // check_violation + errs = append(errs, status.Errorf(codes.OutOfRange, "check violation occurred: %v", err)) + case "2200L": // invalid_text_representation + errs = append(errs, status.Errorf(codes.InvalidArgument, "invalid text representation: %v", err)) + case "22P02": // invalid_text_representation + errs = append(errs, status.Errorf(codes.InvalidArgument, "invalid text representation: %v", err)) + case "23P01": // exclusion_violation + errs = append(errs, status.Errorf(codes.AlreadyExists, "exclusion violation occurred: %v", err)) + case "25006": // read_only_sql_transaction + errs = append(errs, status.Errorf(codes.PermissionDenied, "read-only SQL transaction: %v", err)) + case "22023": // no_data + errs = append(errs, status.Errorf(codes.NotFound, "no data: %v", err)) + case "54000": // too_many_connections + errs = append(errs, status.Errorf(codes.ResourceExhausted, "too many connections: %v", err)) + default: + errs = append(errs, status.Errorf(codes.Unknown, "unknown database error: %v", err)) + } + } else { + errs = append(errs, status.Errorf(codes.Internal, "internal error: %v", err)) + } + + if len(errs) > 0 { + return errs + } + + return nil +} diff --git a/go.mod b/go.mod index 1116c47..dc2c0ca 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( golang.org/x/crypto v0.18.0 golang.org/x/net v0.20.0 google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.32.0 ) @@ -65,7 +66,6 @@ require ( golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/validator/validator.go b/validator/validator.go new file mode 100644 index 0000000..a921481 --- /dev/null +++ b/validator/validator.go @@ -0,0 +1,33 @@ +package validator + +import ( + "fmt" + "time" +) + +// Function to validate a string value based on the minimum and maximum length +func ValidateString(value string, minLen int, maxLen int) error { + n := len(value) + if n < minLen || n > maxLen { + return fmt.Errorf("must contain %d-%d characters", minLen, maxLen) + } + return nil +} + +// Function to validate UserId +func ValidateUserId(userId int64) error { + // Assuming the UserId should be a positive integer + if userId <= 0 { + return fmt.Errorf("UserId must be a positive integer") + } + return nil +} + +// ValidateDuration checks if the given string can be parsed as a duration +func ValidateDuration(durationStr string) error { + _, err := time.ParseDuration(durationStr) + if err != nil { + return fmt.Errorf("invalid duration format: %w", err) + } + return nil +}