github.com/opencontainers/runtime-tools@v0.9.0/specerror/error.go (about)

     1  // Package specerror implements runtime-spec-specific tooling for
     2  // tracking RFC 2119 violations.
     3  package specerror
     4  
     5  import (
     6  	"fmt"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  	rfc2119 "github.com/opencontainers/runtime-tools/error"
    10  )
    11  
    12  const referenceTemplate = "https://github.com/opencontainers/runtime-spec/blob/v%s/%s"
    13  
    14  // Code represents the spec violation, enumerating both
    15  // configuration violations and runtime violations.
    16  type Code int64
    17  
    18  const (
    19  	// NonError represents that an input is not an error
    20  	NonError Code = 0x1a001 + iota
    21  	// NonRFCError represents that an error is not a rfc2119 error
    22  	NonRFCError
    23  )
    24  
    25  type errorTemplate struct {
    26  	Level     rfc2119.Level
    27  	Reference func(version string) (reference string, err error)
    28  }
    29  
    30  // Error represents a runtime-spec violation.
    31  type Error struct {
    32  	// Err holds the RFC 2119 violation.
    33  	Err rfc2119.Error
    34  
    35  	// Code is a matchable holds a Code
    36  	Code Code
    37  }
    38  
    39  // LevelErrors represents Errors filtered into fatal and warnings.
    40  type LevelErrors struct {
    41  	// Warnings holds Errors that were below a compliance-level threshold.
    42  	Warnings []*Error
    43  
    44  	// Error holds errors that were at or above a compliance-level
    45  	// threshold, as well as errors that are not Errors.
    46  	Error *multierror.Error
    47  }
    48  
    49  var ociErrors = map[Code]errorTemplate{}
    50  
    51  func register(code Code, level rfc2119.Level, ref func(versiong string) (string, error)) {
    52  	if _, ok := ociErrors[code]; ok {
    53  		panic(fmt.Sprintf("should not regist a same code twice: %v", code))
    54  	}
    55  
    56  	ociErrors[code] = errorTemplate{Level: level, Reference: ref}
    57  }
    58  
    59  // Error returns the error message with specification reference.
    60  func (err *Error) Error() string {
    61  	return err.Err.Error()
    62  }
    63  
    64  // NewRFCError creates an rfc2119.Error referencing a spec violation.
    65  //
    66  // A version string (for the version of the spec that was violated)
    67  // must be set to get a working URL.
    68  func NewRFCError(code Code, err error, version string) (*rfc2119.Error, error) {
    69  	template := ociErrors[code]
    70  	reference, err2 := template.Reference(version)
    71  	if err2 != nil {
    72  		return nil, err2
    73  	}
    74  	return &rfc2119.Error{
    75  		Level:     template.Level,
    76  		Reference: reference,
    77  		Err:       err,
    78  	}, nil
    79  }
    80  
    81  // NewRFCErrorOrPanic creates an rfc2119.Error referencing a spec
    82  // violation and panics on failure.  This is handy for situations
    83  // where you can't be bothered to check NewRFCError for failure.
    84  func NewRFCErrorOrPanic(code Code, err error, version string) *rfc2119.Error {
    85  	rfcError, err2 := NewRFCError(code, err, version)
    86  	if err2 != nil {
    87  		panic(err2.Error())
    88  	}
    89  	return rfcError
    90  }
    91  
    92  // NewError creates an Error referencing a spec violation.  The error
    93  // can be cast to an *Error for extracting structured information
    94  // about the level of the violation and a reference to the violated
    95  // spec condition.
    96  //
    97  // A version string (for the version of the spec that was violated)
    98  // must be set to get a working URL.
    99  func NewError(code Code, err error, version string) error {
   100  	rfcError, err2 := NewRFCError(code, err, version)
   101  	if err2 != nil {
   102  		return err2
   103  	}
   104  	return &Error{
   105  		Err:  *rfcError,
   106  		Code: code,
   107  	}
   108  }
   109  
   110  // FindError finds an error from a source error (multiple error) and
   111  // returns the error code if found.
   112  // If the source error is nil or empty, return NonError.
   113  // If the source error is not a multiple error, return NonRFCError.
   114  func FindError(err error, code Code) Code {
   115  	if err == nil {
   116  		return NonError
   117  	}
   118  
   119  	if merr, ok := err.(*multierror.Error); ok {
   120  		if merr.ErrorOrNil() == nil {
   121  			return NonError
   122  		}
   123  		for _, e := range merr.Errors {
   124  			if rfcErr, ok := e.(*Error); ok {
   125  				if rfcErr.Code == code {
   126  					return code
   127  				}
   128  			}
   129  		}
   130  	}
   131  	return NonRFCError
   132  }
   133  
   134  // SplitLevel removes RFC 2119 errors with a level less than 'level'
   135  // from the source error.  If the source error is not a multierror, it
   136  // is returned unchanged.
   137  func SplitLevel(errIn error, level rfc2119.Level) (levelErrors LevelErrors, errOut error) {
   138  	merr, ok := errIn.(*multierror.Error)
   139  	if !ok {
   140  		return levelErrors, errIn
   141  	}
   142  	for _, err := range merr.Errors {
   143  		e, ok := err.(*Error)
   144  		if ok && e.Err.Level < level {
   145  			fmt.Println(e)
   146  			levelErrors.Warnings = append(levelErrors.Warnings, e)
   147  			continue
   148  		}
   149  		levelErrors.Error = multierror.Append(levelErrors.Error, err)
   150  	}
   151  	return levelErrors, nil
   152  }