Completed functionality of Object.MarshalJSON
Added a couple more tests Added proper time.Duration to xsd:duration encoding
This commit is contained in:
parent
8420879a85
commit
8af476849f
6 changed files with 333 additions and 7 deletions
|
@ -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
164
object.go
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
84
xsdduration.go
Normal 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
48
xsdduration_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in a new issue