blob: ffc2325f249fbc11b6a4dd3ec16fa8e75b954f16 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package derrors defines internal error values to categorize the different
// types error semantics we support.
package derrors
import (
"errors"
"fmt"
"net/http"
"runtime"
"cloud.google.com/go/errorreporting"
)
//lint:file-ignore ST1012 prefixing error values with Err would stutter
var (
// HasIncompletePackages indicates a module containing packages that
// were processed with a 60x error code.
HasIncompletePackages = errors.New("has incomplete packages")
// NotFound indicates that a requested entity was not found (HTTP 404).
NotFound = errors.New("not found")
// NotFetched means that the proxy returned "not found" with the
// Disable-Module-Fetch header set. We don't know if the module really
// doesn't exist, or the proxy just didn't fetch it.
NotFetched = errors.New("not fetched by proxy")
// InvalidArgument indicates that the input into the request is invalid in
// some way (HTTP 400).
InvalidArgument = errors.New("invalid argument")
// BadModule indicates a problem with a module.
BadModule = errors.New("bad module")
// Excluded indicates that the module is excluded. (See internal/postgres/excluded.go.)
Excluded = errors.New("excluded")
// AlternativeModule indicates that the path of the module zip file differs
// from the path specified in the go.mod file.
AlternativeModule = errors.New("alternative module")
// ModuleTooLarge indicates that the module is too large for us to process.
// This should be temporary: we should obtain sufficient resources to process
// any module, up to the max size allowed by the proxy.
ModuleTooLarge = errors.New("module too large")
// SheddingLoad indicates that the server is overloaded and cannot process the
// module at this time.
SheddingLoad = errors.New("shedding load")
// Cleaned indicates that the module version was cleaned from the DB and
// shouldn't be reprocessed.
Cleaned = errors.New("cleaned")
// Unknown indicates that the error has unknown semantics.
Unknown = errors.New("unknown")
// ProxyTimedOut indicates that a request timed out when fetching from the Module Mirror.
ProxyTimedOut = errors.New("proxy timed out")
// ProxyError is used to capture non-actionable server errors returned from the proxy.
ProxyError = errors.New("proxy error")
// VulnDBError is used to capture non-actionable server errors returned from vulndb.
VulnDBError = errors.New("vulndb error")
// PackageBuildContextNotSupported indicates that the build context for the
// package is not supported.
PackageBuildContextNotSupported = errors.New("package build context not supported")
// PackageMaxImportsLimitExceeded indicates that the package has too many
// imports.
PackageMaxImportsLimitExceeded = errors.New("package max imports limit exceeded")
// PackageMaxFileSizeLimitExceeded indicates that the package contains a file
// that exceeds fetch.MaxFileSize.
PackageMaxFileSizeLimitExceeded = errors.New("package max file size limit exceeded")
// PackageDocumentationHTMLTooLarge indicates that the rendered documentation
// HTML size exceeded the specified limit for dochtml.RenderOptions.
PackageDocumentationHTMLTooLarge = errors.New("package documentation HTML is too large")
// PackageBadImportPath represents an error loading a package because its
// contents do not make up a valid package. This can happen, for
// example, if the .go files fail to parse or declare different package
// names.
// Go files were found in a directory, but the resulting import path is invalid.
PackageBadImportPath = errors.New("package bad import path")
// PackageInvalidContents represents an error loading a package because
// its contents do not make up a valid package. This can happen, for
// example, if the .go files fail to parse or declare different package
// names.
PackageInvalidContents = errors.New("package invalid contents")
// DBModuleInsertInvalid represents a module that was successfully
// fetched but could not be inserted due to invalid arguments to
// postgres.InsertModule.
DBModuleInsertInvalid = errors.New("db module insert invalid")
// ReprocessStatusOK indicates that the module to be reprocessed
// previously had a status of http.StatusOK.
ReprocessStatusOK = errors.New("reprocess status ok")
// ReprocessHasIncompletePackages indicates that the module to be reprocessed
// previously had a status of 290.
ReprocessHasIncompletePackages = errors.New("reprocess has incomplete packages")
// ReprocessBadModule indicates that the module to be reprocessed
// previously had a status of derrors.BadModule.
ReprocessBadModule = errors.New("reprocess bad module")
// ReprocessAlternativeModule indicates that the module to be reprocessed
// previously had a status of derrors.AlternativeModule.
ReprocessAlternative = errors.New("reprocess alternative module")
// ReprocessDBModuleInsertInvalid represents a module to be reprocessed
// that was successfully fetched but could not be inserted due to invalid
// arguments to postgres.InsertModule.
ReprocessDBModuleInsertInvalid = errors.New("reprocess db module insert invalid")
)
var codes = []struct {
err error
code int
}{
{NotFound, http.StatusNotFound},
{InvalidArgument, http.StatusBadRequest},
{Excluded, http.StatusForbidden},
{SheddingLoad, http.StatusServiceUnavailable},
// Since the following aren't HTTP statuses, pick unused codes.
{HasIncompletePackages, 290},
{DBModuleInsertInvalid, 480},
{NotFetched, 481},
{BadModule, 490},
{AlternativeModule, 491},
{ModuleTooLarge, 492},
{Cleaned, 493},
{ProxyTimedOut, 550}, // not a real code
{ProxyError, 551}, // not a real code
{VulnDBError, 552}, // not a real code
// 52x and 54x errors represents modules that need to be reprocessed, and the
// previous status code the module had. Note that the status code
// matters for determining reprocessing order.
{ReprocessStatusOK, 520},
{ReprocessHasIncompletePackages, 521},
{ReprocessBadModule, 540},
{ReprocessAlternative, 541},
{ReprocessDBModuleInsertInvalid, 542},
// 60x errors represents errors that occurred when processing a
// package.
{PackageBuildContextNotSupported, 600},
{PackageMaxImportsLimitExceeded, 601},
{PackageMaxFileSizeLimitExceeded, 602},
{PackageDocumentationHTMLTooLarge, 603},
{PackageInvalidContents, 604},
{PackageBadImportPath, 605},
}
// FromStatus generates an error according for the given status code. It uses
// the given format string and arguments to create the error string according
// to the fmt package. If format is the empty string, then the error
// corresponding to the code is returned unwrapped.
//
// If code is http.StatusOK, it returns nil.
func FromStatus(code int, format string, args ...any) error {
if code == http.StatusOK {
return nil
}
var innerErr = Unknown
for _, e := range codes {
if e.code == code {
innerErr = e.err
break
}
}
if format == "" {
return innerErr
}
return fmt.Errorf(format+": %w", append(args, innerErr)...)
}
// ToStatus returns a status code corresponding to err.
func ToStatus(err error) int {
if err == nil {
return http.StatusOK
}
for _, e := range codes {
if errors.Is(err, e.err) {
return e.code
}
}
return http.StatusInternalServerError
}
// ToReprocessStatus returns the reprocess status code corresponding to the
// provided status.
func ToReprocessStatus(status int) int {
switch status {
case http.StatusOK:
return ToStatus(ReprocessStatusOK)
case ToStatus(HasIncompletePackages):
return ToStatus(ReprocessHasIncompletePackages)
case ToStatus(BadModule):
return ToStatus(ReprocessBadModule)
case ToStatus(AlternativeModule):
return ToStatus(ReprocessAlternative)
case ToStatus(DBModuleInsertInvalid):
return ToStatus(ReprocessDBModuleInsertInvalid)
default:
return status
}
}
// Add adds context to the error.
// The result cannot be unwrapped to recover the original error.
// It does nothing when *errp == nil.
//
// Example:
//
// defer derrors.Add(&err, "copy(%s, %s)", src, dst)
//
// See Wrap for an equivalent function that allows
// the result to be unwrapped.
func Add(errp *error, format string, args ...any) {
if *errp != nil {
*errp = fmt.Errorf("%s: %v", fmt.Sprintf(format, args...), *errp)
}
}
// Wrap adds context to the error and allows
// unwrapping the result to recover the original error.
//
// Example:
//
// defer derrors.Wrap(&err, "copy(%s, %s)", src, dst)
//
// See Add for an equivalent function that does not allow
// the result to be unwrapped.
func Wrap(errp *error, format string, args ...any) {
if *errp != nil {
*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
}
}
// WrapStack is like Wrap, but adds a stack trace if there isn't one already.
func WrapStack(errp *error, format string, args ...any) {
if *errp != nil {
if se := (*StackError)(nil); !errors.As(*errp, &se) {
*errp = NewStackError(*errp)
}
Wrap(errp, format, args...)
}
}
// StackError wraps an error and adds a stack trace.
type StackError struct {
Stack []byte
err error
}
// NewStackError returns a StackError, capturing a stack trace.
func NewStackError(err error) *StackError {
// Limit the stack trace to 16K. Same value used in the errorreporting client,
// cloud.google.com/go@v0.66.0/errorreporting/errors.go.
var buf [16 * 1024]byte
n := runtime.Stack(buf[:], false)
return &StackError{
err: err,
Stack: buf[:n],
}
}
func (e *StackError) Error() string {
return e.err.Error() // ignore the stack
}
func (e *StackError) Unwrap() error {
return e.err
}
// WrapAndReport calls Wrap followed by Report.
func WrapAndReport(errp *error, format string, args ...any) {
Wrap(errp, format, args...)
if *errp != nil {
Report(*errp)
}
}
var repClient *errorreporting.Client
// SetReportingClient sets an errorreporting client, for use by Report.
func SetReportingClient(c *errorreporting.Client) {
repClient = c
}
// Report uses the errorreporting API to report an error.
func Report(err error) {
if repClient != nil {
repClient.Report(errorreporting.Entry{Error: err})
}
}