Skip to content

Commit 3ab6271

Browse files
authored
chore: improve code quality in restic pkg (#736)
1 parent 658514c commit 3ab6271

File tree

3 files changed

+193
-241
lines changed

3 files changed

+193
-241
lines changed

pkg/restic/outputs.go

+78-103
Original file line numberDiff line numberDiff line change
@@ -125,56 +125,105 @@ func (b *BackupProgressEntry) Validate() error {
125125
return nil
126126
}
127127

128-
// readBackupProgressEntries returns the summary event or an error if the command failed.
129-
func readBackupProgressEntries(output io.Reader, logger io.Writer, callback func(event *BackupProgressEntry)) (*BackupProgressEntry, error) {
128+
func (b *BackupProgressEntry) IsError() bool {
129+
return b.MessageType == "error"
130+
}
131+
132+
func (b *BackupProgressEntry) IsSummary() bool {
133+
return b.MessageType == "summary"
134+
}
135+
136+
type RestoreProgressEntry struct {
137+
MessageType string `json:"message_type"` // "summary" or "status"
138+
SecondsElapsed float64 `json:"seconds_elapsed"`
139+
TotalBytes int64 `json:"total_bytes"`
140+
BytesRestored int64 `json:"bytes_restored"`
141+
TotalFiles int64 `json:"total_files"`
142+
FilesRestored int64 `json:"files_restored"`
143+
PercentDone float64 `json:"percent_done"`
144+
}
145+
146+
func (e *RestoreProgressEntry) Validate() error {
147+
if e.MessageType != "summary" && e.MessageType != "status" {
148+
return fmt.Errorf("message_type must be 'summary' or 'status', got %v", e.MessageType)
149+
}
150+
return nil
151+
}
152+
153+
func (r *RestoreProgressEntry) IsError() bool {
154+
return r.MessageType == "error"
155+
}
156+
157+
func (r *RestoreProgressEntry) IsSummary() bool {
158+
return r.MessageType == "summary"
159+
}
160+
161+
type ProgressEntryValidator interface {
162+
Validate() error
163+
IsError() bool
164+
IsSummary() bool
165+
}
166+
167+
// processProgressOutput handles common JSON output processing logic with proper type safety
168+
func processProgressOutput[T ProgressEntryValidator](
169+
output io.Reader,
170+
logger io.Writer,
171+
callback func(T)) (T, error) {
172+
130173
scanner := bufio.NewScanner(output)
131174
scanner.Split(bufio.ScanLines)
132175

133176
nonJSONOutput := bytes.NewBuffer(nil)
177+
var captureNonJSON io.Writer = nonJSONOutput
178+
if logger != nil {
179+
captureNonJSON = io.MultiWriter(nonJSONOutput, logger)
180+
}
134181

135-
var summary *BackupProgressEntry
182+
var summary *T
183+
var nullT T
136184

137-
// remaining events are parsed as JSON
138185
for scanner.Scan() {
139-
var event BackupProgressEntry
140-
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
141-
nonJSONOutput.Write(scanner.Bytes())
142-
if logger != nil {
143-
logger.Write(scanner.Bytes())
144-
logger.Write([]byte("\n"))
145-
}
186+
line := scanner.Bytes()
187+
var event T
188+
189+
if err := json.Unmarshal(line, &event); err != nil {
190+
captureNonJSON.Write(line)
191+
captureNonJSON.Write([]byte("\n"))
146192
continue
147193
}
194+
148195
if err := event.Validate(); err != nil {
149-
nonJSONOutput.Write(scanner.Bytes())
150-
if logger != nil {
151-
logger.Write(scanner.Bytes())
152-
logger.Write([]byte("\n"))
153-
}
196+
captureNonJSON.Write(line)
197+
captureNonJSON.Write([]byte("\n"))
154198
continue
155199
}
156-
if event.MessageType == "error" && logger != nil {
157-
logger.Write(scanner.Bytes())
158-
logger.Write([]byte("\n"))
200+
201+
if event.IsError() && logger != nil {
202+
captureNonJSON.Write(line)
203+
captureNonJSON.Write([]byte("\n"))
159204
}
205+
160206
if callback != nil {
161-
callback(&event)
207+
callback(event)
162208
}
163-
if event.MessageType == "summary" {
164-
if logger != nil {
165-
logger.Write(scanner.Bytes())
166-
logger.Write([]byte("\n"))
167-
}
168-
summary = &event
209+
210+
if event.IsSummary() {
211+
captureNonJSON.Write(line)
212+
captureNonJSON.Write([]byte("\n"))
213+
eventCopy := event // Make a copy to avoid issues with loop variable
214+
summary = &eventCopy
169215
}
170216
}
217+
171218
if err := scanner.Err(); err != nil {
172-
return summary, newErrorWithOutput(err, nonJSONOutput.String())
219+
return nullT, newErrorWithOutput(err, nonJSONOutput.String())
173220
}
221+
174222
if summary == nil {
175-
return nil, newErrorWithOutput(errors.New("no summary event found"), nonJSONOutput.String())
223+
return nullT, newErrorWithOutput(errors.New("no summary event found"), nonJSONOutput.String())
176224
}
177-
return summary, nil
225+
226+
return *summary, nil
178227
}
179228

180229
type LsEntry struct {
@@ -248,80 +297,6 @@ func (r *ForgetResult) Validate() error {
248297
return nil
249298
}
250299

251-
type RestoreProgressEntry struct {
252-
MessageType string `json:"message_type"` // "summary" or "status"
253-
SecondsElapsed float64 `json:"seconds_elapsed"`
254-
TotalBytes int64 `json:"total_bytes"`
255-
BytesRestored int64 `json:"bytes_restored"`
256-
TotalFiles int64 `json:"total_files"`
257-
FilesRestored int64 `json:"files_restored"`
258-
PercentDone float64 `json:"percent_done"`
259-
}
260-
261-
func (e *RestoreProgressEntry) Validate() error {
262-
if e.MessageType != "summary" && e.MessageType != "status" {
263-
return fmt.Errorf("message_type must be 'summary' or 'status', got %v", e.MessageType)
264-
}
265-
return nil
266-
}
267-
268-
// readRestoreProgressEntries returns the summary event or an error if the command failed.
269-
func readRestoreProgressEntries(output io.Reader, logger io.Writer, callback func(event *RestoreProgressEntry)) (*RestoreProgressEntry, error) {
270-
scanner := bufio.NewScanner(output)
271-
scanner.Split(bufio.ScanLines)
272-
273-
nonJSONOutput := bytes.NewBuffer(nil)
274-
275-
var summary *RestoreProgressEntry
276-
277-
// remaining events are parsed as JSON
278-
for scanner.Scan() {
279-
var event RestoreProgressEntry
280-
if err := json.Unmarshal(scanner.Bytes(), &event); err != nil {
281-
nonJSONOutput.Write(scanner.Bytes())
282-
if logger != nil {
283-
logger.Write(scanner.Bytes())
284-
logger.Write([]byte("\n"))
285-
}
286-
continue
287-
}
288-
if err := event.Validate(); err != nil {
289-
// skip it. Best effort parsing, restic will return with a non-zero exit code if it fails.
290-
nonJSONOutput.Write(scanner.Bytes())
291-
if logger != nil {
292-
logger.Write(scanner.Bytes())
293-
logger.Write([]byte("\n"))
294-
}
295-
continue
296-
}
297-
298-
if event.MessageType == "error" && logger != nil {
299-
logger.Write(scanner.Bytes())
300-
logger.Write([]byte("\n"))
301-
}
302-
if callback != nil {
303-
callback(&event)
304-
}
305-
if event.MessageType == "summary" {
306-
if logger != nil {
307-
logger.Write(scanner.Bytes())
308-
logger.Write([]byte("\n"))
309-
}
310-
summary = &event
311-
}
312-
}
313-
314-
if err := scanner.Err(); err != nil {
315-
return summary, newErrorWithOutput(err, nonJSONOutput.String())
316-
}
317-
318-
if summary == nil {
319-
return nil, newErrorWithOutput(errors.New("no summary event found"), nonJSONOutput.String())
320-
}
321-
322-
return summary, nil
323-
}
324-
325300
func ValidateSnapshotId(id string) error {
326301
if len(id) != 64 {
327302
return fmt.Errorf("restic may be out of date (check with `restic self-upgrade`): snapshot ID must be 64 chars, got %v chars", len(id))

pkg/restic/outputs_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ func TestReadBackupProgressEntries(t *testing.T) {
1212

1313
b := bytes.NewBuffer([]byte(testInput))
1414

15-
summary, err := readBackupProgressEntries(b, nil, func(event *BackupProgressEntry) {
15+
summary, err := processProgressOutput[*BackupProgressEntry](b, nil, func(event *BackupProgressEntry) {
1616
t.Logf("event: %v", event)
1717
})
1818
if err != nil {

0 commit comments

Comments
 (0)