Repository navigation
Expand file tree
/
Copy pathenvelope.go
More file actions
76 lines (67 loc) · 2.14 KB
/
Copy pathenvelope.go
File metadata and controls
76 lines (67 loc) · 2.14 KB
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
package models
import (
"encoding/json"
"fmt"
)
// Envelope is the JSON wrapper every non-streaming API answer arrives in.
//
// Unmarshalling a response body straight into its payload type is the mistake
// this type exists to prevent: `{"status":"success","data":{…}}` decodes into
// CheckUpdatesResponse without error and yields an EMPTY list of updates, so a
// client concludes "nothing to install" and never learns it was wrong.
//
// Binary downloads are the one exception — they carry the file itself, with no
// envelope around it.
type Envelope[T any] struct {
Status string `json:"status"`
Data T `json:"data"`
// Set on failures instead of Data. Msg and Error are filled in only by a
// server running in development mode.
Code string `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
Error string `json:"error,omitempty"`
}
// StatusSuccess is the only Status value that carries a payload.
const StatusSuccess = "success"
// APIError is a failure the server described in the response envelope. It is
// returned for a 2xx body whose status is not "success"; transport-level and
// HTTP-status failures surface as SDK errors instead.
type APIError struct {
Code string
Msg string
Cause string
}
func (e *APIError) Error() string {
switch {
case e.Cause != "":
return fmt.Sprintf("%s: %s (%s)", e.Code, e.Msg, e.Cause)
case e.Msg != "":
return fmt.Sprintf("%s: %s", e.Code, e.Msg)
case e.Code != "":
return e.Code
default:
return "unexpected API response"
}
}
// ParseEnvelope decodes an API response body and returns its payload.
//
// resp, err := models.ParseEnvelope[models.CheckUpdatesResponse](body)
//
// A body whose status is not "success" yields an *APIError describing what the
// server reported.
func ParseEnvelope[T any](body []byte) (T, error) {
var envelope Envelope[T]
if err := json.Unmarshal(body, &envelope); err != nil {
var zero T
return zero, fmt.Errorf("failed to decode API response: %w", err)
}
if envelope.Status != StatusSuccess {
var zero T
return zero, &APIError{
Code: envelope.Code,
Msg: envelope.Msg,
Cause: envelope.Error,
}
}
return envelope.Data, nil
}