blob: 574edc0769a5657a8c63cc2f72a6224999c85e66 [file] [log] [blame]
// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cipderr contains an enumeration with possible CIPD error categories.
package cipderr
import (
"context"
"go.chromium.org/luci/common/errors"
)
// Code is returned as part of JSON output by CIPD CLI.
//
// It as an enumeration with broad categories of possible errors.
type Code string
const (
// Authentication or authorization error when contacting the backend.
Auth Code = "auth_error"
// An error doing local I/O (i.e. writing or reading files).
IO Code = "io_error"
// An IO error reading or writing from CIPD CAS.
CAS Code = "cas_error"
// An incorrectly formatted version name, instance ID, etc.
BadArgument Code = "bad_argument_error"
// A requested package is missing or its version can't be resolved.
InvalidVersion Code = "invalid_version_error"
// An error getting a response from the backend.
BackendUnavailable Code = "backend_unavailable_error"
// A generic fatal RPC error, e.g. violation of some precodition.
RPC Code = "rpc_error"
// Something (e.g. a resolved pins file) needs to be regenerated.
Stale Code = "stale_error"
// A hash of downloaded file doesn't match the expected value.
HashMismatch Code = "hash_mismatch_error"
// The admission plugin forbid installation of a package.
NotAdmitted Code = "not_admitted_error"
// A timeout of some sort.
Timeout Code = "timeout_error"
// Unrecognized (possibly transient) error.
Unknown Code = "unknown_error"
)
// Details can be optionally attached to an error.
type Details struct {
Package string `json:"package,omitempty"`
Version string `json:"version,omitempty"`
Subdir string `json:"subdir,omitempty"`
}
var tagKey = errors.NewTagKey("cipderr.Code")
type tagValue struct {
code Code
details *Details
}
// GenerateErrorTagValue is part of errors.TagValueGenerator, allowing this
// pair to be used as en error tag.
func (v tagValue) GenerateErrorTagValue() errors.TagValue {
return errors.TagValue{Key: tagKey, Value: v}
}
// GenerateErrorTagValue is part of errors.TagValueGenerator, allowing this
// code to be used as en error tag.
func (c Code) GenerateErrorTagValue() errors.TagValue {
return errors.TagValue{Key: tagKey, Value: tagValue{code: c}}
}
// WithDetails returns a error tag that attaches this code together with some
// details.
func (c Code) WithDetails(d Details) errors.TagValueGenerator {
return tagValue{code: c, details: &d}
}
// ToCode examines a CIPD error to get a representative error code.
func ToCode(err error) Code {
if val, ok := errors.TagValueIn(tagKey, err); ok {
return val.(tagValue).code
}
deadline := errors.Any(err, func(err error) bool {
return err == context.DeadlineExceeded || err == context.Canceled
})
if deadline {
return Timeout
}
return Unknown
}
// ToDetails extracts error details, if available.
func ToDetails(err error) *Details {
if val, ok := errors.TagValueIn(tagKey, err); ok {
return val.(tagValue).details
}
return nil
}
// AttachDetails attaches details to an error, preserving its code.
//
// Overrides any previous details. Does nothing if `err` is nil.
func AttachDetails(err *error, d Details) {
if *err != nil {
*err = errors.Annotate(*err, "").Tag(ToCode(*err).WithDetails(d)).Err()
}
}