summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/spiceerrors/details_metadata.go
blob: 20d77956fe8e7bcd6825238a1f91d671c4b43448 (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
package spiceerrors

import (
	"fmt"

	"google.golang.org/genproto/googleapis/rpc/errdetails"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/runtime/protoiface"
)

// MetadataKey is the type used to represent the keys of the metadata map in
// the error details.
type MetadataKey string

// DebugTraceErrorDetailsKey is the key used to store the debug trace in the error details.
// The value is expected to be a string containing the proto text of a DebugInformation message.
const DebugTraceErrorDetailsKey MetadataKey = "debug_trace_proto_text"

// AppendDetailsMetadata appends the key-value pair to the error details metadata.
// If the error is nil or is not a status error, it is returned as is.
func AppendDetailsMetadata(err error, key MetadataKey, value string) error {
	if err == nil {
		return nil
	}

	s, ok := status.FromError(err)
	if !ok {
		return err
	}

	var foundErrDetails *errdetails.ErrorInfo
	var otherDetails []protoiface.MessageV1
	for _, details := range s.Details() {
		if errInfo, ok := details.(*errdetails.ErrorInfo); ok {
			foundErrDetails = errInfo
		} else if cast, ok := details.(protoiface.MessageV1); ok {
			otherDetails = append(otherDetails, cast)
		}
	}

	if foundErrDetails != nil {
		if foundErrDetails.Metadata == nil {
			foundErrDetails.Metadata = make(map[string]string, 1)
		}

		foundErrDetails.Metadata[string(key)] = value
	}

	return WithCodeAndDetailsAsError(
		fmt.Errorf("%s", s.Message()),
		s.Code(),
		append(otherDetails, foundErrDetails)...,
	)
}

// WithReplacedDetails replaces the details of the error with the provided details.
// If the error is nil or is not a status error, it is returned as is.
// If the error does not have the details to replace, the provided details are appended.
func WithReplacedDetails[T protoiface.MessageV1](err error, toReplace T) error {
	if err == nil {
		return nil
	}

	s, ok := status.FromError(err)
	if !ok {
		return err
	}

	details := make([]protoiface.MessageV1, 0, len(s.Details()))
	wasReplaced := false
	for _, current := range s.Details() {
		if _, ok := current.(T); ok {
			details = append(details, toReplace)
			wasReplaced = true
			continue
		}

		if cast, ok := current.(protoiface.MessageV1); ok {
			details = append(details, cast)
		}
	}

	if !wasReplaced {
		details = append(details, toReplace)
	}

	return WithCodeAndDetailsAsError(
		fmt.Errorf("%s", s.Message()),
		s.Code(),
		details...,
	)
}

// GetDetails returns the details of the error if they are of the provided type, if any.
func GetDetails[T protoiface.MessageV1](err error) (T, bool) {
	if err == nil {
		return *new(T), false
	}

	s, ok := status.FromError(err)
	if !ok {
		return *new(T), false
	}

	for _, details := range s.Details() {
		if cast, ok := details.(T); ok {
			return cast, true
		}
	}

	return *new(T), false
}