Validation checks data before processing and keeps invalid values out of business operations. Place rules where every caller receives protection: the domain validates its own entities, while an API translates failures into transport-level responses. section 10.3.1 section 11.4.2
A practical validation design separates responsibilities:
- Validate and complete entities in the domain so direct callers cannot bypass required rules or defaults. section 10.3.1
- Return a typed invalid-input error containing the field and reason instead of coupling business code to gRPC status codes. section 10.3.1
- Map invalid input to gRPC INVALID_ARGUMENT at the API layer; the domain remains unaware of the transport. section 10.3.1
- Add an early HTTP safety check when parsing form data, while retaining backend protection against invalid or hostile requests. section 11.4.2
Validating and completing a habit
Trim the name and reject an empty result; default a zero weekly frequency to one; generate a missing ID and creation time; return the completed entity or InvalidInputError. section 10.3.1
Validation functions in table-driven tests
Store a validate func(t *testing.T, got money.Amount, err error) in each test case. Each case can check the error, the value, or whichever result matters, while centralizing assertion behavior in the test table. section 6.1.1
Listing 10.13 internal/habit/create.go: Validate/complete entity
// validateAndCompleteHabit fills the habit with values that we want in
// our database.
// Returns InvalidInputError. #1
func validateAndCompleteHabit(h Habit) (Habit, error) {
// name cannot be empty
h.Name = Name(strings.TrimSpace(string(h.Name))) #2
if h.Name == "" {
return Habit{}, InvalidInputError{ #3
field: "name", reason: "cannot be empty"}
}
if h.WeeklyFrequency == 0 {
h.WeeklyFrequency = 1 #4
}
if h.ID == "" {
h.ID = ID(uuid.NewString()) #5
}
if h.CreationTime.Equal(time.Time{}) {
h.CreationTime = time.Now()
}
return h, nil
}
Inspect the Go implementation that trims names, rejects empty input, applies defaults, creates identifiers, and reports typed validation errors. section 10.3.1
Listing 11.34 create.go: Parsing form values
// create takes a JSON request and creates a Habit from it,
// then redirects to index.
func (s *Server) create(w http.ResponseWriter, r *http.Request) {
habitName := r.FormValue("habitName") #1
weeklyFreq, err := strconv.Atoi(
r.FormValue("habitFrequency")) #2
if err != nil {...}
// call client
// redirect to index
http.Redirect(w, r, "/", http.StatusSeeOther) #3
}
Inspect the HTTP handler that reads form values, parses weekly frequency with strconv.Atoi, and continues through the backend call. section 11.4.2





























