Added UnmarshalJSON methods for some types

Add some tests
This commit is contained in:
Marius Orcsik 2018-07-24 23:11:08 +02:00
parent 57dab0cb45
commit 4dc584b0d4
No known key found for this signature in database
GPG key ID: 889CE8E4FB2D877A
14 changed files with 658 additions and 21 deletions

View file

@ -837,3 +837,11 @@ func (d Dislike) IsObject() bool {
func (d Dislike) IsLink() bool {
return false
}
//// UnmarshalJSON
//func (a *Activity) UnmarshalJSON(data []byte) error {
// ob, err := unmarshal(data, *a)
// *a = ob.(Activity)
//
// return err
//}

View file

@ -1,6 +1,11 @@
package activitypub
import "time"
import (
"fmt"
"time"
"github.com/buger/jsonparser"
)
// Actor Types
const (
@ -76,7 +81,7 @@ type Actor struct {
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
//Context ObjectOrLink `jsonld:"_"`
Context ObjectOrLink `jsonld:"_"`
// The date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
@ -365,3 +370,35 @@ func (p Person) GetType() ActivityVocabularyType {
func (p Person) GetLink() URI {
return p.URL.(URI)
}
// UnmarshalJSON
func (a *Actor) UnmarshalJSON(data []byte) error {
a.ID = getAPObjectID(data)
a.Type = getAPType(data)
a.Name = getAPNaturalLanguageField(data, "name")
a.PreferredUsername = getAPNaturalLanguageField(data, "preferredUsername")
a.Content = getAPNaturalLanguageField(data, "content")
u := getURIField(data, "url")
if len(u) > 0 {
a.URL = u
}
o := OutboxStream{}
v, _, _, err := jsonparser.Get(data, "outbox")
if err != nil {
fmt.Print(err)
}
o.UnmarshalJSON(v)
a.Outbox = o
return nil
}
func (p *Person) UnmarshalJSON(data []byte) error {
a := Actor(*p)
err := a.UnmarshalJSON(data)
*p = Person(a)
return err
}

View file

@ -1,6 +1,8 @@
package activitypub
import "time"
import (
"time"
)
var validCollectionTypes = [...]ActivityVocabularyType{CollectionType, OrderedCollectionType}
@ -315,6 +317,34 @@ func (o *OrderedCollection) IsObject() bool {
return true
}
// UnmarshalJSON
func (o *OrderedCollection) UnmarshalJSON(data []byte) error {
o.ID = getAPObjectID(data)
o.Type = getAPType(data)
o.Name = getAPNaturalLanguageField(data, "name")
o.Content = getAPNaturalLanguageField(data, "content")
u := getURIField(data, "url")
if len(u) > 0 {
o.URL = u
}
return nil
}
// UnmarshalJSON
func (c *Collection) UnmarshalJSON(data []byte) error {
c.ID = getAPObjectID(data)
c.Type = getAPType(data)
c.Name = getAPNaturalLanguageField(data, "name")
c.Content = getAPNaturalLanguageField(data, "content")
u := getURIField(data, "url")
if len(u) > 0 {
c.URL = u
}
return nil
}
/*
func (c *Collection) MarshalJSON() ([]byte, error) {
return nil, nil

View file

@ -79,3 +79,23 @@ func (i Inbox) IsLink() bool {
func (i Inbox) IsObject() bool {
return true
}
// UnmarshalJSON
func (i *InboxStream) UnmarshalJSON(data []byte) error {
c := OrderedCollection(*i)
err := c.UnmarshalJSON(data)
*i = InboxStream(c)
return err
}
// UnmarshalJSON
func (i *Inbox) UnmarshalJSON(data []byte) error {
c := OrderedCollection(*i)
err := c.UnmarshalJSON(data)
*i = Inbox(c)
return err
}

View file

@ -103,3 +103,20 @@ func (m Mention) GetID() ObjectID {
func (m Mention) GetType() ActivityVocabularyType {
return m.Type
}
// UnmarshalJSON
func (l *Link) UnmarshalJSON(data []byte) error {
l.ID = getAPObjectID(data)
l.Type = getAPType(data)
l.MediaType = getAPMimeType(data)
l.Name = getAPNaturalLanguageField(data, "name")
l.HrefLang = getAPLangRefField(data, "hrefLang")
u := getURIField(data, "href")
if len(u) > 0 {
l.Href = u
}
//fmt.Printf("%s\n %#v", data, l)
return nil
}

229
activitypub/marshalling.go Normal file
View file

@ -0,0 +1,229 @@
package activitypub
import (
"fmt"
)
func getAPObjectByType(typ ActivityVocabularyType) (interface{}, error) {
var ret interface{}
var err error
switch typ {
case ObjectType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case LinkType:
ret = Link{}
o := ret.(Link)
o.Type = typ
case ActivityType:
ret = Activity{}
o := ret.(Activity)
o.Type = typ
case IntransitiveActivityType:
ret = IntransitiveActivity{}
o := ret.(IntransitiveActivity)
o.Type = typ
case ActorType:
ret = Actor{}
o := ret.(Actor)
o.Type = typ
case CollectionType:
ret = Collection{}
o := ret.(Collection)
o.Type = typ
case OrderedCollectionType:
ret = Link{}
o := ret.(Link)
o.Type = typ
case ArticleType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case AudioType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case DocumentType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case EventType:
o := Object{}
o.Type = typ
case ImageType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case NoteType:
ret = Object{}
o := ret.(Object)
o.Type = typ
ret = o
case PageType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case PlaceType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case ProfileType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case RelationshipType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case TombstoneType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case VideoType:
ret = Object{}
o := ret.(Object)
o.Type = typ
case MentionType:
ret = Mention{}
o := ret.(Mention)
o.Type = typ
case ApplicationType:
ret = Application{}
o := ret.(Application)
o.Type = typ
case GroupType:
ret = Group{}
o := ret.(Group)
o.Type = typ
case OrganizationType:
ret = Organization{}
o := ret.(Organization)
o.Type = typ
case PersonType:
ret = Person{}
o := ret.(Person)
o.Type = typ
case ServiceType:
ret = Service{}
o := ret.(Service)
o.Type = typ
case AcceptType:
ret = Accept{}
o := ret.(Accept)
o.Type = typ
case AddType:
ret = Add{}
o := ret.(Add)
o.Type = typ
case AnnounceType:
ret = Announce{}
o := ret.(Announce)
o.Type = typ
case ArriveType:
ret = Arrive{}
o := ret.(Arrive)
o.Type = typ
case BlockType:
ret = Block{}
o := ret.(Block)
o.Type = typ
case CreateType:
ret = Create{}
o := ret.(Create)
o.Type = typ
case DeleteType:
ret = Delete{}
o := ret.(Delete)
o.Type = typ
case DislikeType:
ret = Dislike{}
o := ret.(Dislike)
o.Type = typ
case FlagType:
ret = Flag{}
o := ret.(Flag)
o.Type = typ
case FollowType:
ret = Follow{}
o := ret.(Follow)
o.Type = typ
case IgnoreType:
ret = Ignore{}
o := ret.(Ignore)
o.Type = typ
case InviteType:
ret = Invite{}
o := ret.(Invite)
o.Type = typ
case JoinType:
ret = Join{}
o := ret.(Join)
o.Type = typ
case LeaveType:
ret = Leave{}
o := ret.(Leave)
o.Type = typ
case LikeType:
ret = Like{}
o := ret.(Like)
o.Type = typ
case ListenType:
ret = Listen{}
o := ret.(Listen)
o.Type = typ
case MoveType:
ret = Move{}
o := ret.(Move)
o.Type = typ
case OfferType:
ret = Offer{}
o := ret.(Offer)
o.Type = typ
case QuestionType:
ret = Question{}
o := ret.(Question)
o.Type = typ
case RejectType:
ret = Reject{}
o := ret.(Reject)
o.Type = typ
case ReadType:
ret = Read{}
o := ret.(Read)
o.Type = typ
case RemoveType:
ret = Remove{}
o := ret.(Remove)
o.Type = typ
case TentativeRejectType:
ret = TentativeReject{}
o := ret.(TentativeReject)
o.Type = typ
case TentativeAcceptType:
ret = TentativeAccept{}
o := ret.(TentativeAccept)
o.Type = typ
case TravelType:
ret = Travel{}
o := ret.(Travel)
o.Type = typ
case UndoType:
ret = Undo{}
o := ret.(Undo)
o.Type = typ
case UpdateType:
ret = Update{}
o := ret.(Update)
o.Type = typ
case ViewType:
ret = View{}
o := ret.(View)
o.Type = typ
default:
ret = nil
err = fmt.Errorf("%q unrecognized ActivityPub type", typ)
}
return ret, err
}

View file

@ -86,7 +86,7 @@ type (
ActivityVocabularyType string
// ActivityObject is a subtype of Object that describes some form of action that may happen,
// is currently happening, or has already happened
ActivityObject interface{
ActivityObject interface {
GetID() ObjectID
}
// ObjectOrLink describes an object of any kind.
@ -105,7 +105,10 @@ type (
GetLink() URI
}
// ImageOrLink is an interface that Image and Link structs implement
ImageOrLink interface{}
ImageOrLink interface {
ObjectOrLink
LinkOrURI
}
// MimeType is the type for MIME types
MimeType string
// LangRef is the type for a language reference, should be ISO 639-1 language specifier.
@ -135,6 +138,14 @@ func (n NaturalLanguageValue) MarshalJSON() ([]byte, error) {
return json.Marshal(map[LangRef]string(n))
}
// MarshalText serializes the NaturalLanguageValue into Text
func (n NaturalLanguageValue) MarshalText() ([]byte, error) {
for _, v := range n {
return []byte(fmt.Sprintf("%q", v)), nil
}
return nil, nil
}
// Append is syntactic sugar for resizing the NaturalLanguageValue map
// and appending an element
func (n *NaturalLanguageValue) Append(lang LangRef, value string) error {
@ -156,8 +167,17 @@ func (l *LangRef) UnmarshalJSON(data []byte) error {
return nil
}
// UnmarshalText tries to load the NaturalLanguage array from the incoming Text value
func (l *LangRef) UnmarshalText(data []byte) error {
*l = LangRef(data[1 : len(data)-1])
return nil
}
// UnmarshalJSON tries to load the NaturalLanguage array from the incoming json value
func (n *NaturalLanguageValue) UnmarshalJSON(data []byte) error {
if data == nil || len(data) == 0 {
return fmt.Errorf("invalid incoming bytes")
}
if data[0] == '"' {
// a quoted string - loading it to c.URL
if data[len(data)-1] != '"' {
@ -180,11 +200,23 @@ func (n *NaturalLanguageValue) UnmarshalJSON(data []byte) error {
return nil
}
// UnmarshalText tries to load the NaturalLanguage array from the incoming Text value
func (n *NaturalLanguageValue) UnmarshalText(data []byte) error {
if data[0] == '"' {
// a quoted string - loading it to c.URL
if data[len(data)-1] != '"' {
return fmt.Errorf("invalid string value when unmarshalling %T value", n)
}
n.Append(LangRef("-"), string(data[1:len(data)-1]))
}
return nil
}
// Describes an object of any kind.
// The Activity Pub Object type 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.
type Object struct {
// Provides the globally unique identifier for anActivity Pub Object or Link.
// Provides the globally unique identifier for anActivity Pub Object or Link.
ID ObjectID `jsonld:"id,omitempty"`
// Identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
@ -375,9 +407,18 @@ func (c *ContentType) UnmarshalJSON(data []byte) error {
return nil
}
//func (o *Object) UnmarshalJSON(data []byte) error {
// //_o, err := unmarshalJSONFromType(data, ObjectType)
//
// //*o = _o.(Object)
// return nil
//}
// UnmarshalJSON
func (o *Object) UnmarshalJSON(data []byte) error {
o.ID = getAPObjectID(data)
o.Type = getAPType(data)
o.Name = getAPNaturalLanguageField(data, "name")
o.Content = getAPNaturalLanguageField(data, "content")
u := getURIField(data, "url")
if len(u) > 0 {
o.URL = u
}
//fmt.Printf("%s\n %#v", data, o)
return nil
}

View file

@ -52,6 +52,7 @@ func (o OutboxStream) GetType() ActivityVocabularyType {
func (o OutboxStream) IsLink() bool {
return false
}
// IsObject returns true for a OutboxStream object
func (o OutboxStream) IsObject() bool {
return true
@ -76,3 +77,23 @@ func (o Outbox) IsLink() bool {
func (o Outbox) IsObject() bool {
return true
}
// UnmarshalJSON
func (o *OutboxStream) UnmarshalJSON(data []byte) error {
c := OrderedCollection(*o)
err := c.UnmarshalJSON(data)
*o = OutboxStream(c)
return err
}
// UnmarshalJSON
func (o *Outbox) UnmarshalJSON(data []byte) error {
c := OrderedCollection(*o)
err := c.UnmarshalJSON(data)
*o = Outbox(c)
return err
}

View file

@ -0,0 +1,146 @@
package activitypub
import (
"encoding"
"encoding/json"
"reflect"
"strings"
"github.com/buger/jsonparser"
)
var (
apUnmarshalerType = reflect.TypeOf(new(ObjectOrLink)).Elem()
unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
)
type mockObj map[string]json.RawMessage
func getType(j json.RawMessage) ActivityVocabularyType {
mock := make(mockObj, 0)
json.Unmarshal([]byte(j), &mock)
for key, val := range mock {
if strings.ToLower(key) == "type" {
return ActivityVocabularyType(strings.Trim(string(val), "\""))
}
}
return ""
}
func getAPObjectID(data []byte) ObjectID {
i, err := jsonparser.GetString(data, "id")
if err != nil {
return ObjectID("")
}
return ObjectID(i)
}
func getAPType(data []byte) ActivityVocabularyType {
t, err := jsonparser.GetString(data, "type")
typ := ActivityVocabularyType(t)
if err != nil {
return ActivityVocabularyType("")
}
return typ
}
func getAPMimeType(data []byte) MimeType {
t, err := jsonparser.GetString(data, "mediaType")
if err != nil {
return MimeType("")
}
return MimeType(t)
}
func getAPNaturalLanguageField(data []byte, prop string) NaturalLanguageValue {
n := NaturalLanguageValue{}
val, typ, _, err := jsonparser.Get(data, prop)
if err != nil {
return NaturalLanguageValue(nil)
}
switch typ {
case jsonparser.Object:
jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
vv, err := jsonparser.GetString(val, string(key))
n.Append(LangRef(key), vv)
return err
}, prop)
case jsonparser.String:
n.Append("-", string(val))
}
return n
}
func getURIField(data []byte, prop string) URI {
val, err := jsonparser.GetString(data, prop)
if err != nil {
return URI("")
}
return URI(val)
}
func getAPLangRefField(data []byte, prop string) LangRef {
val, err := jsonparser.GetString(data, prop)
if err != nil {
return LangRef("")
}
return LangRef(val)
}
/*
func unmarshal(data []byte, a interface{}) (interface{}, error) {
ta := make(mockObj, 0)
err := jsonld.Unmarshal(data, &ta)
if err != nil {
return nil, err
}
typ := reflect.TypeOf(a)
val := reflect.ValueOf(a)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
cField := typ.Field(i)
cValue := val.Field(i)
cTag := cField.Tag
tag, _ := jsonld.LoadJSONLdTag(cTag)
var vv reflect.Value
for key, j := range ta {
if j == nil {
continue
}
if key == tag.Name {
if cField.Type.Implements(textUnmarshalerType) {
m, _ := cValue.Interface().(encoding.TextUnmarshaler)
m.UnmarshalText(j)
vv = reflect.ValueOf(m)
}
if cField.Type.Implements(unmarshalerType) {
m, _ := cValue.Interface().(json.Unmarshaler)
m.UnmarshalJSON(j)
vv = reflect.ValueOf(m)
}
if cField.Type.Implements(apUnmarshalerType) {
o := getAPObjectByType(getType(j))
if o != nil {
jsonld.Unmarshal([]byte(j), o)
vv = reflect.ValueOf(o)
}
}
}
if vv.CanAddr() {
cValue.Set(vv)
fmt.Printf("\n\nReflected %q %q => %#v\n\n%#v\n", cField.Name, cField.Type, vv, tag.Name)
}
}
}
return a, nil
}
*/

View file

@ -43,3 +43,15 @@ func (i *IRI) UnmarshalJSON(s []byte) error {
*i = IRI(strings.Trim(string(s), "\""))
return nil
}
// UnmarshalText
func (u URI) UnmarshalText(s []byte) error {
u = URI(strings.Trim(string(s), "\""))
return nil
}
// UnmarshalText
func (i IRI) UnmarshalText(s []byte) error {
i = IRI(strings.Trim(string(s), "\""))
return nil
}

View file

@ -2,7 +2,7 @@
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/abc",
"hreflang": "en",
"hrefLang": "en",
"mediaType": "text/html",
"name": "An example link"
}
}

View file

@ -0,0 +1,4 @@
{
"@context":"https://www.w3.org/ns/activitystreams",
"url":"http://littr.git/api/accounts/system"
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "http://example.com/accounts/ana",
"type": "Person",
"name": "ana",
"url": "http://example.com/accounts/ana",
"outbox": {
"id": "http://example.com/accounts/ana/outbox",
"type": "OrderedCollection",
"url": "http://example.com/outbox"
},
"preferredUsername": "Ana"
}

View file

@ -46,6 +46,14 @@ var allTests = tests{
},
},
},
"object_with_url": testPair{
path: "./mocks/object_with_url.json",
expected: true,
blank: &a.Object{},
result: &a.Object{
URL: a.URI("http://littr.git/api/accounts/system"),
},
},
"object_simple": testPair{
path: "./mocks/object_simple.json",
expected: true,
@ -60,23 +68,59 @@ var allTests = tests{
},
//"activity_simple": testPair{
// path: "./mocks/activity_simple.json",
// expected: true,
// expected: false,
// blank: &a.Activity{},
// result: &a.Activity{
// Type: a.ActivityType,
// Summary: a.NaturalLanguageValue{a.LangRef("-"): "Sally did something to a note"},
// Actor: a.Actor(a.Person{
// Type: a.PersonType,
// Name: a.NaturalLanguageValue{
// a.LangRef("-"): "Sally",
// },
// }),
// Object: a.Object{
// Type: a.NoteType,
// Name: a.NaturalLanguageValue{
// a.LangRef("-"): "A Note",
// },
// },
// },
//},
"person_with_outbox": testPair{
path: "./mocks/person_with_outbox.json",
expected: true,
blank: &a.Person{},
result: &a.Person{
ID: a.ObjectID("http://example.com/accounts/ana"),
Type: a.PersonType,
Name: a.NaturalLanguageValue{
a.LangRef("-"): "ana",
},
PreferredUsername: a.NaturalLanguageValue{
a.LangRef("-"): "Ana",
},
URL: a.URI("http://example.com/accounts/ana"),
Outbox: a.OutboxStream{
ID: a.ObjectID("http://example.com/accounts/ana/outbox"),
Type: a.OrderedCollectionType,
URL: a.URI("http://example.com/outbox"),
},
},
},
}
func getFileContents(path string) []byte {
f, _ := os.Open(path)
func getFileContents(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
data := make([]byte, 512)
io.ReadFull(f, data)
data = bytes.Trim(data, "\x00")
return data
return data, nil
}
func Test_ActivityPubUnmarshall(t *testing.T) {
@ -92,13 +136,17 @@ func Test_ActivityPubUnmarshall(t *testing.T) {
}
for k, pair := range allTests {
data := getFileContents(pair.path)
var data []byte
data, err = getFileContents(pair.path)
if err != nil {
f("Error: %s for %s", err, pair.path)
continue
}
object := pair.blank
err = j.Unmarshal(data, object)
if err != nil {
f("Error: %s", err)
f("Error: %s for %s", err, data)
continue
}
expLbl := ""
@ -107,8 +155,19 @@ func Test_ActivityPubUnmarshall(t *testing.T) {
}
if pair.expected != reflect.DeepEqual(object, pair.result) {
f("\n%#v\n should %sequal to\n%#v", object, expLbl, pair.result)
oj, e1 := j.Marshal(object)
if e1 != nil {
f(e1.Error())
}
tj, e2 := j.Marshal(pair.result)
if e2 != nil {
f(e2.Error())
}
f("\n%s\n should %sequal to\n%s", oj, expLbl, tj)
continue
}
//fmt.Printf("%#v", object)
if err == nil {
fmt.Printf(" --- %s: %s\n %s\n", "PASS", k, pair.path)
}