Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timestamps escaped differently in go 1.20 #3507

Open
sobafuchs opened this issue Feb 20, 2023 · 1 comment
Open

timestamps escaped differently in go 1.20 #3507

sobafuchs opened this issue Feb 20, 2023 · 1 comment

Comments

@sobafuchs
Copy link

sobafuchs commented Feb 20, 2023

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
@sobafuchs sobafuchs changed the title error strings parsed differently in go 1.20 timestamps escaped differently in go 1.20 Feb 20, 2023
@monkey92t
Copy link

In go1.20, enforce strict parsing for RFC3339 for Time.Unmarshal. By default, gin uses func (t *Time) UnmarshalJSON(data []byte) method, See golang/go#54580

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants