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  }