-
Notifications
You must be signed in to change notification settings - Fork 7
/
jsendx.go
163 lines (133 loc) · 5.06 KB
/
jsendx.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
/*
Package jsendx implements a custom JSend model to wrap all HTTP responses in a consistent JSON object with default fields.
JSend is a specification that defines rules for formatting JSON responses from web servers.
This implementation extends the JSend model by adding additional fields to enrich the response with application metadata.
This model is particularly suitable for REST-style applications and APIs, as it provides a standardized format for all responses, simplifying the development of API clients.
*/
package jsendx
import (
"context"
"net/http"
"time"
"github.com/Vonage/gosrvlib/pkg/healthcheck"
"github.com/Vonage/gosrvlib/pkg/httpserver"
"github.com/Vonage/gosrvlib/pkg/httputil"
)
const (
okMessage = "OK"
)
// Response wraps data into a JSend compliant response.
type Response struct {
// Program is the application name.
Program string `json:"program"`
// Version is the program semantic version (e.g. 1.2.3).
Version string `json:"version"`
// Release is the program build number that is appended to the version.
Release string `json:"release"`
// DateTime is the human-readable date and time when the response is sent.
DateTime string `json:"datetime"`
// Timestamp is the machine-readable UTC timestamp in nanoseconds since EPOCH.
Timestamp int64 `json:"timestamp"`
// Status code string (i.e.: error, fail, success).
Status httputil.Status `json:"status"`
// Code is the HTTP status code number.
Code int `json:"code"`
// Message is the error or general HTTP status message.
Message string `json:"message"`
// Data is the content payload.
Data any `json:"data"`
}
// AppInfo is a struct containing data to enrich the JSendX response.
type AppInfo struct {
ProgramName string
ProgramVersion string
ProgramRelease string
}
// RouterArgs extra arguments for the router.
type RouterArgs struct {
// TraceIDHeaderName is the Trace ID header name.
TraceIDHeaderName string
// RedactFunc is the function used to redact HTTP request and response dumps in the logs.
RedactFunc httpserver.RedactFn
}
// Wrap sends an Response object.
func Wrap(statusCode int, info *AppInfo, data any) *Response {
now := time.Now().UTC()
return &Response{
Program: info.ProgramName,
Version: info.ProgramVersion,
Release: info.ProgramRelease,
DateTime: now.Format(time.RFC3339),
Timestamp: now.UnixNano(),
Status: httputil.Status(statusCode),
Code: statusCode,
Message: http.StatusText(statusCode),
Data: data,
}
}
// Send sends a JSON respoonse wrapped in a JSendX container.
func Send(ctx context.Context, w http.ResponseWriter, statusCode int, info *AppInfo, data any) {
httputil.SendJSON(ctx, w, statusCode, Wrap(statusCode, info, data))
}
// DefaultNotFoundHandlerFunc http handler called when no matching route is found.
func DefaultNotFoundHandlerFunc(info *AppInfo) http.HandlerFunc {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
Send(r.Context(), w, http.StatusNotFound, info, "invalid endpoint")
},
)
}
// DefaultMethodNotAllowedHandlerFunc http handler called when a request cannot be routed.
func DefaultMethodNotAllowedHandlerFunc(info *AppInfo) http.HandlerFunc {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
Send(r.Context(), w, http.StatusMethodNotAllowed, info, "the request cannot be routed")
},
)
}
// DefaultPanicHandlerFunc http handler to handle panics recovered from http handlers.
func DefaultPanicHandlerFunc(info *AppInfo) http.HandlerFunc {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
Send(r.Context(), w, http.StatusInternalServerError, info, "internal error")
},
)
}
// DefaultIndexHandler returns the route index in JSendX format.
func DefaultIndexHandler(info *AppInfo) httpserver.IndexHandlerFunc {
return func(routes []httpserver.Route) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data := &httpserver.Index{Routes: routes}
Send(r.Context(), w, http.StatusOK, info, data)
}
}
}
// DefaultIPHandler returns the route ip in JSendX format.
func DefaultIPHandler(info *AppInfo, fn httpserver.GetPublicIPFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
status := http.StatusOK
ip, err := fn(r.Context())
if err != nil {
status = http.StatusFailedDependency
}
Send(r.Context(), w, status, info, ip)
}
}
// DefaultPingHandler returns a ping request in JSendX format.
func DefaultPingHandler(info *AppInfo) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Send(r.Context(), w, http.StatusOK, info, okMessage)
}
}
// DefaultStatusHandler returns the server status in JSendX format.
func DefaultStatusHandler(info *AppInfo) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Send(r.Context(), w, http.StatusOK, info, okMessage)
}
}
// HealthCheckResultWriter returns a new healthcheck result writer.
func HealthCheckResultWriter(info *AppInfo) healthcheck.ResultWriter {
return func(ctx context.Context, w http.ResponseWriter, statusCode int, data any) {
Send(ctx, w, statusCode, info, data)
}
}