go-chi-oapi-codegen-todolist/backend/internal/service/validation.go
2025-04-20 15:58:52 +07:00

193 lines
5.6 KiB
Go

package service
import (
"fmt"
"net/mail"
"regexp"
"strings"
"github.com/Sosokker/todolist-backend/internal/domain"
)
const (
MinUsernameLength = 3
MaxUsernameLength = 50
MinPasswordLength = 6
MinTagNameLength = 1
MaxTagNameLength = 50
MaxTagIconLength = 30
MinTodoTitleLength = 1
MinSubtaskDescLength = 1
)
// Regex for simple hex color validation (#RRGGBB)
var hexColorRegex = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
// ValidateUsername checks username constraints.
func ValidateUsername(username string) error {
if len(username) < MinUsernameLength || len(username) > MaxUsernameLength {
return fmt.Errorf("username must be between %d and %d characters: %w", MinUsernameLength, MaxUsernameLength, domain.ErrValidation)
}
// Add other constraints like allowed characters if needed
return nil
}
// ValidateEmail checks if the email format is valid.
func ValidateEmail(email string) error {
if _, err := mail.ParseAddress(email); err != nil {
return fmt.Errorf("invalid email format: %w", domain.ErrValidation)
}
return nil
}
// ValidatePassword checks password length.
func ValidatePassword(password string) error {
if len(password) < MinPasswordLength {
return fmt.Errorf("password must be at least %d characters: %w", MinPasswordLength, domain.ErrValidation)
}
return nil
}
// ValidateSignupInput validates the input for user registration.
func ValidateSignupInput(creds SignupCredentials) error {
if err := ValidateUsername(creds.Username); err != nil {
return err
}
if err := ValidateEmail(creds.Email); err != nil {
return err
}
if err := ValidatePassword(creds.Password); err != nil {
return err
}
return nil
}
// ValidateLoginInput validates the input for user login.
func ValidateLoginInput(creds LoginCredentials) error {
if err := ValidateEmail(creds.Email); err != nil {
return err
}
if creds.Password == "" { // Password presence check
return fmt.Errorf("password is required: %w", domain.ErrValidation)
}
return nil
}
// IsValidHexColor checks if a string is a valid #RRGGBB hex color.
func IsValidHexColor(color string) bool {
return hexColorRegex.MatchString(color)
}
// ValidateTagName checks tag name constraints.
func ValidateTagName(name string) error {
trimmed := strings.TrimSpace(name)
if len(trimmed) < MinTagNameLength || len(trimmed) > MaxTagNameLength {
return fmt.Errorf("tag name must be between %d and %d characters: %w", MinTagNameLength, MaxTagNameLength, domain.ErrValidation)
}
return nil
}
// ValidateTagIcon checks tag icon constraints.
func ValidateTagIcon(icon *string) error {
if icon != nil && len(*icon) > MaxTagIconLength {
return fmt.Errorf("tag icon cannot be longer than %d characters: %w", MaxTagIconLength, domain.ErrValidation)
}
return nil
}
// ValidateCreateTagInput validates input for creating a tag.
func ValidateCreateTagInput(input CreateTagInput) error {
if err := ValidateTagName(input.Name); err != nil {
return err
}
if input.Color != nil && !IsValidHexColor(*input.Color) {
return fmt.Errorf("invalid color format (must be #RRGGBB): %w", domain.ErrValidation)
}
if err := ValidateTagIcon(input.Icon); err != nil {
return err
}
return nil
}
// ValidateUpdateTagInput validates input for updating a tag.
func ValidateUpdateTagInput(input UpdateTagInput) error {
if input.Name != nil {
if err := ValidateTagName(*input.Name); err != nil {
return err
}
}
if input.Color != nil && !IsValidHexColor(*input.Color) {
return fmt.Errorf("invalid color format (must be #RRGGBB): %w", domain.ErrValidation)
}
if err := ValidateTagIcon(input.Icon); err != nil { // Check pointer directly
return err
}
return nil
}
// ValidateTodoTitle checks title constraints.
func ValidateTodoTitle(title string) error {
if len(strings.TrimSpace(title)) < MinTodoTitleLength {
return fmt.Errorf("todo title cannot be empty: %w", domain.ErrValidation)
}
return nil
}
// ValidateCreateTodoInput validates input for creating a todo.
func ValidateCreateTodoInput(input CreateTodoInput) error {
if err := ValidateTodoTitle(input.Title); err != nil {
return err
}
// Optional: Validate Status enum value if needed
// Optional: Validate Deadline is not in the past?
return nil
}
// ValidateUpdateTodoInput validates input for updating a todo.
func ValidateUpdateTodoInput(input UpdateTodoInput) error {
if input.Title != nil {
if err := ValidateTodoTitle(*input.Title); err != nil {
return err
}
}
// Optional: Validate Status enum value if needed
// Optional: Validate Deadline is not in the past?
return nil
}
// ValidateSubtaskDescription checks description constraints.
func ValidateSubtaskDescription(desc string) error {
if len(strings.TrimSpace(desc)) < MinSubtaskDescLength {
return fmt.Errorf("subtask description cannot be empty: %w", domain.ErrValidation)
}
return nil
}
// ValidateCreateSubtaskInput validates input for creating a subtask.
func ValidateCreateSubtaskInput(input CreateSubtaskInput) error {
return ValidateSubtaskDescription(input.Description)
}
// ValidateUpdateSubtaskInput validates input for updating a subtask.
func ValidateUpdateSubtaskInput(input UpdateSubtaskInput) error {
if input.Description != nil {
if err := ValidateSubtaskDescription(*input.Description); err != nil {
return err
}
}
return nil
}
// ValidateListParams checks basic pagination parameters.
func ValidateListParams(limit, offset int) error {
if limit < 0 {
return fmt.Errorf("limit cannot be negative: %w", domain.ErrValidation)
}
if offset < 0 {
return fmt.Errorf("offset cannot be negative: %w", domain.ErrValidation)
}
// Add max limit check if desired
// if limit > MaxListLimit { ... }
return nil
}