Files
website/app/internal/contact/validate.go
Jimmy bf110d0bc7 Split compose dev/prod and relax contact form friction.
Use compose.dev.yaml and compose.prod.yaml with fixed Go cache mounts, block sudo make dev, build Air outside app/tmp for rootless Docker, soften English spam checks, and simplify contact error copy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 11:41:13 +12:00

91 lines
2.1 KiB
Go

package contact
import (
"errors"
"net/mail"
"strings"
"unicode/utf8"
"technical.kiwi/website/internal/contactcheck"
)
const (
maxNameLen = 120
maxEmailLen = 254
maxMessageLen = 8000
minMessageLen = 20
)
// Submission is validated contact form input.
type Submission struct {
Name string
Email string
Message string
}
// Errors maps field names to user-facing messages.
type Errors map[string]string
func (e Errors) Any() bool {
return len(e) > 0
}
// Parse reads and validates a contact form POST.
func Parse(name, email, message string) (Submission, Errors) {
errs := make(Errors)
name = strings.TrimSpace(name)
email = strings.TrimSpace(email)
message = strings.TrimSpace(message)
if name == "" {
errs["name"] = "Name is required."
} else if utf8.RuneCountInString(name) > maxNameLen {
errs["name"] = "Name is too long."
}
if email == "" {
errs["email"] = "Email is required."
} else if utf8.RuneCountInString(email) > maxEmailLen {
errs["email"] = "Email is too long."
} else {
addr, err := mail.ParseAddress(email)
if err != nil {
errs["email"] = "Enter a valid email address."
} else {
email = addr.Address
}
}
if message == "" {
errs["message"] = "Message is required."
} else if utf8.RuneCountInString(message) < minMessageLen {
errs["message"] = "Message must be at least 20 characters."
} else if utf8.RuneCountInString(message) > maxMessageLen {
errs["message"] = "Message is too long."
}
if !errs.Any() {
if err := contactcheck.ValidateEnglish(name, message); err != nil {
switch {
case errors.Is(err, contactcheck.ErrTooShort):
errs["message"] = "Message must be at least 20 characters."
case errors.Is(err, contactcheck.ErrTooLong):
errs["message"] = "Message is too long."
case errors.Is(err, contactcheck.ErrName):
errs["name"] = "Name is too long."
case errors.Is(err, contactcheck.ErrNotEnglish):
errs["message"] = "Please write your message in English."
default:
errs["message"] = "Could not accept this message."
}
}
}
if errs.Any() {
return Submission{}, errs
}
return Submission{Name: name, Email: email, Message: message}, nil
}