如何在Go Mux中最小化重复代码,同时始终返回相同的响应结构?

huangapple go评论87阅读模式
英文:

How to minimize duplicate code in Go Mux when always trying to return same response structure?

问题

我有很多类似以下代码片段的代码,我只是尝试填充我的响应结构体,将输出进行JSON编组,设置状态码并返回结果:

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
}

我尝试创建一个函数,接收r变量(request.http)来接收请求体和响应的状态码。但是我注意到我必须在函数外部再次检查错误代码,然后再次执行相同的响应创建流程。

有经验的Go专家如何尝试最小化这样的代码重复?首先,这样的代码重复是否可以接受?

英文:

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?

答案1

得分: 0

通过将解码调用和错误处理移动到可重用函数中,最小化代码重复:

// decode函数将请求体成功解码为pv指向的值时返回true。否则,decode会写入错误响应并返回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]interface{}{
        "status":  http.StatusBadRequest,
        "message": "error",
        "data":    map[string]interface{}{"error": err.Error()},
    })
    return false
}

// 使用该函数的示例:
func userHandler(rw http.ResponseWriter, r *http.Request) {
    var u UserRequest
    if !decode(rw, r, &u) {
        return
    }
}

希望对你有帮助!

英文:

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
	}
}

答案2

得分: 0

最好将细节抽象化,以提供处理程序的高级概述。

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}))
}

处理程序利用一些方便的辅助函数来消除重复,并提供处理程序的高级概述,而不是底层细节。

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
    OnError OnErrorFunc
}

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)
}

也许你应该编写自己的响应包,或者查看开源项目中提供的内容。然后,你可以在所有地方使用这个响应包,保持相同的响应结构。

英文:

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
	OnError OnErrorFunc
}

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.

huangapple
  • 本文由 发表于 2022年7月9日 14:02:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/72919310.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定