|
| 1 | +# Bug to fix |
| 2 | + |
| 3 | +Route Registration (Happens Once): When your server starts, the setupUserRoutes method runs and registers routes: |
| 4 | +`r.Post("/register", s.registerUser())` |
| 5 | + |
| 6 | +This calls `registerUser()` immediately (not per request) and uses its return value as the handler. |
| 7 | + |
| 8 | +Handler Creation (Happens Once): The `registerUser()` method executes and: |
| 9 | +- Creates any local variables in its scope |
| 10 | +- Returns a handler function via validatePayload |
| 11 | +- The returned handler is stored by the router |
| 12 | +- Request Handling (Happens Per Request): |
| 13 | +- When requests arrive, the previously stored handler function is executed in a new goroutine. |
| 14 | + |
| 15 | + |
| 16 | +The payload variable is: |
| 17 | +- Created once when registerUser() runs during setup |
| 18 | +- It Shared across all requests because: |
| 19 | + - It's in the closure's outer scope. The closure is created once but executed many times. |
| 20 | + - All executions reference the same memory location. |
| 21 | + |
| 22 | +## Solution / Fix |
| 23 | +The fix is moving the payload declaration inside the handler function so each request creates its own instance.\ |
| 24 | +OR\ |
| 25 | +Use middleware to bind and validate the payload. |
| 26 | + |
| 27 | +```go |
| 28 | +func (s *Server) registerUser() http.HandlerFunc { |
| 29 | + return func(w http.ResponseWriter, r *http.Request) { |
| 30 | + var payload domain.RegisterPayload |
| 31 | + if err := validateAndDecode(r, &payload); err != nil { |
| 32 | + badRequestResponse(w, err) |
| 33 | + return |
| 34 | + } |
| 35 | + |
| 36 | + user, err := s.domain.Register(payload) |
| 37 | + // rest of your handler... |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +``` |
| 42 | + |
| 43 | +```go |
| 44 | +// // PayloadValidation is the interface that all payload types must implement |
| 45 | +type PayloadValidation interface { |
| 46 | +IsValid() (bool, map[string]string) |
| 47 | +} |
| 48 | + |
| 49 | +// validateAndDecode decodes the request body into the provided payload and validates it |
| 50 | +func validateAndDecode(r *http.Request, payload PayloadValidation) error { |
| 51 | + // Decode JSON from request body |
| 52 | + if err := json.NewDecoder(r.Body).Decode(payload); err != nil { |
| 53 | + return fmt.Errorf("invalid JSON: %w", err) |
| 54 | + } |
| 55 | + |
| 56 | + // Validate using the IsValid method from the interface |
| 57 | + hasErrors, errors := payload.IsValid() |
| 58 | + if hasErrors { |
| 59 | + // Convert the errors map to a structured error |
| 60 | + // You could return the first error, all errors, or create a custom error type |
| 61 | + for field, message := range errors { |
| 62 | + return fmt.Errorf("%s: %s", field, message) |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + return nil |
| 67 | +} |
| 68 | +``` |
0 commit comments