@@ -10,75 +10,80 @@ import (
1010// Response represents the structure of an HTTP response, including a status code, message, and optional body.
1111// T represents the type of the `Data` field, allowing this structure to be used flexibly across different endpoints.
1212type 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"`
13+ Data T `json:"data,omitempty"`
14+ Meta * Meta `json:"meta,omitempty"`
2615}
2716
2817// Meta provides additional information about the response, such as pagination details.
29- // This is particularly useful for endpoints returning lists of data.
3018type 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.
19+ Page int `json:"page,omitempty"`
20+ PageSize int `json:"page_size,omitempty"`
3621 TotalPages int `json:"total_pages,omitempty"`
37- // TotalItems the total number of items across all pages.
3822 TotalItems int `json:"total_items,omitempty"`
3923}
4024
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.
25+ // ProblemDetails conforms to RFC 9457, providing a standard format for describing errors in HTTP APIs.
26+ type ProblemDetails struct {
27+ Type string `json:"type"` // A URI reference identifying the problem type.
28+ Title string `json:"title"` // A short, human-readable summary of the problem.
29+ Status int `json:"status"` // The HTTP status code.
30+ Detail string `json:"detail,omitempty"` // Detailed explanation of the problem.
31+ Instance string `json:"instance,omitempty"` // A URI reference identifying the specific instance of the problem.
32+ Extensions map [string ]interface {} `json:"extensions,omitempty"` // Custom fields for additional details.
33+ }
34+
35+ // NewProblemDetails creates a ProblemDetails instance with standard fields.
36+ func NewProblemDetails (status int , title , detail string ) * ProblemDetails {
37+ return & ProblemDetails {
38+ Type : "about:blank" , // Replace with a custom URI if desired.
39+ Title : title ,
40+ Status : status ,
41+ Detail : detail ,
42+ }
43+ }
44+
45+ // SendResponse sends a JSON response to the client, supporting both success and error scenarios.
4446//
4547// Parameters:
4648// - w: The http.ResponseWriter to send the response.
4749// - 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" )
50+ // - data: The main payload of the response (only for successful responses).
51+ // - problem: An optional ProblemDetails struct (used for error responses).
52+ // - meta: Optional metadata for successful responses (e.g., pagination details).
53+ func SendResponse [T any ](w http.ResponseWriter , code int , data T , problem * ProblemDetails , meta * Meta ) {
5354
55+ // Handle error responses
56+ if code >= 400 && problem != nil {
57+ writeProblemDetail (w , code , problem )
58+ return
59+ }
60+
61+ // Construct and encode the success response
5462 response := & Response [T ]{
55- Data : data ,
56- Errors : errs ,
57- Meta : meta ,
63+ Data : data ,
64+ Meta : meta ,
5865 }
5966
60- // Attempt to encode the response as JSON
6167 var buffer bytes.Buffer
6268 if err := json .NewEncoder (& buffer ).Encode (response ); err != nil {
6369 log .Printf ("Error writing response: %v" , err )
6470
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- })
71+ // Internal server error fallback using ProblemDetails
72+ internalError := NewProblemDetails (http .StatusInternalServerError , "Internal Server Error" , err .Error ())
73+ writeProblemDetail (w , http .StatusInternalServerError , internalError )
7374 return
7475 }
7576
76- // Set the status code after success encoding
77+ // Send the success response
78+ w .Header ().Set ("Content-Type" , "application/json; charset=utf-8" )
7779 w .WriteHeader (code )
78-
79- // Write the encoded response to the ResponseWriter
8080 if _ , err := w .Write (buffer .Bytes ()); err != nil {
81- // Note: Cannot change status code here as headers are already sent
8281 log .Printf ("Failed to write response body (status=%d): %v" , code , err )
8382 }
8483}
84+
85+ func writeProblemDetail (w http.ResponseWriter , code int , problem * ProblemDetails ) {
86+ w .Header ().Set ("Content-Type" , "application/problem+json; charset=utf-8" )
87+ w .WriteHeader (problem .Status )
88+ _ = json .NewEncoder (w ).Encode (problem )
89+ }
0 commit comments