github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/errors/errors.go (about) 1 package errors 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 "strings" 8 9 "github.com/cozy/cozy-stack/model/app" 10 "github.com/cozy/cozy-stack/model/instance" 11 build "github.com/cozy/cozy-stack/pkg/config" 12 "github.com/cozy/cozy-stack/pkg/config/config" 13 "github.com/cozy/cozy-stack/pkg/consts" 14 "github.com/cozy/cozy-stack/pkg/couchdb" 15 "github.com/cozy/cozy-stack/pkg/jsonapi" 16 "github.com/cozy/cozy-stack/pkg/logger" 17 "github.com/cozy/cozy-stack/web/middlewares" 18 19 "github.com/labstack/echo/v4" 20 ) 21 22 // ErrorHandler is the default error handler of our APIs. 23 func ErrorHandler(err error, c echo.Context) { 24 var je *jsonapi.Error 25 var ce *couchdb.Error 26 27 res := c.Response() 28 req := c.Request() 29 30 var ok bool 31 if _, ok = err.(*echo.HTTPError); ok { 32 // nothing to do 33 } else if os.IsExist(err) { 34 je = jsonapi.Conflict(err) 35 } else if os.IsNotExist(err) { 36 je = jsonapi.NotFound(err) 37 } else if ce, ok = err.(*couchdb.Error); ok { 38 je = &jsonapi.Error{ 39 Status: ce.StatusCode, 40 Title: ce.Name, 41 Detail: ce.Reason, 42 } 43 } else if je, ok = err.(*jsonapi.Error); !ok { 44 je = &jsonapi.Error{ 45 Status: http.StatusInternalServerError, 46 Title: "Unqualified error", 47 Detail: err.Error(), 48 } 49 } 50 51 if build.IsDevRelease() { 52 var log *logger.Entry 53 inst, ok := middlewares.GetInstanceSafe(c) 54 if ok { 55 log = inst.Logger().WithNamespace("http") 56 } else { 57 log = logger.WithNamespace("http") 58 } 59 log.Errorf("%s %s %s", req.Method, req.URL.Path, err) 60 } 61 62 if res.Committed { 63 return 64 } 65 66 if je != nil { 67 if req.Method == http.MethodHead { 68 _ = c.NoContent(je.Status) 69 return 70 } 71 _ = jsonapi.DataError(c, je) 72 return 73 } 74 75 HTMLErrorHandler(err, c) 76 } 77 78 // HTMLErrorHandler is the default fallback error handler for error rendered in 79 // HTML pages, mainly for users, assets and routes that are not part of our API 80 // per-se. 81 func HTMLErrorHandler(err error, c echo.Context) { 82 status := http.StatusInternalServerError 83 req := c.Request() 84 85 if build.IsDevRelease() { 86 var log *logger.Entry 87 inst, ok := middlewares.GetInstanceSafe(c) 88 if ok { 89 log = inst.Logger().WithNamespace("http") 90 } else { 91 log = logger.WithNamespace("http") 92 } 93 log.Errorf("%s %s %s", req.Method, req.URL.Path, err) 94 } 95 96 he, ok := err.(*echo.HTTPError) 97 if ok { 98 status = he.Code 99 if he.Internal != nil { 100 err = he.Internal 101 } 102 } else { 103 he = echo.NewHTTPError(status, err) 104 he.Internal = err 105 } 106 107 var title, value string 108 switch err { 109 case instance.ErrNotFound: 110 status = http.StatusNotFound 111 title = "Error Instance not found Title" 112 value = "Error Instance not found Message" 113 case app.ErrNotFound: 114 status = http.StatusNotFound 115 title = "Error Application not found Title" 116 value = "Error Application not found Message" 117 case app.ErrInvalidSlugName: 118 status = http.StatusBadRequest 119 } 120 121 if title == "" { 122 if status >= 500 { 123 title = "Error Internal Server Error Title" 124 value = "Error Internal Server Error Message" 125 } else { 126 title = "Error Title" 127 value = fmt.Sprintf("%v", he.Message) 128 } 129 } 130 131 accept := req.Header.Get(echo.HeaderAccept) 132 acceptHTML := strings.Contains(accept, echo.MIMETextHTML) 133 acceptJSON := strings.Contains(accept, echo.MIMEApplicationJSON) 134 if req.Method == http.MethodHead { 135 err = c.NoContent(status) 136 } else if acceptJSON { 137 err = c.JSON(status, echo.Map{"error": he.Message}) 138 } else if acceptHTML { 139 i, ok := middlewares.GetInstanceSafe(c) 140 if !ok { 141 i = &instance.Instance{ 142 Domain: req.Host, 143 ContextName: config.DefaultInstanceContext, 144 Locale: consts.DefaultLocale, 145 } 146 } 147 148 inverted := false 149 illustration := "/images/generic-error.svg" 150 var link, linkURL, button, buttonURL string 151 152 switch err { 153 case instance.ErrNotFound: 154 inverted = true 155 illustration = "/images/desert.svg" 156 link = "Error Address forgotten" 157 linkURL = "https://manager.cozycloud.cc/v2/cozy/remind" 158 case app.ErrNotFound: 159 illustration = "/images/desert.svg" 160 } 161 162 err = c.Render(status, "error.html", echo.Map{ 163 "Domain": i.ContextualDomain(), 164 "ContextName": i.ContextName, 165 "Locale": i.Locale, 166 "Title": i.TemplateTitle(), 167 "Favicon": middlewares.Favicon(i), 168 "Inverted": inverted, 169 "Illustration": illustration, 170 "ErrorTitle": title, 171 "Error": value, 172 "Link": link, 173 "LinkURL": linkURL, 174 "SupportEmail": i.SupportEmailAddress(), 175 "Button": button, 176 "ButtonURL": buttonURL, 177 }) 178 } else { 179 err = c.String(status, fmt.Sprintf("%v", he.Message)) 180 } 181 182 if err != nil { 183 logger.WithNamespace("http").Errorf("%s %s %s", req.Method, req.URL.Path, err) 184 } 185 }