• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vocdoni / saas-backend / 21022290289

15 Jan 2026 06:43AM UTC coverage: 62.878% (+0.005%) from 62.873%
21022290289

push

github

altergui
refactor(notifications): lint WriteString(fmt.Sprintf) -> fmt.Fprintf

9 of 9 new or added lines in 1 file covered. (100.0%)

19 existing lines in 2 files now uncovered.

6877 of 10937 relevant lines covered (62.88%)

37.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.13
/errors/errors.go
1
package errors
2

3
import (
4
        "encoding/json"
5
        stderrors "errors"
6
        "fmt"
7
        "net/http"
8
        "runtime"
9

10
        "go.vocdoni.io/dvote/log"
11
)
12

13
// Error is used by handler functions to wrap errors, assigning a unique error code
14
// and also specifying which HTTP Status should be used.
15
type Error struct {
16
        Err        error  `json:"error"` // Original error
17
        Code       int    // Error code
18
        HTTPstatus int    // HTTP status code to return
19
        LogLevel   string // Log level for this error (defaults to "debug")
20
        Data       any    // Optional data to include in the error response
21
}
22

23
// MarshalJSON returns a JSON containing Err.Error() and Code. Field HTTPstatus is ignored.
24
//
25
// Example output: {"error":"account not found","code":4003}
26
func (e Error) MarshalJSON() ([]byte, error) {
168✔
27
        // This anon struct is needed to actually include the error string,
168✔
28
        // since it wouldn't be marshaled otherwise. (json.Marshal doesn't call Err.Error())
168✔
29
        return json.Marshal(
168✔
30
                struct {
168✔
31
                        Error string `json:"error"`
168✔
32
                        Code  int    `json:"code"`
168✔
33
                        Data  any    `json:"data,omitempty"`
168✔
34
                }{
168✔
35
                        Error: e.Err.Error(),
168✔
36
                        Code:  e.Code,
168✔
37
                        Data:  e.Data,
168✔
38
                })
168✔
39
}
168✔
40

41
// UnmarshalJSON parses a JSON containing error, code and optionally data.
42
//
43
// Example input: {"error":"account not found","code":4003}
44
func (e *Error) UnmarshalJSON(data []byte) error {
23✔
45
        // This anon struct is needed to actually set the error string,
23✔
46
        // since it wouldn't be unmarshaled otherwise. (cannot json.Unmarshal string into type error)
23✔
47
        parsed := struct {
23✔
48
                Error string `json:"error"`
23✔
49
                Code  int    `json:"code"`
23✔
50
                Data  any    `json:"data,omitempty"`
23✔
51
        }{}
23✔
52
        if err := json.Unmarshal(data, &parsed); err != nil {
23✔
53
                return err
×
54
        }
×
55
        e.Err = fmt.Errorf("%s", parsed.Error)
23✔
56
        e.Code = parsed.Code
23✔
57
        e.Data = parsed.Data
23✔
58
        return nil
23✔
59
}
60

61
// Error returns the Message contained inside the APIerror
62
func (e Error) Error() string {
186✔
63
        return e.Err.Error()
186✔
64
}
186✔
65

66
// Unwrap returns the error contained inside
67
func (e Error) Unwrap() error {
×
68
        return e.Err
×
69
}
×
70

71
// Is returns true if the Code matches
72
func (e Error) Is(target error) bool {
2✔
73
        t, ok := target.(Error)
2✔
74
        if !ok {
2✔
75
                tp, ok := target.(*Error)
×
76
                if !ok {
×
77
                        return false
×
78
                }
×
79
                t = *tp
×
80
        }
81
        return e.Code == t.Code
2✔
82
}
83

84
// Write serializes a JSON msg using Error.Err and Error.Code
85
// and passes that to http.Error(). It also logs the error with appropriate level.
86
func (e Error) Write(w http.ResponseWriter) {
163✔
87
        msg, err := json.Marshal(e)
163✔
88
        if err != nil {
163✔
89
                log.Warn(err)
×
90
                http.Error(w, "marshal failed", http.StatusInternalServerError)
×
91
                return
×
92
        }
×
93

94
        // Get caller information for better logging
95
        pc, file, line, _ := runtime.Caller(1)
163✔
96
        caller := runtime.FuncForPC(pc).Name()
163✔
97

163✔
98
        // Log the error with appropriate level
163✔
99
        logLevel := e.LogLevel
163✔
100
        if logLevel == "" {
248✔
101
                // Default log level based on HTTP status
85✔
102
                if e.HTTPstatus >= 500 {
85✔
103
                        logLevel = "error"
×
104
                } else {
85✔
105
                        logLevel = "debug"
85✔
106
                }
85✔
107
        }
108

109
        // For 5xx errors, always log with Error level and include internal error details
110
        if e.HTTPstatus >= 500 {
181✔
111
                // For internal errors, log the full error details
18✔
112
                log.Errorw(e.Err, fmt.Sprintf("API error response [%d]: %s (code: %d, caller: %s, file: %s:%d)",
18✔
113
                        e.HTTPstatus, e.Error(), e.Code, caller, file, line))
18✔
114
        } else if log.Level() == log.LogLevelDebug {
308✔
115
                // For 4xx errors, log with debug level
145✔
116
                errMsg := fmt.Sprintf("API error response [%d]: %s (code: %d, caller: %s)",
145✔
117
                        e.HTTPstatus, e.Error(), e.Code, caller)
145✔
118

145✔
119
                switch logLevel {
145✔
120
                case "info":
60✔
121
                        log.Infow(errMsg)
60✔
UNCOV
122
                case "warn":
×
UNCOV
123
                        log.Warnw(errMsg)
×
124
                default:
85✔
125
                        log.Debugw(errMsg) // Default to debug level for unknown log levels
85✔
126
                }
127
        }
128

129
        // Set the content type to JSON
130
        w.Header().Set("Content-Type", "application/json")
163✔
131
        http.Error(w, string(msg), e.HTTPstatus)
163✔
132
}
133

