mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-11-15 16:28:06 +01:00
(experimental) upgraded to encoding/json/v2
This commit is contained in:
1 parent
4d4275c9aa
commit
97dd775455
125 files changed
+433
-364
No files matched your search
+10
-10
@@ -2,7 +2,7 @@ package router_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"encoding/json/v2"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@@ -22,7 +22,7 @@ func TestNewApiErrorWithRawData(t *testing.T) {
|
||||
"rawData_test",
|
||||
)
|
||||
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
expected := `{"data":{},"message":"Message_test.","status":300}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -61,7 +61,7 @@ func TestNewApiErrorWithValidationData(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
expected := `{"data":{"err1":{"code":"validation_invalid_value","message":"Invalid value."},"err2":{"code":"validation_required","message":"Cannot be blank."},"err3":{"err3.1":{"code":"validation_invalid_value","message":"Invalid value."},"err3.2":{"code":"validation_required","message":"Cannot be blank."},"err3.3":{"err3.3.1":{"code":"validation_required","message":"Cannot be blank."}}},"err4":{"code":"mock_code","message":"Mock_error.","mock_resolve":123},"err5":{"err5.1":{"code":"validation_required","message":"Cannot be blank."}}},"message":"Message_test.","status":300}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -93,7 +93,7 @@ func TestNewNotFoundError(t *testing.T) {
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
e := router.NewNotFoundError(s.message, s.data)
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
|
||||
if str := string(result); str != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
|
||||
@@ -118,7 +118,7 @@ func TestNewBadRequestError(t *testing.T) {
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
e := router.NewBadRequestError(s.message, s.data)
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
|
||||
if str := string(result); str != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
|
||||
@@ -143,7 +143,7 @@ func TestNewForbiddenError(t *testing.T) {
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
e := router.NewForbiddenError(s.message, s.data)
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
|
||||
if str := string(result); str != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
|
||||
@@ -168,7 +168,7 @@ func TestNewUnauthorizedError(t *testing.T) {
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
e := router.NewUnauthorizedError(s.message, s.data)
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
|
||||
if str := string(result); str != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
|
||||
@@ -193,7 +193,7 @@ func TestNewInternalServerError(t *testing.T) {
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
e := router.NewInternalServerError(s.message, s.data)
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
|
||||
if str := string(result); str != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
|
||||
@@ -218,7 +218,7 @@ func TestNewTooManyRequestsError(t *testing.T) {
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
e := router.NewTooManyRequestsError(s.message, s.data)
|
||||
result, _ := json.Marshal(e)
|
||||
result, _ := json.Marshal(e, json.Deterministic(true))
|
||||
|
||||
if str := string(result); str != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str)
|
||||
@@ -321,7 +321,7 @@ func TestToApiError(t *testing.T) {
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
raw, err := json.Marshal(router.ToApiError(s.err))
|
||||
raw, err := json.Marshal(router.ToApiError(s.err), json.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+20
-6
@@ -1,7 +1,8 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/json/jsontext"
|
||||
"encoding/json/v2"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -192,7 +193,7 @@ func (e *Event) JSON(status int, data any) error {
|
||||
|
||||
// error response or no fields to pick
|
||||
if rawFields == "" || status < 200 || status > 299 {
|
||||
return json.NewEncoder(e.Response).Encode(data)
|
||||
return json.MarshalWrite(e.Response, data)
|
||||
}
|
||||
|
||||
// pick only the requested fields
|
||||
@@ -201,7 +202,7 @@ func (e *Event) JSON(status int, data any) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewEncoder(e.Response).Encode(modified)
|
||||
return json.MarshalWrite(e.Response, modified)
|
||||
}
|
||||
|
||||
// XML writes an XML response.
|
||||
@@ -359,10 +360,23 @@ func (e *Event) BindBody(dst any) error {
|
||||
contentType := e.Request.Header.Get(headerContentType)
|
||||
|
||||
if strings.HasPrefix(contentType, "application/json") {
|
||||
dec := json.NewDecoder(e.Request.Body)
|
||||
err := dec.Decode(dst)
|
||||
// note: don't use json.UnmarshalRead because it perfoms an extra
|
||||
// whitespace scanning which will trigger the auto reread and
|
||||
// will start again from the beginning and causing an error
|
||||
//
|
||||
// (technically json.UnmarshalRead is the better option here but to make
|
||||
// it work it will require extra interface or other mechanism to temp disable
|
||||
// the auto reread functionality and this could be a footgun for middlewares
|
||||
// that wraps the body so for now it is kept as it is and it should
|
||||
// be similar to the old json.NewDecoder(e.Request.Body).Decode(dst))
|
||||
dec := jsontext.NewDecoder(e.Request.Body)
|
||||
err := json.UnmarshalDecode(dec, dst,
|
||||
// minimize breaking changes with earlier version
|
||||
// @todo remove with the "Stage 2" refactoring
|
||||
json.MatchCaseInsensitiveNames(true),
|
||||
)
|
||||
if err == nil {
|
||||
// manually call Reread because single call of json.Decoder.Decode()
|
||||
// manually call Reread because single call of json.UnmarshalDecode
|
||||
// doesn't ensure that the entire body is a valid json string
|
||||
// and it is not guaranteed that it will reach EOF to trigger the reread reset
|
||||
// (ex. in case of trailing spaces or invalid trailing parts like: `{"test":1},something`)
|
||||
|
||||
+28
-25
@@ -3,7 +3,7 @@ package router_test
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/json/v2"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -328,7 +328,7 @@ func TestEventSetAllGetAll(t *testing.T) {
|
||||
"a": 123,
|
||||
"b": 456,
|
||||
}
|
||||
rawData, err := json.Marshal(data)
|
||||
rawData, err := json.Marshal(data, json.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -340,7 +340,7 @@ func TestEventSetAllGetAll(t *testing.T) {
|
||||
data["c"] = 789
|
||||
|
||||
result := event.GetAll()
|
||||
rawResult, err := json.Marshal(result)
|
||||
rawResult, err := json.Marshal(result, json.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -410,8 +410,8 @@ func TestEventHTML(t *testing.T) {
|
||||
|
||||
func TestEventJSON(t *testing.T) {
|
||||
body := map[string]any{"a": 123, "b": 456, "c": "test"}
|
||||
expectedPickedBody := `{"a":123,"c":"test"}` + "\n"
|
||||
expectedFullBody := `{"a":123,"b":456,"c":"test"}` + "\n"
|
||||
expectedPickedBody := `{"a":123,"c":"test"}`
|
||||
expectedFullBody := `{"a":123,"b":456,"c":"test"}`
|
||||
|
||||
scenarios := []testResponseWriteScenario[any]{
|
||||
{
|
||||
@@ -673,7 +673,7 @@ func TestEventFileFS(t *testing.T) {
|
||||
func TestEventError(t *testing.T) {
|
||||
err := new(router.Event).Error(123, "message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":123}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -684,7 +684,7 @@ func TestEventError(t *testing.T) {
|
||||
func TestEventBadRequestError(t *testing.T) {
|
||||
err := new(router.Event).BadRequestError("message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":400}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -695,7 +695,7 @@ func TestEventBadRequestError(t *testing.T) {
|
||||
func TestEventNotFoundError(t *testing.T) {
|
||||
err := new(router.Event).NotFoundError("message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":404}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -706,7 +706,7 @@ func TestEventNotFoundError(t *testing.T) {
|
||||
func TestEventForbiddenError(t *testing.T) {
|
||||
err := new(router.Event).ForbiddenError("message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":403}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -717,7 +717,7 @@ func TestEventForbiddenError(t *testing.T) {
|
||||
func TestEventUnauthorizedError(t *testing.T) {
|
||||
err := new(router.Event).UnauthorizedError("message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":401}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -728,7 +728,7 @@ func TestEventUnauthorizedError(t *testing.T) {
|
||||
func TestEventTooManyRequestsError(t *testing.T) {
|
||||
err := new(router.Event).TooManyRequestsError("message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":429}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -739,7 +739,7 @@ func TestEventTooManyRequestsError(t *testing.T) {
|
||||
func TestEventInternalServerError(t *testing.T) {
|
||||
err := new(router.Event).InternalServerError("message_test", map[string]any{"a": validation.Required, "b": "test"})
|
||||
|
||||
result, _ := json.Marshal(err)
|
||||
result, _ := json.Marshal(err, json.Deterministic(true))
|
||||
expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":500}`
|
||||
|
||||
if string(result) != expected {
|
||||
@@ -873,7 +873,7 @@ func TestEventBindBody(t *testing.T) {
|
||||
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
|
||||
}
|
||||
|
||||
dstRaw, err := json.Marshal(dst)
|
||||
dstRaw, err := json.Marshal(dst, json.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -925,29 +925,32 @@ func testEventResponseWrite[T any](
|
||||
}
|
||||
|
||||
result := rec.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
if result.StatusCode != scenario.expectedStatus {
|
||||
t.Fatalf("Expected status code %d, got %d", scenario.expectedStatus, result.StatusCode)
|
||||
}
|
||||
|
||||
resultBody, err := io.ReadAll(result.Body)
|
||||
result.Body.Close()
|
||||
rawBody, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response body: %v", err)
|
||||
}
|
||||
strBody := string(rawBody)
|
||||
|
||||
resultBody, err = json.Marshal(string(resultBody))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
// try to deserialize into a map and then serialize again in a
|
||||
// deterministic manner in case it is json
|
||||
var jsonBody any
|
||||
err = json.Unmarshal(rawBody, &jsonBody)
|
||||
if err == nil {
|
||||
normalized, err := json.Marshal(jsonBody, json.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deterministicly serialize json body\n%s\ngot\n%v", jsonBody, err)
|
||||
}
|
||||
strBody = string(normalized)
|
||||
}
|
||||
|
||||
expectedBody, err := json.Marshal(scenario.expectedBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(resultBody, expectedBody) {
|
||||
t.Fatalf("Expected body\n%s\ngot\n%s", expectedBody, resultBody)
|
||||
if scenario.expectedBody != strBody {
|
||||
t.Fatalf("Expected body\n%s\ngot\n%s", scenario.expectedBody, strBody)
|
||||
}
|
||||
|
||||
for k, ev := range scenario.expectedHeaders {
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"encoding/json/v2"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
@@ -176,7 +176,9 @@ func ErrorHandler(resp http.ResponseWriter, req *http.Request, err error) {
|
||||
resp.WriteHeader(apiErr.Status)
|
||||
|
||||
if req.Method != http.MethodHead {
|
||||
if jsonErr := json.NewEncoder(resp).Encode(apiErr); jsonErr != nil {
|
||||
// note: deterministic because some logs may depend on the exact serialized error
|
||||
jsonErr := json.MarshalWrite(resp, apiErr, json.Deterministic(true))
|
||||
if jsonErr != nil {
|
||||
log.Println(jsonErr) // truly rare case, log to stderr only for dev purposes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"encoding/json/v2"
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
@@ -2,7 +2,7 @@ package router_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/json/v2"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -428,7 +428,7 @@ func TestUnmarshalRequestData(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(s.dst)
|
||||
raw, err := json.Marshal(s.dst, json.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user