forked from graniticio/granitic
/
serviceerror.go
187 lines (130 loc) · 5.25 KB
/
serviceerror.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
186
187
// Copyright 2016-2018 Granitic. All rights reserved.
// Use of this source code is governed by an Apache 2.0 license that can be found in the LICENSE file at the root of this project.
/*
Package grncerror defines error-message management types and error handling functions.
The primary type in this package is ServiceErrorManager, which allows an application to manage error definitions (messages and categories)
in a single location and have them looked up and referred to by error codes throughout the application.
A ServiceErrorManager is made available to applications by enabling the ServiceErrorManager facility. This facility is
documented in detail here: http://granitic.io/1.0/ref/service-errors the package documentation for facility/serviceerror
gives a brief example of how to define errors in your application.
*/
package grncerror
import (
"bytes"
"errors"
"fmt"
"github.com/graniticio/granitic/logging"
"github.com/graniticio/granitic/types"
"github.com/graniticio/granitic/ws"
"strings"
)
// Implemented by components that want to decare that they use error codes, so that all codes they
// use can be validated to make sure that they have corresponding definitions.
type ErrorCodeUser interface {
// ErrorCodesInUse returns the set of error codes that this component relies on and the component's name
ErrorCodesInUse() (codes types.StringSet, component string)
// ValidateMissing returns false if the component does not want the codes it uses checked.
ValidateMissing() bool
}
// An instance of ServiceErrorManager contains a map between an error code and a ws.CategorisedError.
type ServiceErrorManager struct {
errors map[string]*ws.CategorisedError
// Logger used by Granitic framework components. Automatically injected.
FrameworkLogger logging.Logger
// Determines whether or not a panic should be triggered if a method on this type is called with
// an error code that is not stored in the map of codes to errors.
PanicOnMissing bool
errorCodeSources []ErrorCodeUser
componentName string
}
// See ioc.ComponentNamer.ComponentName
func (sem *ServiceErrorManager) ComponentName() string {
return sem.componentName
}
// See ioc.ComponentNamer.SetComponentName
func (sem *ServiceErrorManager) SetComponentName(name string) {
sem.componentName = name
}
// Find returns the CategorisedError associated with the supplied code. If the code does not exist and PanicOnMissing
// is false, nil is returned. If PanicOnMissing is true the goroutine panics.
func (sem *ServiceErrorManager) Find(code string) *ws.CategorisedError {
e := sem.errors[code]
if e == nil {
message := fmt.Sprintf("%s could not find error with code %s", sem.componentName, code)
if sem.PanicOnMissing {
panic(message)
} else {
sem.FrameworkLogger.LogWarnf(message)
}
}
return e
}
// LoadErrors parses error definitions from the supplied definitions which will be cast from []interface to [][]string
// Each element of the sub-array is expected to be a []string with three elements.
func (sem *ServiceErrorManager) LoadErrors(definitions []interface{}) {
l := sem.FrameworkLogger
sem.errors = make(map[string]*ws.CategorisedError)
for i, d := range definitions {
e := d.([]interface{})
category, err := ws.CodeToCategory(e[0].(string))
if err != nil {
l.LogWarnf("Error index %d: %s", i, err.Error())
continue
}
code := e[1].(string)
if len(strings.TrimSpace(code)) == 0 {
l.LogWarnf("Error index %d: No code supplied", i)
continue
} else if sem.errors[code] != nil {
l.LogWarnf("Error index %d: Duplicate code", i)
continue
}
message := e[2].(string)
if len(strings.TrimSpace(message)) == 0 {
l.LogWarnf("Error index %d: No message supplied", i)
continue
}
ce := ws.NewCategorisedError(category, code, message)
sem.errors[code] = ce
}
}
// RegisterCodeUser accepts a reference to a component ErrorCodeUser so that the set of error codes actually in use
// can be monitored.
func (sem *ServiceErrorManager) RegisterCodeUser(ecu ErrorCodeUser) {
if sem.errorCodeSources == nil {
sem.errorCodeSources = make([]ErrorCodeUser, 0)
}
sem.errorCodeSources = append(sem.errorCodeSources, ecu)
}
// AllowAccess is called by the IoC container after all components have been configured and started. At this point
// all of the error codes that have been declared to be in use can be compared with the available error code definitions.
//
// If there are any codes that are in use, but do not have a corresponding error definition an error will be returned.
func (sem *ServiceErrorManager) AllowAccess() error {
failed := make(map[string][]string)
for _, es := range sem.errorCodeSources {
c, n := es.ErrorCodesInUse()
for _, ec := range c.Contents() {
if sem.errors[ec] == nil && es.ValidateMissing() {
addMissingCode(n, ec, failed)
}
}
}
if len(failed) > 0 {
var m bytes.Buffer
m.WriteString(fmt.Sprintf("Some components are using error codes that do not have a corresponding error message: \n"))
for k, v := range failed {
m.WriteString(fmt.Sprintf("%s: %q\n", k, v))
}
return errors.New(m.String())
}
return nil
}
func addMissingCode(source, code string, failed map[string][]string) {
fs := failed[source]
if fs == nil {
fs = make([]string, 0)
}
fs = append(fs, code)
failed[source] = fs
}