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 }