Completed functionality of Object.MarshalJSON

Added a couple more tests

Added proper time.Duration to xsd:duration encoding
This commit is contained in:
Marius Orcsik 2019-12-18 13:09:13 +01:00
parent 8420879a85
commit 8af476849f
No known key found for this signature in database
GPG key ID: 889CE8E4FB2D877A
6 changed files with 333 additions and 7 deletions

View file

@ -73,7 +73,7 @@ func (n *NaturalLanguageValues) Set(ref LangRef, v string) error {
// MarshalJSON serializes the NaturalLanguageValues into JSON
func (n NaturalLanguageValues) MarshalJSON() ([]byte, error) {
l := len(n)
if l == 0 {
if l <= 0 {
return nil, nil
}
@ -86,7 +86,7 @@ func (n NaturalLanguageValues) MarshalJSON() ([]byte, error) {
if err != nil {
return nil, err
}
if ll == 0 {
if ll <= 0 {
return nil, nil
}
return b.Bytes(), nil

164
object.go
View file

@ -339,7 +339,7 @@ func writePropName(b *bytes.Buffer, s string) (notEmpty bool) {
if err != nil {
return false
}
if l == 0 {
if l <= 0 {
return false
}
return true
@ -350,7 +350,7 @@ func writeValue(b *bytes.Buffer, s []byte) (notEmpty bool) {
if err != nil {
return false
}
if l == 0 {
if l <= 0 {
return false
}
return true
@ -367,6 +367,32 @@ func writeNaturalLanguageProp(b *bytes.Buffer, n string, nl NaturalLanguageValue
return notEmpty
}
func writeTimeProp(b *bytes.Buffer, n string, t time.Time) (notEmpty bool) {
v, err := t.MarshalJSON()
if err != nil {
return false
}
return writeProp(b, n, v)
}
func writeDurationProp(b *bytes.Buffer, n string, d time.Duration) (notEmpty bool) {
if v, err := marshalXSD(d); err == nil {
return writeProp(b, n, v)
}
return false
}
func writeIRIProp(b *bytes.Buffer, n string, i LinkOrIRI) (notEmpty bool) {
url := i.GetLink()
if len(url) > 0 {
writePropName(b, n)
b.Write([]byte{'"'})
b.Write([]byte(url))
b.Write([]byte{'"'})
return true
}
return false
}
func writeItemProp(b *bytes.Buffer, n string, i Item) (notEmpty bool) {
notEmpty = false
if i == nil {
@ -390,8 +416,37 @@ func writeItemProp(b *bytes.Buffer, n string, i Item) (notEmpty bool) {
return notEmpty
}
func writeItemCollectionProp(b *bytes.Buffer, n string, i ItemCollection) (notEmpty bool) {
return false
func writeItemCollectionProp(b *bytes.Buffer, n string, col ItemCollection) (notEmpty bool) {
notEmpty = false
if len(col) == 0 {
return notEmpty
}
writePropName(b, n)
writeComma := func() { b.WriteString(",") }
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
writeComma()
}
}
b.Write([]byte{'['})
for _, i := range col {
if i.IsObject() {
OnObject(i, func(o *Object) error {
v, err := o.MarshalJSON()
if err != nil {
return nil
}
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeValue(b, v)
return nil
})
} else if i.IsLink() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeValue(b, []byte(i.GetLink()))
}
}
b.Write([]byte{']'})
return notEmpty
}
// MarshalJSON
@ -406,7 +461,6 @@ func (o Object) MarshalJSON() ([]byte, error) {
writeComma()
}
}
if v, err := o.ID.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = writeProp(&b, "id", v)
}
@ -430,6 +484,106 @@ func (o Object) MarshalJSON() ([]byte, error) {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeNaturalLanguageProp(&b, "content", o.Content)
}
if o.Attachment != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "attachment", o.Attachment)
}
if o.AttributedTo != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "attributedTo", o.AttributedTo)
}
if o.Audience != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "audience", o.Audience)
}
if o.Context != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "context", o.Context)
}
if o.Generator != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "generator", o.Generator)
}
if o.Icon != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "icon", o.Icon)
}
if o.Image != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "image", o.Image)
}
if o.InReplyTo != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "inReplyTo", o.InReplyTo)
}
if o.Location != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "location", o.Location)
}
if o.Preview != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "preview", o.Preview)
}
if o.Replies != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "replies", o.Replies)
}
if o.Tag != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "tag", o.Tag)
}
if o.URL != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeIRIProp(&b, "url", o.URL)
}
if o.To != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "to", o.To)
}
if o.Bto != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "bto", o.Bto)
}
if o.CC != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "cc", o.CC)
}
if o.BCC != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "bcc", o.BCC)
}
if !o.Published.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(&b, "published", o.Published)
}
if !o.Updated.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(&b, "updated", o.Updated)
}
if !o.StartTime.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(&b, "startTime", o.StartTime)
}
if !o.EndTime.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(&b, "endTime", o.EndTime)
}
if o.Duration != 0 {
// TODO(marius): maybe don't use 0 as a nil value for Object types
// which can have a valid duration of 0 - (Video, Audio, etc)
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeDurationProp(&b, "duration", o.Duration)
}
if o.Likes != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "likes", o.Likes)
}
if o.Shares != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(&b, "shares", o.Shares)
}
if v, err := o.Source.MarshalJSON(); err == nil && len(v) > 0 {
writeCommaIfNotEmpty(notEmpty)

View file

@ -635,6 +635,45 @@ func TestObject_MarshalJSON(t *testing.T) {
want: []byte(`{"mediaType":"text/stupid"}`),
wantErr: false,
},
{
name: "Attachment",
fields: fields{
Attachment: &Object{
ID: "some example",
Type: VideoType,
},
},
want: []byte(`{"attachment":{"id":"some example","type":"Video"}}`),
wantErr: false,
},
{
name: "AttributedTo",
fields: fields{
AttributedTo: &Actor{
ID: "http://example.com/ana",
Type: PersonType,
},
},
want: []byte(`{"attributedTo":{"id":"http://example.com/ana","type":"Person"}}`),
wantErr: false,
},
{
name: "AttributedToDouble",
fields: fields{
AttributedTo: ItemCollection{
&Actor{
ID: "http://example.com/ana",
Type: PersonType,
},
&Actor{
ID: "http://example.com/GGG",
Type: GroupType,
},
},
},
want: []byte(`{"attributedTo":[{"id":"http://example.com/ana","type":"Person"},{"id":"http://example.com/GGG","type":"Group"}]}`),
wantErr: false,
},
{
name: "Source",
fields: fields{

View file

@ -123,6 +123,7 @@ func JSONGetTime(data []byte, prop string) time.Time {
func JSONGetDuration(data []byte, prop string) time.Duration {
str, _ := jsonparser.GetUnsafeString(data, prop)
// TODO(marius): this needs to be replaced to be compatible with xsd:duration
d, _ := time.ParseDuration(str)
return d
}

84
xsdduration.go Normal file
View file

@ -0,0 +1,84 @@
package activitypub
import (
"bytes"
"fmt"
"time"
)
const day = time.Hour * 24
const week = day * 7
const month = week * 4
const year = month * 12
func Days(d time.Duration) float64 {
dd := d / day
h := d % day
return float64(dd) + float64(h)/(24*60*60*1e9)
}
func Weeks(d time.Duration) float64 {
w := d / week
dd := d % week
return float64(w) + float64(dd)/(7*24*60*60*1e9)
}
func Months(d time.Duration) float64 {
m := d / month
w := d % month
return float64(m) + float64(w)/(4*7*24*60*60*1e9)
}
func Years(d time.Duration) float64 {
y := d / year
m := d % year
return float64(y) + float64(m)/(12*4*7*24*60*60*1e9)
}
func marshalXSD(d time.Duration) ([]byte, error) {
if d == 0 {
return []byte{'P','T','0','S'}, nil
}
neg := d < 0
if neg {
d = -d
}
y := Years(d)
d -= time.Duration(y) * year
m := Months(d)
d -= time.Duration(m) * month
dd := Days(d)
d -= time.Duration(dd) * day
H := d.Hours()
d -= time.Duration(H) * time.Hour
M := d.Minutes()
d -= time.Duration(M) * time.Minute
s := d.Seconds()
d -= time.Duration(s) * time.Second
b := bytes.Buffer{}
if neg {
b.Write([]byte{'-'})
}
b.Write([]byte{'P'})
if y > 0 {
b.WriteString(fmt.Sprintf("%dY", int64(y)))
}
if m > 0 {
b.WriteString(fmt.Sprintf("%dM", int64(m)))
}
if dd > 0 {
b.WriteString(fmt.Sprintf("%dD", int64(dd)))
}
if H + M + s > 0 {
b.Write([]byte{'T'})
if H > 0 {
b.WriteString(fmt.Sprintf("%dH", int64(H)))
}
if M > 0 {
b.WriteString(fmt.Sprintf("%dM", int64(M)))
}
if s > 0 {
b.WriteString(fmt.Sprintf("%dS", int64(s)))
}
}
return b.Bytes(), nil
}

48
xsdduration_test.go Normal file
View file

@ -0,0 +1,48 @@
package activitypub
import (
"reflect"
"testing"
"time"
)
func Test_marshalXSD(t *testing.T) {
tests := []struct {
name string
d time.Duration
want []byte
wantErr bool
}{
{
name: "Zero duration",
d: 0,
want: []byte("PT0S"),
wantErr: false,
},
{
name: "One year",
d: year,
want: []byte("P1Y"),
wantErr: false,
},
{
name: "XSD:duration example 1st",
d: 2*year+6*month+5*day+12*time.Hour+35*time.Minute+30*time.Second,
want: []byte("P2Y6M5DT12H35M30S"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := marshalXSD(tt.d)
if (err != nil) != tt.wantErr {
t.Errorf("marshalXSD() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("marshalXSD() got = %s, want %s", got, tt.want)
}
})
}
}