@@ -7,78 +7,82 @@ import (
77 "net/http"
88)
99
10- // Response represents the structure of an HTTP response, including a status code, message, and optional body.
11- // T represents the type of the `Data` field, allowing this structure to be used flexibly across different endpoints.
10+ // Response represents the structure of an HTTP success response, including optional data and metadata.
1211type Response [T any ] struct {
13- Data T `json:"data,omitempty"`
14- Errors []Error `json:"errors,omitempty"`
15- Meta * Meta `json:"meta,omitempty"`
16- }
17-
18- // Error represents an error in the aPI response, with a structured format to describe issues in a consistent manner.
19- type Error struct {
20- // Code unique error code or HTTP status code for categorizing the error
21- Code int `json:"code"`
22- // Message user-friendly message describing the error.
23- Message string `json:"message"`
24- // Details additional details about the error, often used for validation errors.
25- Details interface {} `json:"details,omitempty"`
12+ Data T `json:"data,omitempty"`
13+ Meta * Meta `json:"meta,omitempty"`
2614}
2715
2816// Meta provides additional information about the response, such as pagination details.
29- // This is particularly useful for endpoints returning lists of data.
3017type Meta struct {
31- // Page the current page number
32- Page int `json:"page,omitempty"`
33- // PageSize the number of items per page
34- PageSize int `json:"page_size,omitempty"`
35- // TotalPages the total number of pages available.
18+ Page int `json:"page,omitempty"`
19+ PageSize int `json:"page_size,omitempty"`
3620 TotalPages int `json:"total_pages,omitempty"`
37- // TotalItems the total number of items across all pages.
3821 TotalItems int `json:"total_items,omitempty"`
3922}
4023
41- // SendResponse sends a JSON response to the client, using a unified structure for both success and error responses.
42- // T represents the type of the `data` payload. This function automatically adapts the response structure
43- // based on whether `data` or `errors` is provided, promoting a consistent API format.
24+ // ProblemDetails conforms to RFC 9457, providing a standard format for describing errors in HTTP APIs.
25+ type ProblemDetails struct {
26+ Type string `json:"type"` // A URI reference identifying the problem type.
27+ Title string `json:"title"` // A short, human-readable summary of the problem.
28+ Status int `json:"status"` // The HTTP status code.
29+ Detail string `json:"detail,omitempty"` // Detailed explanation of the problem.
30+ Instance string `json:"instance,omitempty"` // A URI reference identifying the specific instance of the problem.
31+ Extensions map [string ]interface {} `json:"extensions,omitempty"` // Custom fields for additional details.
32+ }
33+
34+ // NewProblemDetails creates a ProblemDetails instance with standard fields.
35+ func NewProblemDetails (status int , title , detail string ) * ProblemDetails {
36+ return & ProblemDetails {
37+ Type : "about:blank" , // Replace with a custom URI if desired.
38+ Title : title ,
39+ Status : status ,
40+ Detail : detail ,
41+ }
42+ }
43+
44+ // SendResponse sends a JSON response to the client, supporting both success and error scenarios.
4445//
4546// Parameters:
4647// - w: The http.ResponseWriter to send the response.
4748// - code: HTTP status code to indicate success or failure.
48- // - data: The main payload of the response. Use `nil` for error responses.
49- // - errs: A slice of Error structs to describe issues. Use `nil` for successful responses.
50- // - meta: Optional metadata, such as pagination information. Use `nil` if not needed.
51- func SendResponse [T any ](w http.ResponseWriter , code int , data T , errs []Error , meta * Meta ) {
52- w .Header ().Set ("Content-Type" , "application/json; charset=utf-8" )
49+ // - data: The main payload of the response (only for successful responses).
50+ // - problem: An optional ProblemDetails struct (used for error responses).
51+ // - meta: Optional metadata for successful responses (e.g., pagination details).
52+ func SendResponse [T any ](w http.ResponseWriter , code int , data T , problem * ProblemDetails , meta * Meta ) {
5353
54+ // Handle error responses
55+ if code >= 400 && problem != nil {
56+ writeProblemDetail (w , code , problem )
57+ return
58+ }
59+
60+ // Construct and encode the success response
5461 response := & Response [T ]{
55- Data : data ,
56- Errors : errs ,
57- Meta : meta ,
62+ Data : data ,
63+ Meta : meta ,
5864 }
5965
60- // Attempt to encode the response as JSON
6166 var buffer bytes.Buffer
6267 if err := json .NewEncoder (& buffer ).Encode (response ); err != nil {
6368 log .Printf ("Error writing response: %v" , err )
6469
65- w .WriteHeader (http .StatusInternalServerError )
66- _ = json .NewEncoder (w ).Encode (& Response [T ]{
67- Errors : []Error {{
68- Code : http .StatusInternalServerError ,
69- Message : "Internal Server Error" ,
70- Details : err .Error (),
71- }},
72- })
70+ // Internal server error fallback using ProblemDetails
71+ internalError := NewProblemDetails (http .StatusInternalServerError , "Internal Server Error" , err .Error ())
72+ writeProblemDetail (w , http .StatusInternalServerError , internalError )
7373 return
7474 }
7575
76- // Set the status code after success encoding
76+ // Send the success response
77+ w .Header ().Set ("Content-Type" , "application/json; charset=utf-8" )
7778 w .WriteHeader (code )
78-
79- // Write the encoded response to the ResponseWriter
8079 if _ , err := w .Write (buffer .Bytes ()); err != nil {
81- // Note: Cannot change status code here as headers are already sent
8280 log .Printf ("Failed to write response body (status=%d): %v" , code , err )
8381 }
8482}
83+
84+ func writeProblemDetail (w http.ResponseWriter , code int , problem * ProblemDetails ) {
85+ w .Header ().Set ("Content-Type" , "application/problem+json; charset=utf-8" )
86+ w .WriteHeader (problem .Status )
87+ _ = json .NewEncoder (w ).Encode (problem )
88+ }
0 commit comments