mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 02:45:57 +08:00
feat(render): add render package (#5751)
### Summary Add `render` package #### Related Issues / PR's https://github.com/SigNoz/signoz/pull/5710
This commit is contained in:
parent
43ed49f9d9
commit
bd7d14b1ca
@ -4,6 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
codeUnknown string = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
// base is the fundamental struct that implements the error interface.
|
// base is the fundamental struct that implements the error interface.
|
||||||
// The order of the struct is 'TCMEUA'.
|
// The order of the struct is 'TCMEUA'.
|
||||||
type base struct {
|
type base struct {
|
||||||
@ -100,7 +104,7 @@ func Unwrapb(cause error) (typ, string, string, error, string, []string) {
|
|||||||
return base.t, base.c, base.m, base.e, base.u, base.a
|
return base.t, base.c, base.m, base.e, base.u, base.a
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypeInternal, "", cause.Error(), cause, "", []string{}
|
return TypeInternal, codeUnknown, cause.Error(), cause, "", []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ast checks if the provided error matches the specified custom error type.
|
// Ast checks if the provided error matches the specified custom error type.
|
||||||
|
83
pkg/http/render/render.go
Normal file
83
pkg/http/render/render.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"go.signoz.io/signoz/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
Error *responseerror `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseerror struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Errors []responseerroradditional `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseerroradditional struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Success(rw http.ResponseWriter, httpCode int, data interface{}) {
|
||||||
|
body, err := json.Marshal(&response{Status: StatusSuccess.s, Data: data})
|
||||||
|
if err != nil {
|
||||||
|
Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpCode == 0 {
|
||||||
|
httpCode = http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(httpCode)
|
||||||
|
_, _ = rw.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(rw http.ResponseWriter, cause error) {
|
||||||
|
// See if this is an instance of the base error or not
|
||||||
|
t, c, m, _, u, a := errors.Unwrapb(cause)
|
||||||
|
|
||||||
|
// Derive the http code from the error type
|
||||||
|
httpCode := http.StatusInternalServerError
|
||||||
|
switch t {
|
||||||
|
case errors.TypeInvalidInput:
|
||||||
|
httpCode = http.StatusBadRequest
|
||||||
|
case errors.TypeNotFound:
|
||||||
|
httpCode = http.StatusNotFound
|
||||||
|
case errors.TypeAlreadyExists:
|
||||||
|
httpCode = http.StatusConflict
|
||||||
|
case errors.TypeUnauthenticated:
|
||||||
|
httpCode = http.StatusUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
rea := make([]responseerroradditional, len(a))
|
||||||
|
for k, v := range a {
|
||||||
|
rea[k] = responseerroradditional{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(&response{
|
||||||
|
Status: StatusError.s,
|
||||||
|
Error: &responseerror{
|
||||||
|
Code: c,
|
||||||
|
Url: u,
|
||||||
|
Message: m,
|
||||||
|
Errors: rea,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// this should never be the case
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(httpCode)
|
||||||
|
_, _ = rw.Write(body)
|
||||||
|
}
|
116
pkg/http/render/render_test.go
Normal file
116
pkg/http/render/render_test.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.signoz.io/signoz/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSuccess(t *testing.T) {
|
||||||
|
listener, err := net.Listen("tcp", "localhost:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"int64": int64(9),
|
||||||
|
"string": "string",
|
||||||
|
"bool": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
marshalled, err := json.Marshal(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := []byte(fmt.Sprintf(`{"status":"success","data":%s}`, string(marshalled)))
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
Success(rw, http.StatusAccepted, data)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = server.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = server.Shutdown(context.Background())
|
||||||
|
}()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http://"+listener.Addr().String(), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actual, err := io.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusAccepted, res.StatusCode)
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
listener, err := net.Listen("tcp", "localhost:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
name string
|
||||||
|
statusCode int
|
||||||
|
err error
|
||||||
|
expected []byte
|
||||||
|
}{
|
||||||
|
"/already_exists": {
|
||||||
|
name: "AlreadyExists",
|
||||||
|
statusCode: http.StatusConflict,
|
||||||
|
err: errors.New(errors.TypeAlreadyExists, "already_exists", "already exists").WithUrl("https://already_exists"),
|
||||||
|
expected: []byte(`{"status":"error","error":{"code":"already_exists","message":"already exists","url":"https://already_exists"}}`),
|
||||||
|
},
|
||||||
|
"/unauthenticated": {
|
||||||
|
name: "Unauthenticated",
|
||||||
|
statusCode: http.StatusUnauthorized,
|
||||||
|
err: errors.New(errors.TypeUnauthenticated, "not_allowed", "not allowed").WithUrl("https://unauthenticated").WithAdditional("a1", "a2"),
|
||||||
|
expected: []byte(`{"status":"error","error":{"code":"not_allowed","message":"not allowed","url":"https://unauthenticated","errors":[{"message":"a1"},{"message":"a2"}]}}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
tc, ok := testCases[req.URL.Path]
|
||||||
|
if ok {
|
||||||
|
Error(rw, tc.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = server.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = server.Shutdown(context.Background())
|
||||||
|
}()
|
||||||
|
|
||||||
|
for path, tc := range testCases {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest("GET", "http://"+listener.Addr().String()+path, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actual, err := io.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.statusCode, res.StatusCode)
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
pkg/http/render/status.go
Normal file
9
pkg/http/render/status.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
var (
|
||||||
|
StatusSuccess status = status{"success"}
|
||||||
|
StatusError = status{"error"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines custom error types
|
||||||
|
type status struct{ s string }
|
@ -238,6 +238,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
|||||||
return aH, nil
|
return aH, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||||
type structuredResponse struct {
|
type structuredResponse struct {
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
@ -246,11 +247,13 @@ type structuredResponse struct {
|
|||||||
Errors []structuredError `json:"errors"`
|
Errors []structuredError `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||||
type structuredError struct {
|
type structuredError struct {
|
||||||
Code int `json:"code,omitempty"`
|
Code int `json:"code,omitempty"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||||
type ApiResponse struct {
|
type ApiResponse struct {
|
||||||
Status status `json:"status"`
|
Status status `json:"status"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
@ -258,6 +261,7 @@ type ApiResponse struct {
|
|||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||||
func RespondError(w http.ResponseWriter, apiErr model.BaseApiError, data interface{}) {
|
func RespondError(w http.ResponseWriter, apiErr model.BaseApiError, data interface{}) {
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
b, err := json.Marshal(&ApiResponse{
|
b, err := json.Marshal(&ApiResponse{
|
||||||
@ -301,6 +305,7 @@ func RespondError(w http.ResponseWriter, apiErr model.BaseApiError, data interfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||||
func writeHttpResponse(w http.ResponseWriter, data interface{}) {
|
func writeHttpResponse(w http.ResponseWriter, data interface{}) {
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
b, err := json.Marshal(&ApiResponse{
|
b, err := json.Marshal(&ApiResponse{
|
||||||
@ -351,6 +356,7 @@ func (aH *APIHandler) RegisterQueryRangeV4Routes(router *mux.Router, am *AuthMid
|
|||||||
subRouter.HandleFunc("/metric/metric_metadata", am.ViewAccess(aH.getMetricMetadata)).Methods(http.MethodGet)
|
subRouter.HandleFunc("/metric/metric_metadata", am.ViewAccess(aH.getMetricMetadata)).Methods(http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||||
func (aH *APIHandler) Respond(w http.ResponseWriter, data interface{}) {
|
func (aH *APIHandler) Respond(w http.ResponseWriter, data interface{}) {
|
||||||
writeHttpResponse(w, data)
|
writeHttpResponse(w, data)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user