Patterns for decoding and validating input for Go data APIs

A struct for new stuff

Rather than trying to decode user input directly into the main struct for your model, create an interim struct prefixed with `New`.

type User struct {
ID bson.ObjectId
Name string
Email string
PasswordHash string
State string
}

The client is sending a different thing to our internal representation of users — so why should they be the same type?

So create a struct called `NewUser`:

type NewUser struct {
Name string
Email string
Password string
PasswordConfirm string
}

A single decode function that validates too

A well designed `decode` function lets you speak JSON initially, and later extend it as your API evolves if you need to without changing any other code.

// ok represents types capable of validating
// themselves.
type ok interface {
OK() error
}
// decode can be this simple to start with, but can be extended
// later to support different formats and behaviours without
// changing the interface.
func decode(r *http.Request, v ok) error {
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
return err
}
return v.OK()
}
func (u *NewUser) OK() error {
if len(u.Email) == 0 {
return ErrMissingField("email")
}
if len(u.Password) == 0 {
return ErrMissingField("password")
}
if u.Password != u.PasswordConfirm {
return errors.New("passwords don’t match")
}
return nil
}

Dedicated error types for common things

You may have noticed the `ErrMissingField`type above. It makes sense to handle common validation failures in the same way each time — you’ll be able change the error messages etc. in one place.

type ErrMissingField stringfunc (e ErrMissingField) Error() string {
return string(e) + “ is required”
}

Using the decode function

To use the decode function inside one of our handlers, we need only create the appropriate variable to hold the decoded value, and handle the error:

func handleCreateUser(w http.ResponseWriter, r *http.Request) {
var u NewUser
if err := decode(r, &u); err != nil {
respond.With(w, r, http.StatusBadRequest, err)
return
}
}
  • See also some real code on this.
  • Yes I do keep on using `backtics` in the hope that Medium will one day suddenly support them — anyway, you’re probably a programming, so I bet you didn’t even notice.

--

--

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mat Ryer

Mat Ryer

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints