Merge pull request #8 from go-ap/custom-marshalers
Custom Marshalers for the vocab structs
This commit is contained in:
commit
ed094c68d6
41 changed files with 2026 additions and 369 deletions
24
activity.go
24
activity.go
|
@ -793,3 +793,27 @@ func FlattenActivityProperties(act *Activity) *Activity {
|
|||
act.Instrument = Flatten(act.Instrument)
|
||||
return act
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (a Activity) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
write(&b, '{')
|
||||
|
||||
if !writeActivity(&b, a) {
|
||||
return nil, nil
|
||||
}
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (i IntransitiveActivity) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
write(&b, '{')
|
||||
|
||||
if !writeIntransitiveActivity(&b, i) {
|
||||
return nil, nil
|
||||
}
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
|
|
486
activity_test.go
486
activity_test.go
|
@ -2,7 +2,9 @@ package activitypub
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestActivityNew(t *testing.T) {
|
||||
|
@ -977,3 +979,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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
100
actor.go
100
actor.go
|
@ -209,6 +209,28 @@ func (p *PublicKey) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PublicKey) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := true
|
||||
write(&b, '{')
|
||||
if v, err := p.ID.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
notEmpty = !writeProp(&b, "id", v)
|
||||
}
|
||||
if p.Owner != nil {
|
||||
notEmpty = writeIRIProp(&b, "owner", p.Owner) || notEmpty
|
||||
}
|
||||
if len(p.PublicKeyPem) > 0 {
|
||||
notEmpty = writeIRIProp(&b, "publicKeyPem", p.Owner) || notEmpty
|
||||
}
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type (
|
||||
// Application describes a software application.
|
||||
Application = Actor
|
||||
|
@ -364,6 +386,51 @@ func (a *Actor) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a Actor) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(a, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if a.Inbox != nil {
|
||||
notEmpty = writeItemProp(&b, "inbox", a.Inbox) || notEmpty
|
||||
}
|
||||
if a.Outbox != nil {
|
||||
notEmpty = writeItemProp(&b, "outbox", a.Outbox) || notEmpty
|
||||
}
|
||||
if a.Following != nil {
|
||||
notEmpty = writeItemProp(&b, "following", a.Following) || notEmpty
|
||||
}
|
||||
if a.Followers != nil {
|
||||
notEmpty = writeItemProp(&b, "followers", a.Followers) || notEmpty
|
||||
}
|
||||
if a.Liked != nil {
|
||||
notEmpty = writeItemProp(&b, "liked", a.Liked) || notEmpty
|
||||
}
|
||||
if a.Endpoints != nil {
|
||||
if v, err := a.Endpoints.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
notEmpty = writeProp(&b, "endpoints", v) || notEmpty
|
||||
}
|
||||
}
|
||||
if len(a.Streams) > 0 {
|
||||
writePropName(&b, "streams")
|
||||
lNotEmpty := true
|
||||
for _, ss := range a.Streams {
|
||||
lNotEmpty = writeItemCollection(&b, ss) || lNotEmpty
|
||||
}
|
||||
notEmpty = lNotEmpty || notEmpty
|
||||
}
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Endpoints a json object which maps additional (typically server/domain-wide)
|
||||
// endpoints which may be useful either for this actor or someone referencing this actor.
|
||||
// This mapping may be nested inside the actor document as the value or may be a link to
|
||||
|
@ -402,6 +469,37 @@ func (e *Endpoints) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (e Endpoints) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
|
||||
write(&b, '{')
|
||||
if e.OauthAuthorizationEndpoint != nil {
|
||||
notEmpty = writeItemProp(&b, "oauthAuthorizationEndpoint", e.OauthAuthorizationEndpoint) || notEmpty
|
||||
}
|
||||
if e.OauthTokenEndpoint != nil {
|
||||
notEmpty = writeItemProp(&b, "oauthTokenEndpoint", e.OauthTokenEndpoint) || notEmpty
|
||||
}
|
||||
if e.ProvideClientKey != nil {
|
||||
notEmpty = writeItemProp(&b, "provideClientKey", e.ProvideClientKey) || notEmpty
|
||||
}
|
||||
if e.SignClientKey != nil {
|
||||
notEmpty = writeItemProp(&b, "signClientKey", e.SignClientKey) || notEmpty
|
||||
}
|
||||
if e.SharedInbox != nil {
|
||||
notEmpty = writeItemProp(&b, "sharedInbox", e.SharedInbox) || notEmpty
|
||||
}
|
||||
if e.UploadMedia != nil {
|
||||
notEmpty = writeItemProp(&b, "uploadMedia", e.UploadMedia) || notEmpty
|
||||
}
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ToActor
|
||||
func ToActor(it Item) (*Actor, error) {
|
||||
switch i := it.(type) {
|
||||
|
@ -410,6 +508,8 @@ func ToActor(it Item) (*Actor, error) {
|
|||
case Actor:
|
||||
return &i, nil
|
||||
case *Object:
|
||||
// TODO(marius): this is unsafe as Actor has a different memory layout than Actor
|
||||
// Everything should be fine as long as you don't try to read the Actor specific collections
|
||||
return (*Actor)(unsafe.Pointer(i)), nil
|
||||
case Object:
|
||||
return (*Actor)(unsafe.Pointer(&i)), nil
|
||||
|
|
|
@ -131,6 +131,22 @@ type Collection struct {
|
|||
Items ItemCollection `jsonld:"items,omitempty"`
|
||||
}
|
||||
|
||||
type (
|
||||
// FollowersCollection is a collection of followers
|
||||
FollowersCollection = Followers
|
||||
|
||||
// Followers is a Collection type
|
||||
Followers = Collection
|
||||
|
||||
// FollowingCollection is a list of everybody that the actor has followed, added as a side effect.
|
||||
// The following collection MUST be either an OrderedCollection or a Collection and MAY
|
||||
// be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
|
||||
FollowingCollection = Following
|
||||
|
||||
// Following is a type alias for a simple Collection
|
||||
Following = Collection
|
||||
)
|
||||
|
||||
// CollectionNew initializes a new Collection
|
||||
func CollectionNew(id ID) *Collection {
|
||||
c := Collection{ID: id, Type: CollectionType}
|
||||
|
@ -279,6 +295,37 @@ func (c *Collection) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (c Collection) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(c, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if c.Current != nil {
|
||||
notEmpty = writeItemProp(&b, "current", c.Current) || notEmpty
|
||||
}
|
||||
if c.First != nil {
|
||||
notEmpty = writeItemProp(&b, "first", c.First) || notEmpty
|
||||
}
|
||||
if c.Last != nil {
|
||||
notEmpty = writeItemProp(&b, "last", c.Last) || notEmpty
|
||||
}
|
||||
if c.Items != nil {
|
||||
notEmpty = writeItemCollectionProp(&b, "items", c.Items) || notEmpty
|
||||
}
|
||||
notEmpty = writeIntProp(&b, "totalItems", int64(c.TotalItems)) || notEmpty
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ToCollection
|
||||
func ToCollection(it Item) (*Collection, error) {
|
||||
switch i := it.(type) {
|
||||
|
@ -293,3 +340,17 @@ func ToCollection(it Item) (*Collection, error) {
|
|||
}
|
||||
return nil, errors.New("unable to convert to collection")
|
||||
}
|
||||
|
||||
// FollowingNew initializes a new Following
|
||||
func FollowingNew() *Following {
|
||||
id := ID("following")
|
||||
|
||||
i := Following{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
i.Summary = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
|
|
@ -254,6 +254,47 @@ func (c *CollectionPage) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (c CollectionPage) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(c, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if c.Current != nil {
|
||||
notEmpty = writeItemProp(&b, "current", c.Current) || notEmpty
|
||||
}
|
||||
if c.First != nil {
|
||||
notEmpty = writeItemProp(&b, "first", c.First) || notEmpty
|
||||
}
|
||||
if c.Last != nil {
|
||||
notEmpty = writeItemProp(&b, "last", c.Last) || notEmpty
|
||||
}
|
||||
if c.Items != nil {
|
||||
notEmpty = writeItemCollectionProp(&b, "items", c.Items) || notEmpty
|
||||
}
|
||||
if c.PartOf != nil {
|
||||
notEmpty = writeItemProp(&b, "partOf", c.PartOf) || notEmpty
|
||||
}
|
||||
if c.Next != nil {
|
||||
notEmpty = writeItemProp(&b, "next", c.Next) || notEmpty
|
||||
}
|
||||
if c.Prev != nil {
|
||||
notEmpty = writeItemProp(&b, "prev", c.Prev) || notEmpty
|
||||
}
|
||||
|
||||
notEmpty = writeIntProp(&b, "totalItems", int64(c.TotalItems)) || notEmpty
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CollectionNew initializes a new CollectionPage
|
||||
func CollectionPageNew(parent CollectionInterface) *CollectionPage {
|
||||
p := CollectionPage{
|
||||
|
|
|
@ -174,3 +174,10 @@ func TestCollection_IsCollection(t *testing.T) {
|
|||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowersNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowingNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
310
encoding.go
Normal file
310
encoding.go
Normal file
|
@ -0,0 +1,310 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func writeComma(b *[]byte) {
|
||||
if len(*b) > 1 && (*b)[len(*b)-1] != ',' {
|
||||
*b = append(*b, ',')
|
||||
}
|
||||
}
|
||||
func writeProp(b *[]byte, name string, val []byte) (notEmpty bool) {
|
||||
if len(val) == 0 {
|
||||
return false
|
||||
}
|
||||
writeComma(b)
|
||||
success := writePropName(b, name) && writeValue(b, val)
|
||||
if !success {
|
||||
*b = (*b)[:len(*b)-1]
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func write(b *[]byte, c ...byte) {
|
||||
*b = append(*b, c...)
|
||||
}
|
||||
|
||||
func writeS(b *[]byte, s string) {
|
||||
*b = append(*b, s...)
|
||||
}
|
||||
|
||||
func writePropName(b *[]byte, s string) (notEmpty bool) {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
write(b, '"')
|
||||
writeS(b, s)
|
||||
write(b, '"', ':')
|
||||
return true
|
||||
}
|
||||
|
||||
func writeValue(b *[]byte, s []byte) (notEmpty bool) {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
write(b, s...)
|
||||
return true
|
||||
}
|
||||
|
||||
func writeNaturalLanguageProp(b *[]byte, 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 writeStringProp(b *[]byte, n string, s string) (notEmpty bool) {
|
||||
return writeProp(b, n, []byte(fmt.Sprintf(`"%s"`, s)))
|
||||
}
|
||||
func writeBoolProp(b *[]byte, n string, t bool) (notEmpty bool) {
|
||||
return writeProp(b, n, []byte(fmt.Sprintf(`"%t"`, t)))
|
||||
}
|
||||
func writeIntProp(b *[]byte, n string, d int64) (notEmpty bool) {
|
||||
return writeProp(b, n, []byte(fmt.Sprintf("%d", d)))
|
||||
}
|
||||
func writeFloatProp(b *[]byte, n string, f float64) (notEmpty bool) {
|
||||
return writeProp(b, n, []byte(fmt.Sprintf("%f", f)))
|
||||
}
|
||||
func writeTimeProp(b *[]byte, n string, t time.Time) (notEmpty bool) {
|
||||
if v, err := t.MarshalJSON(); err == nil {
|
||||
return writeProp(b, n, v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writeDurationProp(b *[]byte, n string, d time.Duration) (notEmpty bool) {
|
||||
if v, err := marshalXSD(d); err == nil {
|
||||
return writeProp(b, n, v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writeIRIProp(b *[]byte, n string, i LinkOrIRI) (notEmpty bool) {
|
||||
url := i.GetLink().String()
|
||||
if len(url) == 0 {
|
||||
return false
|
||||
}
|
||||
writeStringProp(b, n, url)
|
||||
return true
|
||||
}
|
||||
|
||||
func writeItemProp(b *[]byte, n string, i Item) (notEmpty bool) {
|
||||
if i == nil {
|
||||
return notEmpty
|
||||
}
|
||||
if im, ok := i.(json.Marshaler); ok {
|
||||
v, err := im.MarshalJSON()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return writeProp(b, n, v)
|
||||
}
|
||||
return notEmpty
|
||||
}
|
||||
|
||||
func writeString(b *[]byte, s string) (notEmpty bool) {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
write(b, '"')
|
||||
writeS(b, s)
|
||||
write(b, '"')
|
||||
return true
|
||||
}
|
||||
|
||||
func writeItemCollection(b *[]byte, col ItemCollection) (notEmpty bool) {
|
||||
if len(col) == 0 {
|
||||
return notEmpty
|
||||
}
|
||||
writeCommaIfNotEmpty := func(notEmpty bool) {
|
||||
if notEmpty {
|
||||
write(b, ',')
|
||||
}
|
||||
}
|
||||
write(b, '[')
|
||||
for i, it := range col {
|
||||
if im, ok := it.(json.Marshaler); ok {
|
||||
v, err := im.MarshalJSON()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
writeCommaIfNotEmpty(i > 0)
|
||||
write(b, v...)
|
||||
}
|
||||
}
|
||||
write(b, ']')
|
||||
return true
|
||||
}
|
||||
func writeItemCollectionProp(b *[]byte, n string, col ItemCollection) (notEmpty bool) {
|
||||
if len(col) == 0 {
|
||||
return notEmpty
|
||||
}
|
||||
writeComma(b)
|
||||
success := writePropName(b, n) && writeItemCollection(b, col)
|
||||
if !success {
|
||||
*b = (*b)[:len(*b)-1]
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func writeObject(b *[]byte, o Object) (notEmpty bool) {
|
||||
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 {
|
||||
notEmpty = writeProp(b, "type", v) || notEmpty
|
||||
}
|
||||
if v, err := o.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
notEmpty = writeProp(b, "mediaType", v) || notEmpty
|
||||
}
|
||||
if len(o.Name) > 0 {
|
||||
notEmpty = writeNaturalLanguageProp(b, "name", o.Name) || notEmpty
|
||||
}
|
||||
if len(o.Summary) > 0 {
|
||||
notEmpty = writeNaturalLanguageProp(b, "summary", o.Summary) || notEmpty
|
||||
}
|
||||
if len(o.Content) > 0 {
|
||||
notEmpty = writeNaturalLanguageProp(b, "content", o.Content) || notEmpty
|
||||
}
|
||||
if o.Attachment != nil {
|
||||
notEmpty = writeItemProp(b, "attachment", o.Attachment) || notEmpty
|
||||
}
|
||||
if o.AttributedTo != nil {
|
||||
notEmpty = writeItemProp(b, "attributedTo", o.AttributedTo) || notEmpty
|
||||
}
|
||||
if o.Audience != nil {
|
||||
notEmpty = writeItemProp(b, "audience", o.Audience) || notEmpty
|
||||
}
|
||||
if o.Context != nil {
|
||||
notEmpty = writeItemProp(b, "context", o.Context) || notEmpty
|
||||
}
|
||||
if o.Generator != nil {
|
||||
notEmpty = writeItemProp(b, "generator", o.Generator) || notEmpty
|
||||
}
|
||||
if o.Icon != nil {
|
||||
notEmpty = writeItemProp(b, "icon", o.Icon) || notEmpty
|
||||
}
|
||||
if o.Image != nil {
|
||||
notEmpty = writeItemProp(b, "image", o.Image) || notEmpty
|
||||
}
|
||||
if o.InReplyTo != nil {
|
||||
notEmpty = writeItemProp(b, "inReplyTo", o.InReplyTo) || notEmpty
|
||||
}
|
||||
if o.Location != nil {
|
||||
notEmpty = writeItemProp(b, "location", o.Location) || notEmpty
|
||||
}
|
||||
if o.Preview != nil {
|
||||
notEmpty = writeItemProp(b, "preview", o.Preview) || notEmpty
|
||||
}
|
||||
if o.Replies != nil {
|
||||
notEmpty = writeItemProp(b, "replies", o.Replies) || notEmpty
|
||||
}
|
||||
if o.Tag != nil {
|
||||
notEmpty = writeItemProp(b, "tag", o.Tag) || notEmpty
|
||||
}
|
||||
if o.URL != nil {
|
||||
notEmpty = writeIRIProp(b, "url", o.URL) || notEmpty
|
||||
}
|
||||
if o.To != nil {
|
||||
notEmpty = writeItemProp(b, "to", o.To) || notEmpty
|
||||
}
|
||||
if o.Bto != nil {
|
||||
notEmpty = writeItemProp(b, "bto", o.Bto) || notEmpty
|
||||
}
|
||||
if o.CC != nil {
|
||||
notEmpty = writeItemProp(b, "cc", o.CC) || notEmpty
|
||||
}
|
||||
if o.BCC != nil {
|
||||
notEmpty = writeItemProp(b, "bcc", o.BCC) || notEmpty
|
||||
}
|
||||
if !o.Published.IsZero() {
|
||||
notEmpty = writeTimeProp(b, "published", o.Published) || notEmpty
|
||||
}
|
||||
if !o.Updated.IsZero() {
|
||||
notEmpty = writeTimeProp(b, "updated", o.Updated) || notEmpty
|
||||
}
|
||||
if !o.StartTime.IsZero() {
|
||||
notEmpty = writeTimeProp(b, "startTime", o.StartTime) || notEmpty
|
||||
}
|
||||
if !o.EndTime.IsZero() {
|
||||
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)
|
||||
notEmpty = writeDurationProp(b, "duration", o.Duration) || notEmpty
|
||||
}
|
||||
if o.Likes != nil {
|
||||
notEmpty = writeItemProp(b, "likes", o.Likes) || notEmpty
|
||||
}
|
||||
if o.Shares != nil {
|
||||
notEmpty = writeItemProp(b, "shares", o.Shares) || notEmpty
|
||||
}
|
||||
if v, err := o.Source.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
notEmpty = writeProp(b, "source", v) || notEmpty
|
||||
}
|
||||
return notEmpty
|
||||
}
|
||||
|
||||
func writeActivity(b *[]byte, a Activity) (notEmpty bool) {
|
||||
OnIntransitiveActivity(a, func(i *IntransitiveActivity) error {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
notEmpty = writeIntransitiveActivity(b, *i) || notEmpty
|
||||
return nil
|
||||
})
|
||||
if a.Object != nil {
|
||||
notEmpty = writeItemProp(b, "object", a.Object) || notEmpty
|
||||
}
|
||||
return notEmpty
|
||||
}
|
||||
|
||||
func writeIntransitiveActivity(b *[]byte, i IntransitiveActivity) (notEmpty bool) {
|
||||
OnObject(i, func(o *Object) error {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
notEmpty = writeObject(b, *o) || notEmpty
|
||||
return nil
|
||||
})
|
||||
if i.Actor != nil {
|
||||
notEmpty = writeItemProp(b, "actor", i.Actor) || notEmpty
|
||||
}
|
||||
if i.Target != nil {
|
||||
notEmpty = writeItemProp(b, "target", i.Target) || notEmpty
|
||||
}
|
||||
if i.Result != nil {
|
||||
notEmpty = writeItemProp(b, "result", i.Result) || notEmpty
|
||||
}
|
||||
if i.Origin != nil {
|
||||
notEmpty = writeItemProp(b, "origin", i.Origin) || notEmpty
|
||||
}
|
||||
if i.Instrument != nil {
|
||||
notEmpty = writeItemProp(b, "instrument", i.Instrument) || notEmpty
|
||||
}
|
||||
return notEmpty
|
||||
}
|
||||
|
||||
func writeQuestion(b *[]byte, q Question) (notEmpty bool) {
|
||||
OnIntransitiveActivity(q, func(i *IntransitiveActivity) error {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
notEmpty = writeIntransitiveActivity(b, *i) || notEmpty
|
||||
return nil
|
||||
})
|
||||
if q.OneOf != nil {
|
||||
notEmpty = writeItemProp(b, "oneOf", q.OneOf) || notEmpty
|
||||
} else if q.AnyOf != nil {
|
||||
notEmpty = writeItemProp(b, "oneOf", q.OneOf) || notEmpty
|
||||
}
|
||||
notEmpty = writeBoolProp(b, "closed", q.Closed) || notEmpty
|
||||
return notEmpty
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// FollowersCollection is a collection of followers
|
||||
FollowersCollection = Followers
|
||||
|
||||
// Followers is a Collection type
|
||||
Followers = Collection
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFollowersNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
25
following.go
25
following.go
|
@ -1,25 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// FollowingCollection is a list of everybody that the actor has followed, added as a side effect.
|
||||
// The following collection MUST be either an OrderedCollection or a Collection and MAY
|
||||
// be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
|
||||
FollowingCollection = Following
|
||||
|
||||
// Following is a type alias for a simple Collection
|
||||
Following = Collection
|
||||
)
|
||||
|
||||
// FollowingNew initializes a new Following
|
||||
func FollowingNew() *Following {
|
||||
id := ID("following")
|
||||
|
||||
i := Following{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
i.Summary = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFollowingNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
26
inbox.go
26
inbox.go
|
@ -1,26 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// InboxStream contains all activities received by the actor.
|
||||
// The server SHOULD filter content according to the requester's permission.
|
||||
// In general, the owner of an inbox is likely to be able to access all of their inbox contents.
|
||||
// Depending on access control, some other content may be public, whereas other content may
|
||||
// require authentication for non-owner users, if they can access the inbox at all.
|
||||
InboxStream = Inbox
|
||||
|
||||
// Inbox is a type alias for an Ordered Collection
|
||||
Inbox = OrderedCollection
|
||||
)
|
||||
|
||||
// InboxNew initializes a new Inbox
|
||||
func InboxNew() *OrderedCollection {
|
||||
id := ID("inbox")
|
||||
|
||||
i := OrderedCollection{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInboxNew(t *testing.T) {
|
||||
i := InboxNew()
|
||||
|
||||
id := ID("inbox")
|
||||
if i.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", i, id, id)
|
||||
}
|
||||
if len(i.Name) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Name", i)
|
||||
}
|
||||
if len(i.Content) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Content", i)
|
||||
}
|
||||
if len(i.Summary) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Summary", i)
|
||||
}
|
||||
if i.TotalItems != 0 {
|
||||
t.Errorf("%T should be initialized with 0 TotalItems", i)
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
32
iri.go
32
iri.go
|
@ -47,6 +47,15 @@ func (i *IRI) UnmarshalJSON(s []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (i IRI) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
write(&b, '"')
|
||||
writeS(&b, i.String())
|
||||
write(&b, '"')
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GetID
|
||||
func (i IRI) GetID() ID {
|
||||
return ID(i)
|
||||
|
@ -80,6 +89,29 @@ func FlattenToIRI(it Item) Item {
|
|||
return it
|
||||
}
|
||||
|
||||
func (i IRIs) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
if len(i) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
notEmpty := false
|
||||
writeComma := func() { writeS(&b, ",") }
|
||||
writeCommaIfNotEmpty := func(notEmpty bool) {
|
||||
if notEmpty {
|
||||
writeComma()
|
||||
}
|
||||
}
|
||||
write(&b, '[')
|
||||
for _, iri := range i {
|
||||
writeCommaIfNotEmpty(notEmpty)
|
||||
write(&b, '"')
|
||||
writeS(&b, iri.String())
|
||||
write(&b, '"')
|
||||
}
|
||||
write(&b, ']')
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Contains verifies if IRIs array contains the received one
|
||||
func (i IRIs) Contains(r IRI) bool {
|
||||
if len(i) == 0 {
|
||||
|
|
|
@ -33,6 +33,12 @@ func (i ItemCollection) IsObject() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (i ItemCollection) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
writeItemCollection(&b, i)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Append facilitates adding elements to Item arrays
|
||||
// and ensures ItemCollection implements the Collection interface
|
||||
func (i *ItemCollection) Append(o Item) error {
|
||||
|
|
25
liked.go
25
liked.go
|
@ -1,25 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// LikedCollection is a list of every object from all of the actor's Like activities,
|
||||
// added as a side effect. The liked collection MUST be either an OrderedCollection or
|
||||
// a Collection and MAY be filtered on privileges of an authenticated user or as
|
||||
// appropriate when no authentication is given.
|
||||
LikedCollection = Liked
|
||||
|
||||
// Liked is a type alias for an Ordered Collection
|
||||
Liked = OrderedCollection
|
||||
)
|
||||
|
||||
// LikedCollection initializes a new Outbox
|
||||
func LikedNew() *OrderedCollection {
|
||||
id := ID("liked")
|
||||
|
||||
l := OrderedCollection{ID: id, Type: CollectionType}
|
||||
l.Name = NaturalLanguageValuesNew()
|
||||
l.Content = NaturalLanguageValuesNew()
|
||||
|
||||
l.TotalItems = 0
|
||||
|
||||
return &l
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLikedNew(t *testing.T) {
|
||||
l := LikedNew()
|
||||
|
||||
id := ID("liked")
|
||||
if l.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", l, id, id)
|
||||
}
|
||||
if len(l.Name) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Name", l)
|
||||
}
|
||||
if len(l.Content) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Content", l)
|
||||
}
|
||||
if len(l.Summary) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Summary", l)
|
||||
}
|
||||
if l.TotalItems != 0 {
|
||||
t.Errorf("%T should be initialized with 0 TotalItems", l)
|
||||
}
|
||||
}
|
25
likes.go
25
likes.go
|
@ -1,25 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// LikesCollection is a list of all Like activities with this object as the object property,
|
||||
// added as a side effect. The likes collection MUST be either an OrderedCollection or a Collection
|
||||
// and MAY be filtered on privileges of an authenticated user or as appropriate when
|
||||
// no authentication is given.
|
||||
LikesCollection = Likes
|
||||
|
||||
// Likes is a type alias for an Ordered Collection
|
||||
Likes = OrderedCollection
|
||||
)
|
||||
|
||||
// LikesCollection initializes a new Outbox
|
||||
func LikesNew() *Likes {
|
||||
id := ID("likes")
|
||||
|
||||
l := Likes{ID: id, Type: CollectionType}
|
||||
l.Name = NaturalLanguageValuesNew()
|
||||
l.Content = NaturalLanguageValuesNew()
|
||||
|
||||
l.TotalItems = 0
|
||||
|
||||
return &l
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLikesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"github.com/buger/jsonparser"
|
||||
json "github.com/go-ap/jsonld"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
@ -73,21 +72,47 @@ func (n *NaturalLanguageValues) Set(ref LangRef, v string) error {
|
|||
|
||||
// MarshalJSON serializes the NaturalLanguageValues into JSON
|
||||
func (n NaturalLanguageValues) MarshalJSON() ([]byte, error) {
|
||||
if len(n) == 0 {
|
||||
return json.Marshal(nil)
|
||||
l := len(n)
|
||||
if l <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(n) == 1 {
|
||||
|
||||
b := bytes.Buffer{}
|
||||
if l == 1 {
|
||||
v := n[0]
|
||||
if v.Ref == NilLangRef {
|
||||
return v.MarshalJSON()
|
||||
if len(v.Value) > 0 {
|
||||
v.Value = string(unescape([]byte(v.Value)))
|
||||
ll, err := b.WriteString(strconv.Quote(v.Value))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ll <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
}
|
||||
mm := make(map[LangRef]string)
|
||||
b.Write([]byte{'{'})
|
||||
empty := true
|
||||
for _, val := range n {
|
||||
mm[val.Ref] = val.Value
|
||||
if len(val.Ref) == 0 || len(val.Value) == 0 {
|
||||
continue
|
||||
}
|
||||
if !empty {
|
||||
b.Write([]byte{','})
|
||||
}
|
||||
if v, err := val.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
l, err := b.Write(v)
|
||||
if err == nil && l > 0 {
|
||||
empty = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(mm)
|
||||
b.Write([]byte{'}'})
|
||||
if !empty {
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// First returns the first element in the array
|
||||
|
@ -165,7 +190,7 @@ func (l *LangRefValue) UnmarshalText(data []byte) error {
|
|||
// MarshalJSON serializes the LangRefValue into JSON
|
||||
func (l LangRefValue) MarshalJSON() ([]byte, error) {
|
||||
buf := bytes.Buffer{}
|
||||
if l.Ref != NilLangRef {
|
||||
if l.Ref != NilLangRef && len(l.Ref) > 0{
|
||||
if l.Value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func TestNaturalLanguageValue_MarshalJSON(t *testing.T) {
|
|||
if err1 != nil {
|
||||
t.Errorf("Error: '%s'", err1)
|
||||
}
|
||||
txt := `{"en":"the test"}`
|
||||
txt := `"the test"`
|
||||
if txt != string(out1) {
|
||||
t.Errorf("Different marshal result '%s', instead of '%s'", out1, txt)
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ func TestNaturalLanguageValues_MarshalJSON(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("Failed marshaling '%v'", err)
|
||||
}
|
||||
mRes := "{\"de\":\"test\",\"en\":\"test\"}"
|
||||
mRes := "{\"en\":\"test\",\"de\":\"test\"}"
|
||||
if string(result) != mRes {
|
||||
t.Errorf("Different results '%v' vs. '%v'", string(result), mRes)
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ func TestNaturalLanguageValues_MarshalJSON(t *testing.T) {
|
|||
if err1 != nil {
|
||||
t.Errorf("Failed marshaling '%v'", err1)
|
||||
}
|
||||
mRes1 := `{"en":"test"}`
|
||||
mRes1 := `"test"`
|
||||
if string(result1) != mRes1 {
|
||||
t.Errorf("Different results '%v' vs. '%v'", string(result1), mRes1)
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ func TestNaturalLanguageValues_MarshalJSON(t *testing.T) {
|
|||
if j == nil {
|
||||
t.Errorf("Error marshaling: nil value returned")
|
||||
}
|
||||
expected := fmt.Sprintf("{\"%s\":\"%s\"}", nlv.Ref, nlv.Value)
|
||||
expected := fmt.Sprintf("\"%s\"", nlv.Value)
|
||||
if string(j) != expected {
|
||||
t.Errorf("Wrong value: %s, expected %s", j, expected)
|
||||
}
|
||||
|
|
54
object.go
54
object.go
|
@ -106,6 +106,15 @@ type (
|
|||
MimeType string
|
||||
)
|
||||
|
||||
func (a ActivityVocabularyType) MarshalJSON() ([]byte, error) {
|
||||
if len(a) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
b := make([]byte, 0)
|
||||
writeString(&b, string(a))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Object describes an ActivityPub object of any kind.
|
||||
// It serves as the base type for most of the other kinds of objects defined in the Activity
|
||||
// Vocabulary, including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection.
|
||||
|
@ -308,6 +317,21 @@ func (o *Object) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (o Object) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
notEmpty = writeObject(&b, o)
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Object's To, Bto, CC and BCC properties
|
||||
func (o *Object) Recipients() ItemCollection {
|
||||
var aud ItemCollection
|
||||
|
@ -346,6 +370,16 @@ func (c *MimeType) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (m MimeType) MarshalJSON() ([]byte, error) {
|
||||
if len(m) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
b := make([]byte, 0)
|
||||
writeString(&b, string(m))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// ToObject returns an Object pointer to the data in the current Item
|
||||
// It relies on the fact that all the types in this package have a data layout compatible with Object.
|
||||
func ToObject(it Item) (*Object, error) {
|
||||
|
@ -464,3 +498,23 @@ func (s *Source) UnmarshalJSON(data []byte) error {
|
|||
*s = GetAPSource(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (s Source) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
empty := true
|
||||
write(&b, '{')
|
||||
if len(s.MediaType) > 0 {
|
||||
if v, err := s.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
empty = !writeProp(&b, "mediaType", v)
|
||||
}
|
||||
}
|
||||
if len(s.Content) > 0 {
|
||||
empty = !writeNaturalLanguageProp(&b, "content", s.Content)
|
||||
}
|
||||
if !empty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
17
object_id.go
17
object_id.go
|
@ -1,6 +1,9 @@
|
|||
package activitypub
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ID designates an unique global identifier.
|
||||
// All Objects in [ActivityStreams] should have unique global identifiers.
|
||||
|
@ -22,6 +25,18 @@ func (i *ID) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (i ID) MarshalJSON() ([]byte, error) {
|
||||
if len(i) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
b := bytes.Buffer{}
|
||||
b.Write([]byte{'"'})
|
||||
b.WriteString(string(i))
|
||||
b.Write([]byte{'"'})
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (i *ID) IsValid() bool {
|
||||
return i != nil && len(*i) > 0
|
||||
}
|
||||
|
|
|
@ -11,3 +11,11 @@ func TestID_UnmarshalJSON(t *testing.T) {
|
|||
t.Errorf("Unmarshaled object %T should be an empty string, received %q", o, o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestID_IsValid(t *testing.T) {
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestID_MarshalJSON(t *testing.T) {
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
|
315
object_test.go
315
object_test.go
|
@ -3,6 +3,7 @@ package activitypub
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestObjectNew(t *testing.T) {
|
||||
|
@ -488,3 +489,317 @@ func TestGetAPSource(t *testing.T) {
|
|||
t.Errorf("Content didn't match test value. Received %q, expecting %q", a.MediaType, "text/plain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestObject_Clean(t *testing.T) {
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestObject_IsCollection(t *testing.T) {
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestActivityVocabularyType_MarshalJSON(t *testing.T) {
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestObject_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
|
||||
}
|
||||
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) {
|
||||
o := Object{
|
||||
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,
|
||||
}
|
||||
got, err := o.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 TestSource_MarshalJSON(t *testing.T) {
|
||||
type fields struct {
|
||||
Content NaturalLanguageValues
|
||||
MediaType MimeType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Empty",
|
||||
fields: fields{},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "MediaType",
|
||||
fields: fields{
|
||||
MediaType: MimeType("blank"),
|
||||
},
|
||||
want: []byte(`{"mediaType":"blank"}`),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OneContentValue",
|
||||
fields: fields{
|
||||
Content: NaturalLanguageValues{
|
||||
{Value: "test"},
|
||||
},
|
||||
},
|
||||
want: []byte(`{"content":"test"}`),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "MultipleContentValues",
|
||||
fields: fields{
|
||||
Content: NaturalLanguageValues{
|
||||
{
|
||||
Ref: "en",
|
||||
Value: "test",
|
||||
},
|
||||
{
|
||||
Ref: "fr",
|
||||
Value: "teste",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []byte(`{"contentMap":{"en":"test","fr":"teste"}}`),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := Source{
|
||||
Content: tt.fields.Content,
|
||||
MediaType: tt.fields.MediaType,
|
||||
}
|
||||
got, err := s.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,53 @@ type OrderedCollection struct {
|
|||
OrderedItems ItemCollection `jsonld:"orderedItems,omitempty"`
|
||||
}
|
||||
|
||||
type (
|
||||
// InboxStream contains all activities received by the actor.
|
||||
// The server SHOULD filter content according to the requester's permission.
|
||||
// In general, the owner of an inbox is likely to be able to access all of their inbox contents.
|
||||
// Depending on access control, some other content may be public, whereas other content may
|
||||
// require authentication for non-owner users, if they can access the inbox at all.
|
||||
InboxStream = Inbox
|
||||
|
||||
// Inbox is a type alias for an Ordered Collection
|
||||
Inbox = OrderedCollection
|
||||
|
||||
// LikedCollection is a list of every object from all of the actor's Like activities,
|
||||
// added as a side effect. The liked collection MUST be either an OrderedCollection or
|
||||
// a Collection and MAY be filtered on privileges of an authenticated user or as
|
||||
// appropriate when no authentication is given.
|
||||
LikedCollection = Liked
|
||||
|
||||
// Liked is a type alias for an Ordered Collection
|
||||
Liked = OrderedCollection
|
||||
|
||||
// LikesCollection is a list of all Like activities with this object as the object property,
|
||||
// added as a side effect. The likes collection MUST be either an OrderedCollection or a Collection
|
||||
// and MAY be filtered on privileges of an authenticated user or as appropriate when
|
||||
// no authentication is given.
|
||||
LikesCollection = Likes
|
||||
|
||||
// Likes is a type alias for an Ordered Collection
|
||||
Likes = OrderedCollection
|
||||
|
||||
// OutboxStream contains activities the user has published,
|
||||
// subject to the ability of the requestor to retrieve the activity (that is,
|
||||
// the contents of the outbox are filtered by the permissions of the person reading it).
|
||||
OutboxStream = Outbox
|
||||
|
||||
// Outbox is a type alias for an Ordered Collection
|
||||
Outbox = OrderedCollection
|
||||
|
||||
// SharesCollection is a list of all Announce activities with this object as the object property,
|
||||
// added as a side effect. The shares collection MUST be either an OrderedCollection or a Collection
|
||||
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication
|
||||
// is given.
|
||||
SharesCollection = Shares
|
||||
|
||||
// Shares is a type alias for an Ordered Collection
|
||||
Shares = OrderedCollection
|
||||
)
|
||||
|
||||
// GetType returns the OrderedCollection's type
|
||||
func (o OrderedCollection) GetType() ActivityVocabularyType {
|
||||
return o.Type
|
||||
|
@ -244,6 +291,37 @@ func (o *OrderedCollection) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (o OrderedCollection) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(o, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if o.Current != nil {
|
||||
notEmpty = writeItemProp(&b, "current", o.Current) || notEmpty
|
||||
}
|
||||
if o.First != nil {
|
||||
notEmpty = writeItemProp(&b, "first", o.First) || notEmpty
|
||||
}
|
||||
if o.Last != nil {
|
||||
notEmpty = writeItemProp(&b, "last", o.Last) || notEmpty
|
||||
}
|
||||
if o.OrderedItems != nil {
|
||||
notEmpty = writeItemCollectionProp(&b, "orderedItems", o.OrderedItems) || notEmpty
|
||||
}
|
||||
notEmpty = writeIntProp(&b, "totalItems", int64(o.TotalItems)) || notEmpty
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// OrderedCollectionPageNew initializes a new OrderedCollectionPage
|
||||
func OrderedCollectionPageNew(parent CollectionInterface) *OrderedCollectionPage {
|
||||
p := OrderedCollectionPage{
|
||||
|
@ -306,3 +384,68 @@ func copyOrderedCollectionToPage(c *OrderedCollection, p *OrderedCollectionPage)
|
|||
p.PartOf = c.GetLink()
|
||||
return nil
|
||||
}
|
||||
|
||||
// InboxNew initializes a new Inbox
|
||||
func InboxNew() *OrderedCollection {
|
||||
id := ID("inbox")
|
||||
|
||||
i := OrderedCollection{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// LikedCollection initializes a new Outbox
|
||||
func LikedNew() *OrderedCollection {
|
||||
id := ID("liked")
|
||||
|
||||
l := OrderedCollection{ID: id, Type: CollectionType}
|
||||
l.Name = NaturalLanguageValuesNew()
|
||||
l.Content = NaturalLanguageValuesNew()
|
||||
|
||||
l.TotalItems = 0
|
||||
|
||||
return &l
|
||||
}
|
||||
|
||||
// LikesCollection initializes a new Outbox
|
||||
func LikesNew() *Likes {
|
||||
id := ID("likes")
|
||||
|
||||
l := Likes{ID: id, Type: CollectionType}
|
||||
l.Name = NaturalLanguageValuesNew()
|
||||
l.Content = NaturalLanguageValuesNew()
|
||||
|
||||
l.TotalItems = 0
|
||||
|
||||
return &l
|
||||
}
|
||||
|
||||
// OutboxNew initializes a new Outbox
|
||||
func OutboxNew() *Outbox {
|
||||
id := ID("outbox")
|
||||
|
||||
i := Outbox{ID: id, Type: OrderedCollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
i.TotalItems = 0
|
||||
i.OrderedItems = make(ItemCollection, 0)
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// SharesNew initializes a new Shares
|
||||
func SharesNew() *Shares {
|
||||
id := ID("Shares")
|
||||
|
||||
i := Shares{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
|
|
@ -260,6 +260,45 @@ func (o *OrderedCollectionPage) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
// MarshalJSON
|
||||
func (c OrderedCollectionPage) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(c, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if c.Current != nil {
|
||||
notEmpty = writeItemProp(&b, "current", c.Current) || notEmpty
|
||||
}
|
||||
if c.First != nil {
|
||||
notEmpty = writeItemProp(&b, "first", c.First) || notEmpty
|
||||
}
|
||||
if c.Last != nil {
|
||||
notEmpty = writeItemProp(&b, "last", c.Last) || notEmpty
|
||||
}
|
||||
if c.OrderedItems != nil {
|
||||
notEmpty = writeItemCollectionProp(&b, "orderedItems", c.OrderedItems) || notEmpty
|
||||
}
|
||||
if c.PartOf != nil {
|
||||
notEmpty = writeItemProp(&b, "partOf", c.PartOf) || notEmpty
|
||||
}
|
||||
if c.Next != nil {
|
||||
notEmpty = writeItemProp(&b, "next", c.Next) || notEmpty
|
||||
}
|
||||
if c.Prev != nil {
|
||||
notEmpty = writeItemProp(&b, "prev", c.Prev) || notEmpty
|
||||
}
|
||||
notEmpty = writeIntProp(&b, "totalItems", int64(c.TotalItems)) || notEmpty
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ToOrderedCollectionPage
|
||||
func ToOrderedCollectionPage(it Item) (*OrderedCollectionPage, error) {
|
||||
|
|
|
@ -215,3 +215,74 @@ func TestToOrderedCollection(t *testing.T) {
|
|||
func TestOrderedCollection_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInboxNew(t *testing.T) {
|
||||
i := InboxNew()
|
||||
|
||||
id := ID("inbox")
|
||||
if i.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", i, id, id)
|
||||
}
|
||||
if len(i.Name) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Name", i)
|
||||
}
|
||||
if len(i.Content) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Content", i)
|
||||
}
|
||||
if len(i.Summary) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Summary", i)
|
||||
}
|
||||
if i.TotalItems != 0 {
|
||||
t.Errorf("%T should be initialized with 0 TotalItems", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikedNew(t *testing.T) {
|
||||
l := LikedNew()
|
||||
|
||||
id := ID("liked")
|
||||
if l.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", l, id, id)
|
||||
}
|
||||
if len(l.Name) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Name", l)
|
||||
}
|
||||
if len(l.Content) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Content", l)
|
||||
}
|
||||
if len(l.Summary) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Summary", l)
|
||||
}
|
||||
if l.TotalItems != 0 {
|
||||
t.Errorf("%T should be initialized with 0 TotalItems", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutboxNew(t *testing.T) {
|
||||
o := OutboxNew()
|
||||
|
||||
id := ID("outbox")
|
||||
if o.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", o, id, id)
|
||||
}
|
||||
if len(o.Name) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Name", o)
|
||||
}
|
||||
if len(o.Content) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Content", o)
|
||||
}
|
||||
if len(o.Summary) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Summary", o)
|
||||
}
|
||||
if o.TotalItems != 0 {
|
||||
t.Errorf("%T should be initialized with 0 TotalItems", o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
24
outbox.go
24
outbox.go
|
@ -1,24 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// OutboxStream contains activities the user has published,
|
||||
// subject to the ability of the requestor to retrieve the activity (that is,
|
||||
// the contents of the outbox are filtered by the permissions of the person reading it).
|
||||
OutboxStream = Outbox
|
||||
|
||||
// Outbox is a type alias for an Ordered Collection
|
||||
Outbox = OrderedCollection
|
||||
)
|
||||
|
||||
// OutboxNew initializes a new Outbox
|
||||
func OutboxNew() *Outbox {
|
||||
id := ID("outbox")
|
||||
|
||||
i := Outbox{ID: id, Type: OrderedCollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
i.TotalItems = 0
|
||||
i.OrderedItems = make(ItemCollection, 0)
|
||||
|
||||
return &i
|
||||
}
|
102
outbox_test.go
102
outbox_test.go
|
@ -1,102 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutboxNew(t *testing.T) {
|
||||
o := OutboxNew()
|
||||
|
||||
id := ID("outbox")
|
||||
if o.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", o, id, id)
|
||||
}
|
||||
if len(o.Name) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Name", o)
|
||||
}
|
||||
if len(o.Content) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Content", o)
|
||||
}
|
||||
if len(o.Summary) != 0 {
|
||||
t.Errorf("%T should be initialized with 0 length Summary", o)
|
||||
}
|
||||
if o.TotalItems != 0 {
|
||||
t.Errorf("%T should be initialized with 0 TotalItems", o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutboxStream_GetID(t *testing.T) {
|
||||
o := OutboxStream{}
|
||||
if o.GetID() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", o, o.GetID())
|
||||
}
|
||||
id := ID("test_out_stream")
|
||||
o.ID = id
|
||||
if o.GetID() != id {
|
||||
t.Errorf("%T should have %T as %q", o, id, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutboxStream_GetType(t *testing.T) {
|
||||
o := OutboxStream{}
|
||||
|
||||
if o.GetType() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", o, o.GetType())
|
||||
}
|
||||
|
||||
o.Type = OrderedCollectionType
|
||||
if o.GetType() != OrderedCollectionType {
|
||||
t.Errorf("%T should have %T as %q", o, o.GetType(), OrderedCollectionType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutboxStream_Append(t *testing.T) {
|
||||
o := OutboxStream{}
|
||||
|
||||
val := Object{ID: ID("grrr")}
|
||||
|
||||
o.Append(val)
|
||||
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutbox_Append(t *testing.T) {
|
||||
o := OutboxNew()
|
||||
|
||||
val := Object{ID: ID("grrr")}
|
||||
|
||||
o.Append(val)
|
||||
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutbox_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutbox_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutbox_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutbox_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutbox_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutbox_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOutbox_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
49
place.go
49
place.go
|
@ -102,22 +102,22 @@ type Place struct {
|
|||
Source Source `jsonld:"source,omitempty"`
|
||||
// Accuracy indicates the accuracy of position coordinates on a Place objects.
|
||||
// Expressed in properties of percentage. e.g. "94.0" means "94.0% accurate".
|
||||
Accuracy float64
|
||||
Accuracy float64 `jsonld:"accuracy,omitempty"`
|
||||
// Altitude indicates the altitude of a place. The measurement units is indicated using the units property.
|
||||
// If units is not specified, the default is assumed to be "m" indicating meters.
|
||||
Altitude float64
|
||||
Altitude float64 `jsonld:"altitude,omitempty"`
|
||||
// Latitude the latitude of a place
|
||||
Latitude float64
|
||||
Latitude float64 `jsonld:"latitude,omitempty"`
|
||||
// Longitude the longitude of a place
|
||||
Longitude float64
|
||||
Longitude float64 `jsonld:"longitude,omitempty"`
|
||||
// Radius the radius from the given latitude and longitude for a Place.
|
||||
// The units is expressed by the units property. If units is not specified,
|
||||
// the default is assumed to be "m" indicating "meters".
|
||||
Radius int64
|
||||
Radius int64 `jsonld:"radius,omitempty"`
|
||||
// Specifies the measurement units for the radius and altitude properties on a Place object.
|
||||
// If not specified, the default is assumed to be "m" for "meters".
|
||||
// Values "cm" | " feet" | " inches" | " km" | " m" | " miles" | xsd:anyURI
|
||||
Units string
|
||||
Units string `jsonld:"units,omitempty"`
|
||||
}
|
||||
|
||||
// IsLink returns false for Place objects
|
||||
|
@ -218,6 +218,41 @@ func (p *Place) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (p Place) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(p, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if p.Accuracy > 0 {
|
||||
notEmpty = writeFloatProp(&b, "accuracy", p.Accuracy) || notEmpty
|
||||
}
|
||||
if p.Altitude > 0 {
|
||||
notEmpty = writeFloatProp(&b, "altitude", p.Altitude) || notEmpty
|
||||
}
|
||||
if p.Latitude > 0 {
|
||||
notEmpty = writeFloatProp(&b, "latitude", p.Latitude) || notEmpty
|
||||
}
|
||||
if p.Longitude > 0 {
|
||||
notEmpty = writeFloatProp(&b, "longitude", p.Longitude) || notEmpty
|
||||
}
|
||||
if p.Radius > 0 {
|
||||
notEmpty = writeIntProp(&b, "radius", p.Radius) || notEmpty
|
||||
}
|
||||
if len(p.Units) > 0 {
|
||||
notEmpty = writeStringProp(&b, "radius", p.Units) || notEmpty
|
||||
}
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Place object's To, Bto, CC and BCC properties
|
||||
func (p *Place) Recipients() ItemCollection {
|
||||
var aud ItemCollection
|
||||
|
@ -226,7 +261,7 @@ func (p *Place) Recipients() ItemCollection {
|
|||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (p *Place) Clean(){
|
||||
func (p *Place) Clean() {
|
||||
p.BCC = nil
|
||||
p.Bto = nil
|
||||
}
|
||||
|
|
21
profile.go
21
profile.go
|
@ -199,6 +199,27 @@ func (p *Profile) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (p Profile) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(p, func(o *Object) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
if p.Describes != nil {
|
||||
notEmpty = writeItemProp(&b, "describes", p.Describes) || notEmpty
|
||||
}
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Profile object's To, Bto, CC and BCC properties
|
||||
func (p *Profile) Recipients() ItemCollection {
|
||||
var aud ItemCollection
|
||||
|
|
11
question.go
11
question.go
|
@ -229,6 +229,17 @@ func (q *Question) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (q Question) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
write(&b, '{')
|
||||
|
||||
if !writeQuestion(&b, q) {
|
||||
return nil, nil
|
||||
}
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// QuestionNew initializes a Question activity
|
||||
func QuestionNew(id ID) *Question {
|
||||
q := Question{ID: id, Type: QuestionType}
|
||||
|
|
|
@ -108,12 +108,12 @@ type Relationship struct {
|
|||
Source Source `jsonld:"source,omitempty"`
|
||||
// Subject Subject On a Relationship object, the subject property identifies one of the connected individuals.
|
||||
// For instance, for a Relationship object describing "John is related to Sally", subject would refer to John.
|
||||
Subject Item
|
||||
Subject Item `jsonld:"subject,omitempty"`
|
||||
// Object
|
||||
Object Item
|
||||
Object Item `jsonld:"object,omitempty"`
|
||||
// Relationship On a Relationship object, the relationship property identifies the kind
|
||||
// of relationship that exists between subject and object.
|
||||
Relationship Item
|
||||
Relationship Item `jsonld:"relationship,omitempty"`
|
||||
}
|
||||
|
||||
// IsLink returns false for Relationship objects
|
||||
|
@ -211,6 +211,34 @@ func (r *Relationship) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (r Relationship) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(r, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
|
||||
if r.Subject != nil {
|
||||
notEmpty = writeItemProp(&b, "subject", r.Subject) || notEmpty
|
||||
}
|
||||
if r.Object != nil {
|
||||
notEmpty = writeItemProp(&b, "object", r.Object) || notEmpty
|
||||
}
|
||||
if r.Relationship != nil {
|
||||
notEmpty = writeItemProp(&b, "relationship", r.Relationship) || notEmpty
|
||||
}
|
||||
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Relationship object's To, Bto, CC and BCC properties
|
||||
func (r *Relationship) Recipients() ItemCollection {
|
||||
var aud ItemCollection
|
||||
|
@ -219,12 +247,11 @@ func (r *Relationship) Recipients() ItemCollection {
|
|||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (r *Relationship) Clean(){
|
||||
func (r *Relationship) Clean() {
|
||||
r.BCC = nil
|
||||
r.Bto = nil
|
||||
}
|
||||
|
||||
|
||||
// ToRelationship
|
||||
func ToRelationship(it Item) (*Relationship, error) {
|
||||
switch i := it.(type) {
|
||||
|
|
25
shares.go
25
shares.go
|
@ -1,25 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
type (
|
||||
// SharesCollection is a list of all Announce activities with this object as the object property,
|
||||
// added as a side effect. The shares collection MUST be either an OrderedCollection or a Collection
|
||||
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication
|
||||
// is given.
|
||||
SharesCollection = Shares
|
||||
|
||||
// Shares is a type alias for an Ordered Collection
|
||||
Shares = OrderedCollection
|
||||
)
|
||||
|
||||
// SharesNew initializes a new Shares
|
||||
func SharesNew() *Shares {
|
||||
id := ID("Shares")
|
||||
|
||||
i := Shares{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSharesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
25
tombstone.go
25
tombstone.go
|
@ -202,6 +202,31 @@ func (t *Tombstone) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON
|
||||
func (t Tombstone) MarshalJSON() ([]byte, error) {
|
||||
b := make([]byte, 0)
|
||||
notEmpty := false
|
||||
write(&b, '{')
|
||||
|
||||
OnObject(t, func(o *Object) error {
|
||||
notEmpty = writeObject(&b, *o)
|
||||
return nil
|
||||
})
|
||||
if len(t.FormerType) > 0 {
|
||||
if v, err := t.FormerType.MarshalJSON(); err == nil && len(v) > 0 {
|
||||
notEmpty = writeProp(&b, "formerType", v) || notEmpty
|
||||
}
|
||||
}
|
||||
if !t.Deleted.IsZero() {
|
||||
notEmpty = writeTimeProp(&b, "deleted", t.Deleted) || notEmpty
|
||||
}
|
||||
if notEmpty {
|
||||
write(&b, '}')
|
||||
return b, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Tombstone object's To, Bto, CC and BCC properties
|
||||
func (t *Tombstone) Recipients() ItemCollection {
|
||||
var aud ItemCollection
|
||||
|
|
|
@ -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