diff --git a/object.go b/object.go index 4e9be02..6e94b9f 100644 --- a/object.go +++ b/object.go @@ -309,32 +309,389 @@ func (o Object) MarshalJSON() ([]byte, error) { return nil, nil } -/* // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (o *Object) UnmarshalBinary(data []byte) error { - return errors.New(fmt.Sprintf("UnmarshalBinary is not implemented for %T", *o)) + return o.GobDecode(data) } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (o Object) MarshalBinary() ([]byte, error) { - w := &bytes.Buffer{} - enc := gobEncoder{ w: w, enc: gob.NewEncoder(w) } - if _, err := enc.writeObjectGobValue(o); err != nil { - return nil, err + return o.GobEncode() +} + +func gobDecodeItem(it Item, data []byte) error { + return nil +} + +func gobEncodeItemCollection(g *gob.Encoder, col ItemCollection) error { + return g.Encode(col) +} + +func gobEncodeItem(it Item) ([]byte, error) { + b := bytes.Buffer{} + var err error + if IsIRI(it) { + g := gob.NewEncoder(&b) + err = gobEncodeStringLikeType(g, []byte(it.GetLink())) } - return w.Bytes(), nil + if IsItemCollection(it) { + g := gob.NewEncoder(&b) + err = OnItemCollection(it, func(col *ItemCollection) error { + return gobEncodeItemCollection(g, *col) + }) + } + switch it.GetType() { + case "", ObjectType, ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType: + err = OnObject(it, func(ob *Object) error { + bytes, err := ob.GobEncode() + b.Write(bytes) + return err + }) + case LinkType, MentionType: + err = OnLink(it, func(l *Link) error { + bytes, err := l.GobEncode() + b.Write(bytes) + return err + }) + case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType, + FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType, + RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType: + err = OnActivity(it, func(act *Activity) error { + bytes, err := act.GobEncode() + b.Write(bytes) + return err + }) + case IntransitiveActivityType, ArriveType, TravelType: + err = OnIntransitiveActivity(it, func(act *IntransitiveActivity) error { + bytes, err := act.GobEncode() + b.Write(bytes) + return err + }) + case ActorType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType: + err = OnActor(it, func(a *Actor) error { + bytes, err := a.GobEncode() + b.Write(bytes) + return err + }) + case CollectionType: + err = OnCollection(it, func(c *Collection) error { + return nil + }) + case OrderedCollectionType: + err = OnOrderedCollection(it, func(c *OrderedCollection) error { + return nil + }) + case CollectionPageType: + err = OnCollectionPage(it, func(p *CollectionPage) error { + return nil + }) + case OrderedCollectionPageType: + err = OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error { + return nil + }) + case PlaceType: + err = OnPlace(it, func(p *Place) error { + return nil + }) + case ProfileType: + err = OnProfile(it, func(p *Profile) error { + return nil + }) + case RelationshipType: + err = OnRelationship(it, func(r *Relationship) error { + return nil + }) + case TombstoneType: + err = OnTombstone(it, func(t *Tombstone) error { + return nil + }) + case QuestionType: + err = OnQuestion(it, func(q *Question) error { + return nil + }) + } + return b.Bytes(), err +} + +func mapObjectProperties(mm map[string][]byte, o *Object) (hasData bool, err error) { + if len(o.ID) > 0 { + if mm["id"], err = o.ID.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if len(o.Type) > 0 { + if mm["type"], err = o.Type.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if len(o.MediaType) > 0 { + if mm["mediaType"], err = o.MediaType.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if len(o.Name) > 0 { + if mm["name"], err = o.Name.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.Attachment != nil { + if mm["attachment"], err = gobEncodeItem(o.Attachment); err != nil { + return hasData, err + } + hasData = true + } + if o.AttributedTo != nil { + if mm["attributedTo"], err = gobEncodeItem(o.AttributedTo); err != nil { + return hasData, err + } + hasData = true + } + if o.Audience != nil { + if mm["audience"], err = gobEncodeItem(o.Audience); err != nil { + return hasData, err + } + hasData = true + } + if o.Content != nil { + if mm["content"], err = o.Content.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.Context != nil { + if mm["context"], err = gobEncodeItem(o.Context); err != nil { + return hasData, err + } + hasData = true + } + if len(o.MediaType) > 0 { + if mm["mediaType"], err = o.MediaType.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if !o.EndTime.IsZero() { + if mm["endTime"], err = o.EndTime.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.Generator != nil { + if mm["generator"], err = gobEncodeItem(o.Generator); err != nil { + return hasData, err + } + hasData = true + } + if o.Icon != nil { + if mm["icon"], err = gobEncodeItem(o.Icon); err != nil { + return hasData, err + } + hasData = true + } + if o.Image != nil { + if mm["image"], err = gobEncodeItem(o.Image); err != nil { + return hasData, err + } + hasData = true + } + if o.InReplyTo != nil { + if mm["inReplyTo"], err = gobEncodeItem(o.InReplyTo); err != nil { + return hasData, err + } + hasData = true + } + if o.Location != nil { + if mm["location"], err = gobEncodeItem(o.Location); err != nil { + return hasData, err + } + hasData = true + } + if o.Preview != nil { + if mm["preview"], err = gobEncodeItem(o.Preview); err != nil { + return hasData, err + } + hasData = true + } + if !o.Published.IsZero() { + if mm["published"], err = o.Published.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.Replies != nil { + if mm["replies"], err = gobEncodeItem(o.Replies); err != nil { + return hasData, err + } + hasData = true + } + if !o.StartTime.IsZero() { + if mm["startTime"], err = o.StartTime.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if len(o.Summary) > 0 { + if mm["summary"], err = o.Summary.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.Tag != nil { + if mm["tag"], err = gobEncodeItem(o.Tag); err != nil { + return hasData, err + } + hasData = true + } + if !o.Updated.IsZero() { + if mm["updated"], err = o.Updated.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.Tag != nil { + if mm["tag"], err = gobEncodeItem(o.Tag); err != nil { + return hasData, err + } + hasData = true + } + if !o.Updated.IsZero() { + if mm["updated"], err = o.Updated.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + if o.URL != nil { + if mm["url"], err = gobEncodeItem(o.URL); err != nil { + return hasData, err + } + hasData = true + } + if o.To != nil { + if mm["to"], err = gobEncodeItem(o.To); err != nil { + return hasData, err + } + hasData = true + } + if o.Bto != nil { + if mm["bto"], err = gobEncodeItem(o.Bto); err != nil { + return hasData, err + } + hasData = true + } + if o.CC != nil { + if mm["cc"], err = gobEncodeItem(o.CC); err != nil { + return hasData, err + } + hasData = true + } + if o.BCC != nil { + if mm["bcc"], err = gobEncodeItem(o.BCC); err != nil { + return hasData, err + } + hasData = true + } + if o.Duration > 0 { + if mm["duration"], err = gobEncodeInt64(int64(o.Duration)); err != nil { + return hasData, err + } + hasData = true + } + if o.Likes != nil { + if mm["likes"], err = gobEncodeItem(o.Likes); err != nil { + return hasData, err + } + hasData = true + } + if o.Shares != nil { + if mm["shares"], err = gobEncodeItem(o.Shares); err != nil { + return hasData, err + } + hasData = true + } + if o.Shares != nil { + if mm["shares"], err = gobEncodeItem(o.Shares); err != nil { + return hasData, err + } + hasData = true + } + if len(o.Source.MediaType)+len(o.Source.Content) > 0 { + if mm["source"], err = o.Source.GobEncode(); err != nil { + return hasData, err + } + hasData = true + } + + return hasData, nil } // GobEncode func (o Object) GobEncode() ([]byte, error) { - return o.MarshalBinary() + mm := make(map[string][]byte) + hasData, err := mapObjectProperties(mm, &o) + if err != nil { + return nil, err + } + if !hasData { + return []byte{}, nil + } + bb := bytes.Buffer{} + g := gob.NewEncoder(&bb) + if err := g.Encode(mm); err != nil { + return nil, err + } + return bb.Bytes(), nil +} + +func unmapObjectProperties(mm map[string][]byte, o *Object) error { + if raw, ok := mm["id"]; ok { + if err := o.ID.GobDecode(raw); err != nil { + return err + } + } + if raw, ok := mm["type"]; ok { + if err := o.Type.GobDecode(raw); err != nil { + return err + } + } + if raw, ok := mm["mediaType"]; ok { + if err := o.MediaType.GobDecode(raw); err != nil { + return err + } + } + if raw, ok := mm["name"]; ok { + if err := o.Name.GobDecode(raw); err != nil { + return err + } + } + if raw, ok := mm["attachment"]; ok { + if err := gobDecodeItem(o.Attachment, raw); err != nil { + return err + } + } + if raw, ok := mm["source"]; ok { + if err := o.Source.GobDecode(raw); err != nil { + return err + } + } + return nil } // GobDecode func (o *Object) GobDecode(data []byte) error { - return o.UnmarshalBinary(data) + if len(data) == 0 { + return nil + } + mm := make(map[string][]byte) + g := gob.NewDecoder(bytes.NewReader(data)) + if err := g.Decode(&mm); err != nil { + return err + } + return unmapObjectProperties(mm, o) } -*/ // Recipients performs recipient de-duplication on the Object's To, Bto, CC and BCC properties func (o *Object) Recipients() ItemCollection { diff --git a/object_test.go b/object_test.go index 6a5a255..612d39b 100644 --- a/object_test.go +++ b/object_test.go @@ -1,6 +1,7 @@ package activitypub import ( + "bytes" "reflect" "testing" "time" @@ -976,3 +977,105 @@ func TestObject_Equals(t *testing.T) { }) } } + +func TestObject_GobEncode(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 Item + 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: []byte{}, + wantErr: false, + }, + // NOTE(marius): this doesn't really work because the types are not consistent between gobValue + // and our used encoding method + //{ + // name: "with ID", + // fields: fields{ID: ID("https://example.com")}, + // want: gobValue(Object{ID: "https://example.com"}), + // 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.GobEncode() + if (err != nil) != tt.wantErr { + t.Errorf("GobEncode() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !bytes.Equal(got, tt.want) { + t.Errorf("GobEncode() got/want =\n%v\n%v\n", got, tt.want) + } + }) + } +}