code.gitea.io/gitea@v1.22.3/modules/private/request.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package private 5 6 import ( 7 "fmt" 8 "io" 9 "net/http" 10 11 "code.gitea.io/gitea/modules/httplib" 12 "code.gitea.io/gitea/modules/json" 13 ) 14 15 // ResponseText is used to get the response as text, instead of parsing it as JSON. 16 type ResponseText struct { 17 Text string 18 } 19 20 // ResponseExtra contains extra information about the response, especially for error responses. 21 type ResponseExtra struct { 22 StatusCode int 23 UserMsg string 24 Error error 25 } 26 27 type responseCallback struct { 28 Callback func(resp *http.Response, extra *ResponseExtra) 29 } 30 31 func (re *ResponseExtra) HasError() bool { 32 return re.Error != nil 33 } 34 35 type responseError struct { 36 statusCode int 37 errorString string 38 } 39 40 func (re responseError) Error() string { 41 if re.errorString == "" { 42 return fmt.Sprintf("internal API error response, status=%d", re.statusCode) 43 } 44 return fmt.Sprintf("internal API error response, status=%d, err=%s", re.statusCode, re.errorString) 45 } 46 47 // requestJSONResp sends a request to the gitea server and then parses the response. 48 // If the status code is not 2xx, or any error occurs, the ResponseExtra.Error field is guaranteed to be non-nil, 49 // and the ResponseExtra.UserMsg field will be set to a message for the end user. 50 // Caller should check the ResponseExtra.HasError() first to see whether the request fails. 51 // 52 // * If the "res" is a struct pointer, the response will be parsed as JSON 53 // * If the "res" is ResponseText pointer, the response will be stored as text in it 54 // * If the "res" is responseCallback pointer, the callback function should set the ResponseExtra fields accordingly 55 func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra ResponseExtra) { 56 resp, err := req.Response() 57 if err != nil { 58 extra.UserMsg = "Internal Server Connection Error" 59 extra.Error = fmt.Errorf("unable to contact gitea %q: %w", req.GoString(), err) 60 return nil, extra 61 } 62 defer resp.Body.Close() 63 64 extra.StatusCode = resp.StatusCode 65 66 // if the status code is not 2xx, try to parse the error response 67 if resp.StatusCode/100 != 2 { 68 var respErr Response 69 if err := json.NewDecoder(resp.Body).Decode(&respErr); err != nil { 70 extra.UserMsg = "Internal Server Error Decoding Failed" 71 extra.Error = fmt.Errorf("unable to decode error response %q: %w", req.GoString(), err) 72 return nil, extra 73 } 74 extra.UserMsg = respErr.UserMsg 75 if extra.UserMsg == "" { 76 extra.UserMsg = "Internal Server Error (no message for end users)" 77 } 78 extra.Error = responseError{statusCode: resp.StatusCode, errorString: respErr.Err} 79 return res, extra 80 } 81 82 // now, the StatusCode must be 2xx 83 var v any = res 84 if respText, ok := v.(*ResponseText); ok { 85 // get the whole response as a text string 86 bs, err := io.ReadAll(resp.Body) 87 if err != nil { 88 extra.UserMsg = "Internal Server Response Reading Failed" 89 extra.Error = fmt.Errorf("unable to read response %q: %w", req.GoString(), err) 90 return nil, extra 91 } 92 respText.Text = string(bs) 93 return res, extra 94 } else if cb, ok := v.(*responseCallback); ok { 95 // pass the response to callback, and let the callback update the ResponseExtra 96 extra.StatusCode = resp.StatusCode 97 cb.Callback(resp, &extra) 98 return nil, extra 99 } else if err := json.NewDecoder(resp.Body).Decode(res); err != nil { 100 // decode the response into the given struct 101 extra.UserMsg = "Internal Server Response Decoding Failed" 102 extra.Error = fmt.Errorf("unable to decode response %q: %w", req.GoString(), err) 103 return nil, extra 104 } 105 106 if respMsg, ok := v.(*Response); ok { 107 // if the "res" is Response structure, try to get the UserMsg from it and update the ResponseExtra 108 extra.UserMsg = respMsg.UserMsg 109 if respMsg.Err != "" { 110 // usually this shouldn't happen, because the StatusCode is 2xx, there should be no error. 111 // but we still handle the "err" response, in case some people return error messages by status code 200. 112 extra.Error = responseError{statusCode: resp.StatusCode, errorString: respMsg.Err} 113 } 114 } 115 116 return res, extra 117 } 118 119 // requestJSONClientMsg sends a request to the gitea server, server only responds text message status=200 with "success" body 120 // If the request succeeds (200), the argument clientSuccessMsg will be used as ResponseExtra.UserMsg. 121 func requestJSONClientMsg(req *httplib.Request, clientSuccessMsg string) ResponseExtra { 122 _, extra := requestJSONResp(req, &ResponseText{}) 123 if extra.HasError() { 124 return extra 125 } 126 extra.UserMsg = clientSuccessMsg 127 return extra 128 }