Added Activity/IntransitiveActivity/Question Json marshaling and tests

This commit is contained in:
Marius Orcsik 2019-12-18 17:34:47 +01:00
parent 8af476849f
commit 4599863fae
No known key found for this signature in database
GPG key ID: 889CE8E4FB2D877A
5 changed files with 868 additions and 262 deletions

View file

@ -1,6 +1,7 @@
package activitypub
import (
"bytes"
"errors"
"time"
"unsafe"
@ -793,3 +794,27 @@ func FlattenActivityProperties(act *Activity) *Activity {
act.Instrument = Flatten(act.Instrument)
return act
}
// MarshalJSON
func (a Activity) MarshalJSON() ([]byte, error) {
b := bytes.Buffer{}
b.Write([]byte{'{'})
if !writeActivity(&b, a) {
return nil, nil
}
b.Write([]byte{'}'})
return b.Bytes(), nil
}
// MarshalJSON
func (i IntransitiveActivity) MarshalJSON() ([]byte, error) {
b := bytes.Buffer{}
b.Write([]byte{'{'})
if !writeIntransitiveActivity(&b, i) {
return nil, nil
}
b.Write([]byte{'}'})
return b.Bytes(), nil
}

View file

@ -2,7 +2,9 @@ package activitypub
import (
"fmt"
"reflect"
"testing"
"time"
)
func TestActivityNew(t *testing.T) {
@ -976,4 +978,487 @@ func TestActivity_GetLink(t *testing.T) {
func TestActivity_GetType(t *testing.T) {
t.Skipf("TODO")
func TestActivity_MarshalJSON(t *testing.T) {
type fields struct {
ID ID
Type ActivityVocabularyType
Name NaturalLanguageValues
Attachment Item
AttributedTo Item
Audience ItemCollection
Content NaturalLanguageValues
Context Item
MediaType MimeType
EndTime time.Time
Generator Item
Icon Item
Image Item
InReplyTo Item
Location Item
Preview Item
Published time.Time
Replies Item
StartTime time.Time
Summary NaturalLanguageValues
Tag ItemCollection
Updated time.Time
URL LinkOrIRI
To ItemCollection
Bto ItemCollection
CC ItemCollection
BCC ItemCollection
Duration time.Duration
Likes Item
Shares Item
Source Source
Actor Item
Target Item
Result Item
Origin Item
Instrument Item
Object Item
}
tests := []struct {
name string
fields fields
want []byte
wantErr bool
}{
{
name: "Empty",
fields: fields{},
want: nil,
wantErr: false,
},
{
name: "JustID",
fields: fields{
ID: ID("example.com"),
},
want: []byte(`{"id":"example.com"}`),
wantErr: false,
},
{
name: "JustType",
fields: fields{
Type: ActivityVocabularyType("myType"),
},
want: []byte(`{"type":"myType"}`),
wantErr: false,
},
{
name: "JustOneName",
fields: fields{
Name: NaturalLanguageValues{
{Ref: NilLangRef, Value: "ana"},
},
},
want: []byte(`{"name":"ana"}`),
wantErr: false,
},
{
name: "MoreNames",
fields: fields{
Name: NaturalLanguageValues{
{Ref: "en", Value: "anna"},
{Ref: "fr", Value: "anne"},
},
},
want: []byte(`{"nameMap":{"en":"anna","fr":"anne"}}`),
wantErr: false,
},
{
name: "JustOneSummary",
fields: fields{
Summary: NaturalLanguageValues{
{Ref: NilLangRef, Value: "test summary"},
},
},
want: []byte(`{"summary":"test summary"}`),
wantErr: false,
},
{
name: "MoreSummaryEntries",
fields: fields{
Summary: NaturalLanguageValues{
{Ref: "en", Value: "test summary"},
{Ref: "fr", Value: "teste summary"},
},
},
want: []byte(`{"summaryMap":{"en":"test summary","fr":"teste summary"}}`),
wantErr: false,
},
{
name: "JustOneContent",
fields: fields{
Content: NaturalLanguageValues{
{Ref: NilLangRef, Value: "test content"},
},
},
want: []byte(`{"content":"test content"}`),
wantErr: false,
},
{
name: "MoreContentEntries",
fields: fields{
Content: NaturalLanguageValues{
{Ref: "en", Value: "test content"},
{Ref: "fr", Value: "teste content"},
},
},
want: []byte(`{"contentMap":{"en":"test content","fr":"teste content"}}`),
wantErr: false,
},
{
name: "MediaType",
fields: fields{
MediaType: MimeType("text/stupid"),
},
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{
Source: Source{
MediaType: MimeType("text/plain"),
Content: NaturalLanguageValues{},
},
},
want: []byte(`{"source":{"mediaType":"text/plain"}}`),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Activity{
ID: tt.fields.ID,
Type: tt.fields.Type,
Name: tt.fields.Name,
Attachment: tt.fields.Attachment,
AttributedTo: tt.fields.AttributedTo,
Audience: tt.fields.Audience,
Content: tt.fields.Content,
Context: tt.fields.Context,
MediaType: tt.fields.MediaType,
EndTime: tt.fields.EndTime,
Generator: tt.fields.Generator,
Icon: tt.fields.Icon,
Image: tt.fields.Image,
InReplyTo: tt.fields.InReplyTo,
Location: tt.fields.Location,
Preview: tt.fields.Preview,
Published: tt.fields.Published,
Replies: tt.fields.Replies,
StartTime: tt.fields.StartTime,
Summary: tt.fields.Summary,
Tag: tt.fields.Tag,
Updated: tt.fields.Updated,
URL: tt.fields.URL,
To: tt.fields.To,
Bto: tt.fields.Bto,
CC: tt.fields.CC,
BCC: tt.fields.BCC,
Duration: tt.fields.Duration,
Likes: tt.fields.Likes,
Shares: tt.fields.Shares,
Source: tt.fields.Source,
Actor: tt.fields.Actor,
Target: tt.fields.Target,
Result: tt.fields.Result,
Origin: tt.fields.Origin,
Instrument: tt.fields.Instrument,
Object: tt.fields.Object,
}
got, err := a.MarshalJSON()
if (err != nil) != tt.wantErr {
t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MarshalJSON() got = %s, want %s", got, tt.want)
}
})
}
}
func TestIntransitiveActivity_MarshalJSON(t *testing.T) {
type fields struct {
ID ID
Type ActivityVocabularyType
Name NaturalLanguageValues
Attachment Item
AttributedTo Item
Audience ItemCollection
Content NaturalLanguageValues
Context Item
MediaType MimeType
EndTime time.Time
Generator Item
Icon Item
Image Item
InReplyTo Item
Location Item
Preview Item
Published time.Time
Replies Item
StartTime time.Time
Summary NaturalLanguageValues
Tag ItemCollection
Updated time.Time
URL LinkOrIRI
To ItemCollection
Bto ItemCollection
CC ItemCollection
BCC ItemCollection
Duration time.Duration
Likes Item
Shares Item
Source Source
Actor CanReceiveActivities
Target Item
Result Item
Origin Item
Instrument Item
}
tests := []struct {
name string
fields fields
want []byte
wantErr bool
}{
{
name: "Empty",
fields: fields{},
want: nil,
wantErr: false,
},
{
name: "JustID",
fields: fields{
ID: ID("example.com"),
},
want: []byte(`{"id":"example.com"}`),
wantErr: false,
},
{
name: "JustType",
fields: fields{
Type: ActivityVocabularyType("myType"),
},
want: []byte(`{"type":"myType"}`),
wantErr: false,
},
{
name: "JustOneName",
fields: fields{
Name: NaturalLanguageValues{
{Ref: NilLangRef, Value: "ana"},
},
},
want: []byte(`{"name":"ana"}`),
wantErr: false,
},
{
name: "MoreNames",
fields: fields{
Name: NaturalLanguageValues{
{Ref: "en", Value: "anna"},
{Ref: "fr", Value: "anne"},
},
},
want: []byte(`{"nameMap":{"en":"anna","fr":"anne"}}`),
wantErr: false,
},
{
name: "JustOneSummary",
fields: fields{
Summary: NaturalLanguageValues{
{Ref: NilLangRef, Value: "test summary"},
},
},
want: []byte(`{"summary":"test summary"}`),
wantErr: false,
},
{
name: "MoreSummaryEntries",
fields: fields{
Summary: NaturalLanguageValues{
{Ref: "en", Value: "test summary"},
{Ref: "fr", Value: "teste summary"},
},
},
want: []byte(`{"summaryMap":{"en":"test summary","fr":"teste summary"}}`),
wantErr: false,
},
{
name: "JustOneContent",
fields: fields{
Content: NaturalLanguageValues{
{Ref: NilLangRef, Value: "test content"},
},
},
want: []byte(`{"content":"test content"}`),
wantErr: false,
},
{
name: "MoreContentEntries",
fields: fields{
Content: NaturalLanguageValues{
{Ref: "en", Value: "test content"},
{Ref: "fr", Value: "teste content"},
},
},
want: []byte(`{"contentMap":{"en":"test content","fr":"teste content"}}`),
wantErr: false,
},
{
name: "MediaType",
fields: fields{
MediaType: MimeType("text/stupid"),
},
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{
Source: Source{
MediaType: MimeType("text/plain"),
Content: NaturalLanguageValues{},
},
},
want: []byte(`{"source":{"mediaType":"text/plain"}}`),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := IntransitiveActivity{
ID: tt.fields.ID,
Type: tt.fields.Type,
Name: tt.fields.Name,
Attachment: tt.fields.Attachment,
AttributedTo: tt.fields.AttributedTo,
Audience: tt.fields.Audience,
Content: tt.fields.Content,
Context: tt.fields.Context,
MediaType: tt.fields.MediaType,
EndTime: tt.fields.EndTime,
Generator: tt.fields.Generator,
Icon: tt.fields.Icon,
Image: tt.fields.Image,
InReplyTo: tt.fields.InReplyTo,
Location: tt.fields.Location,
Preview: tt.fields.Preview,
Published: tt.fields.Published,
Replies: tt.fields.Replies,
StartTime: tt.fields.StartTime,
Summary: tt.fields.Summary,
Tag: tt.fields.Tag,
Updated: tt.fields.Updated,
URL: tt.fields.URL,
To: tt.fields.To,
Bto: tt.fields.Bto,
CC: tt.fields.CC,
BCC: tt.fields.BCC,
Duration: tt.fields.Duration,
Likes: tt.fields.Likes,
Shares: tt.fields.Shares,
Source: tt.fields.Source,
Actor: tt.fields.Actor,
Target: tt.fields.Target,
Result: tt.fields.Result,
Origin: tt.fields.Origin,
Instrument: tt.fields.Instrument,
}
got, err := i.MarshalJSON()
if (err != nil) != tt.wantErr {
t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MarshalJSON() got = %s, want %s", got, tt.want)
}
})
}
}

348
encoding.go Normal file
View file

@ -0,0 +1,348 @@
package activitypub
import (
"bytes"
"fmt"
"time"
)
func writeProp(b *bytes.Buffer, name string, val []byte) (notEmpty bool) {
if len(val) == 0 {
return false
}
writePropName(b, name)
return writeValue(b, val)
}
func writePropName(b *bytes.Buffer, s string) (notEmpty bool) {
if len(s) == 0 {
return false
}
b.Write([]byte{'"'})
b.WriteString(s)
b.Write([]byte{'"', ':'})
return true
}
func writeValue(b *bytes.Buffer, s []byte) (notEmpty bool) {
if len(s) == 0 {
return false
}
b.Write(s)
return true
}
func writeNaturalLanguageProp(b *bytes.Buffer, n string, nl NaturalLanguageValues) (notEmpty bool) {
l := nl.Count()
if l > 1 {
n += "Map"
}
if v, err := nl.MarshalJSON(); err == nil && len(v) > 0 {
return writeProp(b, n, v)
}
return false
}
func writeBoolProp(b *bytes.Buffer, n string, t bool) (notEmpty bool) {
return writeProp(b, n, []byte(fmt.Sprintf("%t", t)))
}
func writeTimeProp(b *bytes.Buffer, n string, t time.Time) (notEmpty bool) {
if v, err := t.MarshalJSON(); err == nil {
return writeProp(b, n, v)
}
return false
}
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 {
return false
}
writePropName(b, n)
b.Write([]byte{'"'})
b.Write([]byte(url))
b.Write([]byte{'"'})
return true
}
func writeItemProp(b *bytes.Buffer, n string, i Item) (notEmpty bool) {
if i == nil {
return notEmpty
}
if i.IsObject() {
OnObject(i, func(o *Object) error {
v, err := o.MarshalJSON()
if err != nil {
return nil
}
notEmpty = writeProp(b, n, v)
return nil
})
} else if i.IsCollection() {
OnCollection(i, func(c CollectionInterface) error {
notEmpty = writeItemCollectionProp(b, n, c.Collection()) || notEmpty
return nil
})
}
return notEmpty
}
func writeItemCollectionProp(b *bytes.Buffer, n string, col ItemCollection) (notEmpty bool) {
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) || notEmpty
return nil
})
} else if i.IsLink() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeValue(b, []byte(i.GetLink())) || notEmpty
}
}
b.Write([]byte{']'})
return notEmpty
}
func writeObject(b *bytes.Buffer, o Object) (notEmpty bool) {
writeComma := func() { b.WriteString(",") }
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
writeComma()
}
}
if v, err := o.ID.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = writeProp(b, "id", v) || notEmpty
}
if v, err := o.Type.MarshalJSON(); err == nil && len(v) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeProp(b, "type", v) || notEmpty
}
if v, err := o.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeProp(b, "mediaType", v) || notEmpty
}
if len(o.Name) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeNaturalLanguageProp(b, "name", o.Name) || notEmpty
}
if len(o.Summary) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeNaturalLanguageProp(b, "summary", o.Summary) || notEmpty
}
if len(o.Content) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeNaturalLanguageProp(b, "content", o.Content) || notEmpty
}
if o.Attachment != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "attachment", o.Attachment) || notEmpty
}
if o.AttributedTo != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "attributedTo", o.AttributedTo) || notEmpty
}
if o.Audience != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "audience", o.Audience) || notEmpty
}
if o.Context != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "context", o.Context) || notEmpty
}
if o.Generator != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "generator", o.Generator) || notEmpty
}
if o.Icon != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "icon", o.Icon) || notEmpty
}
if o.Image != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "image", o.Image) || notEmpty
}
if o.InReplyTo != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "inReplyTo", o.InReplyTo) || notEmpty
}
if o.Location != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "location", o.Location) || notEmpty
}
if o.Preview != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "preview", o.Preview) || notEmpty
}
if o.Replies != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "replies", o.Replies) || notEmpty
}
if o.Tag != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "tag", o.Tag) || notEmpty
}
if o.URL != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeIRIProp(b, "url", o.URL) || notEmpty
}
if o.To != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "to", o.To) || notEmpty
}
if o.Bto != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "bto", o.Bto) || notEmpty
}
if o.CC != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "cc", o.CC) || notEmpty
}
if o.BCC != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "bcc", o.BCC) || notEmpty
}
if !o.Published.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(b, "published", o.Published) || notEmpty
}
if !o.Updated.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(b, "updated", o.Updated) || notEmpty
}
if !o.StartTime.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(b, "startTime", o.StartTime) || notEmpty
}
if !o.EndTime.IsZero() {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeTimeProp(b, "endTime", o.EndTime) || notEmpty
}
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) || notEmpty
}
if o.Likes != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "likes", o.Likes) || notEmpty
}
if o.Shares != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "shares", o.Shares) || notEmpty
}
if v, err := o.Source.MarshalJSON(); err == nil && len(v) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeProp(b, "source", v) || notEmpty
}
return notEmpty
}
func writeActivity(b *bytes.Buffer, a Activity) (notEmpty bool) {
writeComma := func() { b.WriteString(",") }
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
writeComma()
}
}
OnIntransitiveActivity(a, func(i *IntransitiveActivity) error {
if i == nil {
return nil
}
notEmpty = writeIntransitiveActivity(b, *i) || notEmpty
return nil
})
if a.Object != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "object", a.Object) || notEmpty
}
return notEmpty
}
func writeIntransitiveActivity(b *bytes.Buffer, i IntransitiveActivity) (notEmpty bool) {
writeComma := func() { b.WriteString(",") }
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
writeComma()
}
}
OnObject(i, func(o *Object) error {
if o == nil {
return nil
}
notEmpty = writeObject(b, *o) || notEmpty
return nil
})
if i.Actor != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "actor", i.Actor) || notEmpty
}
if i.Target != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "target", i.Target) || notEmpty
}
if i.Result != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "result", i.Result) || notEmpty
}
if i.Origin != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "origin", i.Origin) || notEmpty
}
if i.Instrument != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "instrument", i.Instrument) || notEmpty
}
return notEmpty
}
func writeQuestion(b *bytes.Buffer, q Question) (notEmpty bool) {
writeComma := func() { b.WriteString(",") }
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
writeComma()
}
}
OnIntransitiveActivity(q, func(i *IntransitiveActivity) error {
if i == nil {
return nil
}
notEmpty = writeIntransitiveActivity(b, *i) || notEmpty
return nil
})
if q.OneOf != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "oneOf", q.OneOf) || notEmpty
} else if q.AnyOf != nil {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeItemProp(b, "oneOf", q.OneOf) || notEmpty
}
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeBoolProp(b, "closed", q.Closed) || notEmpty
return notEmpty
}

