Home > Back-end >  How to minimize duplicate code in Go Mux when always trying to return same response structure?
How to minimize duplicate code in Go Mux when always trying to return same response structure?

Time:07-10

I have tons of code similar to the following code snippet that I just try to fill my response struct, json marshal the output, set status code and return the result:

if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
            response := responses.UserResponse{
                Status:  http.StatusBadRequest,
                Message: "error",
                Data:    map[string]interface{}{"error": err.Error()},
            }
            rw.WriteHeader(http.StatusBadRequest)
            errRes, _ := json.Marshal(response)
            rw.Write(errRes)
            return
        }

I tried to create a function that receives r variable (request.http) to receive the body and also status code of the response. But noticed that I have to again check error code outside of the function and then do the same response creation flow again.

How someone expert in Go tries to minimize code duplications like these? Is this OK to have code duplication like these in first place?

CodePudding user response:

Minimize code duplication by moving the decode call and error handling to a reusable function:

// Decode returns true if the request body is successfully decoded
// to the value pointed to by pv. Otherwise, decode writes an error
// response and returns false.
func decode(rw http.ResponseWriter, r *http.Request, pv interface{}) bool {
    err := json.NewDecoder(r.Body).Decode(pv)
    if err == nil {
        return true
    }
    rw.WriteHeader(http.StatusBadRequest)
    json.NewEncoder(rw).Encode(map[string]any{
        "status":  http.StatusBadRequest,
        "message": "error",
        "data":    map[string]any{"error": err.Error()},
    })
    return false
}

Use the function like this:

func userHandler(rw http.ResponseWriter, r *http.Request) {
    var u UserRequest
    if !decode(rw, r, &u) {
        return
    }
}

CodePudding user response:

It is preferable to abstract details to provide a high-level picture of what your handler does.

func (h *rideHandler) handleCancelRideByPassenger(w http.ResponseWriter, r *http.Request) {

    ctx := r.Context()

    user := getUser(ctx)

    req := &cancelRequest{}

    if err := decode(r, req); err != nil {
        h.logger.Error("cancel ride: problem while decoding body request", zap.String("ip", r.RemoteAddr), zap.Error(err))
        h.respond.BadRequest(w, NewRESTError(reasonDecoding, "problem while decoding input parameters"))
        return
    }
    req.PublicID = chi.URLParam(r, "id")

    err := h.rideService.CancelRide(ctx, req, user)
    if err != nil {
        var validationErr *ValidationError
        switch {
        case errors.As(err, &validationErr):
            h.respond.BadRequest(w, NewRESTValidationError(reasonValidation, "problem while validating request", validationErr))
            return
        default:
            h.respond.InternalServerError(w, NewRESTError(reasonInternalError, "unknown problem occurred"))
            return
        }
    }

    h.respond.Ok(w, NewRESTResponse(&cancelRideResponse{Success: true}))

}

Handler utilizes some handy sugar functions to remove duplication and provide high-level overview of what handler does instead underlying details.

func decode(request *http.Request, val interface{}) error {
    dec := json.NewDecoder(request.Body)
    dec.DisallowUnknownFields()
    return dec.Decode(val)
}

type Responder struct {
    Encoder Encoder
    Before BeforeFunc
    After AfterFunc
    one rror one rrorFunc
}

func (r *Responder) writeResponse(w http.ResponseWriter, v interface{}, status int) {

    if r.Before != nil {
        status, v = r.Before(w, v, status)
    }

    encoder := JSON
    if r.Encoder != nil {
        encoder = r.Encoder
    }

    w.Header().Set("Content-Type", encoder.ContentType())
    w.WriteHeader(status)
    if err := encoder.Encode(w, v); err != nil {
        if r.OnError != nil {
            r.OnError(err)
        }
    }

    if r.After != nil {
        r.After(v, status)
    }

}

func (r *Responder) Ok(w http.ResponseWriter, v interface{}) {
    r.writeResponse(w, v, http.StatusOK)
}

Probably you should write your own respond package or check what is available in open source. Then you can use this respond package with the same response structure everywhere.

  • Related