mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(api): align response envelope schema
Switch response envelope business code to numeric and make message consistently present. Add trace_id and optional details, and remove the duplicate DTO envelope definition. Improve middleware path exclusion handling and add a time-based trace ID fallback if crypto RNG fails. BREAKING CHANGE: response envelope `code` is now `int` (was `string`) and `message` semantics/defaults changed; clients must update parsing.
This commit is contained in:
@@ -1,12 +1,16 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
// ResponseEnvelope is the standard wrapper for EZ-API JSON responses.
|
// ResponseEnvelope is the standard wrapper for EZ-API JSON responses.
|
||||||
// Code is a stable business code string (e.g., ok, invalid_request, not_found).
|
// Code is a numeric business code: 0 for success, non-zero for errors.
|
||||||
// Message is empty for success and mirrors the top-level error for failures.
|
// Message is "success" for success, error description for failures.
|
||||||
// Data holds the original response payload.
|
// Data holds the original response payload for success; null for errors.
|
||||||
|
// TraceID is a request correlation identifier.
|
||||||
|
// Details holds optional structured error information.
|
||||||
// swagger:model ResponseEnvelope
|
// swagger:model ResponseEnvelope
|
||||||
type ResponseEnvelope struct {
|
type ResponseEnvelope struct {
|
||||||
Code string `json:"code" example:"ok"`
|
Code int `json:"code" example:"0"`
|
||||||
|
Message string `json:"message" example:"success"`
|
||||||
Data any `json:"data" swaggertype:"object"`
|
Data any `json:"data" swaggertype:"object"`
|
||||||
Message string `json:"message" example:""`
|
TraceID string `json:"trace_id" example:"a1b2c3d4e5f6g7h8"`
|
||||||
|
Details any `json:"details,omitempty" swaggertype:"object"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
package dto
|
|
||||||
|
|
||||||
// ResponseEnvelope is the standard API response wrapper.
|
|
||||||
// All JSON API responses are wrapped in this envelope format.
|
|
||||||
// @Description Standard API response envelope
|
|
||||||
type ResponseEnvelope struct {
|
|
||||||
// Business code: 0 for success, non-zero for errors
|
|
||||||
// 0 = success
|
|
||||||
// 1xxx = common errors (invalid param, unauthorized, forbidden, rate limited)
|
|
||||||
// 4xxx = client errors (resource not found, conflict, invalid state)
|
|
||||||
// 5xxx = server errors (internal error, service unavailable, timeout)
|
|
||||||
Code int `json:"code" example:"0"`
|
|
||||||
|
|
||||||
// Human-readable message: "success" for success, error description for errors
|
|
||||||
Message string `json:"message" example:"success"`
|
|
||||||
|
|
||||||
// Response data: business payload for success, null for errors
|
|
||||||
Data any `json:"data"`
|
|
||||||
|
|
||||||
// Request trace ID for debugging and log correlation
|
|
||||||
TraceID string `json:"trace_id" example:"a1b2c3d4e5f6g7h8"`
|
|
||||||
|
|
||||||
// Optional error details (e.g., field validation errors)
|
|
||||||
Details any `json:"details,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorResponse is returned when an error occurs.
|
|
||||||
// @Description Error response format
|
|
||||||
type ErrorResponse struct {
|
|
||||||
// Business error code (non-zero)
|
|
||||||
Code int `json:"code" example:"4001"`
|
|
||||||
|
|
||||||
// Error message
|
|
||||||
Message string `json:"message" example:"resource not found"`
|
|
||||||
|
|
||||||
// Always null for errors
|
|
||||||
Data any `json:"data" example:"null"`
|
|
||||||
|
|
||||||
// Request trace ID
|
|
||||||
TraceID string `json:"trace_id" example:"a1b2c3d4e5f6g7h8"`
|
|
||||||
|
|
||||||
// Optional structured error details
|
|
||||||
Details any `json:"details,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessResponse is returned for successful operations.
|
|
||||||
// @Description Success response format
|
|
||||||
type SuccessResponse struct {
|
|
||||||
// Always 0 for success
|
|
||||||
Code int `json:"code" example:"0"`
|
|
||||||
|
|
||||||
// Always "success"
|
|
||||||
Message string `json:"message" example:"success"`
|
|
||||||
|
|
||||||
// Response payload
|
|
||||||
Data any `json:"data"`
|
|
||||||
|
|
||||||
// Request trace ID
|
|
||||||
TraceID string `json:"trace_id" example:"a1b2c3d4e5f6g7h8"`
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,10 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -227,6 +229,12 @@ func ResponseEnvelope() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isExcludedPath(path string) bool {
|
func isExcludedPath(path string) bool {
|
||||||
|
if path == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for len(path) > 1 && strings.HasSuffix(path, "/") {
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
}
|
||||||
if excludedPaths[path] {
|
if excludedPaths[path] {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -298,7 +306,9 @@ func getTraceID(c *gin.Context) string {
|
|||||||
|
|
||||||
func generateTraceID() string {
|
func generateTraceID() string {
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
_, _ = rand.Read(b)
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return fmt.Sprintf("%032x", time.Now().UnixNano())
|
||||||
|
}
|
||||||
return hex.EncodeToString(b)
|
return hex.EncodeToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user