From f4bf0361774929fe9631d1ebb859999999fad659 Mon Sep 17 00:00:00 2001 From: Aren Patel Date: Sun, 4 Apr 2021 18:06:14 -0700 Subject: [PATCH 1/6] RFC 3339 support for both Marshal and Unmarshal. --- constants.go | 4 +- models_test.go | 18 ++- request_test.go | 285 ++++++++++++++++++++++++++--------------------- response.go | 8 +- response_test.go | 160 +++++++++++++++++--------- 5 files changed, 283 insertions(+), 192 deletions(-) diff --git a/constants.go b/constants.go index 71fddd0f..10339bd2 100644 --- a/constants.go +++ b/constants.go @@ -1,5 +1,7 @@ package jsonapi +import "time" + const ( // StructTag annotation strings annotationJSONAPI = "jsonapi" @@ -12,7 +14,7 @@ const ( annotationRFC3339 = "rfc3339" annotationSeperator = "," - rfc3339TimeFormat = "2006-01-02T15:04:05Z07:00" + rfc3339TimeFormat = time.RFC3339 iso8601TimeFormat = "2006-01-02T15:04:05Z" // MediaType is the identifier for the JSON API media type diff --git a/models_test.go b/models_test.go index 6e444369..f552d4fe 100644 --- a/models_test.go +++ b/models_test.go @@ -25,16 +25,14 @@ type WithPointer struct { FloatVal *float32 `jsonapi:"attr,float-val"` } -type Timestamp struct { - ID int `jsonapi:"primary,timestamps"` - Time time.Time `jsonapi:"attr,timestamp,iso8601"` - Next *time.Time `jsonapi:"attr,next,iso8601"` -} - -type TimestampRFC3339 struct { - ID int `jsonapi:"primary,timestamps"` - Time time.Time `jsonapi:"attr,timestamp,rfc3339"` - Next *time.Time `jsonapi:"attr,next,rfc3339"` +type TimestampModel struct { + ID int `jsonapi:"primary,timestamps"` + DefaultV time.Time `jsonapi:"attr,defaultv"` + DefaultP *time.Time `jsonapi:"attr,defaultp"` + ISO8601V time.Time `jsonapi:"attr,iso8601v,iso8601"` + ISO8601P *time.Time `jsonapi:"attr,iso8601p,iso8601"` + RFC3339V time.Time `jsonapi:"attr,rfc3339v,rfc3339"` + RFC3339P *time.Time `jsonapi:"attr,rfc3339p,rfc3339"` } type Car struct { diff --git a/request_test.go b/request_test.go index 09199d28..323aaaff 100644 --- a/request_test.go +++ b/request_test.go @@ -3,6 +3,7 @@ package jsonapi import ( "bytes" "encoding/json" + "errors" "fmt" "io" "reflect" @@ -341,147 +342,175 @@ func TestUnmarshalSetsAttrs(t *testing.T) { } } -func TestUnmarshalParsesISO8601(t *testing.T) { - payload := &OnePayload{ - Data: &Node{ - Type: "timestamps", - Attributes: map[string]interface{}{ - "timestamp": "2016-08-17T08:27:12Z", +func TestUnmarshal_Times(t *testing.T) { + aTime := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) + + for _, tc := range []struct { + desc string + inputPayload *OnePayload + wantErr bool + verifcation func(tm *TimestampModel) error + }{ + // Default: + { + desc: "default_byValue", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "defaultv": aTime.Unix(), + }, + }, + }, + verifcation: func(tm *TimestampModel) error { + if !tm.DefaultV.Equal(aTime) { + return errors.New("times not equal!") + } + return nil }, }, - } - - in := bytes.NewBuffer(nil) - json.NewEncoder(in).Encode(payload) - - out := new(Timestamp) - - if err := UnmarshalPayload(in, out); err != nil { - t.Fatal(err) - } - - expected := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) - - if !out.Time.Equal(expected) { - t.Fatal("Parsing the ISO8601 timestamp failed") - } -} - -func TestUnmarshalParsesISO8601TimePointer(t *testing.T) { - payload := &OnePayload{ - Data: &Node{ - Type: "timestamps", - Attributes: map[string]interface{}{ - "next": "2016-08-17T08:27:12Z", + { + desc: "default_byPointer", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "defaultp": aTime.Unix(), + }, + }, + }, + verifcation: func(tm *TimestampModel) error { + if !tm.DefaultP.Equal(aTime) { + return errors.New("times not equal!") + } + return nil }, }, - } - - in := bytes.NewBuffer(nil) - json.NewEncoder(in).Encode(payload) - - out := new(Timestamp) - - if err := UnmarshalPayload(in, out); err != nil { - t.Fatal(err) - } - - expected := time.Date(2016, 8, 17, 8, 27, 12, 0, time.UTC) - - if !out.Next.Equal(expected) { - t.Fatal("Parsing the ISO8601 timestamp failed") - } -} - -func TestUnmarshalInvalidISO8601(t *testing.T) { - payload := &OnePayload{ - Data: &Node{ - Type: "timestamps", - Attributes: map[string]interface{}{ - "timestamp": "17 Aug 16 08:027 MST", + { + desc: "default_invalid", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "defaultv": "not a timestamp!", + }, + }, }, + wantErr: true, }, - } - - in := bytes.NewBuffer(nil) - json.NewEncoder(in).Encode(payload) - - out := new(Timestamp) - - if err := UnmarshalPayload(in, out); err != ErrInvalidISO8601 { - t.Fatalf("Expected ErrInvalidISO8601, got %v", err) - } -} - -func TestUnmarshalParsesRFC3339(t *testing.T) { - payload := &OnePayload{ - Data: &Node{ - Type: "timestamps", - Attributes: map[string]interface{}{ - "timestamp": "2020-03-16T23:09:59+00:00", + // ISO 8601: + { + desc: "iso8601_byValue", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "iso8601v": "2016-08-17T08:27:12Z", + }, + }, + }, + verifcation: func(tm *TimestampModel) error { + if !tm.ISO8601V.Equal(aTime) { + return errors.New("times not equal!") + } + return nil }, }, - } - - in := bytes.NewBuffer(nil) - json.NewEncoder(in).Encode(payload) - - out := new(TimestampRFC3339) - - if err := UnmarshalPayload(in, out); err != nil { - t.Fatal(err) - } - - expected := time.Date(2020, 3, 16, 23, 9, 59, 0, time.UTC) - - if !out.Time.Equal(expected) { - t.Fatal("Parsing the RFC3339 timestamp failed") - } -} - -func TestUnmarshalParsesRFC3339TimePointer(t *testing.T) { - payload := &OnePayload{ - Data: &Node{ - Type: "timestamps", - Attributes: map[string]interface{}{ - "next": "2020-03-16T23:09:59+00:00", + { + desc: "iso8601_byPointer", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "iso8601p": "2016-08-17T08:27:12Z", + }, + }, + }, + verifcation: func(tm *TimestampModel) error { + if !tm.ISO8601P.Equal(aTime) { + return errors.New("times not equal!") + } + return nil }, }, - } - - in := bytes.NewBuffer(nil) - json.NewEncoder(in).Encode(payload) - - out := new(TimestampRFC3339) - - if err := UnmarshalPayload(in, out); err != nil { - t.Fatal(err) - } - - expected := time.Date(2020, 3, 16, 23, 9, 59, 0, time.UTC) - - if !out.Next.Equal(expected) { - t.Fatal("Parsing the RFC3339 timestamp failed") - } -} - -func TestUnmarshalInvalidRFC3339(t *testing.T) { - payload := &OnePayload{ - Data: &Node{ - Type: "timestamps", - Attributes: map[string]interface{}{ - "timestamp": "17 Aug 16 08:027 MST", + { + desc: "iso8601_invalid", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "iso8601v": "not a timestamp", + }, + }, }, + wantErr: true, }, - } - - in := bytes.NewBuffer(nil) - json.NewEncoder(in).Encode(payload) - - out := new(TimestampRFC3339) + // RFC 3339 + { + desc: "rfc3339_byValue", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "rfc3339v": "2016-08-17T08:27:12Z", + }, + }, + }, + verifcation: func(tm *TimestampModel) error { + if got, want := tm.RFC3339V, aTime; got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "rfc3339_byPointer", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "rfc3339p": "2016-08-17T08:27:12Z", + }, + }, + }, + verifcation: func(tm *TimestampModel) error { + if got, want := *tm.RFC3339P, aTime; got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "rfc3339_invalid", + inputPayload: &OnePayload{ + Data: &Node{ + Type: "timestamps", + Attributes: map[string]interface{}{ + "rfc3339v": "not a timestamp", + }, + }, + }, + wantErr: true, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + // Serialize the OnePayload using the standard JSON library. + in := bytes.NewBuffer(nil) + if err := json.NewEncoder(in).Encode(tc.inputPayload); err != nil { + t.Fatal(err) + } - if err := UnmarshalPayload(in, out); err != ErrInvalidRFC3339 { - t.Fatalf("Expected ErrInvalidRFC3339, got %v", err) + out := &TimestampModel{} + err := UnmarshalPayload(in, out) + if got, want := (err != nil), tc.wantErr; got != want { + t.Fatalf("UnmarshalPayload error: got %v, want %v", got, want) + } + if tc.verifcation != nil { + if err := tc.verifcation(out); err != nil { + t.Fatal(err) + } + } + }) } } diff --git a/response.go b/response.go index 3f8ab73d..41ca3856 100644 --- a/response.go +++ b/response.go @@ -283,7 +283,7 @@ func visitModelNode(model interface{}, included *map[string]*Node, node.ClientID = clientID } } else if annotation == annotationAttribute { - var omitEmpty, iso8601 bool + var omitEmpty, iso8601, rfc3339 bool if len(args) > 2 { for _, arg := range args[2:] { @@ -292,6 +292,8 @@ func visitModelNode(model interface{}, included *map[string]*Node, omitEmpty = true case annotationISO8601: iso8601 = true + case annotationRFC3339: + rfc3339 = true } } } @@ -309,6 +311,8 @@ func visitModelNode(model interface{}, included *map[string]*Node, if iso8601 { node.Attributes[args[1]] = t.UTC().Format(iso8601TimeFormat) + } else if rfc3339 { + node.Attributes[args[1]] = t.UTC().Format(rfc3339TimeFormat) } else { node.Attributes[args[1]] = t.Unix() } @@ -329,6 +333,8 @@ func visitModelNode(model interface{}, included *map[string]*Node, if iso8601 { node.Attributes[args[1]] = tm.UTC().Format(iso8601TimeFormat) + } else if rfc3339 { + node.Attributes[args[1]] = tm.UTC().Format(rfc3339TimeFormat) } else { node.Attributes[args[1]] = tm.Unix() } diff --git a/response_test.go b/response_test.go index 5b425955..adb45bef 100644 --- a/response_test.go +++ b/response_test.go @@ -3,6 +3,7 @@ package jsonapi import ( "bytes" "encoding/json" + "fmt" "reflect" "sort" "testing" @@ -470,58 +471,113 @@ func TestOmitsZeroTimes(t *testing.T) { } } -func TestMarshalISO8601Time(t *testing.T) { - testModel := &Timestamp{ - ID: 5, - Time: time.Date(2016, 8, 17, 8, 27, 12, 23849, time.UTC), - } - - out := bytes.NewBuffer(nil) - if err := MarshalPayload(out, testModel); err != nil { - t.Fatal(err) - } - - resp := new(OnePayload) - if err := json.NewDecoder(out).Decode(resp); err != nil { - t.Fatal(err) - } - - data := resp.Data - - if data.Attributes == nil { - t.Fatalf("Expected attributes") - } - - if data.Attributes["timestamp"] != "2016-08-17T08:27:12Z" { - t.Fatal("Timestamp was not serialised into ISO8601 correctly") - } -} - -func TestMarshalISO8601TimePointer(t *testing.T) { - tm := time.Date(2016, 8, 17, 8, 27, 12, 23849, time.UTC) - testModel := &Timestamp{ - ID: 5, - Next: &tm, - } - - out := bytes.NewBuffer(nil) - if err := MarshalPayload(out, testModel); err != nil { - t.Fatal(err) - } - - resp := new(OnePayload) - if err := json.NewDecoder(out).Decode(resp); err != nil { - t.Fatal(err) - } - - data := resp.Data - - if data.Attributes == nil { - t.Fatalf("Expected attributes") - } - - if data.Attributes["next"] != "2016-08-17T08:27:12Z" { - t.Fatal("Next was not serialised into ISO8601 correctly") +func TestMarshal_Times(t *testing.T) { + aTime := time.Date(2016, 8, 17, 8, 27, 12, 23849, time.UTC) + + for _, tc := range []struct { + desc string + input *TimestampModel + verifcation func(data map[string]interface{}) error + }{ + { + desc: "default_byValue", + input: &TimestampModel{ + ID: 5, + DefaultV: aTime, + }, + verifcation: func(root map[string]interface{}) error { + v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["defaultv"].(float64) + if got, want := int64(v), aTime.Unix(); got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "default_byPointer", + input: &TimestampModel{ + ID: 5, + DefaultP: &aTime, + }, + verifcation: func(root map[string]interface{}) error { + v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["defaultp"].(float64) + if got, want := int64(v), aTime.Unix(); got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "iso8601_byValue", + input: &TimestampModel{ + ID: 5, + ISO8601V: aTime, + }, + verifcation: func(root map[string]interface{}) error { + v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["iso8601v"].(string) + if got, want := v, aTime.UTC().Format(iso8601TimeFormat); got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "iso8601_byPointer", + input: &TimestampModel{ + ID: 5, + ISO8601P: &aTime, + }, + verifcation: func(root map[string]interface{}) error { + v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["iso8601p"].(string) + if got, want := v, aTime.UTC().Format(iso8601TimeFormat); got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "rfc3339_byValue", + input: &TimestampModel{ + ID: 5, + RFC3339V: aTime, + }, + verifcation: func(root map[string]interface{}) error { + v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339v"].(string) + if got, want := v, aTime.UTC().Format(rfc3339TimeFormat); got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + { + desc: "rfc3339_byPointer", + input: &TimestampModel{ + ID: 5, + RFC3339P: &aTime, + }, + verifcation: func(root map[string]interface{}) error { + v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339p"].(string) + if got, want := v, aTime.UTC().Format(rfc3339TimeFormat); got != want { + return fmt.Errorf("got %v, want %v", got, want) + } + return nil + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + out := bytes.NewBuffer(nil) + if err := MarshalPayload(out, tc.input); err != nil { + t.Fatal(err) + } + // Use the standard JSON library to traverse the genereated JSON payload. + data := map[string]interface{}{} + json.Unmarshal(out.Bytes(), &data) + if tc.verifcation != nil { + if err := tc.verifcation(data); err != nil { + t.Fatal(err) + } + } + }) } } From 6a4c8f0579a6972bc9d325b5630b260ff3ee29f7 Mon Sep 17 00:00:00 2001 From: Aren Patel Date: Sun, 4 Apr 2021 18:21:13 -0700 Subject: [PATCH 2/6] Post merge cleanup --- request.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/request.go b/request.go index 8f76cda6..68bb3397 100644 --- a/request.go +++ b/request.go @@ -448,21 +448,20 @@ func handleStringSlice(attribute interface{}) (reflect.Value, error) { } func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) { - var isIso8601 bool - var isRFC3339 bool + var isISO8601, isRFC3339 bool v := reflect.ValueOf(attribute) if len(args) > 2 { for _, arg := range args[2:] { if arg == annotationISO8601 { - isIso8601 = true + isISO8601 = true } else if arg == annotationRFC3339 { isRFC3339 = true } } } - if isIso8601 { + if isISO8601 { var tm string if v.Kind() == reflect.String { tm = v.Interface().(string) From 95596763c642c06dee241861d5ba2152a82460de Mon Sep 17 00:00:00 2001 From: Aren Patel Date: Mon, 5 Apr 2021 10:25:46 -0700 Subject: [PATCH 3/6] Update request_test.go Co-authored-by: Quetzy Garcia --- request_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request_test.go b/request_test.go index 323aaaff..a0599d07 100644 --- a/request_test.go +++ b/request_test.go @@ -349,7 +349,7 @@ func TestUnmarshal_Times(t *testing.T) { desc string inputPayload *OnePayload wantErr bool - verifcation func(tm *TimestampModel) error + verification func(tm *TimestampModel) error }{ // Default: { From 18866219186100d5a0472aea17dbbbab4622622c Mon Sep 17 00:00:00 2001 From: Aren Patel Date: Mon, 5 Apr 2021 10:29:42 -0700 Subject: [PATCH 4/6] Spelling --- request_test.go | 18 +++++++++--------- response_test.go | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/request_test.go b/request_test.go index a0599d07..300c7de3 100644 --- a/request_test.go +++ b/request_test.go @@ -349,7 +349,7 @@ func TestUnmarshal_Times(t *testing.T) { desc string inputPayload *OnePayload wantErr bool - verification func(tm *TimestampModel) error + verification func(tm *TimestampModel) error }{ // Default: { @@ -362,7 +362,7 @@ func TestUnmarshal_Times(t *testing.T) { }, }, }, - verifcation: func(tm *TimestampModel) error { + verification: func(tm *TimestampModel) error { if !tm.DefaultV.Equal(aTime) { return errors.New("times not equal!") } @@ -379,7 +379,7 @@ func TestUnmarshal_Times(t *testing.T) { }, }, }, - verifcation: func(tm *TimestampModel) error { + verification: func(tm *TimestampModel) error { if !tm.DefaultP.Equal(aTime) { return errors.New("times not equal!") } @@ -409,7 +409,7 @@ func TestUnmarshal_Times(t *testing.T) { }, }, }, - verifcation: func(tm *TimestampModel) error { + verification: func(tm *TimestampModel) error { if !tm.ISO8601V.Equal(aTime) { return errors.New("times not equal!") } @@ -426,7 +426,7 @@ func TestUnmarshal_Times(t *testing.T) { }, }, }, - verifcation: func(tm *TimestampModel) error { + verification: func(tm *TimestampModel) error { if !tm.ISO8601P.Equal(aTime) { return errors.New("times not equal!") } @@ -456,7 +456,7 @@ func TestUnmarshal_Times(t *testing.T) { }, }, }, - verifcation: func(tm *TimestampModel) error { + verification: func(tm *TimestampModel) error { if got, want := tm.RFC3339V, aTime; got != want { return fmt.Errorf("got %v, want %v", got, want) } @@ -473,7 +473,7 @@ func TestUnmarshal_Times(t *testing.T) { }, }, }, - verifcation: func(tm *TimestampModel) error { + verification: func(tm *TimestampModel) error { if got, want := *tm.RFC3339P, aTime; got != want { return fmt.Errorf("got %v, want %v", got, want) } @@ -505,8 +505,8 @@ func TestUnmarshal_Times(t *testing.T) { if got, want := (err != nil), tc.wantErr; got != want { t.Fatalf("UnmarshalPayload error: got %v, want %v", got, want) } - if tc.verifcation != nil { - if err := tc.verifcation(out); err != nil { + if tc.verification != nil { + if err := tc.verification(out); err != nil { t.Fatal(err) } } diff --git a/response_test.go b/response_test.go index adb45bef..97b4adab 100644 --- a/response_test.go +++ b/response_test.go @@ -475,9 +475,9 @@ func TestMarshal_Times(t *testing.T) { aTime := time.Date(2016, 8, 17, 8, 27, 12, 23849, time.UTC) for _, tc := range []struct { - desc string - input *TimestampModel - verifcation func(data map[string]interface{}) error + desc string + input *TimestampModel + verification func(data map[string]interface{}) error }{ { desc: "default_byValue", @@ -485,7 +485,7 @@ func TestMarshal_Times(t *testing.T) { ID: 5, DefaultV: aTime, }, - verifcation: func(root map[string]interface{}) error { + verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["defaultv"].(float64) if got, want := int64(v), aTime.Unix(); got != want { return fmt.Errorf("got %v, want %v", got, want) @@ -499,7 +499,7 @@ func TestMarshal_Times(t *testing.T) { ID: 5, DefaultP: &aTime, }, - verifcation: func(root map[string]interface{}) error { + verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["defaultp"].(float64) if got, want := int64(v), aTime.Unix(); got != want { return fmt.Errorf("got %v, want %v", got, want) @@ -513,7 +513,7 @@ func TestMarshal_Times(t *testing.T) { ID: 5, ISO8601V: aTime, }, - verifcation: func(root map[string]interface{}) error { + verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["iso8601v"].(string) if got, want := v, aTime.UTC().Format(iso8601TimeFormat); got != want { return fmt.Errorf("got %v, want %v", got, want) @@ -527,7 +527,7 @@ func TestMarshal_Times(t *testing.T) { ID: 5, ISO8601P: &aTime, }, - verifcation: func(root map[string]interface{}) error { + verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["iso8601p"].(string) if got, want := v, aTime.UTC().Format(iso8601TimeFormat); got != want { return fmt.Errorf("got %v, want %v", got, want) @@ -541,7 +541,7 @@ func TestMarshal_Times(t *testing.T) { ID: 5, RFC3339V: aTime, }, - verifcation: func(root map[string]interface{}) error { + verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339v"].(string) if got, want := v, aTime.UTC().Format(rfc3339TimeFormat); got != want { return fmt.Errorf("got %v, want %v", got, want) @@ -555,7 +555,7 @@ func TestMarshal_Times(t *testing.T) { ID: 5, RFC3339P: &aTime, }, - verifcation: func(root map[string]interface{}) error { + verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339p"].(string) if got, want := v, aTime.UTC().Format(rfc3339TimeFormat); got != want { return fmt.Errorf("got %v, want %v", got, want) @@ -572,8 +572,8 @@ func TestMarshal_Times(t *testing.T) { // Use the standard JSON library to traverse the genereated JSON payload. data := map[string]interface{}{} json.Unmarshal(out.Bytes(), &data) - if tc.verifcation != nil { - if err := tc.verifcation(data); err != nil { + if tc.verification != nil { + if err := tc.verification(data); err != nil { t.Fatal(err) } } From 05878cd0a19c9ac683311bcb255ee9a605869f16 Mon Sep 17 00:00:00 2001 From: Aren Patel Date: Mon, 5 Apr 2021 10:37:17 -0700 Subject: [PATCH 5/6] Update request.go Co-authored-by: Quetzy Garcia --- request.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/request.go b/request.go index 68bb3397..1540943f 100644 --- a/request.go +++ b/request.go @@ -482,14 +482,11 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) } if isRFC3339 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { + if v.Kind() != reflect.String { return reflect.ValueOf(time.Now()), ErrInvalidRFC3339 } - t, err := time.Parse(time.RFC3339, tm) + t, err := time.Parse(time.RFC3339, v.Interface().(string)) if err != nil { return reflect.ValueOf(time.Now()), ErrInvalidRFC3339 } From 97e3c1f5e29550fdb2a1ff409d1b9026a3831105 Mon Sep 17 00:00:00 2001 From: Aren Patel Date: Mon, 5 Apr 2021 11:06:05 -0700 Subject: [PATCH 6/6] Simplify the ISO 8601 logic. No need for the const rfc3339TimeFormat use time.RFC3339 directly. --- constants.go | 3 --- request.go | 7 ++----- response.go | 4 ++-- response_test.go | 4 ++-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/constants.go b/constants.go index 10339bd2..35bbe054 100644 --- a/constants.go +++ b/constants.go @@ -1,7 +1,5 @@ package jsonapi -import "time" - const ( // StructTag annotation strings annotationJSONAPI = "jsonapi" @@ -14,7 +12,6 @@ const ( annotationRFC3339 = "rfc3339" annotationSeperator = "," - rfc3339TimeFormat = time.RFC3339 iso8601TimeFormat = "2006-01-02T15:04:05Z" // MediaType is the identifier for the JSON API media type diff --git a/request.go b/request.go index 1540943f..f665857f 100644 --- a/request.go +++ b/request.go @@ -462,14 +462,11 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) } if isISO8601 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { + if v.Kind() != reflect.String { return reflect.ValueOf(time.Now()), ErrInvalidISO8601 } - t, err := time.Parse(iso8601TimeFormat, tm) + t, err := time.Parse(iso8601TimeFormat, v.Interface().(string)) if err != nil { return reflect.ValueOf(time.Now()), ErrInvalidISO8601 } diff --git a/response.go b/response.go index 41ca3856..b44e4e97 100644 --- a/response.go +++ b/response.go @@ -312,7 +312,7 @@ func visitModelNode(model interface{}, included *map[string]*Node, if iso8601 { node.Attributes[args[1]] = t.UTC().Format(iso8601TimeFormat) } else if rfc3339 { - node.Attributes[args[1]] = t.UTC().Format(rfc3339TimeFormat) + node.Attributes[args[1]] = t.UTC().Format(time.RFC3339) } else { node.Attributes[args[1]] = t.Unix() } @@ -334,7 +334,7 @@ func visitModelNode(model interface{}, included *map[string]*Node, if iso8601 { node.Attributes[args[1]] = tm.UTC().Format(iso8601TimeFormat) } else if rfc3339 { - node.Attributes[args[1]] = tm.UTC().Format(rfc3339TimeFormat) + node.Attributes[args[1]] = tm.UTC().Format(time.RFC3339) } else { node.Attributes[args[1]] = tm.Unix() } diff --git a/response_test.go b/response_test.go index 97b4adab..b1d5967a 100644 --- a/response_test.go +++ b/response_test.go @@ -543,7 +543,7 @@ func TestMarshal_Times(t *testing.T) { }, verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339v"].(string) - if got, want := v, aTime.UTC().Format(rfc3339TimeFormat); got != want { + if got, want := v, aTime.UTC().Format(time.RFC3339); got != want { return fmt.Errorf("got %v, want %v", got, want) } return nil @@ -557,7 +557,7 @@ func TestMarshal_Times(t *testing.T) { }, verification: func(root map[string]interface{}) error { v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339p"].(string) - if got, want := v, aTime.UTC().Format(rfc3339TimeFormat); got != want { + if got, want := v, aTime.UTC().Format(time.RFC3339); got != want { return fmt.Errorf("got %v, want %v", got, want) } return nil