134
// Withf returns a copy of Error with the Sprintf formatted string appended at the end of e.Err
135
func (e Error) Withf(format string, args ...any) Error {
65✔
136
        return Error{
65✔
137
                Err:        fmt.Errorf("%w: %v", e.Err, fmt.Sprintf(format, args...)),
65✔
138
                Code:       e.Code,
65✔
139
                HTTPstatus: e.HTTPstatus,
65✔
140
                LogLevel:   e.LogLevel,
65✔
141
        }
65✔
142
}
65✔
143

144
// With returns a copy of Error with the string appended at the end of e.Err
145
func (e Error) With(s string) Error {
18✔
146
        return Error{
18✔
147
                Err:        fmt.Errorf("%w: %v", e.Err, s),
18✔
148
                Code:       e.Code,
18✔
149
                HTTPstatus: e.HTTPstatus,
18✔
150
                LogLevel:   e.LogLevel,
18✔
151
        }
18✔
152
}
18✔
153

154
// WithErr returns a copy of Error with err.Error() appended at the end of e.Err
155
// The original error is preserved for logging purposes
156
func (e Error) WithErr(err error) Error {
10✔
157
        return Error{
10✔
158
                Err:        fmt.Errorf("%w: %w", e.Err, err),
10✔
159
                Code:       e.Code,
10✔
160
                HTTPstatus: e.HTTPstatus,
10✔
161
                LogLevel:   e.LogLevel,
10✔
162
        }
10✔
163
}
10✔
164

165
// WithLogLevel returns a copy of Error with the specified log level
UNCOV
166
func (e Error) WithLogLevel(level string) Error {
×
UNCOV
167
        return Error{
×
168
                Err:        e.Err,
×
169
                Code:       e.Code,
×
170
                HTTPstatus: e.HTTPstatus,
×
171
                LogLevel:   level,
×
172
        }
×
173
}
×
174

175
func (e Error) WithData(data any) Error {
4✔
176
        return Error{
4✔
177
                Err:        e.Err,
4✔
178
                Code:       e.Code,
4✔
179
                HTTPstatus: e.HTTPstatus,
4✔
180
                LogLevel:   e.LogLevel,
4✔
181
                Data:       data,
4✔
182
        }
4✔
183
}
4✔
184

185
// As finds the first error in err's tree that matches target, and if one is found, sets
186
// target to that error value and returns true. Otherwise, it returns false.
187
//
188
// The tree consists of err itself, followed by the errors obtained by repeatedly
189
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
190
// errors, As examines err followed by a depth-first traversal of its children.
191
//
192
// An error matches target if the error's concrete value is assignable to the value
193
// pointed to by target, or if the error has a method As(any) bool such that
194
// As(target) returns true. In the latter case, the As method is responsible for
195
// setting target.
196
//
197
// An error type might provide an As method so it can be treated as if it were a
198
// different error type.
199
//
200
// As panics if target is not a non-nil pointer to either a type that implements
201
// error, or to any interface type.
UNCOV
202
func As(err error, target any) bool {
×
UNCOV
203
        return stderrors.As(err, target)
×
204
}
×
205

206
// Is reports whether any error in err's tree matches target.
207
//
208
// The tree consists of err itself, followed by the errors obtained by repeatedly
209
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
210
// errors, Is examines err followed by a depth-first traversal of its children.
211
//
212
// An error is considered to match a target if it is equal to that target or if
213
// it implements a method Is(error) bool such that Is(target) returns true.
214
//
215
// An error type might provide an Is method so it can be treated as equivalent
216
// to an existing error. For example, if MyError defines
217
//
218
//        func (m MyError) Is(target error) bool { return target == fs.ErrExist }
219
//
220
// then Is(MyError{}, fs.ErrExist) returns true. See [syscall.Errno.Is] for
221
// an example in the standard library. An Is method should only shallowly
222
// compare err and the target and not call [Unwrap] on either.
223
func Is(err error, target error) bool {
26✔
224
        return stderrors.Is(err, target)
26✔
225
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc