package rest import ( "bytes" "fmt" "html/template" "io" "net/http" "net/url" "runtime" "strings" log "github.com/go-pkgz/lgr" "github.com/go-pkgz/rest" "github.com/umputun/remark42/backend/app/templates" ) // All error codes for UI mapping and translation const ( ErrInternal = 0 // any internal error ErrCommentNotFound = 1 // can't find comment ErrDecode = 2 // failed to unmarshal incoming request ErrNoAccess = 3 // rejected by auth ErrCommentValidation = 4 // validation failed ErrPostNotFound = 5 // can't find post ErrSiteNotFound = 6 // can't find site ErrUserBlocked = 7 // user blocked ErrReadOnly = 8 // write failed on read only ErrCommentRejected = 9 // general error on rejected comment change ErrCommentEditExpired = 10 // too late for edit ErrCommentEditChanged = 11 // parent comment cannot be changed ErrVoteRejected = 12 // general error on vote rejected ErrVoteSelf = 13 // vote for own comment ErrVoteDbl = 14 // already voted for the comment ErrVoteMax = 15 // too many votes for the comment ErrVoteMinScore = 16 // min score reached for the comment ErrActionRejected = 17 // general error for rejected actions ErrAssetNotFound = 18 // requested file not found ErrCommentRestrictWords = 19 // restricted words in a comment ErrImgNotFound = 20 // posted image not found in the storage ) // errTmplData store data for error message type errTmplData struct { Error string Details string } // SendErrorHTML makes html body from error_response.html.tmpl template and responds with provided http status code, // error code is not included in render as it is intended for UI developers and not for the users func SendErrorHTML(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int) { // MustExecute behaves like template.Execute, but panics if an error occurs. MustExecute := func(tmpl *template.Template, wr io.Writer, data any) { if err = tmpl.Execute(wr, data); err != nil { panic(err) } } MustRead := func(path string) string { file, e := templates.Read(path) if e != nil { panic(e) } return string(file) } tmplstr := MustRead("error_response.html.tmpl") tmpl := template.Must(template.New("error").Parse(tmplstr)) log.Printf("[WARN] %s", errDetailsMsg(r, httpStatusCode, err, details, errCode)) msg := bytes.Buffer{} MustExecute(tmpl, &msg, errTmplData{ Error: err.Error(), Details: details, }) HTMLResponse(w, httpStatusCode, msg.String()) } // SendErrorJSON makes {error: blah, details: blah, code: 42} json body and responds with error code func SendErrorJSON(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int) { log.Printf("[WARN] %s", errDetailsMsg(r, httpStatusCode, err, details, errCode)) w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpStatusCode) rest.RenderJSON(w, rest.JSON{"error": err.Error(), "details": details, "code": errCode}) } // HTMLResponse writes HTML content with the given status code func HTMLResponse(w http.ResponseWriter, status int, html string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(status) _, _ = w.Write([]byte(html)) } // PlainTextResponse writes plain text content with the given status code func PlainTextResponse(w http.ResponseWriter, status int, text string) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(status) _, _ = w.Write([]byte(text)) } func errDetailsMsg(r *http.Request, httpStatusCode int, err error, details string, errCode int) string { uinfoStr := "" if user, e := GetUserInfo(r); e == nil { uinfoStr = user.Name + "/" + user.ID + " - " } q := r.URL.String() if qun, e := url.QueryUnescape(q); e == nil { q = qun } srcFileInfo := "" if pc, file, line, ok := runtime.Caller(2); ok { fnameElems := strings.Split(file, "/") funcNameElems := strings.Split(runtime.FuncForPC(pc).Name(), "/") srcFileInfo = fmt.Sprintf("[%s:%d %s]", strings.Join(fnameElems[len(fnameElems)-3:], "/"), line, funcNameElems[len(funcNameElems)-1]) } return fmt.Sprintf("%s - %v - %d (%d) - %s%s - %s", details, err, httpStatusCode, errCode, uinfoStr, q, srcFileInfo) }