Common pitfalls when using GoActivityPub¶
Changing the type of an object¶
This can be quite a disruptive operation as in GoActivityPub types are related
to the shape the object has in memory and on disk. So if changing from an actor
type to an object type some properties (like preferredUsername) will be lost.
Similarly for activities or for specifically shaped objects (like
Tombstone, or
Place).
It’ always safe to change from a more restricted object type to a “larger” one
(ie, from Note to
Profile for
example, because the later has the additional describes property).
Changing from an Object type to a Link type is even more fraught and should probably be done with a manual copy as the shapes of the two types are very different.
Using the wrong OnXXX function for the wrong types¶
To abstract over the fact that ActivityPub allows non-functional[1] properties to be simple objects or IRIs or even arrays composed of these, we created some convenience functions in the activitypub package to allow developers to avoid checking for all these options.
The most common of them is:
import ap "github.com/go-ap/activitypub"
// We load "example" from an external request, and it represents an object
// that implements interface ap.ObjectOrLink.
var example Item = new(ap.Tombstone)
ap.OnObject(example, func(ob *ap.Object) error {
// do something that requires access to specific properties of an ap.Object
ob.AttributedTo = ap.IRI("https://example.com/test")
return nil
})
This functionality relies on unsafe behaviour when asserting the
ap.ObjectOrLink interface to a pointer to ap.Object.
For example if the interface holds a pointer to one of the other types that
implements it, and which can have a different memory layout than ap.Object,
some information can be lost.
Generally this works as most of the package’s types are compatible with the
Object one and each function’s documenation should specify where it should be
used.
When using the other OnXXX functions the problem is more pervasive, especially
where the difference in structure is not immediately apparent. An example, the
Question object (which is an intransitive activity) doesn’t conform to the
memory model of an Activity, and should not be used with OnActivity, but
either with OnInstransitiveActivity or with OnQuestion.
import ap "github.com/go-ap/activitypub"
var example ap.Item = new(ap.Question)
err := ap.OnObject(example, func (ob *ap.Object) error {
// works
ob.ID = ap.IRI("https://example.com")
ob.Type = ap.QuestionType
return nil
})
err := ap.OnIntransitiveActivity(example, func (act *ap.IntransitiveActivity) error {
// still works
act.Actor = ap.IRI("https://example.com/1")
})
err := ap.OnActivity(example, func (act *ap.Activity) error {
// should work, but it should be used from an OnInstransitiveActivity call
// as above
act.Actor = ap.IRI("https://example.com/2")
// does not work, as the object property does not exist in the
// Question type, which is an intransitive activity
act.Object = ap.IRI("https://example.com/lorem-ipsum")
return nil
})
err := ap.OnQuestion(example, func(q *ap.Question) error {
// and this also works, accessing the Question specific properties
q.AnyOf = ap.ItemCollection{}
q.Closed = true
return nil
})
Reassigning to the original pointer from inside OnXXX function¶
One very important caveat is that the pointer to the Item interface should not be reassigned from inside the functions.
If the type encapsulated by the interface is has a more restricted shape than the original type, the extra information contained will be lost in the following example.
import ap "github.com/go-ap/activitypub"
// In this example "it" will hold a pointer to an ap.Actor
var it ap.Item = ...// client.LoadActor("https://example.com/actor/1")
_ = ap.OnObject(it, func(ob *ap.Object) error {
// reassigning the ob pointer to the it interface is valid syntactically
// but we're losing the extra properties that Actor has compared to Object.
it = ob // Wrong: possibility of losing information
// Instead, it's enough to manipulate the object, and that will preserve
// the information in the outside scope, so there is no need to reassign.
ob.Name = "Jean Doe"
return nil
})
// "it" is now of type pointer to Object and the Actor specific properties
// like "preferredUsername", "endpoints", "streams" or "publicKey" are no
// longer accessible.
//
// Unfortunatelly we don't currently allow passing the resulting pointer to
// ToActor and receive back a pointer to Actor, because in general there is no
// guarantee that the memory is shaped as an Actor struct. In this particular
// case it would be, but not always.
_, err := ap.ToActor(it)
if err != nil {
panic(err) // panic: unable to convert *activitypub.Object to *activitypub.Actor
}
Here’s a directional table of how types can be converted:
OrderedCollectionPage |
OrderedCollection |
Object |
CollectionPage |
Collection |
Object |
Activity |
IntransitiveActivity |
Object |
Question |
IntransitiveActivity |
Object |
| - | Actor |
Object |
| - | Place |
Object |
| - | Tombstone |
Object |
| - | Profile |
Object |
| - | Relationship |
Object |
| - | Mention |
Link |
[1] https://www.w3.org/TR/activitystreams-vocabulary/#properties
NaturalLanguageValues are not ordered¶
The type we’re using for holding content values is a Go map, and as such the order of the elements is not guaranteed.
This makes it that marshaling the type as JSON can lead to different results.
This makes it that tests that have to assert equality on these maps are hard to write. Currently we’re using a very imperfect method of generating all possible JSON values and trying until one succeeds.
Ideas (and patches) welcome.