summaryrefslogtreecommitdiff
path: root/vendor/github.com/opencontainers/runtime-tools/specerror/error.go
blob: 5de9492d9ac47ccbbcecc21710ad1bc4803f4184 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Package specerror implements runtime-spec-specific tooling for
// tracking RFC 2119 violations.
package specerror

import (
	"fmt"

	"github.com/hashicorp/go-multierror"
	rfc2119 "github.com/opencontainers/runtime-tools/error"
)

const referenceTemplate = "https://github.com/opencontainers/runtime-spec/blob/v%s/%s"

// Code represents the spec violation, enumerating both
// configuration violations and runtime violations.
type Code int64

const (
	// NonError represents that an input is not an error
	NonError Code = 0x1a001 + iota
	// NonRFCError represents that an error is not a rfc2119 error
	NonRFCError
)

type errorTemplate struct {
	Level     rfc2119.Level
	Reference func(version string) (reference string, err error)
}

// Error represents a runtime-spec violation.
type Error struct {
	// Err holds the RFC 2119 violation.
	Err rfc2119.Error

	// Code is a matchable holds a Code
	Code Code
}

// LevelErrors represents Errors filtered into fatal and warnings.
type LevelErrors struct {
	// Warnings holds Errors that were below a compliance-level threshold.
	Warnings []*Error

	// Error holds errors that were at or above a compliance-level
	// threshold, as well as errors that are not Errors.
	Error *multierror.Error
}

var ociErrors = map[Code]errorTemplate{}

func register(code Code, level rfc2119.Level, ref func(versiong string) (string, error)) {
	if _, ok := ociErrors[code]; ok {
		panic(fmt.Sprintf("should not regist a same code twice: %v", code))
	}

	ociErrors[code] = errorTemplate{Level: level, Reference: ref}
}

// Error returns the error message with specification reference.
func (err *Error) Error() string {
	return err.Err.Error()
}

// NewRFCError creates an rfc2119.Error referencing a spec violation.
//
// A version string (for the version of the spec that was violated)
// must be set to get a working URL.
func NewRFCError(code Code, err error, version string) (*rfc2119.Error, error) {
	template := ociErrors[code]
	reference, err2 := template.Reference(version)
	if err2 != nil {
		return nil, err2
	}
	return &rfc2119.Error{
		Level:     template.Level,
		Reference: reference,
		Err:       err,
	}, nil
}

// NewRFCErrorOrPanic creates an rfc2119.Error referencing a spec
// violation and panics on failure.  This is handy for situations
// where you can't be bothered to check NewRFCError for failure.
func NewRFCErrorOrPanic(code Code, err error, version string) *rfc2119.Error {
	rfcError, err2 := NewRFCError(code, err, version)
	if err2 != nil {
		panic(err2.Error())
	}
	return rfcError
}

// NewError creates an Error referencing a spec violation.  The error
// can be cast to an *Error for extracting structured information
// about the level of the violation and a reference to the violated
// spec condition.
//
// A version string (for the version of the spec that was violated)
// must be set to get a working URL.
func NewError(code Code, err error, version string) error {
	rfcError, err2 := NewRFCError(code, err, version)
	if err2 != nil {
		return err2
	}
	return &Error{
		Err:  *rfcError,
		Code: code,
	}
}

// FindError finds an error from a source error (multiple error) and
// returns the error code if found.
// If the source error is nil or empty, return NonError.
// If the source error is not a multiple error, return NonRFCError.
func FindError(err error, code Code) Code {
	if err == nil {
		return NonError
	}

	if merr, ok := err.(*multierror.Error); ok {
		if merr.ErrorOrNil() == nil {
			return NonError
		}
		for _, e := range merr.Errors {
			if rfcErr, ok := e.(*Error); ok {
				if rfcErr.Code == code {
					return code
				}
			}
		}
	}
	return NonRFCError
}

// SplitLevel removes RFC 2119 errors with a level less than 'level'
// from the source error.  If the source error is not a multierror, it
// is returned unchanged.
func SplitLevel(errIn error, level rfc2119.Level) (levelErrors LevelErrors, errOut error) {
	merr, ok := errIn.(*multierror.Error)
	if !ok {
		return levelErrors, errIn
	}
	for _, err := range merr.Errors {
		e, ok := err.(*Error)
		if ok && e.Err.Level < level {
			fmt.Println(e)
			levelErrors.Warnings = append(levelErrors.Warnings, e)
			continue
		}
		levelErrors.Error = multierror.Append(levelErrors.Error, err)
	}
	return levelErrors, nil
}