forked from infobloxopen/atlas-app-toolkit
-
Notifications
You must be signed in to change notification settings - Fork 1
/
container.go
185 lines (143 loc) · 4.86 KB
/
container.go
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// The Error Container entity serves a purpose for keeping track of errors,
// details and fields information. This component can be used explicitly (for
// example when we want to sequentially fill it with details and fields), as
// well as implicitly (all errors that are returned from handler are
// transformed to an Error Container, and passed as GRPCStatus to a gRPC Gateway).
package errors
import (
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/golang/protobuf/proto"
"github.com/infobloxopen/atlas-app-toolkit/rpc/errdetails"
"github.com/infobloxopen/atlas-app-toolkit/rpc/errfields"
)
// Container struct is an entity that servers a purpose of error container and
// consist of methods to append details, field errors and setting general
// error code/message.
type Container struct {
// details field contains an array of error details.
details []*errdetails.TargetInfo
// fields field contains per-field error map.
fields *errfields.FieldInfo
// errCode, errMessage field contain the general error message.
errCode codes.Code
errMessage string
// errSet flag indicates whether the error was set by calling one of
// following methods: Set, WithDetail(s), WithField(s).
errSet bool
// Mapper structure performs necessary mappings.
Mapper
}
// NewContainer function returns a new entity of error container.
func NewContainer(code codes.Code, format string, args ...interface{}) *Container {
return (&Container{}).New(code, format, args...)
}
func InitContainer() *Container {
return (&Container{}).New(codes.Unknown, "Unknown")
}
// Error function returns error message currently associated with container.
func (c Container) Error() string { return c.errMessage }
// GRPCStatus function returns an error container as GRPC status.
func (c *Container) GRPCStatus() *status.Status {
protoArr := []proto.Message{}
if c.fields != nil {
protoArr = append(protoArr, proto.Message(c.fields))
}
for _, d := range c.details {
protoArr = append(protoArr, proto.Message(d))
}
if s, err := status.New(c.errCode, c.errMessage).WithDetails(protoArr...); err == nil {
return s
}
return nil
}
// New function instantinates general error code and error message for error
// container.
func (c *Container) New(code codes.Code, format string, args ...interface{}) *Container {
c.errCode = code
c.errMessage = fmt.Sprintf(format, args...)
c.details = nil
c.fields = nil
c.errSet = false
return c
}
// IsSet function returns flag that determines whether the main error code and
// error message were set or not.
func (c *Container) IsSet() bool {
return c.errSet
}
// Set function initializes general error code and error message for error
// container and also appends a detail with the same content to a an error
// container's 'details' section.
func (c *Container) Set(target string, code codes.Code, format string, args ...interface{}) *Container {
c.errCode = code
c.errMessage = fmt.Sprintf(format, args...)
c.errSet = true
c = c.WithDetail(c.errCode, target, c.errMessage)
return c
}
// IfSet function initializes general error code and error message for error
// container if and only if any error was set previously by calling Set,
// WithField(s), WithDetail(s).
func (c *Container) IfSet(code codes.Code, format string, args ...interface{}) error {
if c.errSet {
c.errCode = code
c.errMessage = fmt.Sprintf(format, args...)
return c
}
return nil
}
// WithDetail function appends a new Detail to an error container's 'details'
// section.
func (c *Container) WithDetail(code codes.Code, target string, format string, args ...interface{}) *Container {
c.errSet = true
if c.details == nil {
c.details = []*errdetails.TargetInfo{}
}
c.details = append(c.details, errdetails.Newf(code, target, format, args...))
return c
}
// WithDetails function appends a list of error details to an error
// container's 'details' section.
func (c *Container) WithDetails(details ...*errdetails.TargetInfo) *Container {
if len(details) == 0 {
return c
}
c.errSet = true
if c.details == nil {
c.details = []*errdetails.TargetInfo{}
}
c.details = append(c.details, details...)
return c
}
// WithField function appends a field error detail to an error container's
// 'fields' section.
func (c *Container) WithField(target string, format string, args ...interface{}) *Container {
c.errSet = true
if c.fields == nil {
c.fields = &errfields.FieldInfo{}
}
c.fields.AddField(target, fmt.Sprintf(format, args...))
return c
}
// WithFields function appends a several fields error details to an error
// container's 'fields' section.
func (c *Container) WithFields(fields map[string][]string) *Container {
if len(fields) == 0 {
return c
}
var hasDesc bool
for k, v := range fields {
for _, vVal := range v {
if vVal != "" && k != "" {
c.WithField(k, vVal)
hasDesc = true
}
}
}
if hasDesc {
c.errSet = true
}
return c
}