go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/common/cipderr/cipderr.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package cipderr contains an enumeration with possible CIPD error categories.
    16  package cipderr
    17  
    18  import (
    19  	"context"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  )
    23  
    24  // Code is returned as part of JSON output by CIPD CLI.
    25  //
    26  // It as an enumeration with broad categories of possible errors.
    27  type Code string
    28  
    29  const (
    30  	// Authentication or authorization error when contacting the backend.
    31  	Auth Code = "auth_error"
    32  	// An error doing local I/O (i.e. writing or reading files).
    33  	IO Code = "io_error"
    34  	// An IO error reading or writing from CIPD CAS.
    35  	CAS Code = "cas_error"
    36  	// An incorrectly formatted version name, instance ID, etc.
    37  	BadArgument Code = "bad_argument_error"
    38  	// A requested package is missing or its version can't be resolved.
    39  	InvalidVersion Code = "invalid_version_error"
    40  	// An error getting a response from the backend.
    41  	BackendUnavailable Code = "backend_unavailable_error"
    42  	// A generic fatal RPC error, e.g. violation of some precodition.
    43  	RPC Code = "rpc_error"
    44  	// Something (e.g. a resolved pins file) needs to be regenerated.
    45  	Stale Code = "stale_error"
    46  	// A hash of downloaded file doesn't match the expected value.
    47  	HashMismatch Code = "hash_mismatch_error"
    48  	// The admission plugin forbid installation of a package.
    49  	NotAdmitted Code = "not_admitted_error"
    50  	// A timeout of some sort.
    51  	Timeout Code = "timeout_error"
    52  	// Unrecognized (possibly transient) error.
    53  	Unknown Code = "unknown_error"
    54  )
    55  
    56  // Details can be optionally attached to an error.
    57  type Details struct {
    58  	Package string `json:"package,omitempty"`
    59  	Version string `json:"version,omitempty"`
    60  	Subdir  string `json:"subdir,omitempty"`
    61  }
    62  
    63  var tagKey = errors.NewTagKey("cipderr.Code")
    64  
    65  type tagValue struct {
    66  	code    Code
    67  	details *Details
    68  }
    69  
    70  // GenerateErrorTagValue is part of errors.TagValueGenerator, allowing this
    71  // pair to be used as en error tag.
    72  func (v tagValue) GenerateErrorTagValue() errors.TagValue {
    73  	return errors.TagValue{Key: tagKey, Value: v}
    74  }
    75  
    76  // GenerateErrorTagValue is part of errors.TagValueGenerator, allowing this
    77  // code to be used as en error tag.
    78  func (c Code) GenerateErrorTagValue() errors.TagValue {
    79  	return errors.TagValue{Key: tagKey, Value: tagValue{code: c}}
    80  }
    81  
    82  // WithDetails returns a error tag that attaches this code together with some
    83  // details.
    84  func (c Code) WithDetails(d Details) errors.TagValueGenerator {
    85  	return tagValue{code: c, details: &d}
    86  }
    87  
    88  // ToCode examines a CIPD error to get a representative error code.
    89  func ToCode(err error) Code {
    90  	if val, ok := errors.TagValueIn(tagKey, err); ok {
    91  		return val.(tagValue).code
    92  	}
    93  	deadline := errors.Any(err, func(err error) bool {
    94  		return err == context.DeadlineExceeded || err == context.Canceled
    95  	})
    96  	if deadline {
    97  		return Timeout
    98  	}
    99  	return Unknown
   100  }
   101  
   102  // ToDetails extracts error details, if available.
   103  func ToDetails(err error) *Details {
   104  	if val, ok := errors.TagValueIn(tagKey, err); ok {
   105  		return val.(tagValue).details
   106  	}
   107  	return nil
   108  }
   109  
   110  // AttachDetails attaches details to an error, preserving its code.
   111  //
   112  // Overrides any previous details. Does nothing if `err` is nil.
   113  func AttachDetails(err *error, d Details) {
   114  	if *err != nil {
   115  		*err = errors.Annotate(*err, "").Tag(ToCode(*err).WithDetails(d)).Err()
   116  	}
   117  }