View file

@ -3,6 +3,7 @@ package activitypub
import (
"errors"
"time"
"unsafe"
)
// IntransitiveActivity Instances of IntransitiveActivity are a subtype of Activity representing intransitive actions.
@ -256,6 +257,14 @@ func IntransitiveActivityNew(id ID, typ ActivityVocabularyType) *IntransitiveAct
// ToIntransitiveActivity
func ToIntransitiveActivity(it Item) (*IntransitiveActivity, error) {
switch i := it.(type) {
case *Activity:
return (*IntransitiveActivity)(unsafe.Pointer(i)), nil
case Activity:
return (*IntransitiveActivity)(unsafe.Pointer(&i)), nil
case *Question:
return (*IntransitiveActivity)(unsafe.Pointer(i)), nil
case Question:
return (*IntransitiveActivity)(unsafe.Pointer(&i)), nil
case *IntransitiveActivity:
return i, nil
case IntransitiveActivity:

263
object.go
View file

@ -320,275 +320,14 @@ func (o *Object) UnmarshalJSON(data []byte) error {
return nil
}
func writeProp(b *bytes.Buffer, name string, val []byte) (notEmpty bool) {
notEmpty = false
if len(val) == 0 {
return notEmpty
}
writePropName(b, name)
return writeValue(b, val)
}
func writePropName(b *bytes.Buffer, s string) (notEmpty bool) {
if len(s) == 0 {
return false
}
b.Write([]byte{'"'})
l, err := b.WriteString(s)
b.Write([]byte{'"', ':'})
if err != nil {
return false
}
if l <= 0 {
return false
}
return true
}
func writeValue(b *bytes.Buffer, s []byte) (notEmpty bool) {
l, err := b.Write(s)
if err != nil {
return false
}
if l <= 0 {
return false
}
return true
}
func writeNaturalLanguageProp(b *bytes.Buffer, n string, nl NaturalLanguageValues) (notEmpty bool) {
l := nl.Count()
if l > 1 {
n += "Map"
}
if v, err := nl.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = writeProp(b, n, v)
}
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 {
return notEmpty
}
if i.IsObject() {
OnObject(i, func(o *Object) error {
v, err := o.MarshalJSON()
if err != nil {
return nil
}
notEmpty = writeProp(b, n, v)
return nil
})
} else if i.IsCollection() {
OnCollection(i, func(c CollectionInterface) error {
notEmpty = writeItemCollectionProp(b, n, c.Collection())
return nil
})
}
return notEmpty
}
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
func (o Object) MarshalJSON() ([]byte, error) {
b := bytes.Buffer{}
notEmpty := false
b.Write([]byte{'{'})
writeComma := func() { b.WriteString(",") }
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
writeComma()
}
}
if v, err := o.ID.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = writeProp(&b, "id", v)
}
if v, err := o.Type.MarshalJSON(); err == nil && len(v) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeProp(&b, "type", v)
}
if v, err := o.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeProp(&b, "mediaType", v)
}
if len(o.Name) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeNaturalLanguageProp(&b, "name", o.Name)
}
if len(o.Summary) > 0 {
writeCommaIfNotEmpty(notEmpty)
notEmpty = writeNaturalLanguageProp(&b, "summary", o.Summary)
}
if len(o.Content) > 0 {
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)
}
notEmpty = writeObject(&b, o)
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)
notEmpty = writeProp(&b, "source", v)
}
if notEmpty {
b.Write([]byte{'}'})
return b.Bytes(), nil