128 lines · 4.4 KB · Go
RawDownload
// Copyright 2023 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package private import (	"fmt"	"io"	"net/http" 	"forgejo.org/modules/httplib"	"forgejo.org/modules/json") // ResponseText is used to get the response as text, instead of parsing it as JSON.type ResponseText struct {	Text string} // ResponseExtra contains extra information about the response, especially for error responses.type ResponseExtra struct {	StatusCode int	UserMsg    string	Error      error} type responseCallback struct {	Callback func(resp *http.Response, extra *ResponseExtra)} func (re *ResponseExtra) HasError() bool {	return re.Error != nil} type responseError struct {	statusCode  int	errorString string} func (re responseError) Error() string {	if re.errorString == "" {		return fmt.Sprintf("internal API error response, status=%d", re.statusCode)	}	return fmt.Sprintf("internal API error response, status=%d, err=%s", re.statusCode, re.errorString)} // requestJSONResp sends a request to the gitea server and then parses the response.// If the status code is not 2xx, or any error occurs, the ResponseExtra.Error field is guaranteed to be non-nil,// and the ResponseExtra.UserMsg field will be set to a message for the end user.// Caller should check the ResponseExtra.HasError() first to see whether the request fails.//// * If the "res" is a struct pointer, the response will be parsed as JSON// * If the "res" is ResponseText pointer, the response will be stored as text in it// * If the "res" is responseCallback pointer, the callback function should set the ResponseExtra fields accordinglyfunc requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra ResponseExtra) {	resp, err := req.Response()	if err != nil {		extra.UserMsg = "Internal Server Connection Error"		extra.Error = fmt.Errorf("unable to contact gitea %q: %w", req.GoString(), err)		return nil, extra	}	defer resp.Body.Close() 	extra.StatusCode = resp.StatusCode 	// if the status code is not 2xx, try to parse the error response	if resp.StatusCode/100 != 2 {		var respErr Response		if err := json.NewDecoder(resp.Body).Decode(&respErr); err != nil {			extra.UserMsg = "Internal Server Error Decoding Failed"			extra.Error = fmt.Errorf("unable to decode error response %q: %w", req.GoString(), err)			return nil, extra		}		extra.UserMsg = respErr.UserMsg		if extra.UserMsg == "" {			extra.UserMsg = "Internal Server Error (no message for end users)"		}		extra.Error = responseError{statusCode: resp.StatusCode, errorString: respErr.Err}		return res, extra	} 	// now, the StatusCode must be 2xx	var v any = res	if respText, ok := v.(*ResponseText); ok {		// get the whole response as a text string		bs, err := io.ReadAll(resp.Body)		if err != nil {			extra.UserMsg = "Internal Server Response Reading Failed"			extra.Error = fmt.Errorf("unable to read response %q: %w", req.GoString(), err)			return nil, extra		}		respText.Text = string(bs)		return res, extra	} else if cb, ok := v.(*responseCallback); ok {		// pass the response to callback, and let the callback update the ResponseExtra		extra.StatusCode = resp.StatusCode		cb.Callback(resp, &extra)		return nil, extra	} else if err := json.NewDecoder(resp.Body).Decode(res); err != nil {		// decode the response into the given struct		extra.UserMsg = "Internal Server Response Decoding Failed"		extra.Error = fmt.Errorf("unable to decode response %q: %w", req.GoString(), err)		return nil, extra	} 	if respMsg, ok := v.(*Response); ok {		// if the "res" is Response structure, try to get the UserMsg from it and update the ResponseExtra		extra.UserMsg = respMsg.UserMsg		if respMsg.Err != "" {			// usually this shouldn't happen, because the StatusCode is 2xx, there should be no error.			// but we still handle the "err" response, in case some people return error messages by status code 200.			extra.Error = responseError{statusCode: resp.StatusCode, errorString: respMsg.Err}		}	} 	return res, extra} // requestJSONClientMsg sends a request to the gitea server, server only responds text message status=200 with "success" body// If the request succeeds (200), the argument clientSuccessMsg will be used as ResponseExtra.UserMsg.func requestJSONClientMsg(req *httplib.Request, clientSuccessMsg string) ResponseExtra {	_, extra := requestJSONResp(req, &ResponseText{})	if extra.HasError() {		return extra	}	extra.UserMsg = clientSuccessMsg	return extra}