From c4d6f93c22010cac3a058964e2050de54fa27bd4 Mon Sep 17 00:00:00 2001 From: Marius Orcsik Date: Thu, 19 Dec 2019 13:34:04 +0100 Subject: [PATCH] Added actor Json marshal --- actor.go | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++ encoding.go | 23 ++++++++- object.go | 12 ++--- 3 files changed, 160 insertions(+), 10 deletions(-) diff --git a/actor.go b/actor.go index 5a7e66c..0609291 100644 --- a/actor.go +++ b/actor.go @@ -1,6 +1,7 @@ package activitypub import ( + "bytes" "errors" "github.com/buger/jsonparser" "time" @@ -209,6 +210,36 @@ func (p *PublicKey) UnmarshalJSON(data []byte) error { } return nil } + +func (p PublicKey) MarshalJSON() ([]byte, error) { + b := bytes.Buffer{} + writeComma := func() { b.WriteString(",") } + writeCommaIfNotEmpty := func(notEmpty bool) { + if notEmpty { + writeComma() + } + } + notEmpty := true + b.Write([]byte{'{'}) + if v, err := p.ID.MarshalJSON(); err == nil && len(v) > 0 { + notEmpty = !writeProp(&b, "id", v) + } + if p.Owner != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeIRIProp(&b, "owner", p.Owner) || notEmpty + } + if len(p.PublicKeyPem) > 0 { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeIRIProp(&b, "publicKeyPem", p.Owner) || notEmpty + } + + if notEmpty { + b.Write([]byte{'}'}) + return b.Bytes(), nil + } + return nil, nil +} + type ( // Application describes a software application. Application = Actor @@ -364,6 +395,65 @@ func (a *Actor) UnmarshalJSON(data []byte) error { return nil } +func (a Actor) MarshalJSON() ([]byte, error) { + b := bytes.Buffer{} + notEmpty := false + b.Write([]byte{'{'}) + + OnObject(a, func(o *Object) error { + notEmpty = writeObject(&b, *o) + return nil + }) + writeComma := func() { b.WriteString(",") } + writeCommaIfNotEmpty := func(notEmpty bool) { + if notEmpty { + writeComma() + } + } + if a.Inbox != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "inbox", a.Inbox) || notEmpty + } + if a.Outbox != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "outbox", a.Outbox) || notEmpty + } + if a.Following != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "following", a.Following) || notEmpty + } + if a.Followers != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "followers", a.Followers) || notEmpty + } + if a.Liked != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "liked", a.Liked) || notEmpty + } + if a.Endpoints != nil { + writeCommaIfNotEmpty(notEmpty) + if v, err := a.Endpoints.MarshalJSON(); err == nil && len(v) > 0 { + notEmpty = writeProp(&b, "endpoints", v) || notEmpty + } + } + if len(a.Streams) > 0 { + writeCommaIfNotEmpty(notEmpty) + writePropName(&b, "streams") + lNotEmpty := true + for _, ss := range a.Streams { + writeCommaIfNotEmpty(lNotEmpty) + lNotEmpty = writeItemCollection(&b, ss) || lNotEmpty + } + notEmpty = lNotEmpty || notEmpty + } + + if notEmpty { + b.Write([]byte{'}'}) + return b.Bytes(), 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 +492,49 @@ func (e *Endpoints) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON +func (e Endpoints) MarshalJSON() ([]byte, error) { + b := bytes.Buffer{} + notEmpty := false + + writeComma := func() { b.WriteString(",") } + writeCommaIfNotEmpty := func(notEmpty bool) { + if notEmpty { + writeComma() + } + } + b.Write([]byte{'{'}) + if e.OauthAuthorizationEndpoint != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "oauthAuthorizationEndpoint", e.OauthAuthorizationEndpoint) || notEmpty + } + if e.OauthTokenEndpoint != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "oauthTokenEndpoint", e.OauthTokenEndpoint) || notEmpty + } + if e.ProvideClientKey != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "provideClientKey", e.ProvideClientKey) || notEmpty + } + if e.SignClientKey != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "signClientKey", e.SignClientKey) || notEmpty + } + if e.SharedInbox != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "sharedInbox", e.SharedInbox) || notEmpty + } + if e.UploadMedia != nil { + writeCommaIfNotEmpty(notEmpty) + notEmpty = writeItemProp(&b, "uploadMedia", e.UploadMedia) || notEmpty + } + if notEmpty { + b.Write([]byte{'}'}) + return b.Bytes(), nil + } + return nil, nil +} + // ToActor func ToActor(it Item) (*Actor, error) { switch i := it.(type) { @@ -410,6 +543,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 diff --git a/encoding.go b/encoding.go index 5559b1b..fae2717 100644 --- a/encoding.go +++ b/encoding.go @@ -46,6 +46,9 @@ func writeNaturalLanguageProp(b *bytes.Buffer, n string, nl NaturalLanguageValue func writeBoolProp(b *bytes.Buffer, n string, t bool) (notEmpty bool) { return writeProp(b, n, []byte(fmt.Sprintf("%t", t))) } +func writeIntProp(b *bytes.Buffer, n string, d int) (notEmpty bool) { + return writeProp(b, n, []byte(fmt.Sprintf("%d", d))) +} func writeTimeProp(b *bytes.Buffer, n string, t time.Time) (notEmpty bool) { if v, err := t.MarshalJSON(); err == nil { return writeProp(b, n, v) @@ -94,11 +97,20 @@ func writeItemProp(b *bytes.Buffer, n string, i Item) (notEmpty bool) { return notEmpty } -func writeItemCollectionProp(b *bytes.Buffer, n string, col ItemCollection) (notEmpty bool) { +func writeString(b *bytes.Buffer, s string) (notEmpty bool) { + if len(s) == 0 { + return false + } + b.Write([]byte{'"'}) + b.WriteString(s) + b.Write([]byte{'"'}) + return true +} + +func writeItemCollection(b *bytes.Buffer, col ItemCollection) (notEmpty bool) { if len(col) == 0 { return notEmpty } - writePropName(b, n) writeComma := func() { b.WriteString(",") } writeCommaIfNotEmpty := func(notEmpty bool) { if notEmpty { @@ -125,6 +137,13 @@ func writeItemCollectionProp(b *bytes.Buffer, n string, col ItemCollection) (not b.Write([]byte{']'}) return notEmpty } +func writeItemCollectionProp(b *bytes.Buffer, n string, col ItemCollection) (notEmpty bool) { + if len(col) == 0 { + return notEmpty + } + writePropName(b, n) + return writeItemCollection(b, col) +} func writeObject(b *bytes.Buffer, o Object) (notEmpty bool) { writeComma := func() { b.WriteString(",") } diff --git a/object.go b/object.go index e987461..f447098 100644 --- a/object.go +++ b/object.go @@ -112,9 +112,7 @@ func (a ActivityVocabularyType) MarshalJSON() ([]byte, error) { return nil, nil } b := bytes.Buffer{} - b.Write([]byte{'"'}) - b.WriteString(string(a)) - b.Write([]byte{'"'}) + writeString(&b, string(a)) return b.Bytes(), nil } @@ -374,14 +372,12 @@ func (c *MimeType) UnmarshalJSON(data []byte) error { } // MarshalJSON -func (c MimeType) MarshalJSON() ([]byte, error) { - if len(c) == 0 { +func (m MimeType) MarshalJSON() ([]byte, error) { + if len(m) == 0 { return nil, nil } b := bytes.Buffer{} - b.Write([]byte{'"'}) - b.WriteString(string(c)) - b.Write([]byte{'"'}) + writeString(&b, string(m)) return b.Bytes(), nil }