Description
I've discovered that timestamps are escaped differently in Go 1.20. This may not be a huge issue, but if you do snapshot testing and test against the validation errors from gin, it will cause them to fail when comparing payloads between Go 1.19 and Go 1.20 because timestamps used to have extra escaping that now have disappeared in Go 1.20. Was this intentional or am I doing something weird here?
How to reproduce
go.mod
module gin-bug
go 1.20
require (
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.8.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
main.go
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
type ImportRecord struct {
Timestamp time.Time `json:"timestamp" example:"2022-11-22T10:00:00Z" validate:"required"`
}
type CreateRecordsRequestBody struct {
Records []ImportRecord `json:"timeseries"`
}
func main() {
r := gin.Default()
r.POST("/", func(c *gin.Context) {
var model CreateRecordsRequestBody
if err := c.ShouldBindWith(&model, binding.JSON); err != nil {
fmt.Printf("request to %s failed while validating request body, error=%v", c.FullPath(), err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"detail": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
main_test.go
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"testing"
"github.com/bradleyjkemp/cupaloy/v2"
"github.com/stretchr/testify/require"
)
func TestThing(t *testing.T) {
type record struct {
Timestamp string `json:"timestamp"`
}
type requestBody struct {
Records []record `json:"timeseries"`
}
body := requestBody{
Records: []record{
{
Timestamp: "01.01.2022 22:00",
},
},
}
buf, err := json.Marshal(body)
require.Nil(t, err)
send, err := http.NewRequest(http.MethodPost, "http://localhost:8080", bytes.NewReader(buf))
require.Nil(t, err)
res, err := http.DefaultClient.Do(send)
require.Nil(t, err)
defer res.Body.Close()
ss := cupaloy.New(
cupaloy.SnapshotSubdirectory("testdata/snapshots"), cupaloy.SnapshotFileExtension(".json"),
)
resBody, err := ioutil.ReadAll(res.Body)
require.Nil(t, err)
ss.SnapshotT(t, resBody)
}
When I run in one terminal go run .
and then in another go test ./...
with go version 1.19.4 the first time, I get the following error message, as I should as snapshot tests fail the first time you run them because there is no saved file to compare the payload to. But note the escaping around the timestamps:
❯ go test ./...
--- FAIL: TestThing (0.00s)
main_test.go:50: snapshot created for test TestThing, with contents:
{"detail":"parsing time \"\\\"01.01.2022 22:00\\\"\" as \"\\\"2006-01-02T15:0
4:05Z07:00\\\"\": cannot parse \"1.2022 22:00\\\"\" as \"2006\""}
I then switched my go version to 1.20 and ran go test ./...
again:
❯ go test ./...
--- FAIL: TestThing (0.00s)
main_test.go:50: snapshot not equal:
--- Previous
+++ Current
@@ -1,2 +1,2 @@
-{"detail":"parsing time \"\\\"01.01.2022 22:00\\\"\" as \"\\\"2006-01-02T15:
04:05Z07:00\\\"\": cannot parse \"1.2022 22:00\\\"\" as \"2006\""}
+{"detail":"parsing time \"01.01.2022 22:00\" as \"2006-01-02T15:04:05Z07:00\
": cannot parse \"01.01.2022 22:00\" as \"2006\""}
Note how in the +
section the escaping is now better and doesn't include \\\
.
Expectations
I would have expected the error messages to stay the same, even though the escaping in 1.19 was worse.
Actual result
Something changed between go versions that caused the escaping of the timestamps to change in Gin's validation errors.
I've also confirmed that this has nothing to do with cupaloy
, the snapshot testing library I'm using. I sent requests to my local server from main.go
using httpie
and have the same escaping discrepancy between go versions:
request.json
{
"timeseries": [
{
"timestamp": "01.01.2022 22:00",
}
]
}
go 1.19.4
❯ http post :8080 < ../request.json
HTTP/1.1 400 Bad Request
Content-Length: 142
Content-Type: application/json; charset=utf-8
Date: Mon, 20 Feb 2023 11:02:13 GMT
{
"detail": "parsing time \"\\\"01.01.2022 22:00\\\"\" as \"\\\"2006-01-02T15:04:05Z07:00\\\"\": cannot parse \"1.2022 22:00\\\"\" as \"2006\""
}
go 1.20
❯ http post :8080 < ../request.json
HTTP/1.1 400 Bad Request
Content-Length: 126
Content-Type: application/json; charset=utf-8
Date: Mon, 20 Feb 2023 11:03:20 GMT
{
"detail": "parsing time \"01.01.2022 22:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"01.01.2022 22:00\" as \"2006\""
}
Environment
- go version: 1.19.4 and 1.20
- gin version (or commit ref): 1.8.2 (see go.mod above)
- operating system: M1 Ventura 13.2
Comment From: monkey92t
In go1.20, enforce strict parsing for RFC3339 for Time.Unmarshal. By default, gin uses func (t *Time) UnmarshalJSON(data []byte)
method, See https://github.com/golang/go/issues/54580