mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 11:39:02 +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"
|
||||
)
|
||||
|
||||
const (
|
||||
codeUnknown string = "unknown"
|
||||
)
|
||||
|
||||
// base is the fundamental struct that implements the error interface.
|
||||
// The order of the struct is 'TCMEUA'.
|
||||
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 TypeInternal, "", cause.Error(), cause, "", []string{}
|
||||
return TypeInternal, codeUnknown, cause.Error(), cause, "", []string{}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// todo(remove): Implemented at render package (go.signoz.io/signoz/pkg/http/render) with the new error structure
|
||||
type structuredResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
Total int `json:"total"`
|
||||
@ -246,11 +247,13 @@ type structuredResponse struct {
|
||||
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 {
|
||||
Code int `json:"code,omitempty"`
|
||||
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 {
|
||||
Status status `json:"status"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
@ -258,6 +261,7 @@ type ApiResponse struct {
|
||||
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{}) {
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
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{}) {
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
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)
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
writeHttpResponse(w, data)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user