forked from Mirrors/jsonapi
Add RFC3339 timestamp (#201)
@omarismail LGTM. I'm curious what you think about perhaps documenting these `iso8601` and `rfc3339` in the `Readme.md`? How did you find that this tag option/value existed? How can we make this better for others vs having to search the library implementation?
This commit is contained in:
parent
b10ff4bf78
commit
c0ee6d2554
|
@ -9,8 +9,10 @@ const (
|
||||||
annotationRelation = "relation"
|
annotationRelation = "relation"
|
||||||
annotationOmitEmpty = "omitempty"
|
annotationOmitEmpty = "omitempty"
|
||||||
annotationISO8601 = "iso8601"
|
annotationISO8601 = "iso8601"
|
||||||
|
annotationRFC3339 = "rfc3339"
|
||||||
annotationSeperator = ","
|
annotationSeperator = ","
|
||||||
|
|
||||||
|
rfc3339TimeFormat = "2006-01-02T15:04:05Z07:00"
|
||||||
iso8601TimeFormat = "2006-01-02T15:04:05Z"
|
iso8601TimeFormat = "2006-01-02T15:04:05Z"
|
||||||
|
|
||||||
// MediaType is the identifier for the JSON API media type
|
// MediaType is the identifier for the JSON API media type
|
||||||
|
|
|
@ -31,6 +31,12 @@ type Timestamp struct {
|
||||||
Next *time.Time `jsonapi:"attr,next,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 Car struct {
|
type Car struct {
|
||||||
ID *string `jsonapi:"primary,cars"`
|
ID *string `jsonapi:"primary,cars"`
|
||||||
Make *string `jsonapi:"attr,make,omitempty"`
|
Make *string `jsonapi:"attr,make,omitempty"`
|
||||||
|
|
26
request.go
26
request.go
|
@ -23,6 +23,9 @@ var (
|
||||||
// ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes
|
// ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes
|
||||||
// "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string.
|
// "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string.
|
||||||
ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps")
|
ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps")
|
||||||
|
// ErrInvalidRFC3339 is returned when a struct has a time.Time type field and includes
|
||||||
|
// "rfc3339" in the tag spec, but the JSON value was not an RFC3339 timestamp string.
|
||||||
|
ErrInvalidRFC3339 = errors.New("Only strings can be parsed as dates, RFC3339 timestamps")
|
||||||
// ErrUnknownFieldNumberType is returned when the JSON value was a float
|
// ErrUnknownFieldNumberType is returned when the JSON value was a float
|
||||||
// (numeric) but the Struct field was a non numeric type (i.e. not int, uint,
|
// (numeric) but the Struct field was a non numeric type (i.e. not int, uint,
|
||||||
// float, etc)
|
// float, etc)
|
||||||
|
@ -446,12 +449,15 @@ func handleStringSlice(attribute interface{}) (reflect.Value, error) {
|
||||||
|
|
||||||
func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
|
func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) {
|
||||||
var isIso8601 bool
|
var isIso8601 bool
|
||||||
|
var isRFC3339 bool
|
||||||
v := reflect.ValueOf(attribute)
|
v := reflect.ValueOf(attribute)
|
||||||
|
|
||||||
if len(args) > 2 {
|
if len(args) > 2 {
|
||||||
for _, arg := range args[2:] {
|
for _, arg := range args[2:] {
|
||||||
if arg == annotationISO8601 {
|
if arg == annotationISO8601 {
|
||||||
isIso8601 = true
|
isIso8601 = true
|
||||||
|
} else if arg == annotationRFC3339 {
|
||||||
|
isRFC3339 = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,6 +482,26 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value)
|
||||||
return reflect.ValueOf(t), nil
|
return reflect.ValueOf(t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isRFC3339 {
|
||||||
|
var tm string
|
||||||
|
if v.Kind() == reflect.String {
|
||||||
|
tm = v.Interface().(string)
|
||||||
|
} else {
|
||||||
|
return reflect.ValueOf(time.Now()), ErrInvalidRFC3339
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, tm)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.ValueOf(time.Now()), ErrInvalidRFC3339
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldValue.Kind() == reflect.Ptr {
|
||||||
|
return reflect.ValueOf(&t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(t), nil
|
||||||
|
}
|
||||||
|
|
||||||
var at int64
|
var at int64
|
||||||
|
|
||||||
if v.Kind() == reflect.Float64 {
|
if v.Kind() == reflect.Float64 {
|
||||||
|
|
|
@ -413,6 +413,78 @@ func TestUnmarshalInvalidISO8601(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalParsesRFC3339(t *testing.T) {
|
||||||
|
payload := &OnePayload{
|
||||||
|
Data: &Node{
|
||||||
|
Type: "timestamps",
|
||||||
|
Attributes: map[string]interface{}{
|
||||||
|
"timestamp": "2020-03-16T23:09:59+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
in := bytes.NewBuffer(nil)
|
||||||
|
json.NewEncoder(in).Encode(payload)
|
||||||
|
|
||||||
|
out := new(TimestampRFC3339)
|
||||||
|
|
||||||
|
if err := UnmarshalPayload(in, out); err != ErrInvalidRFC3339 {
|
||||||
|
t.Fatalf("Expected ErrInvalidRFC3339, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalRelationshipsWithoutIncluded(t *testing.T) {
|
func TestUnmarshalRelationshipsWithoutIncluded(t *testing.T) {
|
||||||
data, err := json.Marshal(samplePayloadWithoutIncluded())
|
data, err := json.Marshal(samplePayloadWithoutIncluded())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue