Merge pull request #7 from go-ap/crossing_the_streams
Merging back the Activitystreams package
This commit is contained in:
commit
b8b0448aff
63 changed files with 9932 additions and 1784 deletions
795
activity.go
Normal file
795
activity.go
Normal file
|
@ -0,0 +1,795 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Activity Types
|
||||
const (
|
||||
AcceptType ActivityVocabularyType = "Accept"
|
||||
AddType ActivityVocabularyType = "Add"
|
||||
AnnounceType ActivityVocabularyType = "Announce"
|
||||
ArriveType ActivityVocabularyType = "Arrive"
|
||||
BlockType ActivityVocabularyType = "Block"
|
||||
CreateType ActivityVocabularyType = "Create"
|
||||
DeleteType ActivityVocabularyType = "Delete"
|
||||
DislikeType ActivityVocabularyType = "Dislike"
|
||||
FlagType ActivityVocabularyType = "Flag"
|
||||
FollowType ActivityVocabularyType = "Follow"
|
||||
IgnoreType ActivityVocabularyType = "Ignore"
|
||||
InviteType ActivityVocabularyType = "Invite"
|
||||
JoinType ActivityVocabularyType = "Join"
|
||||
LeaveType ActivityVocabularyType = "Leave"
|
||||
LikeType ActivityVocabularyType = "Like"
|
||||
ListenType ActivityVocabularyType = "Listen"
|
||||
MoveType ActivityVocabularyType = "Move"
|
||||
OfferType ActivityVocabularyType = "Offer"
|
||||
QuestionType ActivityVocabularyType = "Question"
|
||||
RejectType ActivityVocabularyType = "Reject"
|
||||
ReadType ActivityVocabularyType = "Read"
|
||||
RemoveType ActivityVocabularyType = "Remove"
|
||||
TentativeRejectType ActivityVocabularyType = "TentativeReject"
|
||||
TentativeAcceptType ActivityVocabularyType = "TentativeAccept"
|
||||
TravelType ActivityVocabularyType = "Travel"
|
||||
UndoType ActivityVocabularyType = "Undo"
|
||||
UpdateType ActivityVocabularyType = "Update"
|
||||
ViewType ActivityVocabularyType = "View"
|
||||
)
|
||||
|
||||
type ActivityVocabularyTypes []ActivityVocabularyType
|
||||
|
||||
func (a ActivityVocabularyTypes) Contains(typ ActivityVocabularyType) bool {
|
||||
for _, v := range a {
|
||||
if v == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContentManagementActivityTypes use case primarily deals with activities that involve the creation, modification or deletion of content.
|
||||
// This includes, for instance, activities such as "John created a new note", "Sally updated an article", and "Joe deleted the photo".
|
||||
var ContentManagementActivityTypes = ActivityVocabularyTypes{
|
||||
CreateType,
|
||||
DeleteType,
|
||||
UpdateType,
|
||||
}
|
||||
|
||||
// CollectionManagementActivityTypes use case primarily deals with activities involving the management of content within collections.
|
||||
// Examples of collections include things like folders, albums, friend lists, etc.
|
||||
// This includes, for instance, activities such as "Sally added a file to Folder A", "John moved the file from Folder A to Folder B", etc.
|
||||
var CollectionManagementActivityTypes = ActivityVocabularyTypes{
|
||||
AddType,
|
||||
MoveType,
|
||||
RemoveType,
|
||||
}
|
||||
|
||||
// ReactionsActivityTypes use case primarily deals with reactions to content.
|
||||
// This can include activities such as liking or disliking content, ignoring updates, flagging content as being inappropriate, accepting or rejecting objects, etc.
|
||||
var ReactionsActivityTypes = ActivityVocabularyTypes{
|
||||
AcceptType,
|
||||
BlockType,
|
||||
DislikeType,
|
||||
FlagType,
|
||||
IgnoreType,
|
||||
LikeType,
|
||||
RejectType,
|
||||
TentativeAcceptType,
|
||||
TentativeRejectType,
|
||||
}
|
||||
|
||||
// EventRSVPActivityTypes use case primarily deals with invitations to events and RSVP type responses.
|
||||
var EventRSVPActivityTypes = ActivityVocabularyTypes{
|
||||
AcceptType,
|
||||
IgnoreType,
|
||||
InviteType,
|
||||
RejectType,
|
||||
TentativeAcceptType,
|
||||
TentativeRejectType,
|
||||
}
|
||||
|
||||
// GroupManagementActivityTypes use case primarily deals with management of groups.
|
||||
// It can include, for instance, activities such as "John added Sally to Group A", "Sally joined Group A", "Joe left Group A", etc.
|
||||
var GroupManagementActivityTypes = ActivityVocabularyTypes{
|
||||
AddType,
|
||||
JoinType,
|
||||
LeaveType,
|
||||
RemoveType,
|
||||
}
|
||||
|
||||
// ContentExperienceActivityTypes use case primarily deals with describing activities involving listening to, reading, or viewing content.
|
||||
// For instance, "Sally read the article", "Joe listened to the song".
|
||||
var ContentExperienceActivityTypes = ActivityVocabularyTypes{
|
||||
ListenType,
|
||||
ReadType,
|
||||
ViewType,
|
||||
}
|
||||
|
||||
// GeoSocialEventsActivityTypes use case primarily deals with activities involving geo-tagging type activities.
|
||||
// For instance, it can include activities such as "Joe arrived at work", "Sally left work", and "John is travel from home to work".
|
||||
var GeoSocialEventsActivityTypes = ActivityVocabularyTypes{
|
||||
ArriveType,
|
||||
LeaveType,
|
||||
TravelType,
|
||||
}
|
||||
|
||||
// NotificationActivityTypes use case primarily deals with calling attention to particular objects or notifications.
|
||||
var NotificationActivityTypes = ActivityVocabularyTypes{
|
||||
AnnounceType,
|
||||
}
|
||||
|
||||
// QuestionActivityTypes use case primarily deals with representing inquiries of any type.
|
||||
// See 5.4 Representing Questions for more information.
|
||||
var QuestionActivityTypes = ActivityVocabularyTypes{
|
||||
QuestionType,
|
||||
}
|
||||
|
||||
// RelationshipManagementActivityTypes use case primarily deals with representing activities involving the management of interpersonal and social relationships
|
||||
// (e.g. friend requests, management of social network, etc). See 5.2 Representing Relationships Between Entities for more information.
|
||||
var RelationshipManagementActivityTypes = ActivityVocabularyTypes{
|
||||
AcceptType,
|
||||
AddType,
|
||||
BlockType,
|
||||
CreateType,
|
||||
DeleteType,
|
||||
FollowType,
|
||||
IgnoreType,
|
||||
InviteType,
|
||||
RejectType,
|
||||
}
|
||||
|
||||
// NegatingActivityTypes use case primarily deals with the ability to redact previously completed activities.
|
||||
// See 5.5 Inverse Activities and "Undo" for more information.
|
||||
var NegatingActivityTypes = ActivityVocabularyTypes{
|
||||
UndoType,
|
||||
}
|
||||
|
||||
// OffersActivityTypes use case deals with activities involving offering one object to another.
|
||||
// It can include, for instance, activities such as "Company A is offering a discount on purchase of Product Z to Sally", "Sally is offering to add a File to Folder A", etc.
|
||||
var OffersActivityTypes = ActivityVocabularyTypes{
|
||||
OfferType,
|
||||
}
|
||||
|
||||
var IntransitiveActivityTypes = ActivityVocabularyTypes{
|
||||
ArriveType,
|
||||
TravelType,
|
||||
QuestionType,
|
||||
}
|
||||
|
||||
var ActivityTypes = ActivityVocabularyTypes{
|
||||
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,
|
||||
}
|
||||
|
||||
// HasRecipients is an interface implemented by activities to return their audience
|
||||
// for further propagation
|
||||
type HasRecipients interface {
|
||||
// Recipients is a method that should do a recipients de-duplication step and then return
|
||||
// the remaining recipients
|
||||
Recipients() ItemCollection
|
||||
Clean()
|
||||
}
|
||||
|
||||
// Activity is a subtype of Object that describes some form of action that may happen,
|
||||
// is currently happening, or has already happened.
|
||||
// The Activity type itself serves as an abstract base type for all types of activities.
|
||||
// It is important to note that the Activity type itself does not carry any specific semantics
|
||||
// about the kind of action being taken.
|
||||
type Activity struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// CanReceiveActivities describes one or more entities that either performed or are expected to perform the activity.
|
||||
// Any single activity can have multiple actors. The actor may be specified using an indirect Link.
|
||||
Actor Item `jsonld:"actor,omitempty"`
|
||||
// Target describes the indirect object, or target, of the activity.
|
||||
// The precise meaning of the target is largely dependent on the type of action being described
|
||||
// but will often be the object of the English preposition "to".
|
||||
// For instance, in the activity "John added a movie to his wishlist",
|
||||
// the target of the activity is John's wishlist. An activity can have more than one target.
|
||||
Target Item `jsonld:"target,omitempty"`
|
||||
// Result describes the result of the activity. For instance, if a particular action results in the creation
|
||||
// of a new resource, the result property can be used to describe that new resource.
|
||||
Result Item `jsonld:"result,omitempty"`
|
||||
// Origin describes an indirect object of the activity from which the activity is directed.
|
||||
// The precise meaning of the origin is the object of the English preposition "from".
|
||||
// For instance, in the activity "John moved an item to List B from List A", the origin of the activity is "List A".
|
||||
Origin Item `jsonld:"origin,omitempty"`
|
||||
// Instrument identifies one or more objects used (or to be used) in the completion of an Activity.
|
||||
Instrument Item `jsonld:"instrument,omitempty"`
|
||||
// Object When used within an Activity, describes the direct object of the activity.
|
||||
// For instance, in the activity "John added a movie to his wishlist",
|
||||
// the object of the activity is the movie added.
|
||||
// When used within a Relationship describes the entity to which the subject is related.
|
||||
Object Item `jsonld:"object,omitempty"`
|
||||
}
|
||||
|
||||
// GetType returns the ActivityVocabulary type of the current Activity
|
||||
func (a Activity) GetType() ActivityVocabularyType {
|
||||
return a.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for Activity objects
|
||||
func (a Activity) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the Activity object
|
||||
func (a Activity) GetID() ObjectID {
|
||||
return a.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the Activity object
|
||||
func (a Activity) GetLink() IRI {
|
||||
return IRI(a.ID)
|
||||
}
|
||||
|
||||
// IsObject returns true for Activity objects
|
||||
func (a Activity) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Activity objects
|
||||
func (a Activity) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func removeFromCollection(col ItemCollection, items ...Item) ItemCollection {
|
||||
result := make(ItemCollection, 0)
|
||||
if len(items) == 0 {
|
||||
return col
|
||||
}
|
||||
for _, ob := range col {
|
||||
found := false
|
||||
for _, it := range items {
|
||||
if IRI(ob.GetID()).Equals(IRI(it.GetID()), false) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result = append(result, ob)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func removeFromAudience(a *Activity, items ...Item) error {
|
||||
if a.To != nil {
|
||||
a.To = removeFromCollection(a.To, items...)
|
||||
}
|
||||
if a.Bto != nil {
|
||||
a.Bto = removeFromCollection(a.Bto, items...)
|
||||
}
|
||||
if a.CC != nil {
|
||||
a.CC = removeFromCollection(a.CC, items...)
|
||||
}
|
||||
if a.BCC != nil {
|
||||
a.BCC = removeFromCollection(a.BCC, items...)
|
||||
}
|
||||
if a.Audience != nil {
|
||||
a.Audience = removeFromCollection(a.Audience, items...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Activity's To, Bto, CC and BCC properties
|
||||
func (a *Activity) Recipients() ItemCollection {
|
||||
var alwaysRemove ItemCollection
|
||||
if a.GetType() == BlockType && a.Object != nil {
|
||||
alwaysRemove = append(alwaysRemove, a.Object)
|
||||
}
|
||||
if a.Actor != nil {
|
||||
alwaysRemove = append(alwaysRemove, a.Actor)
|
||||
}
|
||||
if len(alwaysRemove) > 0 {
|
||||
removeFromAudience(a, alwaysRemove...)
|
||||
}
|
||||
rec, _ := ItemCollectionDeduplication(&a.To, &a.Bto, &a.CC, &a.BCC, &a.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (a *Activity) Clean() {
|
||||
a.BCC = nil
|
||||
a.Bto = nil
|
||||
}
|
||||
|
||||
type (
|
||||
// Accept indicates that the actor accepts the object. The target property can be used in certain circumstances to indicate
|
||||
// the context into which the object has been accepted.
|
||||
Accept = Activity
|
||||
|
||||
// Add indicates that the actor has added the object to the target. If the target property is not explicitly specified,
|
||||
// the target would need to be determined implicitly by context.
|
||||
// The origin can be used to identify the context from which the object originated.
|
||||
Add = Activity
|
||||
|
||||
// Announce indicates that the actor is calling the target's attention the object.
|
||||
// The origin typically has no defined meaning.
|
||||
Announce = Activity
|
||||
|
||||
// Block indicates that the actor is blocking the object. Blocking is a stronger form of Ignore.
|
||||
// The typical use is to support social systems that allow one user to block activities or content of other users.
|
||||
// The target and origin typically have no defined meaning.
|
||||
Block = Ignore
|
||||
|
||||
// Create indicates that the actor has created the object.
|
||||
Create = Activity
|
||||
|
||||
// Delete indicates that the actor has deleted the object.
|
||||
// If specified, the origin indicates the context from which the object was deleted.
|
||||
Delete = Activity
|
||||
|
||||
// Dislike indicates that the actor dislikes the object.
|
||||
Dislike = Activity
|
||||
|
||||
// Flag indicates that the actor is "flagging" the object.
|
||||
// Flagging is defined in the sense common to many social platforms as reporting content as being
|
||||
// inappropriate for any number of reasons.
|
||||
Flag = Activity
|
||||
|
||||
// Follow indicates that the actor is "following" the object. Following is defined in the sense typically used within
|
||||
// Social systems in which the actor is interested in any activity performed by or on the object.
|
||||
// The target and origin typically have no defined meaning.
|
||||
Follow = Activity
|
||||
|
||||
// Ignore indicates that the actor is ignoring the object. The target and origin typically have no defined meaning.
|
||||
Ignore = Activity
|
||||
|
||||
// Invite is a specialization of Offer in which the actor is extending an invitation for the object to the target.
|
||||
Invite = Offer
|
||||
|
||||
// Join indicates that the actor has joined the object. The target and origin typically have no defined meaning.
|
||||
Join = Activity
|
||||
|
||||
// Leave indicates that the actor has left the object. The target and origin typically have no meaning.
|
||||
Leave = Activity
|
||||
|
||||
// Like indicates that the actor likes, recommends or endorses the object.
|
||||
// The target and origin typically have no defined meaning.
|
||||
Like = Activity
|
||||
|
||||
// Listen inherits all properties from Activity.
|
||||
Listen = Activity
|
||||
|
||||
// Move indicates that the actor has moved object from origin to target.
|
||||
// If the origin or target are not specified, either can be determined by context.
|
||||
Move = Activity
|
||||
|
||||
// Offer indicates that the actor is offering the object.
|
||||
// If specified, the target indicates the entity to which the object is being offered.
|
||||
Offer = Activity
|
||||
|
||||
// Reject indicates that the actor is rejecting the object. The target and origin typically have no defined meaning.
|
||||
Reject = Activity
|
||||
|
||||
// Read indicates that the actor has read the object.
|
||||
Read = Activity
|
||||
|
||||
// Remove indicates that the actor is removing the object. If specified,
|
||||
// the origin indicates the context from which the object is being removed.
|
||||
Remove = Activity
|
||||
|
||||
// TentativeReject is a specialization of Reject in which the rejection is considered tentative.
|
||||
TentativeReject = Reject
|
||||
|
||||
// TentativeAccept is a specialization of Accept indicating that the acceptance is tentative.
|
||||
TentativeAccept = Accept
|
||||
|
||||
// Undo indicates that the actor is undoing the object. In most cases, the object will be an Activity describing
|
||||
// some previously performed action (for instance, a person may have previously "liked" an article but,
|
||||
// for whatever reason, might choose to undo that like at some later point in time).
|
||||
// The target and origin typically have no defined meaning.
|
||||
Undo = Activity
|
||||
|
||||
// Update indicates that the actor has updated the object. Note, however, that this vocabulary does not define a mechanism
|
||||
// for describing the actual set of modifications made to object.
|
||||
// The target and origin typically have no defined meaning.
|
||||
Update = Activity
|
||||
|
||||
// View indicates that the actor has viewed the object.
|
||||
View = Activity
|
||||
)
|
||||
|
||||
// AcceptNew initializes an Accept activity
|
||||
func AcceptNew(id ObjectID, ob Item) *Accept {
|
||||
a := ActivityNew(id, AcceptType, ob)
|
||||
o := Accept(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// AddNew initializes an Add activity
|
||||
func AddNew(id ObjectID, ob Item, trgt Item) *Add {
|
||||
a := ActivityNew(id, AddType, ob)
|
||||
o := Add(*a)
|
||||
o.Target = trgt
|
||||
return &o
|
||||
}
|
||||
|
||||
// AnnounceNew initializes an Announce activity
|
||||
func AnnounceNew(id ObjectID, ob Item) *Announce {
|
||||
a := ActivityNew(id, AnnounceType, ob)
|
||||
o := Announce(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// BlockNew initializes a Block activity
|
||||
func BlockNew(id ObjectID, ob Item) *Block {
|
||||
a := ActivityNew(id, BlockType, ob)
|
||||
o := Block(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// CreateNew initializes a Create activity
|
||||
func CreateNew(id ObjectID, ob Item) *Create {
|
||||
a := ActivityNew(id, CreateType, ob)
|
||||
o := Create(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// DeleteNew initializes a Delete activity
|
||||
func DeleteNew(id ObjectID, ob Item) *Delete {
|
||||
a := ActivityNew(id, DeleteType, ob)
|
||||
o := Delete(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// DislikeNew initializes a Dislike activity
|
||||
func DislikeNew(id ObjectID, ob Item) *Dislike {
|
||||
a := ActivityNew(id, DislikeType, ob)
|
||||
o := Dislike(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// FlagNew initializes a Flag activity
|
||||
func FlagNew(id ObjectID, ob Item) *Flag {
|
||||
a := ActivityNew(id, FlagType, ob)
|
||||
o := Flag(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// FollowNew initializes a Follow activity
|
||||
func FollowNew(id ObjectID, ob Item) *Follow {
|
||||
a := ActivityNew(id, FollowType, ob)
|
||||
o := Follow(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// IgnoreNew initializes an Ignore activity
|
||||
func IgnoreNew(id ObjectID, ob Item) *Ignore {
|
||||
a := ActivityNew(id, IgnoreType, ob)
|
||||
o := Ignore(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// InviteNew initializes an Invite activity
|
||||
func InviteNew(id ObjectID, ob Item) *Invite {
|
||||
a := ActivityNew(id, InviteType, ob)
|
||||
o := Invite(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// JoinNew initializes a Join activity
|
||||
func JoinNew(id ObjectID, ob Item) *Join {
|
||||
a := ActivityNew(id, JoinType, ob)
|
||||
o := Join(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// LeaveNew initializes a Leave activity
|
||||
func LeaveNew(id ObjectID, ob Item) *Leave {
|
||||
a := ActivityNew(id, LeaveType, ob)
|
||||
o := Leave(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// LikeNew initializes a Like activity
|
||||
func LikeNew(id ObjectID, ob Item) *Like {
|
||||
a := ActivityNew(id, LikeType, ob)
|
||||
o := Like(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// ListenNew initializes a Listen activity
|
||||
func ListenNew(id ObjectID, ob Item) *Listen {
|
||||
a := ActivityNew(id, ListenType, ob)
|
||||
o := Listen(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// MoveNew initializes a Move activity
|
||||
func MoveNew(id ObjectID, ob Item) *Move {
|
||||
a := ActivityNew(id, MoveType, ob)
|
||||
o := Move(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// OfferNew initializes an Offer activity
|
||||
func OfferNew(id ObjectID, ob Item) *Offer {
|
||||
a := ActivityNew(id, OfferType, ob)
|
||||
o := Offer(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// RejectNew initializes a Reject activity
|
||||
func RejectNew(id ObjectID, ob Item) *Reject {
|
||||
a := ActivityNew(id, RejectType, ob)
|
||||
o := Reject(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// ReadNew initializes a Read activity
|
||||
func ReadNew(id ObjectID, ob Item) *Read {
|
||||
a := ActivityNew(id, ReadType, ob)
|
||||
o := Read(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// RemoveNew initializes a Remove activity
|
||||
func RemoveNew(id ObjectID, ob Item, trgt Item) *Remove {
|
||||
a := ActivityNew(id, RemoveType, ob)
|
||||
o := Remove(*a)
|
||||
o.Target = trgt
|
||||
return &o
|
||||
}
|
||||
|
||||
// TentativeRejectNew initializes a TentativeReject activity
|
||||
func TentativeRejectNew(id ObjectID, ob Item) *TentativeReject {
|
||||
a := ActivityNew(id, TentativeRejectType, ob)
|
||||
o := TentativeReject(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// TentativeAcceptNew initializes a TentativeAccept activity
|
||||
func TentativeAcceptNew(id ObjectID, ob Item) *TentativeAccept {
|
||||
a := ActivityNew(id, TentativeAcceptType, ob)
|
||||
o := TentativeAccept(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// UndoNew initializes an Undo activity
|
||||
func UndoNew(id ObjectID, ob Item) *Undo {
|
||||
a := ActivityNew(id, UndoType, ob)
|
||||
o := Undo(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// UpdateNew initializes an Update activity
|
||||
func UpdateNew(id ObjectID, ob Item) *Update {
|
||||
a := ActivityNew(id, UpdateType, ob)
|
||||
u := Update(*a)
|
||||
return &u
|
||||
}
|
||||
|
||||
// ViewNew initializes a View activity
|
||||
func ViewNew(id ObjectID, ob Item) *View {
|
||||
a := ActivityNew(id, ViewType, ob)
|
||||
o := View(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// ActivityNew initializes a basic activity
|
||||
func ActivityNew(id ObjectID, typ ActivityVocabularyType, ob Item) *Activity {
|
||||
if !ActivityTypes.Contains(typ) {
|
||||
typ = ActivityType
|
||||
}
|
||||
a := Activity{ID: id, Type: typ}
|
||||
a.Name = NaturalLanguageValuesNew()
|
||||
a.Content = NaturalLanguageValuesNew()
|
||||
|
||||
a.Object = ob
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (a *Activity) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
a.ID = JSONGetObjectID(data)
|
||||
a.Type = JSONGetType(data)
|
||||
a.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
a.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
a.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
a.Context = JSONGetItem(data, "context")
|
||||
a.URL = JSONGetURIItem(data, "url")
|
||||
a.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
a.Generator = JSONGetItem(data, "generator")
|
||||
a.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
a.Attachment = JSONGetItem(data, "attachment")
|
||||
a.Location = JSONGetItem(data, "location")
|
||||
a.Published = JSONGetTime(data, "published")
|
||||
a.StartTime = JSONGetTime(data, "startTime")
|
||||
a.EndTime = JSONGetTime(data, "endTime")
|
||||
a.Duration = JSONGetDuration(data, "duration")
|
||||
a.Icon = JSONGetItem(data, "icon")
|
||||
a.Preview = JSONGetItem(data, "preview")
|
||||
a.Image = JSONGetItem(data, "image")
|
||||
a.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
a.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
a.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
a.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
a.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
a.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
a.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
a.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
a.Tag = tag
|
||||
}
|
||||
a.Actor = JSONGetItem(data, "actor")
|
||||
a.Target = JSONGetItem(data, "target")
|
||||
a.Instrument = JSONGetItem(data, "instrument")
|
||||
a.Origin = JSONGetItem(data, "origin")
|
||||
a.Result = JSONGetItem(data, "result")
|
||||
a.Object = JSONGetItem(data, "object")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToActivity
|
||||
func ToActivity(it Item) (*Activity, error) {
|
||||
switch i := it.(type) {
|
||||
case *Activity:
|
||||
return i, nil
|
||||
case Activity:
|
||||
return &i, nil
|
||||
case *IntransitiveActivity:
|
||||
return (*Activity)(unsafe.Pointer(i)), nil
|
||||
case IntransitiveActivity:
|
||||
return (*Activity)(unsafe.Pointer(&i)), nil
|
||||
case *Question:
|
||||
return (*Activity)(unsafe.Pointer(i)), nil
|
||||
case Question:
|
||||
return (*Activity)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, errors.New("unable to convert activity")
|
||||
}
|
||||
|
||||
// FlattenActivityProperties flattens the Activity's properties from Object type to IRI
|
||||
func FlattenActivityProperties(act *Activity) *Activity {
|
||||
act.Object = Flatten(act.Object)
|
||||
act.Actor = Flatten(act.Actor)
|
||||
act.Target = Flatten(act.Target)
|
||||
act.Result = Flatten(act.Result)
|
||||
act.Origin = Flatten(act.Origin)
|
||||
act.Result = Flatten(act.Result)
|
||||
act.Instrument = Flatten(act.Instrument)
|
||||
return act
|
||||
}
|
971
activity_test.go
Normal file
971
activity_test.go
Normal file
|
@ -0,0 +1,971 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestActivityNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
var testType ActivityVocabularyType = "Accept"
|
||||
|
||||
a := ActivityNew(testValue, testType, nil)
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != testType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, testType)
|
||||
}
|
||||
|
||||
g := ActivityNew(testValue, "", nil)
|
||||
|
||||
if g.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", g.ID, testValue)
|
||||
}
|
||||
if g.Type != ActivityType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", g.Type, ActivityType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := AcceptNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != AcceptType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, AcceptType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := AddNew(testValue, nil, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != AddType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, AddType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnounceNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := AnnounceNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != AnnounceType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, AnnounceType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := BlockNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != BlockType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, BlockType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := CreateNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != CreateType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, CreateType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := DeleteNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != DeleteType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, DeleteType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDislikeNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := DislikeNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != DislikeType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, DislikeType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := FlagNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != FlagType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, FlagType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := FollowNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != FollowType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, FollowType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := IgnoreNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != IgnoreType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, IgnoreType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := InviteNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != InviteType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, InviteType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := JoinNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != JoinType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, JoinType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeaveNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := LeaveNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != LeaveType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, LeaveType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikeNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := LikeNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != LikeType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, LikeType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := ListenNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != ListenType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, ListenType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := MoveNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != MoveType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, MoveType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOfferNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := OfferNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != OfferType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, OfferType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := RejectNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != RejectType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, RejectType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := ReadNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != ReadType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, ReadType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := RemoveNew(testValue, nil, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != RemoveType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, RemoveType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTentativeRejectNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := TentativeRejectNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != TentativeRejectType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, TentativeRejectType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTentativeAcceptNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := TentativeAcceptNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != TentativeAcceptType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, TentativeAcceptType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUndoNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := UndoNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != UndoType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, UndoType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := UpdateNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != UpdateType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, UpdateType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := ViewNew(testValue, nil)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != ViewType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, ViewType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityRecipients(t *testing.T) {
|
||||
bob := PersonNew("bob")
|
||||
alice := PersonNew("alice")
|
||||
foo := OrganizationNew("foo")
|
||||
bar := GroupNew("bar")
|
||||
|
||||
a := ActivityNew("t", "test", nil)
|
||||
|
||||
a.To.Append(bob)
|
||||
a.To.Append(alice)
|
||||
a.To.Append(foo)
|
||||
a.To.Append(bar)
|
||||
if len(a.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
a.To.Append(bar)
|
||||
a.To.Append(alice)
|
||||
a.To.Append(foo)
|
||||
a.To.Append(bob)
|
||||
if len(a.To) != 8 {
|
||||
t.Errorf("%T.To should have exactly 8(eight) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
a.Recipients()
|
||||
if len(a.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
b := ActivityNew("t", "test", nil)
|
||||
|
||||
b.To.Append(bar)
|
||||
b.To.Append(alice)
|
||||
b.To.Append(foo)
|
||||
b.To.Append(bob)
|
||||
b.Bto.Append(bar)
|
||||
b.Bto.Append(alice)
|
||||
b.Bto.Append(foo)
|
||||
b.Bto.Append(bob)
|
||||
b.CC.Append(bar)
|
||||
b.CC.Append(alice)
|
||||
b.CC.Append(foo)
|
||||
b.CC.Append(bob)
|
||||
b.BCC.Append(bar)
|
||||
b.BCC.Append(alice)
|
||||
b.BCC.Append(foo)
|
||||
b.BCC.Append(bob)
|
||||
|
||||
b.Recipients()
|
||||
if len(b.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", b, len(b.To))
|
||||
}
|
||||
if len(b.Bto) != 0 {
|
||||
t.Errorf("%T.Bto should have exactly 0(zero) elements, not %d", b, len(b.Bto))
|
||||
}
|
||||
if len(b.CC) != 0 {
|
||||
t.Errorf("%T.CC should have exactly 0(zero) elements, not %d", b, len(b.CC))
|
||||
}
|
||||
if len(b.BCC) != 0 {
|
||||
t.Errorf("%T.BCC should have exactly 0(zero) elements, not %d", b, len(b.BCC))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockRecipients(t *testing.T) {
|
||||
bob := PersonNew("bob")
|
||||
alice := PersonNew("alice")
|
||||
foo := OrganizationNew("foo")
|
||||
bar := GroupNew("bar")
|
||||
|
||||
a := BlockNew("bbb", bob)
|
||||
|
||||
a.To.Append(bob)
|
||||
a.To.Append(alice)
|
||||
a.To.Append(foo)
|
||||
a.To.Append(bar)
|
||||
if len(a.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
a.To.Append(bar)
|
||||
a.To.Append(alice)
|
||||
a.To.Append(foo)
|
||||
a.To.Append(bob)
|
||||
if len(a.To) != 8 {
|
||||
t.Errorf("%T.To should have exactly 8(eight) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
a.Recipients()
|
||||
if len(a.To) != 3 {
|
||||
t.Errorf("%T.To should have exactly 3(three) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
b := BlockNew("t", bob)
|
||||
|
||||
b.To.Append(bar)
|
||||
b.To.Append(alice)
|
||||
b.To.Append(foo)
|
||||
b.To.Append(bob)
|
||||
b.Bto.Append(bar)
|
||||
b.Bto.Append(alice)
|
||||
b.Bto.Append(foo)
|
||||
b.Bto.Append(bob)
|
||||
b.CC.Append(bar)
|
||||
b.CC.Append(alice)
|
||||
b.CC.Append(foo)
|
||||
b.CC.Append(bob)
|
||||
b.BCC.Append(bar)
|
||||
b.BCC.Append(alice)
|
||||
b.BCC.Append(foo)
|
||||
b.BCC.Append(bob)
|
||||
|
||||
b.Recipients()
|
||||
if len(b.To) != 3 {
|
||||
t.Errorf("%T.To should have exactly 3(three) elements, not %d", b, len(b.To))
|
||||
}
|
||||
if len(b.Bto) != 0 {
|
||||
t.Errorf("%T.Bto should have exactly 0(zero) elements, not %d", b, len(b.Bto))
|
||||
}
|
||||
if len(b.CC) != 0 {
|
||||
t.Errorf("%T.CC should have exactly 0(zero) elements, not %d", b, len(b.CC))
|
||||
}
|
||||
if len(b.BCC) != 0 {
|
||||
t.Errorf("%T.BCC should have exactly 0(zero) elements, not %d", b, len(b.BCC))
|
||||
}
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(b.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
c := CreateNew("act", o)
|
||||
c.To.Append(to)
|
||||
c.CC.Append(cc)
|
||||
c.BCC.Append(cc)
|
||||
|
||||
c.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(c.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDislike_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
d := DislikeNew("act", o)
|
||||
d.To.Append(to)
|
||||
d.CC.Append(cc)
|
||||
d.BCC.Append(cc)
|
||||
|
||||
d.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(d.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(d.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(d.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(d.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLike_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
l := LikeNew("act", o)
|
||||
l.To.Append(to)
|
||||
l.CC.Append(cc)
|
||||
l.BCC.Append(cc)
|
||||
|
||||
l.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(l.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(l.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(l.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(l.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
u := UpdateNew("act", o)
|
||||
u.To.Append(to)
|
||||
u.CC.Append(cc)
|
||||
u.BCC.Append(cc)
|
||||
|
||||
u.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(u.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(u.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(u.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(u.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivity_GetID(t *testing.T) {
|
||||
a := ActivityNew("test", ActivityType, Person{})
|
||||
|
||||
if a.GetID() != "test" {
|
||||
t.Errorf("%T should return an empty %T object. Received %#v", a, a.GetID(), a.GetID())
|
||||
}
|
||||
}
|
||||
func TestActivity_GetIDGetType(t *testing.T) {
|
||||
a := ActivityNew("test", ActivityType, Person{})
|
||||
|
||||
if a.GetID() != "test" || a.GetType() != ActivityType {
|
||||
t.Errorf("%T should not return an empty %T object. Received %#v", a, a.GetID(), a.GetID())
|
||||
}
|
||||
}
|
||||
func TestActivity_IsLink(t *testing.T) {
|
||||
a := ActivityNew("test", ActivityType, Person{})
|
||||
|
||||
if a.IsLink() {
|
||||
t.Errorf("%T should not respond true to IsLink", a)
|
||||
}
|
||||
}
|
||||
func TestActivity_IsObject(t *testing.T) {
|
||||
a := ActivityNew("test", ActivityType, Person{})
|
||||
|
||||
if !a.IsObject() {
|
||||
t.Errorf("%T should respond true to IsObject", a)
|
||||
}
|
||||
}
|
||||
|
||||
func checkDedup(list ItemCollection, recIds *[]ObjectID) error {
|
||||
for _, rec := range list {
|
||||
for _, id := range *recIds {
|
||||
if rec.GetID() == id {
|
||||
return fmt.Errorf("%T[%s] already stored in recipients list, Deduplication faild", rec, id)
|
||||
}
|
||||
}
|
||||
*recIds = append(*recIds, rec.GetID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestActivity_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
c := ActivityNew("act", ActivityType, o)
|
||||
c.To.Append(to)
|
||||
c.CC.Append(cc)
|
||||
c.BCC.Append(cc)
|
||||
|
||||
c.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(c.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlock_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
b := BlockNew("act", o)
|
||||
b.To.Append(to)
|
||||
b.CC.Append(cc)
|
||||
b.BCC.Append(cc)
|
||||
|
||||
b.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(b.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivity_UnmarshalJSON(t *testing.T) {
|
||||
a := Activity{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
a.UnmarshalJSON(dataEmpty)
|
||||
if a.ID != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", a, a.ID)
|
||||
}
|
||||
if a.Type != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", a, a.Type)
|
||||
}
|
||||
if a.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", a, a.AttributedTo)
|
||||
}
|
||||
if len(a.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", a, a.Name)
|
||||
}
|
||||
if len(a.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", a, a.Summary)
|
||||
}
|
||||
if len(a.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", a, a.Content)
|
||||
}
|
||||
if a.URL != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", a, a.URL)
|
||||
}
|
||||
if !a.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", a, a.Published)
|
||||
}
|
||||
if !a.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", a, a.StartTime)
|
||||
}
|
||||
if !a.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", a, a.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate_UnmarshalJSON(t *testing.T) {
|
||||
c := Create{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
c.UnmarshalJSON(dataEmpty)
|
||||
if c.ID != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", c, c.ID)
|
||||
}
|
||||
if c.Type != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", c, c.Type)
|
||||
}
|
||||
if c.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", c, c.AttributedTo)
|
||||
}
|
||||
if len(c.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", c, c.Name)
|
||||
}
|
||||
if len(c.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", c, c.Summary)
|
||||
}
|
||||
if len(c.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", c, c.Content)
|
||||
}
|
||||
if c.URL != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", c, c.URL)
|
||||
}
|
||||
if !c.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", c, c.Published)
|
||||
}
|
||||
if !c.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", c, c.StartTime)
|
||||
}
|
||||
if !c.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", c, c.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDislike_UnmarshalJSON(t *testing.T) {
|
||||
d := Dislike{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
d.UnmarshalJSON(dataEmpty)
|
||||
if d.ID != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", d, d.ID)
|
||||
}
|
||||
if d.Type != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", d, d.Type)
|
||||
}
|
||||
if d.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", d, d.AttributedTo)
|
||||
}
|
||||
if len(d.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", d, d.Name)
|
||||
}
|
||||
if len(d.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", d, d.Summary)
|
||||
}
|
||||
if len(d.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", d, d.Content)
|
||||
}
|
||||
if d.URL != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", d, d.URL)
|
||||
}
|
||||
if !d.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", d, d.Published)
|
||||
}
|
||||
if !d.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", d, d.StartTime)
|
||||
}
|
||||
if !d.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", d, d.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLike_UnmarshalJSON(t *testing.T) {
|
||||
l := Like{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
l.UnmarshalJSON(dataEmpty)
|
||||
if l.ID != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", l, l.ID)
|
||||
}
|
||||
if l.Type != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", l, l.Type)
|
||||
}
|
||||
if l.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", l, l.AttributedTo)
|
||||
}
|
||||
if len(l.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", l, l.Name)
|
||||
}
|
||||
if len(l.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", l, l.Summary)
|
||||
}
|
||||
if len(l.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", l, l.Content)
|
||||
}
|
||||
if l.URL != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", l, l.URL)
|
||||
}
|
||||
if !l.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", l, l.Published)
|
||||
}
|
||||
if !l.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", l, l.StartTime)
|
||||
}
|
||||
if !l.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", l, l.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate_UnmarshalJSON(t *testing.T) {
|
||||
u := Update{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
u.UnmarshalJSON(dataEmpty)
|
||||
if u.ID != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", u, u.ID)
|
||||
}
|
||||
if u.Type != "" {
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", u, u.Type)
|
||||
}
|
||||
if u.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", u, u.AttributedTo)
|
||||
}
|
||||
if len(u.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", u, u.Name)
|
||||
}
|
||||
if len(u.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", u, u.Summary)
|
||||
}
|
||||
if len(u.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", u, u.Content)
|
||||
}
|
||||
if u.URL != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", u, u.URL)
|
||||
}
|
||||
if !u.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", u, u.Published)
|
||||
}
|
||||
if !u.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", u, u.StartTime)
|
||||
}
|
||||
if !u.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", u, u.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToActivity(t *testing.T) {
|
||||
var it Item
|
||||
act := ActivityNew(ObjectID("test"), CreateType, nil)
|
||||
it = act
|
||||
|
||||
a, err := ToActivity(it)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if a != act {
|
||||
t.Errorf("Invalid activity returned by ToActivity #%v", a)
|
||||
}
|
||||
|
||||
ob := ObjectNew(ArticleType)
|
||||
it = ob
|
||||
|
||||
o, err := ToActivity(it)
|
||||
if err == nil {
|
||||
t.Errorf("Error returned when calling ToActivity with object should not be nil")
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("Invalid return by ToActivity #%v, should have been nil", o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenActivityProperties(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestValidEventRSVPActivityType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
func TestValidGroupManagementActivityType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestActivity_Clean(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestActivity_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
522
actors.go
522
actors.go
|
@ -2,175 +2,407 @@ package activitypub
|
|||
|
||||
import (
|
||||
"errors"
|
||||
as "github.com/go-ap/activitystreams"
|
||||
"github.com/buger/jsonparser"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// CanReceiveActivities Types
|
||||
const (
|
||||
ApplicationType ActivityVocabularyType = "Application"
|
||||
GroupType ActivityVocabularyType = "Group"
|
||||
OrganizationType ActivityVocabularyType = "Organization"
|
||||
PersonType ActivityVocabularyType = "Person"
|
||||
ServiceType ActivityVocabularyType = "Service"
|
||||
)
|
||||
|
||||
var ActorTypes = ActivityVocabularyTypes{
|
||||
ApplicationType,
|
||||
GroupType,
|
||||
OrganizationType,
|
||||
PersonType,
|
||||
ServiceType,
|
||||
}
|
||||
|
||||
// CanReceiveActivities is generally one of the ActivityStreams Actor Types, but they don't have to be.
|
||||
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
|
||||
// Actors are retrieved like any other Object in ActivityPub.
|
||||
// Like other ActivityStreams objects, actors have an id, which is a URI.
|
||||
type CanReceiveActivities Item
|
||||
|
||||
// Actor is generally one of the ActivityStreams actor Types, but they don't have to be.
|
||||
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
|
||||
// Actors are retrieved like any other Object in ActivityPub.
|
||||
// Like other ActivityStreams objects, actors have an id, which is a URI.
|
||||
type Actor struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor;
|
||||
// see 5.2 Inbox.
|
||||
Inbox Item `jsonld:"inbox,omitempty"`
|
||||
// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor;
|
||||
// see 5.1 Outbox.
|
||||
Outbox Item `jsonld:"outbox,omitempty"`
|
||||
// A link to an [ActivityStreams] collection of the actors that this actor is following;
|
||||
// see 5.4 Following Collection
|
||||
Following Item `jsonld:"following,omitempty"`
|
||||
// A link to an [ActivityStreams] collection of the actors that follow this actor;
|
||||
// see 5.3 Followers Collection.
|
||||
Followers Item `jsonld:"followers,omitempty"`
|
||||
// A link to an [ActivityStreams] collection of objects this actor has liked;
|
||||
// see 5.5 Liked Collection.
|
||||
Liked Item `jsonld:"liked,omitempty"`
|
||||
// A short username which may be used to refer to the actor, with no uniqueness guarantees.
|
||||
PreferredUsername NaturalLanguageValues `jsonld:"preferredUsername,omitempty,collapsible"`
|
||||
// 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 a JSON-LD document with these properties.
|
||||
Endpoints *Endpoints `jsonld:"endpoints,omitempty"`
|
||||
// A list of supplementary Collections which may be of interest.
|
||||
Streams []ItemCollection `jsonld:"streams,omitempty"`
|
||||
PublicKey PublicKey `jsonld:"publicKey,omitempty"`
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the current Actor
|
||||
func (a Actor) GetID() ObjectID {
|
||||
return a.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Actor
|
||||
func (a Actor) GetLink() IRI {
|
||||
return IRI(a.ID)
|
||||
}
|
||||
|
||||
// GetType returns the type of the current Actor
|
||||
func (a Actor) GetType() ActivityVocabularyType {
|
||||
return a.Type
|
||||
}
|
||||
|
||||
// IsLink validates if currentActivity Pub Actor is a Link
|
||||
func (a Actor) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject validates if currentActivity Pub Actor is an Object
|
||||
func (a Actor) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Actor Objects
|
||||
func (a Actor) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PublicKey holds the ActivityPub compatible public key data
|
||||
type PublicKey struct {
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
Owner ObjectOrLink `jsonld:"owner,omitempty"`
|
||||
PublicKeyPem string `jsonld:"publicKeyPem,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PublicKey) UnmarshalJSON(data []byte) error {
|
||||
if id, err := jsonparser.GetString(data, "id"); err == nil {
|
||||
p.ID = ObjectID(id)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
if o, err := jsonparser.GetString(data, "owner"); err == nil {
|
||||
p.Owner = IRI(o)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
if pub, err := jsonparser.GetString(data, "publicKeyPem"); err == nil {
|
||||
p.PublicKeyPem = pub
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
type (
|
||||
// Application describes a software application.
|
||||
Application = Actor
|
||||
|
||||
// Group represents a formal or informal collective of Actors.
|
||||
Group = Actor
|
||||
|
||||
// Organization represents an organization.
|
||||
Organization = Actor
|
||||
|
||||
// Person represents an individual person.
|
||||
Person = Actor
|
||||
|
||||
// Service represents a service of any kind.
|
||||
Service = Actor
|
||||
)
|
||||
|
||||
// ActorNew initializes an CanReceiveActivities type actor
|
||||
func ActorNew(id ObjectID, typ ActivityVocabularyType) *Actor {
|
||||
if !ActorTypes.Contains(typ) {
|
||||
typ = ActorType
|
||||
}
|
||||
|
||||
a := Actor{ID: id, Type: typ}
|
||||
a.Name = NaturalLanguageValuesNew()
|
||||
a.Content = NaturalLanguageValuesNew()
|
||||
a.Summary = NaturalLanguageValuesNew()
|
||||
in := OrderedCollectionNew(ObjectID("test-inbox"))
|
||||
out := OrderedCollectionNew(ObjectID("test-outbox"))
|
||||
liked := OrderedCollectionNew(ObjectID("test-liked"))
|
||||
|
||||
a.Inbox = in
|
||||
a.Outbox = out
|
||||
a.Liked = liked
|
||||
a.PreferredUsername = NaturalLanguageValuesNew()
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
// ApplicationNew initializes an Application type actor
|
||||
func ApplicationNew(id ObjectID) *Application {
|
||||
a := ActorNew(id, ApplicationType)
|
||||
o := Application(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// GroupNew initializes a Group type actor
|
||||
func GroupNew(id ObjectID) *Group {
|
||||
a := ActorNew(id, GroupType)
|
||||
o := Group(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// OrganizationNew initializes an Organization type actor
|
||||
func OrganizationNew(id ObjectID) *Organization {
|
||||
a := ActorNew(id, OrganizationType)
|
||||
o := Organization(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// PersonNew initializes a Person type actor
|
||||
func PersonNew(id ObjectID) *Person {
|
||||
a := ActorNew(id, PersonType)
|
||||
o := Person(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// ServiceNew initializes a Service type actor
|
||||
func ServiceNew(id ObjectID) *Service {
|
||||
a := ActorNew(id, ServiceType)
|
||||
o := Service(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
func (a *Actor) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
a.ID = JSONGetObjectID(data)
|
||||
a.Type = JSONGetType(data)
|
||||
a.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
a.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
a.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
a.Context = JSONGetItem(data, "context")
|
||||
a.URL = JSONGetURIItem(data, "url")
|
||||
a.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
a.Generator = JSONGetItem(data, "generator")
|
||||
a.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
a.Attachment = JSONGetItem(data, "attachment")
|
||||
a.Location = JSONGetItem(data, "location")
|
||||
a.Published = JSONGetTime(data, "published")
|
||||
a.StartTime = JSONGetTime(data, "startTime")
|
||||
a.EndTime = JSONGetTime(data, "endTime")
|
||||
a.Duration = JSONGetDuration(data, "duration")
|
||||
a.Icon = JSONGetItem(data, "icon")
|
||||
a.Preview = JSONGetItem(data, "preview")
|
||||
a.Image = JSONGetItem(data, "image")
|
||||
a.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
a.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
a.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
a.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
a.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
a.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
a.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
a.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
a.Tag = tag
|
||||
}
|
||||
a.Likes = JSONGetItem(data, "likes")
|
||||
a.Shares = JSONGetItem(data, "shares")
|
||||
a.Source = GetAPSource(data)
|
||||
a.PreferredUsername = JSONGetNaturalLanguageField(data, "preferredUsername")
|
||||
a.Followers = JSONGetItem(data, "followers")
|
||||
a.Following = JSONGetItem(data, "following")
|
||||
a.Inbox = JSONGetItem(data, "inbox")
|
||||
a.Outbox = JSONGetItem(data, "outbox")
|
||||
a.Liked = JSONGetItem(data, "liked")
|
||||
a.Endpoints = JSONGetActorEndpoints(data, "endpoints")
|
||||
a.Streams = JSONGetStreams(data, "streams")
|
||||
a.PublicKey = JSONGetPublicKey(data, "publicKey")
|
||||
return 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
|
||||
// a JSON-LD document with these properties.
|
||||
type Endpoints struct {
|
||||
// UploadMedia Upload endpoint URI for this user for binary data.
|
||||
UploadMedia as.Item `jsonld:"uploadMedia,omitempty"`
|
||||
UploadMedia Item `jsonld:"uploadMedia,omitempty"`
|
||||
// OauthAuthorizationEndpoint Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication
|
||||
// to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being
|
||||
// the id of the requested ActivityStreams object.
|
||||
OauthAuthorizationEndpoint as.Item `jsonld:"oauthAuthorizationEndpoint,omitempty"`
|
||||
OauthAuthorizationEndpoint Item `jsonld:"oauthAuthorizationEndpoint,omitempty"`
|
||||
// OauthTokenEndpoint If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
|
||||
// this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant.
|
||||
OauthTokenEndpoint as.Item `jsonld:"oauthTokenEndpoint,omitempty"`
|
||||
OauthTokenEndpoint Item `jsonld:"oauthTokenEndpoint,omitempty"`
|
||||
// ProvideClientKey If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
|
||||
// this endpoint specifies a URI at which a client may acquire an access token.
|
||||
ProvideClientKey as.Item `jsonld:"provideClientKey,omitempty"`
|
||||
ProvideClientKey Item `jsonld:"provideClientKey,omitempty"`
|
||||
// SignClientKey If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization,
|
||||
// this endpoint specifies a URI at which browser-authenticated users may authorize a client's public
|
||||
// key for client to server interactions.
|
||||
SignClientKey as.Item `jsonld:"signClientKey,omitempty"`
|
||||
SignClientKey Item `jsonld:"signClientKey,omitempty"`
|
||||
// SharedInbox An optional endpoint used for wide delivery of publicly addressed activities and activities sent to followers.
|
||||
// SharedInbox endpoints SHOULD also be publicly readable OrderedCollection objects containing objects addressed to the
|
||||
// Public special collection. Reading from the sharedInbox endpoint MUST NOT present objects which are not addressed to the Public endpoint.
|
||||
SharedInbox as.Item `jsonld:"sharedInbox,omitempty"`
|
||||
}
|
||||
|
||||
// Actor is generally one of the ActivityStreams actor Types, but they don't have to be.
|
||||
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
|
||||
// Actors are retrieved like any other Object in ActivityPub.
|
||||
// Like other ActivityStreams objects, actors have an id, which is a URI.
|
||||
type actor struct {
|
||||
Parent
|
||||
// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor;
|
||||
// see 5.2 Inbox.
|
||||
Inbox as.Item `jsonld:"inbox,omitempty"`
|
||||
// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor;
|
||||
// see 5.1 Outbox.
|
||||
Outbox as.Item `jsonld:"outbox,omitempty"`
|
||||
// A link to an [ActivityStreams] collection of the actors that this actor is following;
|
||||
// see 5.4 Following Collection
|
||||
Following as.Item `jsonld:"following,omitempty"`
|
||||
// A link to an [ActivityStreams] collection of the actors that follow this actor;
|
||||
// see 5.3 Followers Collection.
|
||||
Followers as.Item `jsonld:"followers,omitempty"`
|
||||
// A link to an [ActivityStreams] collection of objects this actor has liked;
|
||||
// see 5.5 Liked Collection.
|
||||
Liked as.Item `jsonld:"liked,omitempty"`
|
||||
// A short username which may be used to refer to the actor, with no uniqueness guarantees.
|
||||
PreferredUsername as.NaturalLanguageValues `jsonld:"preferredUsername,omitempty,collapsible"`
|
||||
// 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 a JSON-LD document with these properties.
|
||||
Endpoints *Endpoints `jsonld:"endpoints,omitempty"`
|
||||
// A list of supplementary Collections which may be of interest.
|
||||
Streams []as.ItemCollection `jsonld:"streams,omitempty"`
|
||||
}
|
||||
|
||||
type (
|
||||
// Application describes a software application.
|
||||
Application = actor
|
||||
|
||||
// Group represents a formal or informal collective of Actors.
|
||||
Group = actor
|
||||
|
||||
// Organization represents an organization.
|
||||
Organization = actor
|
||||
|
||||
// Person represents an individual person.
|
||||
Person = actor
|
||||
|
||||
// Service represents a service of any kind.
|
||||
Service = actor
|
||||
)
|
||||
|
||||
// actorNew initializes an actor type actor
|
||||
func actorNew(id as.ObjectID, typ as.ActivityVocabularyType) *actor {
|
||||
if !as.ActorTypes.Contains(typ) {
|
||||
typ = as.ActorType
|
||||
}
|
||||
|
||||
a := actor{Parent: Object{Parent: as.Parent{ID: id, Type: typ}}}
|
||||
a.Name = as.NaturalLanguageValuesNew()
|
||||
a.Content = as.NaturalLanguageValuesNew()
|
||||
a.Summary = as.NaturalLanguageValuesNew()
|
||||
in := as.OrderedCollectionNew(as.ObjectID("test-inbox"))
|
||||
out := as.OrderedCollectionNew(as.ObjectID("test-outbox"))
|
||||
liked := as.OrderedCollectionNew(as.ObjectID("test-liked"))
|
||||
|
||||
a.Inbox = in
|
||||
a.Outbox = out
|
||||
a.Liked = liked
|
||||
a.PreferredUsername = as.NaturalLanguageValuesNew()
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
// ApplicationNew initializes an Application type actor
|
||||
func ApplicationNew(id as.ObjectID) *Application {
|
||||
return actorNew(id, as.ApplicationType)
|
||||
}
|
||||
|
||||
// GroupNew initializes a Group type actor
|
||||
func GroupNew(id as.ObjectID) *Group {
|
||||
return actorNew(id, as.GroupType)
|
||||
}
|
||||
|
||||
// OrganizationNew initializes an Organization type actor
|
||||
func OrganizationNew(id as.ObjectID) *Organization {
|
||||
return actorNew(id, as.OrganizationType)
|
||||
}
|
||||
|
||||
// PersonNew initializes a Person type actor
|
||||
func PersonNew(id as.ObjectID) *Person {
|
||||
return actorNew(id, as.PersonType)
|
||||
}
|
||||
|
||||
// ServiceNew initializes a Service type actor
|
||||
func ServiceNew(id as.ObjectID) *Service {
|
||||
return actorNew(id, as.ServiceType)
|
||||
}
|
||||
|
||||
func (a *actor) UnmarshalJSON(data []byte) error {
|
||||
if as.ItemTyperFunc == nil {
|
||||
as.ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
a.Parent.UnmarshalJSON(data)
|
||||
a.PreferredUsername = as.JSONGetNaturalLanguageField(data, "preferredUsername")
|
||||
a.Followers = as.JSONGetItem(data, "followers")
|
||||
a.Following = as.JSONGetItem(data, "following")
|
||||
a.Inbox = as.JSONGetItem(data, "inbox")
|
||||
a.Outbox = as.JSONGetItem(data, "outbox")
|
||||
a.Liked = as.JSONGetItem(data, "liked")
|
||||
a.Endpoints = JSONGetActorEndpoints(data, "endpoints")
|
||||
// TODO(marius): Streams needs custom unmarshalling
|
||||
//a.Streams = as.JSONGetItems(data, "streams")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToPerson
|
||||
func ToPerson(it as.Item) (*Person, error) {
|
||||
switch i := it.(type) {
|
||||
case *as.Object:
|
||||
return &Person{Parent: Object{Parent: *i}}, nil
|
||||
case as.Object:
|
||||
return &Person{Parent: Object{Parent: i}}, nil
|
||||
case *Object:
|
||||
return &Person{Parent: *i}, nil
|
||||
case Object:
|
||||
return &Person{Parent: i}, nil
|
||||
case *actor:
|
||||
return i, nil
|
||||
case actor:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert object")
|
||||
SharedInbox Item `jsonld:"sharedInbox,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (e *Endpoints) UnmarshalJSON(data []byte) error {
|
||||
e.OauthAuthorizationEndpoint = as.JSONGetItem(data, "oauthAuthorizationEndpoint")
|
||||
e.OauthTokenEndpoint = as.JSONGetItem(data, "oauthTokenEndpoint")
|
||||
e.UploadMedia = as.JSONGetItem(data, "uploadMedia")
|
||||
e.ProvideClientKey = as.JSONGetItem(data, "provideClientKey")
|
||||
e.SignClientKey = as.JSONGetItem(data, "signClientKey")
|
||||
e.SharedInbox = as.JSONGetItem(data, "sharedInbox")
|
||||
e.OauthAuthorizationEndpoint = JSONGetItem(data, "oauthAuthorizationEndpoint")
|
||||
e.OauthTokenEndpoint = JSONGetItem(data, "oauthTokenEndpoint")
|
||||
e.UploadMedia = JSONGetItem(data, "uploadMedia")
|
||||
e.ProvideClientKey = JSONGetItem(data, "provideClientKey")
|
||||
e.SignClientKey = JSONGetItem(data, "signClientKey")
|
||||
e.SharedInbox = JSONGetItem(data, "sharedInbox")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToActor
|
||||
func ToActor(it Item) (*Actor, error) {
|
||||
switch i := it.(type) {
|
||||
case *Actor:
|
||||
return i, nil
|
||||
case Actor:
|
||||
return &i, nil
|
||||
case *Object:
|
||||
return (*Actor)(unsafe.Pointer(i)), nil
|
||||
case Object:
|
||||
return (*Actor)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, errors.New("unable to convert object")
|
||||
}
|
||||
|
|
210
actors_test.go
210
actors_test.go
|
@ -1,15 +1,15 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestActorNew(t *testing.T) {
|
||||
var testValue = as.ObjectID("test")
|
||||
var testType = as.ApplicationType
|
||||
var testValue = ObjectID("test")
|
||||
var testType = ApplicationType
|
||||
|
||||
o := actorNew(testValue, testType)
|
||||
o := ActorNew(testValue, testType)
|
||||
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
|
@ -18,72 +18,114 @@ func TestActorNew(t *testing.T) {
|
|||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, testType)
|
||||
}
|
||||
|
||||
n := actorNew(testValue, "")
|
||||
n := ActorNew(testValue, "")
|
||||
if n.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", n.ID, testValue)
|
||||
}
|
||||
if n.Type != as.ActorType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", n.Type, as.ActorType)
|
||||
if n.Type != ActorType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", n.Type, ActorType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonNew(t *testing.T) {
|
||||
var testValue = as.ObjectID("test")
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
o := PersonNew(testValue)
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
}
|
||||
if o.Type != as.PersonType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.PersonType)
|
||||
if o.Type != PersonType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, PersonType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationNew(t *testing.T) {
|
||||
var testValue = as.ObjectID("test")
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
o := ApplicationNew(testValue)
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
}
|
||||
if o.Type != as.ApplicationType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.ApplicationType)
|
||||
if o.Type != ApplicationType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, ApplicationType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupNew(t *testing.T) {
|
||||
var testValue = as.ObjectID("test")
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
o := GroupNew(testValue)
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
}
|
||||
if o.Type != as.GroupType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.GroupType)
|
||||
if o.Type != GroupType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, GroupType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizationNew(t *testing.T) {
|
||||
var testValue = as.ObjectID("test")
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
o := OrganizationNew(testValue)
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
}
|
||||
if o.Type != as.OrganizationType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.OrganizationType)
|
||||
if o.Type != OrganizationType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, OrganizationType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceNew(t *testing.T) {
|
||||
var testValue = as.ObjectID("test")
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
o := ServiceNew(testValue)
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
}
|
||||
if o.Type != as.ServiceType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.ServiceType)
|
||||
if o.Type != ServiceType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, ServiceType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActor_IsLink(t *testing.T) {
|
||||
m := ActorNew("test", ActorType)
|
||||
if m.IsLink() {
|
||||
t.Errorf("%#v should not be a valid Link", m.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActor_IsObject(t *testing.T) {
|
||||
m := ActorNew("test", ActorType)
|
||||
if !m.IsObject() {
|
||||
t.Errorf("%#v should be a valid object", m.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActor_Object(t *testing.T) {
|
||||
m := ActorNew("test", ActorType)
|
||||
if reflect.DeepEqual(ObjectID(""), m.GetID()) {
|
||||
t.Errorf("%#v should not be an empty activity pub object", m.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestActor_Type(t *testing.T) {
|
||||
m := ActorNew("test", ActorType)
|
||||
if m.GetType() != ActorType {
|
||||
t.Errorf("%#v should be an empty Link object", m.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerson_IsLink(t *testing.T) {
|
||||
m := PersonNew("test")
|
||||
if m.IsLink() {
|
||||
t.Errorf("%T should not be a valid Link", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerson_IsObject(t *testing.T) {
|
||||
m := PersonNew("test")
|
||||
if !m.IsObject() {
|
||||
t.Errorf("%T should be a valid object", m)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,36 +133,140 @@ func TestActor_UnmarshalJSON(t *testing.T) {
|
|||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestActor_GetActor(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestActor_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestActor_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestActor_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestApplication_GetActor(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestApplication_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestApplication_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestApplication_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestApplication_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestApplication_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestGroup_GetActor(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestGroup_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestGroup_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestGroup_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestGroup_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestGroup_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrganization_GetActor(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrganization_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrganization_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrganization_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrganization_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrganization_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPerson_GetActor(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPerson_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPerson_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPerson_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func validateEmptyPerson(p Person, t *testing.T) {
|
||||
if p.ID != "" {
|
||||
t.Errorf("Unmarshalled object %T should have empty ID, received %q", p, p.ID)
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", p, p.ID)
|
||||
}
|
||||
if p.Type != "" {
|
||||
t.Errorf("Unmarshalled object %T should have empty Type, received %q", p, p.Type)
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", p, p.Type)
|
||||
}
|
||||
if p.AttributedTo != nil {
|
||||
t.Errorf("Unmarshalled object %T should have empty AttributedTo, received %q", p, p.AttributedTo)
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", p, p.AttributedTo)
|
||||
}
|
||||
if len(p.Name) != 0 {
|
||||
t.Errorf("Unmarshalled object %T should have empty Name, received %q", p, p.Name)
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", p, p.Name)
|
||||
}
|
||||
if len(p.Summary) != 0 {
|
||||
t.Errorf("Unmarshalled object %T should have empty Summary, received %q", p, p.Summary)
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", p, p.Summary)
|
||||
}
|
||||
if len(p.Content) != 0 {
|
||||
t.Errorf("Unmarshalled object %T should have empty Content, received %q", p, p.Content)
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", p, p.Content)
|
||||
}
|
||||
if p.URL != nil {
|
||||
t.Errorf("Unmarshalled object %T should have empty URL, received %v", p, p.URL)
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", p, p.URL)
|
||||
}
|
||||
if !p.Published.IsZero() {
|
||||
t.Errorf("Unmarshalled object %T should have empty Published, received %q", p, p.Published)
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", p, p.Published)
|
||||
}
|
||||
if !p.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshalled object %T should have empty StartTime, received %q", p, p.StartTime)
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", p, p.StartTime)
|
||||
}
|
||||
if !p.Updated.IsZero() {
|
||||
t.Errorf("Unmarshalled object %T should have empty Updated, received %q", p, p.Updated)
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", p, p.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
as "github.com/go-ap/activitystreams"
|
||||
pub "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
type RequestSignFn func(*http.Request) error
|
||||
|
@ -30,7 +30,7 @@ type CanSign interface {
|
|||
}
|
||||
|
||||
type Client interface {
|
||||
LoadIRI(as.IRI) (as.Item, error)
|
||||
LoadIRI(pub.IRI) (pub.Item, error)
|
||||
}
|
||||
|
||||
// UserAgent value that the client uses when performing requests
|
||||
|
@ -48,10 +48,10 @@ var defaultSign RequestSignFn = func(r *http.Request) error { return nil }
|
|||
|
||||
type err struct {
|
||||
msg string
|
||||
iri as.IRI
|
||||
iri pub.IRI
|
||||
}
|
||||
|
||||
func errorf(i as.IRI, msg string, p ...interface{}) error {
|
||||
func errorf(i pub.IRI, msg string, p ...interface{}) error {
|
||||
return &err{
|
||||
msg: fmt.Sprintf(msg, p...),
|
||||
iri: i,
|
||||
|
@ -82,7 +82,7 @@ func (c *client) SignFn(fn RequestSignFn) {
|
|||
}
|
||||
|
||||
// LoadIRI tries to dereference an IRI and load the full ActivityPub object it represents
|
||||
func (c *client) LoadIRI(id as.IRI) (as.Item, error) {
|
||||
func (c *client) LoadIRI(id pub.IRI) (pub.Item, error) {
|
||||
if len(id) == 0 {
|
||||
return nil, errorf(id, "Invalid IRI, nil value")
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func (c *client) LoadIRI(id as.IRI) (as.Item, error) {
|
|||
return nil, errorf(id, "Invalid IRI: %s", err)
|
||||
}
|
||||
var err error
|
||||
var obj as.Item
|
||||
var obj pub.Item
|
||||
|
||||
var resp *http.Response
|
||||
if resp, err = c.Get(id.String()); err != nil {
|
||||
|
@ -115,7 +115,7 @@ func (c *client) LoadIRI(id as.IRI) (as.Item, error) {
|
|||
return obj, err
|
||||
}
|
||||
|
||||
return as.UnmarshalJSON(body)
|
||||
return pub.UnmarshalJSON(body)
|
||||
}
|
||||
|
||||
func (c *client) req(method string, url string, body io.Reader) (*http.Request, error) {
|
||||
|
@ -133,7 +133,7 @@ func (c *client) req(method string, url string, body io.Reader) (*http.Request,
|
|||
req.Header.Set("Content-Type", ContentTypeJsonLD)
|
||||
}
|
||||
if err = c.signFn(req); err != nil {
|
||||
err := errorf(as.IRI(req.URL.String()), "Unable to sign request (method %q, previous error: %s)", req.Method, err)
|
||||
err := errorf(pub.IRI(req.URL.String()), "Unable to sign request (method %q, previous error: %s)", req.Method, err)
|
||||
return req, err
|
||||
}
|
||||
return req, nil
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
as "github.com/go-ap/activitystreams"
|
||||
pub "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
|
@ -18,7 +18,7 @@ func TestNewClient(t *testing.T) {
|
|||
func TestErr_Error(t *testing.T) {
|
||||
e := err{
|
||||
msg: "test",
|
||||
iri: as.IRI(""),
|
||||
iri: pub.IRI(""),
|
||||
}
|
||||
|
||||
if len(e.Error()) == 0 {
|
||||
|
@ -30,7 +30,7 @@ func TestErr_Error(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClient_LoadIRI(t *testing.T) {
|
||||
empty := as.IRI("")
|
||||
empty := pub.IRI("")
|
||||
c := NewClient()
|
||||
|
||||
var err error
|
||||
|
@ -41,7 +41,7 @@ func TestClient_LoadIRI(t *testing.T) {
|
|||
t.Logf("Valid error received: %s", err)
|
||||
}
|
||||
|
||||
inv := as.IRI("example.com")
|
||||
inv := pub.IRI("example.com")
|
||||
_, err = c.LoadIRI(inv)
|
||||
if err == nil {
|
||||
t.Errorf("LoadIRI should have failed when using invalid http url")
|
||||
|
|
35
client/internal.go
Normal file
35
client/internal.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ErrorHandlerFunc is a data type for the default ErrorHandler function of the package
|
||||
type ErrorHandlerFunc func(...error) http.HandlerFunc
|
||||
|
||||
// ErrorHandler is the error handler callback for the ActivityPub package.
|
||||
// It can be overloaded from the packages that require it
|
||||
var ErrorHandler ErrorHandlerFunc = func(errors ...error) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
output := bytes.Buffer{}
|
||||
for i, e := range errors {
|
||||
output.WriteString(fmt.Sprintf("#%d %s\n", i, e.Error()))
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
w.Write(output.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Routes
|
||||
// {actor}/ -> HandleActorGET (requires LoadActorMiddleware(r, w), HandleError(r, w))
|
||||
// {actor}/{inbox} -> HandleActorInboxGET
|
||||
// {actor}/{outbox} -> HandleActorOutboxGET
|
||||
|
||||
// S2S
|
||||
// {actor}/inbox -> InboxRequest (requires LoadActorMIddleware(r, w))
|
||||
|
||||
// C2S
|
||||
// {actor}/outbox -> OutboxRequest
|
283
collection_page.go
Normal file
283
collection_page.go
Normal file
|
@ -0,0 +1,283 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CollectionPage is a Collection that contains a large number of items and when it becomes impractical
|
||||
// for an implementation to serialize every item contained by a Collection using the items
|
||||
// property alone. In such cases, the items within a Collection can be divided into distinct subsets or "pages".
|
||||
type CollectionPage struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// In a paged Collection, indicates the page that contains the most recently updated member items.
|
||||
Current ObjectOrLink `jsonld:"current,omitempty"`
|
||||
// In a paged Collection, indicates the furthest preceeding page of items in the collection.
|
||||
First ObjectOrLink `jsonld:"first,omitempty"`
|
||||
// In a paged Collection, indicates the furthest proceeding page of the collection.
|
||||
Last ObjectOrLink `jsonld:"last,omitempty"`
|
||||
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
|
||||
// This number might not reflect the actual number of items serialized within the Collection object instance.
|
||||
TotalItems uint `jsonld:"totalItems"`
|
||||
// Identifies the items contained in a collection. The items might be unordered.
|
||||
Items ItemCollection `jsonld:"items,omitempty"`
|
||||
// Identifies the Collection to which a CollectionPage objects items belong.
|
||||
PartOf Item `jsonld:"partOf,omitempty"`
|
||||
// In a paged Collection, indicates the next page of items.
|
||||
Next Item `jsonld:"next,omitempty"`
|
||||
// In a paged Collection, identifies the previous page of items.
|
||||
Prev Item `jsonld:"prev,omitempty"`
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the CollectionPage object
|
||||
func (c CollectionPage) GetID() ObjectID {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetType returns the CollectionPage's type
|
||||
func (c CollectionPage) GetType() ActivityVocabularyType {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for a CollectionPage object
|
||||
func (c CollectionPage) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a CollectionPage object
|
||||
func (c CollectionPage) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns true for CollectionPage objects
|
||||
func (c CollectionPage) IsCollection() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the CollectionPage object
|
||||
func (c CollectionPage) GetLink() IRI {
|
||||
return IRI(c.ID)
|
||||
}
|
||||
|
||||
// Collection returns the ColleCollectionPagection items
|
||||
func (c CollectionPage) Collection() ItemCollection {
|
||||
return c.Items
|
||||
}
|
||||
|
||||
// Count returns the maximum between the length of Items in the collection page and its TotalItems property
|
||||
func (c *CollectionPage) Count() uint {
|
||||
if c.TotalItems > 0 {
|
||||
return c.TotalItems
|
||||
}
|
||||
return uint(len(c.Items))
|
||||
}
|
||||
|
||||
// Append adds an element to a CollectionPage
|
||||
func (c *CollectionPage) Append(ob Item) error {
|
||||
c.Items = append(c.Items, ob)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contains verifies if CollectionPage array contains the received one
|
||||
func (c CollectionPage) Contains(r IRI) bool {
|
||||
if len(c.Items) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, iri := range c.Items {
|
||||
if r.Equals(iri.GetLink(), false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (c *CollectionPage) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
c.ID = JSONGetObjectID(data)
|
||||
c.Type = JSONGetType(data)
|
||||
c.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
c.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
c.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
c.Context = JSONGetItem(data, "context")
|
||||
c.URL = JSONGetURIItem(data, "url")
|
||||
c.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
c.Generator = JSONGetItem(data, "generator")
|
||||
c.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
c.Attachment = JSONGetItem(data, "attachment")
|
||||
c.Location = JSONGetItem(data, "location")
|
||||
c.Published = JSONGetTime(data, "published")
|
||||
c.StartTime = JSONGetTime(data, "startTime")
|
||||
c.EndTime = JSONGetTime(data, "endTime")
|
||||
c.Duration = JSONGetDuration(data, "duration")
|
||||
c.Icon = JSONGetItem(data, "icon")
|
||||
c.Preview = JSONGetItem(data, "preview")
|
||||
c.Image = JSONGetItem(data, "image")
|
||||
c.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
c.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
c.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
c.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
c.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
c.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
c.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
c.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
c.Tag = tag
|
||||
}
|
||||
|
||||
c.TotalItems = uint(JSONGetInt(data, "totalItems"))
|
||||
c.Items = JSONGetItems(data, "items")
|
||||
|
||||
c.Current = JSONGetItem(data, "current")
|
||||
c.First = JSONGetItem(data, "first")
|
||||
c.Last = JSONGetItem(data, "last")
|
||||
|
||||
c.Next = JSONGetItem(data, "next")
|
||||
c.Prev = JSONGetItem(data, "prev")
|
||||
c.PartOf = JSONGetItem(data, "partOf")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectionNew initializes a new CollectionPage
|
||||
func CollectionPageNew(parent CollectionInterface) *CollectionPage {
|
||||
p := CollectionPage{
|
||||
PartOf: parent.GetLink(),
|
||||
}
|
||||
if pc, ok := parent.(*Collection); ok {
|
||||
copyCollectionToPage(pc, &p)
|
||||
}
|
||||
p.Type = CollectionPageType
|
||||
return &p
|
||||
}
|
||||
|
||||
func copyCollectionToPage(c *Collection, p *CollectionPage) error {
|
||||
p.ID = c.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToCollectionPage
|
||||
func ToCollectionPage(it Item) (*CollectionPage, error) {
|
||||
switch i := it.(type) {
|
||||
case *CollectionPage:
|
||||
return i, nil
|
||||
case CollectionPage:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to collection page")
|
||||
}
|
145
collection_page_test.go
Normal file
145
collection_page_test.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCollectionPageNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
c := CollectionNew(testValue)
|
||||
p := CollectionPageNew(c)
|
||||
if reflect.DeepEqual(p.Collection, c) {
|
||||
t.Errorf("Invalid collection parent '%v'", p.PartOf)
|
||||
}
|
||||
if p.PartOf != c.GetLink() {
|
||||
t.Errorf("Invalid collection '%v'", p.PartOf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionPage_Append(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
p := CollectionPageNew(c)
|
||||
p.Append(val)
|
||||
|
||||
if p.PartOf != c.GetLink() {
|
||||
t.Errorf("Collection page should point to collection %q", c.GetLink())
|
||||
}
|
||||
if p.Count() != 1 {
|
||||
t.Errorf("Collection page of %q should have exactly one element", p.GetID())
|
||||
}
|
||||
if !reflect.DeepEqual(p.Items[0], val) {
|
||||
t.Errorf("First item in Inbox is does not match %q", val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionPage_UnmarshalJSON(t *testing.T) {
|
||||
p := CollectionPage{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
p.UnmarshalJSON(dataEmpty)
|
||||
if p.ID != "" {
|
||||
t.Errorf("Unmarshaled object should have empty ID, received %q", p.ID)
|
||||
}
|
||||
if p.Type != "" {
|
||||
t.Errorf("Unmarshaled object should have empty Type, received %q", p.Type)
|
||||
}
|
||||
if p.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object should have empty AttributedTo, received %q", p.AttributedTo)
|
||||
}
|
||||
if len(p.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Name, received %q", p.Name)
|
||||
}
|
||||
if len(p.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Summary, received %q", p.Summary)
|
||||
}
|
||||
if len(p.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Content, received %q", p.Content)
|
||||
}
|
||||
if p.TotalItems != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty TotalItems, received %d", p.TotalItems)
|
||||
}
|
||||
if len(p.Items) > 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Items, received %v", p.Items)
|
||||
}
|
||||
if p.URL != nil {
|
||||
t.Errorf("Unmarshaled object should have empty URL, received %v", p.URL)
|
||||
}
|
||||
if !p.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Published, received %q", p.Published)
|
||||
}
|
||||
if !p.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty StartTime, received %q", p.StartTime)
|
||||
}
|
||||
if !p.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Updated, received %q", p.Updated)
|
||||
}
|
||||
if p.PartOf != nil {
|
||||
t.Errorf("Unmarshaled object should have empty PartOf, received %q", p.PartOf)
|
||||
}
|
||||
if p.Current != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Current, received %q", p.Current)
|
||||
}
|
||||
if p.First != nil {
|
||||
t.Errorf("Unmarshaled object should have empty First, received %q", p.First)
|
||||
}
|
||||
if p.Last != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Last, received %q", p.Last)
|
||||
}
|
||||
if p.Next != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Next, received %q", p.Next)
|
||||
}
|
||||
if p.Prev != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Prev, received %q", p.Prev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionPage_Collection(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
p := CollectionPageNew(c)
|
||||
|
||||
if !reflect.DeepEqual(p.Collection(), p.Items) {
|
||||
t.Errorf("Collection items should be equal %v %v", p.Collection(), p.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionPage_Count(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
p := CollectionPageNew(c)
|
||||
|
||||
if p.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", p.TotalItems)
|
||||
}
|
||||
if len(p.Items) > 0 {
|
||||
t.Errorf("Empty object should have empty Items, received %v", p.Items)
|
||||
}
|
||||
if p.Count() != uint(len(p.Items)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, p.Count(), len(p.Items))
|
||||
}
|
||||
|
||||
p.Append(IRI("test"))
|
||||
if p.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", p.TotalItems)
|
||||
}
|
||||
if p.Count() != uint(len(p.Items)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, p.Count(), len(p.Items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCollectionPage(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestCollectionPage_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
324
collections.go
Normal file
324
collections.go
Normal file
|
@ -0,0 +1,324 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CollectionOfItems ActivityVocabularyType = "ItemCollection"
|
||||
|
||||
var CollectionTypes = ActivityVocabularyTypes{
|
||||
CollectionOfItems,
|
||||
CollectionType,
|
||||
OrderedCollectionType,
|
||||
CollectionPageType,
|
||||
OrderedCollectionPageType,
|
||||
}
|
||||
|
||||
type CollectionInterface interface {
|
||||
ObjectOrLink
|
||||
Collection() ItemCollection
|
||||
Append(ob Item) error
|
||||
Count() uint
|
||||
Contains(IRI) bool
|
||||
}
|
||||
|
||||
// Collection is a subtype of Activity Pub Object that represents ordered or unordered sets of Activity Pub Object or Link instances.
|
||||
type Collection struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// In a paged Collection, indicates the page that contains the most recently updated member items.
|
||||
Current ObjectOrLink `jsonld:"current,omitempty"`
|
||||
// In a paged Collection, indicates the furthest preceeding page of items in the collection.
|
||||
First ObjectOrLink `jsonld:"first,omitempty"`
|
||||
// In a paged Collection, indicates the furthest proceeding page of the collection.
|
||||
Last ObjectOrLink `jsonld:"last,omitempty"`
|
||||
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
|
||||
// This number might not reflect the actual number of items serialized within the Collection object instance.
|
||||
TotalItems uint `jsonld:"totalItems"`
|
||||
// Identifies the items contained in a collection. The items might be ordered or unordered.
|
||||
Items ItemCollection `jsonld:"items,omitempty"`
|
||||
}
|
||||
|
||||
// CollectionNew initializes a new Collection
|
||||
func CollectionNew(id ObjectID) *Collection {
|
||||
c := Collection{ID: id, Type: CollectionType}
|
||||
c.Name = NaturalLanguageValuesNew()
|
||||
c.Content = NaturalLanguageValuesNew()
|
||||
c.Summary = NaturalLanguageValuesNew()
|
||||
return &c
|
||||
}
|
||||
|
||||
// OrderedCollectionNew initializes a new OrderedCollection
|
||||
func OrderedCollectionNew(id ObjectID) *OrderedCollection {
|
||||
o := OrderedCollection{ID: id, Type: OrderedCollectionType}
|
||||
o.Name = NaturalLanguageValuesNew()
|
||||
o.Content = NaturalLanguageValuesNew()
|
||||
|
||||
return &o
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the Collection object
|
||||
func (c Collection) GetID() ObjectID {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetType returns the Collection's type
|
||||
func (c Collection) GetType() ActivityVocabularyType {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for a Collection object
|
||||
func (c Collection) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Collection object
|
||||
func (c Collection) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns true for Collection objects
|
||||
func (c Collection) IsCollection() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the Collection object
|
||||
func (c Collection) GetLink() IRI {
|
||||
return IRI(c.ID)
|
||||
}
|
||||
|
||||
// Collection returns the Collection's items
|
||||
func (c Collection) Collection() ItemCollection {
|
||||
return c.Items
|
||||
}
|
||||
|
||||
// Append adds an element to a Collection
|
||||
func (c *Collection) Append(ob Item) error {
|
||||
c.Items = append(c.Items, ob)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count returns the maximum between the length of Items in collection and its TotalItems property
|
||||
func (c *Collection) Count() uint {
|
||||
if c.TotalItems > 0 {
|
||||
return c.TotalItems
|
||||
}
|
||||
return uint(len(c.Items))
|
||||
}
|
||||
|
||||
// Contains verifies if Collection array contains the received one
|
||||
func (c Collection) Contains(r IRI) bool {
|
||||
if len(c.Items) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, iri := range c.Items {
|
||||
if r.Equals(iri.GetLink(), false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (c *Collection) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
c.ID = JSONGetObjectID(data)
|
||||
c.Type = JSONGetType(data)
|
||||
c.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
c.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
c.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
c.Context = JSONGetItem(data, "context")
|
||||
c.URL = JSONGetURIItem(data, "url")
|
||||
c.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
c.Generator = JSONGetItem(data, "generator")
|
||||
c.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
c.Attachment = JSONGetItem(data, "attachment")
|
||||
c.Location = JSONGetItem(data, "location")
|
||||
c.Published = JSONGetTime(data, "published")
|
||||
c.StartTime = JSONGetTime(data, "startTime")
|
||||
c.EndTime = JSONGetTime(data, "endTime")
|
||||
c.Duration = JSONGetDuration(data, "duration")
|
||||
c.Icon = JSONGetItem(data, "icon")
|
||||
c.Preview = JSONGetItem(data, "preview")
|
||||
c.Image = JSONGetItem(data, "image")
|
||||
c.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
c.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
c.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
c.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
c.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
c.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
c.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
c.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
c.Tag = tag
|
||||
}
|
||||
|
||||
c.TotalItems = uint(JSONGetInt(data, "totalItems"))
|
||||
c.Items = JSONGetItems(data, "items")
|
||||
|
||||
c.Current = JSONGetItem(data, "current")
|
||||
c.First = JSONGetItem(data, "first")
|
||||
c.Last = JSONGetItem(data, "last")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flatten checks if Item can be flatten to an IRI or array of IRIs and returns it if so
|
||||
func Flatten(it Item) Item {
|
||||
if it.IsCollection() {
|
||||
if c, ok := it.(CollectionInterface); ok {
|
||||
it = FlattenItemCollection(c.Collection())
|
||||
}
|
||||
}
|
||||
if it != nil && len(it.GetLink()) > 0 {
|
||||
return it.GetLink()
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
// FlattenItemCollection flattens the Collection's properties from Object type to IRI
|
||||
func FlattenItemCollection(c ItemCollection) ItemCollection {
|
||||
if c != nil && len(c) > 0 {
|
||||
for i, it := range c {
|
||||
c[i] = FlattenToIRI(it)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ToItemCollection
|
||||
func ToItemCollection(it Item) (*ItemCollection, error) {
|
||||
switch i := it.(type) {
|
||||
case *ItemCollection:
|
||||
return i, nil
|
||||
case ItemCollection:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to item collection")
|
||||
}
|
||||
|
||||
// ToCollection
|
||||
func ToCollection(it Item) (*Collection, error) {
|
||||
switch i := it.(type) {
|
||||
case *Collection:
|
||||
return i, nil
|
||||
case Collection:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to collection")
|
||||
}
|
172
collections_test.go
Normal file
172
collections_test.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCollectionNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
c := CollectionNew(testValue)
|
||||
|
||||
if c.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", c.ID, testValue)
|
||||
}
|
||||
if c.Type != CollectionType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", c.Type, CollectionType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_Append(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
c := CollectionNew(id)
|
||||
c.Append(val)
|
||||
|
||||
if c.Count() != 1 {
|
||||
t.Errorf("Inbox collection of %q should have one element", c.GetID())
|
||||
}
|
||||
if !reflect.DeepEqual(c.Items[0], val) {
|
||||
t.Errorf("First item in Inbox is does not match %q", val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_Collection(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if !reflect.DeepEqual(c.Collection(), c.Items) {
|
||||
t.Errorf("Collection items should be equal %v %v", c.Collection(), c.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_GetID(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if c.GetID() != id {
|
||||
t.Errorf("GetID should return %s, received %s", id, c.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_GetLink(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
link := IRI(id)
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if c.GetLink() != link {
|
||||
t.Errorf("GetLink should return %q, received %q", link, c.GetLink())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_GetType(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if c.GetType() != CollectionType {
|
||||
t.Errorf("Collection Type should be %q, received %q", CollectionType, c.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_IsLink(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if c.IsLink() != false {
|
||||
t.Errorf("Collection should not be a link, received %t", c.IsLink())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_IsObject(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if c.IsObject() != true {
|
||||
t.Errorf("Collection should be an object, received %t", c.IsObject())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_UnmarshalJSON(t *testing.T) {
|
||||
c := Collection{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
c.UnmarshalJSON(dataEmpty)
|
||||
if c.ID != "" {
|
||||
t.Errorf("Unmarshaled object should have empty ID, received %q", c.ID)
|
||||
}
|
||||
if c.Type != "" {
|
||||
t.Errorf("Unmarshaled object should have empty Type, received %q", c.Type)
|
||||
}
|
||||
if c.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object should have empty AttributedTo, received %q", c.AttributedTo)
|
||||
}
|
||||
if len(c.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Name, received %q", c.Name)
|
||||
}
|
||||
if len(c.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Summary, received %q", c.Summary)
|
||||
}
|
||||
if len(c.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Content, received %q", c.Content)
|
||||
}
|
||||
if c.TotalItems != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty TotalItems, received %d", c.TotalItems)
|
||||
}
|
||||
if len(c.Items) > 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Items, received %v", c.Items)
|
||||
}
|
||||
if c.URL != nil {
|
||||
t.Errorf("Unmarshaled object should have empty URL, received %v", c.URL)
|
||||
}
|
||||
if !c.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Published, received %q", c.Published)
|
||||
}
|
||||
if !c.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty StartTime, received %q", c.StartTime)
|
||||
}
|
||||
if !c.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Updated, received %q", c.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollection_Count(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := CollectionNew(id)
|
||||
|
||||
if c.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", c.TotalItems)
|
||||
}
|
||||
if len(c.Items) > 0 {
|
||||
t.Errorf("Empty object should have empty Items, received %v", c.Items)
|
||||
}
|
||||
if c.Count() != uint(len(c.Items)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, c.Count(), len(c.Items))
|
||||
}
|
||||
|
||||
c.Append(IRI("test"))
|
||||
if c.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", c.TotalItems)
|
||||
}
|
||||
if c.Count() != uint(len(c.Items)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, c.Count(), len(c.Items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestCollection_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
71
followers.go
71
followers.go
|
@ -1,78 +1,9 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
type (
|
||||
// FollowersCollection is a collection of followers
|
||||
FollowersCollection = Followers
|
||||
|
||||
// Followers is a Collection type
|
||||
Followers as.Collection
|
||||
Followers = Collection
|
||||
)
|
||||
|
||||
// FollowersNew initializes a new Followers
|
||||
func FollowersNew() *Followers {
|
||||
id := as.ObjectID("followers")
|
||||
|
||||
i := Followers{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
i.Name = as.NaturalLanguageValuesNew()
|
||||
i.Content = as.NaturalLanguageValuesNew()
|
||||
i.Summary = as.NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// Append adds an element to an Followers
|
||||
func (f *Followers) Append(ob as.Item) error {
|
||||
f.Items = append(f.Items, ob)
|
||||
f.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to Followers
|
||||
func (f Followers) GetID() *as.ObjectID {
|
||||
return f.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Followers object
|
||||
func (f Followers) GetLink() as.IRI {
|
||||
return as.IRI(f.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Followers's type
|
||||
func (f Followers) GetType() as.ActivityVocabularyType {
|
||||
return f.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Followers object
|
||||
func (f Followers) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Followers object
|
||||
func (f Followers) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (f *Followers) UnmarshalJSON(data []byte) error {
|
||||
if as.ItemTyperFunc == nil {
|
||||
as.ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
c := as.Collection(*f)
|
||||
err := c.UnmarshalJSON(data)
|
||||
|
||||
*f = Followers(c)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (f Followers) Collection() as.CollectionInterface {
|
||||
c := as.Collection(f)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -2,38 +2,6 @@ package activitypub
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestFollowers_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowers_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowersNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
67
following.go
67
following.go
|
@ -1,9 +1,5 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
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
|
||||
|
@ -11,70 +7,19 @@ type (
|
|||
FollowingCollection = Following
|
||||
|
||||
// Following is a type alias for a simple Collection
|
||||
Following as.Collection
|
||||
Following = Collection
|
||||
)
|
||||
|
||||
// FollowingNew initializes a new Following
|
||||
func FollowingNew() *Following {
|
||||
id := as.ObjectID("following")
|
||||
id := ObjectID("following")
|
||||
|
||||
i := Following{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
i.Name = as.NaturalLanguageValuesNew()
|
||||
i.Content = as.NaturalLanguageValuesNew()
|
||||
i.Summary = as.NaturalLanguageValuesNew()
|
||||
i := Following{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
i.Summary = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// Append adds an element to an Following
|
||||
func (f *Following) Append(ob as.Item) error {
|
||||
f.Items = append(f.Items, ob)
|
||||
f.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to Following
|
||||
func (f Following) GetID() *as.ObjectID {
|
||||
return f.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Following object
|
||||
func (f Following) GetLink() as.IRI {
|
||||
return as.IRI(f.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Following's type
|
||||
func (f Following) GetType() as.ActivityVocabularyType {
|
||||
return f.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Following object
|
||||
func (f Following) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Following object
|
||||
func (f Following) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (f *Following) UnmarshalJSON(data []byte) error {
|
||||
if as.ItemTyperFunc == nil {
|
||||
as.ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
c := as.Collection(*f)
|
||||
err := c.UnmarshalJSON(data)
|
||||
|
||||
*f = Following(c)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (f Following) Collection() as.CollectionInterface {
|
||||
c := as.Collection(f)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -2,38 +2,6 @@ package activitypub
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestFollowing_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowing_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFollowingNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,5 @@ go 1.13
|
|||
|
||||
require (
|
||||
github.com/buger/jsonparser v0.0.0-20181023193515-52c6e1462ebd
|
||||
github.com/go-ap/activitystreams v0.0.0-20191201184731-495abe28e08d
|
||||
github.com/go-ap/jsonld v0.0.0-20191123195936-1e43eac08b0c
|
||||
)
|
||||
|
|
107
helpers.go
107
helpers.go
|
@ -3,41 +3,40 @@ package activitypub
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
type withObjectFn func (*Object) error
|
||||
type withActivityFn func (*activitystreams.Activity) error
|
||||
type withIntransitiveActivityFn func (*activitystreams.IntransitiveActivity) error
|
||||
type withQuestionFn func (*activitystreams.Question) error
|
||||
type withActivityFn func (*Activity) error
|
||||
type withIntransitiveActivityFn func (*IntransitiveActivity) error
|
||||
type withQuestionFn func (*Question) error
|
||||
type withPersonFn func (*Person) error
|
||||
type withCollectionInterfaceFn func (collection activitystreams.CollectionInterface) error
|
||||
type withCollectionFn func (collection *activitystreams.Collection) error
|
||||
type withCollectionPageFn func (*activitystreams.CollectionPage) error
|
||||
type withOrderedCollectionFn func (*activitystreams.OrderedCollection) error
|
||||
type withOrderedCollectionPageFn func (*activitystreams.OrderedCollectionPage) error
|
||||
type withItemCollectionFn func (collection *activitystreams.ItemCollection) error
|
||||
type withCollectionInterfaceFn func (collection CollectionInterface) error
|
||||
type withCollectionFn func (collection *Collection) error
|
||||
type withCollectionPageFn func (*CollectionPage) error
|
||||
type withOrderedCollectionFn func (*OrderedCollection) error
|
||||
type withOrderedCollectionPageFn func (*OrderedCollectionPage) error
|
||||
type withItemCollectionFn func (collection *ItemCollection) error
|
||||
|
||||
// OnObject
|
||||
func OnObject(it activitystreams.Item, fn withObjectFn) error {
|
||||
if activitystreams.ActivityTypes.Contains(it.GetType()) {
|
||||
return OnActivity(it, func(a *activitystreams.Activity) error {
|
||||
ob, err := ToObject(&a.Parent)
|
||||
func OnObject(it Item, fn withObjectFn) error {
|
||||
if ActivityTypes.Contains(it.GetType()) {
|
||||
return OnActivity(it, func(a *Activity) error {
|
||||
ob, err := ToObject(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(ob)
|
||||
})
|
||||
} else if activitystreams.ActorTypes.Contains(it.GetType()) {
|
||||
} else if ActorTypes.Contains(it.GetType()) {
|
||||
return OnPerson(it, func(p *Person) error {
|
||||
ob, err := ToObject(&p.Parent)
|
||||
ob, err := ToObject(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(ob)
|
||||
})
|
||||
} else if it.IsCollection() {
|
||||
return OnCollection(it, func(col activitystreams.CollectionInterface) error {
|
||||
return OnCollection(it, func(col CollectionInterface) error {
|
||||
for _, it := range col.Collection() {
|
||||
err := OnObject(it, fn)
|
||||
if err != nil {
|
||||
|
@ -56,11 +55,11 @@ func OnObject(it activitystreams.Item, fn withObjectFn) error {
|
|||
}
|
||||
|
||||
// OnActivity
|
||||
func OnActivity(it activitystreams.Item, fn withActivityFn) error {
|
||||
if !activitystreams.ActivityTypes.Contains(it.GetType()) {
|
||||
func OnActivity(it Item, fn withActivityFn) error {
|
||||
if !ActivityTypes.Contains(it.GetType()) {
|
||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Activity", it, it.GetType()))
|
||||
}
|
||||
act, err := activitystreams.ToActivity(it)
|
||||
act, err := ToActivity(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -68,14 +67,14 @@ func OnActivity(it activitystreams.Item, fn withActivityFn) error {
|
|||
}
|
||||
|
||||
// OnIntransitiveActivity
|
||||
func OnIntransitiveActivity(it activitystreams.Item, fn withIntransitiveActivityFn) error {
|
||||
if !activitystreams.IntransitiveActivityTypes.Contains(it.GetType()) {
|
||||
func OnIntransitiveActivity(it Item, fn withIntransitiveActivityFn) error {
|
||||
if !IntransitiveActivityTypes.Contains(it.GetType()) {
|
||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Activity", it, it.GetType()))
|
||||
}
|
||||
if it.GetType() == activitystreams.QuestionType {
|
||||
if it.GetType() == QuestionType {
|
||||
errors.New(fmt.Sprintf("For %T[%s] you need to use OnQuestion function", it, it.GetType()))
|
||||
}
|
||||
act, err := activitystreams.ToIntransitiveActivity(it)
|
||||
act, err := ToIntransitiveActivity(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -83,11 +82,11 @@ func OnIntransitiveActivity(it activitystreams.Item, fn withIntransitiveActivity
|
|||
}
|
||||
|
||||
// OnQuestion
|
||||
func OnQuestion(it activitystreams.Item, fn withQuestionFn) error {
|
||||
if it.GetType() != activitystreams.QuestionType {
|
||||
func OnQuestion(it Item, fn withQuestionFn) error {
|
||||
if it.GetType() != QuestionType {
|
||||
errors.New(fmt.Sprintf("For %T[%s] can't be converted to Question", it, it.GetType()))
|
||||
}
|
||||
act, err := activitystreams.ToQuestion(it)
|
||||
act, err := ToQuestion(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -95,11 +94,11 @@ func OnQuestion(it activitystreams.Item, fn withQuestionFn) error {
|
|||
}
|
||||
|
||||
// OnPerson
|
||||
func OnPerson(it activitystreams.Item, fn withPersonFn) error {
|
||||
if !activitystreams.ActorTypes.Contains(it.GetType()) {
|
||||
func OnPerson(it Item, fn withPersonFn) error {
|
||||
if !ActorTypes.Contains(it.GetType()) {
|
||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Person", it, it.GetType()))
|
||||
}
|
||||
pers, err := ToPerson(it)
|
||||
pers, err := ToActor(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -107,27 +106,27 @@ func OnPerson(it activitystreams.Item, fn withPersonFn) error {
|
|||
}
|
||||
|
||||
// OnCollection
|
||||
func OnCollection(it activitystreams.Item, fn withCollectionInterfaceFn) error {
|
||||
func OnCollection(it Item, fn withCollectionInterfaceFn) error {
|
||||
switch it.GetType() {
|
||||
case activitystreams.CollectionOfItems:
|
||||
col, err := activitystreams.ToItemCollection(it)
|
||||
case CollectionOfItems:
|
||||
col, err := ToItemCollection(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := activitystreams.Collection{
|
||||
c := Collection{
|
||||
TotalItems: uint(len(*col)),
|
||||
Items: *col,
|
||||
}
|
||||
return fn(&c)
|
||||
case activitystreams.CollectionType:
|
||||
col, err := activitystreams.ToCollection(it)
|
||||
case CollectionType:
|
||||
col, err := ToCollection(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(col)
|
||||
case activitystreams.CollectionPageType:
|
||||
return OnCollectionPage(it, func(p *activitystreams.CollectionPage) error {
|
||||
col, err := activitystreams.ToCollection(&p.ParentCollection)
|
||||
case CollectionPageType:
|
||||
return OnCollectionPage(it, func(p *CollectionPage) error {
|
||||
col, err := ToCollection(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -139,11 +138,11 @@ func OnCollection(it activitystreams.Item, fn withCollectionInterfaceFn) error {
|
|||
}
|
||||
|
||||
// OnCollectionPage
|
||||
func OnCollectionPage(it activitystreams.Item, fn withCollectionPageFn) error {
|
||||
if it.GetType() != activitystreams.CollectionPageType {
|
||||
func OnCollectionPage(it Item, fn withCollectionPageFn) error {
|
||||
if it.GetType() != CollectionPageType {
|
||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Collection Page", it, it.GetType()))
|
||||
}
|
||||
col, err := activitystreams.ToCollectionPage(it)
|
||||
col, err := ToCollectionPage(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -151,17 +150,17 @@ func OnCollectionPage(it activitystreams.Item, fn withCollectionPageFn) error {
|
|||
}
|
||||
|
||||
// OnOrderedCollection
|
||||
func OnOrderedCollection(it activitystreams.Item, fn withOrderedCollectionFn) error {
|
||||
func OnOrderedCollection(it Item, fn withOrderedCollectionFn) error {
|
||||
switch it.GetType() {
|
||||
case activitystreams.OrderedCollectionType:
|
||||
col, err := activitystreams.ToOrderedCollection(it)
|
||||
case OrderedCollectionType:
|
||||
col, err := ToOrderedCollection(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(col)
|
||||
case activitystreams.OrderedCollectionPageType:
|
||||
return OnOrderedCollectionPage(it, func(p *activitystreams.OrderedCollectionPage) error {
|
||||
col, err := activitystreams.ToOrderedCollection(&p.OrderedCollection)
|
||||
case OrderedCollectionPageType:
|
||||
return OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error {
|
||||
col, err := ToOrderedCollection(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -173,11 +172,11 @@ func OnOrderedCollection(it activitystreams.Item, fn withOrderedCollectionFn) er
|
|||
}
|
||||
|
||||
// OnOrderedCollectionPage
|
||||
func OnOrderedCollectionPage(it activitystreams.Item, fn withOrderedCollectionPageFn) error {
|
||||
if it.GetType() != activitystreams.OrderedCollectionPageType {
|
||||
func OnOrderedCollectionPage(it Item, fn withOrderedCollectionPageFn) error {
|
||||
if it.GetType() != OrderedCollectionPageType {
|
||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to OrderedCollection Page", it, it.GetType()))
|
||||
}
|
||||
col, err := activitystreams.ToOrderedCollectionPage(it)
|
||||
col, err := ToOrderedCollectionPage(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -185,11 +184,11 @@ func OnOrderedCollectionPage(it activitystreams.Item, fn withOrderedCollectionPa
|
|||
}
|
||||
|
||||
// OnOrderedCollectionPage
|
||||
func OnItemCOllection(it activitystreams.Item, fn withOrderedCollectionPageFn) error {
|
||||
if it.GetType() != activitystreams.OrderedCollectionPageType {
|
||||
func OnItemCOllection(it Item, fn withOrderedCollectionPageFn) error {
|
||||
if it.GetType() != OrderedCollectionPageType {
|
||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to OrderedCollection Page", it, it.GetType()))
|
||||
}
|
||||
col, err := activitystreams.ToOrderedCollectionPage(it)
|
||||
col, err := ToOrderedCollectionPage(it)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"github.com/go-ap/activitystreams"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOnObject(t *testing.T) {
|
||||
ob := activitystreams.ObjectNew(activitystreams.ArticleType)
|
||||
ob := ObjectNew(ArticleType)
|
||||
|
||||
err := OnObject(ob, func(o *Object) error {
|
||||
return nil
|
||||
|
@ -28,10 +27,10 @@ func TestOnObject(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOnActivity(t *testing.T) {
|
||||
ob := activitystreams.ObjectNew(activitystreams.ArticleType)
|
||||
act := activitystreams.ActivityNew("test", activitystreams.CreateType, ob)
|
||||
ob := ObjectNew(ArticleType)
|
||||
act := ActivityNew("test", CreateType, ob)
|
||||
|
||||
err := OnActivity(act, func(a *activitystreams.Activity) error {
|
||||
err := OnActivity(act, func(a *Activity) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -39,7 +38,7 @@ func TestOnActivity(t *testing.T) {
|
|||
t.Errorf("Unexpected error returned %s", err)
|
||||
}
|
||||
|
||||
err = OnActivity(act, func(a *activitystreams.Activity) error {
|
||||
err = OnActivity(act, func(a *Activity) error {
|
||||
if a.Type != act.Type {
|
||||
t.Errorf("In function type %s different than expected, %s", a.Type, act.Type)
|
||||
}
|
||||
|
@ -57,9 +56,9 @@ func TestOnActivity(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOnIntransitiveActivity(t *testing.T) {
|
||||
act := activitystreams.IntransitiveActivityNew("test", activitystreams.ArriveType)
|
||||
act := IntransitiveActivityNew("test", ArriveType)
|
||||
|
||||
err := OnIntransitiveActivity(act, func(a *activitystreams.IntransitiveActivity) error {
|
||||
err := OnIntransitiveActivity(act, func(a *IntransitiveActivity) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -67,7 +66,7 @@ func TestOnIntransitiveActivity(t *testing.T) {
|
|||
t.Errorf("Unexpected error returned %s", err)
|
||||
}
|
||||
|
||||
err = OnIntransitiveActivity(act, func(a *activitystreams.IntransitiveActivity) error {
|
||||
err = OnIntransitiveActivity(act, func(a *IntransitiveActivity) error {
|
||||
if a.Type != act.Type {
|
||||
t.Errorf("In function type %s different than expected, %s", a.Type, act.Type)
|
||||
}
|
||||
|
@ -82,9 +81,9 @@ func TestOnIntransitiveActivity(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOnQuestion(t *testing.T) {
|
||||
act := activitystreams.QuestionNew("test")
|
||||
act := QuestionNew("test")
|
||||
|
||||
err := OnQuestion(act, func(a *activitystreams.Question) error {
|
||||
err := OnQuestion(act, func(a *Question) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -92,7 +91,7 @@ func TestOnQuestion(t *testing.T) {
|
|||
t.Errorf("Unexpected error returned %s", err)
|
||||
}
|
||||
|
||||
err = OnQuestion(act, func(a *activitystreams.Question) error {
|
||||
err = OnQuestion(act, func(a *Question) error {
|
||||
if a.Type != act.Type {
|
||||
t.Errorf("In function type %s different than expected, %s", a.Type, act.Type)
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ func TestOnQuestion(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOnPerson(t *testing.T) {
|
||||
pers := activitystreams.PersonNew("testPerson")
|
||||
pers := PersonNew("testPerson")
|
||||
err := OnPerson(pers, func(a *Person) error {
|
||||
return nil
|
||||
})
|
||||
|
|
67
inbox.go
67
inbox.go
|
@ -1,9 +1,5 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
type (
|
||||
// InboxStream contains all activities received by the actor.
|
||||
// The server SHOULD filter content according to the requester's permission.
|
||||
|
@ -13,69 +9,18 @@ type (
|
|||
InboxStream = Inbox
|
||||
|
||||
// Inbox is a type alias for an Ordered Collection
|
||||
Inbox as.OrderedCollection
|
||||
Inbox = OrderedCollection
|
||||
)
|
||||
|
||||
// InboxNew initializes a new Inbox
|
||||
func InboxNew() *as.OrderedCollection {
|
||||
id := as.ObjectID("inbox")
|
||||
func InboxNew() *OrderedCollection {
|
||||
id := ObjectID("inbox")
|
||||
|
||||
i := as.OrderedCollection{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
i.Name = as.NaturalLanguageValuesNew()
|
||||
i.Content = as.NaturalLanguageValuesNew()
|
||||
i := OrderedCollection{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// Append adds an element to an Inbox
|
||||
func (i *Inbox) Append(ob as.Item) error {
|
||||
i.OrderedItems = append(i.OrderedItems, ob)
|
||||
i.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to Inbox
|
||||
func (i Inbox) GetID() *as.ObjectID {
|
||||
return i.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Inbox object
|
||||
func (i Inbox) GetLink() as.IRI {
|
||||
return as.IRI(i.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Inbox's type
|
||||
func (i Inbox) GetType() as.ActivityVocabularyType {
|
||||
return i.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Inbox object
|
||||
func (i Inbox) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Inbox object
|
||||
func (i Inbox) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (i *Inbox) UnmarshalJSON(data []byte) error {
|
||||
if as.ItemTyperFunc == nil {
|
||||
as.ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
c := as.OrderedCollection(*i)
|
||||
err := c.UnmarshalJSON(data)
|
||||
|
||||
*i = Inbox(c)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (i Inbox) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(i)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInboxNew(t *testing.T) {
|
||||
i := InboxNew()
|
||||
|
||||
id := as.ObjectID("inbox")
|
||||
id := ObjectID("inbox")
|
||||
if i.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", i, id, id)
|
||||
}
|
||||
|
@ -26,83 +24,3 @@ func TestInboxNew(t *testing.T) {
|
|||
t.Errorf("%T should be initialized with 0 TotalItems", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxStream_GetID(t *testing.T) {
|
||||
o := InboxStream{}
|
||||
if *o.GetID() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", o, o.GetID())
|
||||
}
|
||||
id := as.ObjectID("test_out_stream")
|
||||
o.ID = id
|
||||
if *o.GetID() != id {
|
||||
t.Errorf("%T should have %T as %q", o, id, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxStream_GetType(t *testing.T) {
|
||||
o := InboxStream{}
|
||||
|
||||
if o.GetType() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", o, o.GetType())
|
||||
}
|
||||
|
||||
o.Type = as.OrderedCollectionType
|
||||
if o.GetType() != as.OrderedCollectionType {
|
||||
t.Errorf("%T should have %T as %q", o, o.GetType(), as.OrderedCollectionType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxStream_Append(t *testing.T) {
|
||||
o := InboxStream{}
|
||||
|
||||
val := as.Object{ID: as.ObjectID("grrr")}
|
||||
|
||||
o.Append(val)
|
||||
if o.TotalItems != 1 {
|
||||
t.Errorf("%T should have exactly an element, found %d", o, o.TotalItems)
|
||||
}
|
||||
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
||||
}
|
||||
}
|
||||
func TestInbox_Append(t *testing.T) {
|
||||
i := InboxNew()
|
||||
|
||||
val := as.Object{ID: as.ObjectID("grrr")}
|
||||
|
||||
i.Append(val)
|
||||
if i.TotalItems != 0 {
|
||||
t.Errorf("%T should have exactly an element, found %d", i, i.TotalItems)
|
||||
}
|
||||
if !reflect.DeepEqual(i.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", i, i.OrderedItems, val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInbox_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInbox_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInbox_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInbox_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInbox_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInbox_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestInbox_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
290
intransitive_activity.go
Normal file
290
intransitive_activity.go
Normal file
|
@ -0,0 +1,290 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IntransitiveActivity Instances of IntransitiveActivity are a subtype of Activity representing intransitive actions.
|
||||
// The object property is therefore inappropriate for these activities.
|
||||
type IntransitiveActivity struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// CanReceiveActivities describes one or more entities that either performed or are expected to perform the activity.
|
||||
// Any single activity can have multiple actors. The actor may be specified using an indirect Link.
|
||||
Actor CanReceiveActivities `jsonld:"actor,omitempty"`
|
||||
// Target describes the indirect object, or target, of the activity.
|
||||
// The precise meaning of the target is largely dependent on the type of action being described
|
||||
// but will often be the object of the English preposition "to".
|
||||
// For instance, in the activity "John added a movie to his wishlist",
|
||||
// the target of the activity is John's wishlist. An activity can have more than one target.
|
||||
Target Item `jsonld:"target,omitempty"`
|
||||
// Result describes the result of the activity. For instance, if a particular action results in the creation
|
||||
// of a new resource, the result property can be used to describe that new resource.
|
||||
Result Item `jsonld:"result,omitempty"`
|
||||
// Origin describes an indirect object of the activity from which the activity is directed.
|
||||
// The precise meaning of the origin is the object of the English preposition "from".
|
||||
// For instance, in the activity "John moved an item to List B from List A", the origin of the activity is "List A".
|
||||
Origin Item `jsonld:"origin,omitempty"`
|
||||
// Instrument identifies one or more objects used (or to be used) in the completion of an Activity.
|
||||
Instrument Item `jsonld:"instrument,omitempty"`
|
||||
}
|
||||
|
||||
type (
|
||||
// Arrive is an IntransitiveActivity that indicates that the actor has arrived at the location.
|
||||
// The origin can be used to identify the context from which the actor originated.
|
||||
// The target typically has no defined meaning.
|
||||
Arrive = IntransitiveActivity
|
||||
|
||||
// Travel indicates that the actor is traveling to target from origin.
|
||||
// Travel is an IntransitiveObject whose actor specifies the direct object.
|
||||
// If the target or origin are not specified, either can be determined by context.
|
||||
Travel = IntransitiveActivity
|
||||
)
|
||||
|
||||
// Recipients performs recipient de-duplication on the Activity's To, Bto, CC and BCC properties
|
||||
func (i *IntransitiveActivity) Recipients() ItemCollection {
|
||||
actor := make(ItemCollection, 0)
|
||||
actor.Append(i.Actor)
|
||||
rec, _ := ItemCollectionDeduplication(&actor, &i.To, &i.Bto, &i.CC, &i.BCC, &i.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (i *IntransitiveActivity) Clean() {
|
||||
i.BCC = nil
|
||||
i.Bto = nil
|
||||
}
|
||||
|
||||
// GetType returns the ActivityVocabulary type of the current Intransitive Activity
|
||||
func (i IntransitiveActivity) GetType() ActivityVocabularyType {
|
||||
return i.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for Activity objects
|
||||
func (i IntransitiveActivity) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the IntransitiveActivity object
|
||||
func (i IntransitiveActivity) GetID() ObjectID {
|
||||
return i.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the IntransitiveActivity object
|
||||
func (i IntransitiveActivity) GetLink() IRI {
|
||||
return IRI(i.ID)
|
||||
}
|
||||
|
||||
// IsObject returns true for IntransitiveActivity objects
|
||||
func (i IntransitiveActivity) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for IntransitiveActivity objects
|
||||
func (i IntransitiveActivity) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (i *IntransitiveActivity) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
i.ID = JSONGetObjectID(data)
|
||||
i.Type = JSONGetType(data)
|
||||
i.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
i.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
i.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
i.Context = JSONGetItem(data, "context")
|
||||
i.URL = JSONGetURIItem(data, "url")
|
||||
i.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
i.Generator = JSONGetItem(data, "generator")
|
||||
i.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
i.Attachment = JSONGetItem(data, "attachment")
|
||||
i.Location = JSONGetItem(data, "location")
|
||||
i.Published = JSONGetTime(data, "published")
|
||||
i.StartTime = JSONGetTime(data, "startTime")
|
||||
i.EndTime = JSONGetTime(data, "endTime")
|
||||
i.Duration = JSONGetDuration(data, "duration")
|
||||
i.Icon = JSONGetItem(data, "icon")
|
||||
i.Preview = JSONGetItem(data, "preview")
|
||||
i.Image = JSONGetItem(data, "image")
|
||||
i.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
i.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
i.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
i.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
i.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
i.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
i.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
i.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
i.Tag = tag
|
||||
}
|
||||
i.Actor = JSONGetItem(data, "actor")
|
||||
i.Target = JSONGetItem(data, "target")
|
||||
i.Result = JSONGetItem(data, "result")
|
||||
i.Origin = JSONGetItem(data, "origin")
|
||||
i.Instrument = JSONGetItem(data, "instrument")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IntransitiveActivityNew initializes a intransitive activity
|
||||
func IntransitiveActivityNew(id ObjectID, typ ActivityVocabularyType) *IntransitiveActivity {
|
||||
if !IntransitiveActivityTypes.Contains(typ) {
|
||||
typ = IntransitiveActivityType
|
||||
}
|
||||
i := IntransitiveActivity{ID: id, Type: typ}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// ToIntransitiveActivity
|
||||
func ToIntransitiveActivity(it Item) (*IntransitiveActivity, error) {
|
||||
switch i := it.(type) {
|
||||
case *IntransitiveActivity:
|
||||
return i, nil
|
||||
case IntransitiveActivity:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to intransitive activity")
|
||||
}
|
||||
|
||||
// FlattenIntransitiveActivityProperties flattens the IntransitiveActivity's properties from Object type to IRI
|
||||
func FlattenIntransitiveActivityProperties(act *IntransitiveActivity) *IntransitiveActivity {
|
||||
act.Actor = Flatten(act.Actor)
|
||||
act.Target = Flatten(act.Target)
|
||||
act.Result = Flatten(act.Result)
|
||||
act.Origin = Flatten(act.Origin)
|
||||
act.Result = Flatten(act.Result)
|
||||
act.Instrument = Flatten(act.Instrument)
|
||||
return act
|
||||
}
|
||||
|
||||
// ArriveNew initializes an Arrive activity
|
||||
func ArriveNew(id ObjectID) *Arrive {
|
||||
a := IntransitiveActivityNew(id, ArriveType)
|
||||
o := Arrive(*a)
|
||||
return &o
|
||||
}
|
||||
|
||||
// TravelNew initializes a Travel activity
|
||||
func TravelNew(id ObjectID) *Travel {
|
||||
a := IntransitiveActivityNew(id, TravelType)
|
||||
o := Travel(*a)
|
||||
return &o
|
||||
}
|
264
intransitive_activity_test.go
Normal file
264
intransitive_activity_test.go
Normal file
|
@ -0,0 +1,264 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIntransitiveActivityNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
var testType ActivityVocabularyType = "Arrive"
|
||||
|
||||
a := IntransitiveActivityNew(testValue, testType)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("IntransitiveActivity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != testType {
|
||||
t.Errorf("IntransitiveActivity Type '%v' different than expected '%v'", a.Type, testType)
|
||||
}
|
||||
|
||||
g := IntransitiveActivityNew(testValue, "")
|
||||
|
||||
if g.ID != testValue {
|
||||
t.Errorf("IntransitiveActivity Id '%v' different than expected '%v'", g.ID, testValue)
|
||||
}
|
||||
if g.Type != IntransitiveActivityType {
|
||||
t.Errorf("IntransitiveActivity Type '%v' different than expected '%v'", g.Type, IntransitiveActivityType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntransitiveActivityRecipients(t *testing.T) {
|
||||
bob := PersonNew("bob")
|
||||
alice := PersonNew("alice")
|
||||
foo := OrganizationNew("foo")
|
||||
bar := GroupNew("bar")
|
||||
|
||||
a := IntransitiveActivityNew("test", "t")
|
||||
|
||||
a.To.Append(bob)
|
||||
a.To.Append(alice)
|
||||
a.To.Append(foo)
|
||||
a.To.Append(bar)
|
||||
if len(a.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
a.To.Append(bar)
|
||||
a.To.Append(alice)
|
||||
a.To.Append(foo)
|
||||
a.To.Append(bob)
|
||||
if len(a.To) != 8 {
|
||||
t.Errorf("%T.To should have exactly 8(eight) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
a.Recipients()
|
||||
if len(a.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", a, len(a.To))
|
||||
}
|
||||
|
||||
b := ActivityNew("t", "test", nil)
|
||||
|
||||
b.To.Append(bar)
|
||||
b.To.Append(alice)
|
||||
b.To.Append(foo)
|
||||
b.To.Append(bob)
|
||||
b.Bto.Append(bar)
|
||||
b.Bto.Append(alice)
|
||||
b.Bto.Append(foo)
|
||||
b.Bto.Append(bob)
|
||||
b.CC.Append(bar)
|
||||
b.CC.Append(alice)
|
||||
b.CC.Append(foo)
|
||||
b.CC.Append(bob)
|
||||
b.BCC.Append(bar)
|
||||
b.BCC.Append(alice)
|
||||
b.BCC.Append(foo)
|
||||
b.BCC.Append(bob)
|
||||
|
||||
b.Recipients()
|
||||
if len(b.To) != 4 {
|
||||
t.Errorf("%T.To should have exactly 4(four) elements, not %d", b, len(b.To))
|
||||
}
|
||||
if len(b.Bto) != 0 {
|
||||
t.Errorf("%T.Bto should have exactly 0(zero) elements, not %d", b, len(b.Bto))
|
||||
}
|
||||
if len(b.CC) != 0 {
|
||||
t.Errorf("%T.CC should have exactly 0(zero) elements, not %d", b, len(b.CC))
|
||||
}
|
||||
if len(b.BCC) != 0 {
|
||||
t.Errorf("%T.BCC should have exactly 0(zero) elements, not %d", b, len(b.BCC))
|
||||
}
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(b.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(b.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
func TestIntransitiveActivity_GetLink(t *testing.T) {
|
||||
i := IntransitiveActivityNew("test", QuestionType)
|
||||
|
||||
if i.GetID() != "test" {
|
||||
t.Errorf("%T should return an empty %T object. Received %#v", i, i, i)
|
||||
}
|
||||
}
|
||||
func TestIntransitiveActivity_GetObject(t *testing.T) {
|
||||
i := IntransitiveActivityNew("test", QuestionType)
|
||||
|
||||
if i.GetID() != "test" || i.GetType() != QuestionType {
|
||||
t.Errorf("%T should not return an empty %T object. Received %#v", i, i, i)
|
||||
}
|
||||
}
|
||||
func TestIntransitiveActivity_IsLink(t *testing.T) {
|
||||
i := IntransitiveActivityNew("test", QuestionType)
|
||||
|
||||
if i.IsLink() {
|
||||
t.Errorf("%T should not respond true to IsLink", i)
|
||||
}
|
||||
}
|
||||
func TestIntransitiveActivity_IsObject(t *testing.T) {
|
||||
i := IntransitiveActivityNew("test", ActivityType)
|
||||
|
||||
if !i.IsObject() {
|
||||
t.Errorf("%T should respond true to IsObject", i)
|
||||
}
|
||||
}
|
||||
func TestIntransitiveActivity_Recipients(t *testing.T) {
|
||||
to := PersonNew("bob")
|
||||
o := ObjectNew(ArticleType)
|
||||
cc := PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
|
||||
c := IntransitiveActivityNew("act", IntransitiveActivityType)
|
||||
c.To.Append(to)
|
||||
c.CC.Append(cc)
|
||||
c.BCC.Append(cc)
|
||||
|
||||
c.Recipients()
|
||||
|
||||
var err error
|
||||
recIds := make([]ObjectID, 0)
|
||||
err = checkDedup(c.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.Bto, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.CC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkDedup(c.BCC, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntransitiveActivity_GetID(t *testing.T) {
|
||||
a := IntransitiveActivityNew("test", IntransitiveActivityType)
|
||||
|
||||
if a.GetID() != "test" {
|
||||
t.Errorf("%T should return an empty %T object. Received %#v", a, a.GetID(), a.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntransitiveActivity_GetType(t *testing.T) {
|
||||
{
|
||||
a := IntransitiveActivityNew("test", IntransitiveActivityType)
|
||||
if a.GetType() != IntransitiveActivityType {
|
||||
t.Errorf("GetType should return %q for %T, received %q", IntransitiveActivityType, a, a.GetType())
|
||||
}
|
||||
}
|
||||
{
|
||||
a := IntransitiveActivityNew("test", ArriveType)
|
||||
if a.GetType() != ArriveType {
|
||||
t.Errorf("GetType should return %q for %T, received %q", ArriveType, a, a.GetType())
|
||||
}
|
||||
}
|
||||
{
|
||||
a := IntransitiveActivityNew("test", QuestionType)
|
||||
if a.GetType() != QuestionType {
|
||||
t.Errorf("GetType should return %q for %T, received %q", QuestionType, a, a.GetType())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToIntransitiveActivity(t *testing.T) {
|
||||
var it Item
|
||||
act := IntransitiveActivityNew("test", TravelType)
|
||||
it = act
|
||||
|
||||
a, err := ToIntransitiveActivity(it)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if a != act {
|
||||
t.Errorf("Invalid activity returned by ToActivity #%v", a)
|
||||
}
|
||||
|
||||
ob := ObjectNew(ArticleType)
|
||||
it = ob
|
||||
|
||||
o, err := ToIntransitiveActivity(it)
|
||||
if err == nil {
|
||||
t.Errorf("Error returned when calling ToActivity with object should not be nil")
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("Invalid return by ToActivity #%v, should have been nil", o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenIntransitiveActivityProperties(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestIntransitiveActivity_Clean(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestIntransitiveActivity_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestIntransitiveActivity_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestArriveNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := ArriveNew(testValue)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != ArriveType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, ArriveType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTravelNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := TravelNew(testValue)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != TravelType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, TravelType)
|
||||
}
|
||||
}
|
161
iri.go
Normal file
161
iri.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const PublicNS = IRI("https://www.w3.org/ns/activitystreams#Public")
|
||||
|
||||
type (
|
||||
// IRI is a Internationalized Resource Identifiers (IRIs) RFC3987
|
||||
IRI string
|
||||
IRIs []IRI
|
||||
)
|
||||
|
||||
// String returns the String value of the IRI object
|
||||
func (i IRI) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
// GetLink
|
||||
func (i IRI) GetLink() IRI {
|
||||
return i
|
||||
}
|
||||
|
||||
// URL
|
||||
func (i IRI) URL() (*url.URL, error) {
|
||||
return url.Parse(i.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (i *IRI) UnmarshalJSON(s []byte) error {
|
||||
*i = IRI(strings.Trim(string(s), "\""))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID
|
||||
func (i IRI) GetID() ObjectID {
|
||||
return ObjectID(i)
|
||||
}
|
||||
|
||||
// GetType
|
||||
func (i IRI) GetType() ActivityVocabularyType {
|
||||
return LinkType
|
||||
}
|
||||
|
||||
// IsLink
|
||||
func (i IRI) IsLink() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsObject
|
||||
func (i IRI) IsObject() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCollection returns false for IRI objects
|
||||
func (i IRI) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// FlattenToIRI checks if Item can be flatten to an IRI and returns it if so
|
||||
func FlattenToIRI(it Item) Item {
|
||||
if it != nil && it.IsObject() && len(it.GetLink()) > 0 {
|
||||
return it.GetLink()
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
// Contains verifies if IRIs array contains the received one
|
||||
func (i IRIs) Contains(r IRI) bool {
|
||||
if len(i) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, iri := range i {
|
||||
if r.Equals(iri, false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validURL(u *url.URL) bool {
|
||||
return len(u.Scheme) > 0 && len(u.Host) > 0
|
||||
}
|
||||
|
||||
// Equals verifies if our receiver IRI is equals with the "with" IRI
|
||||
// It ignores the protocol
|
||||
// It tries to use the URL representation if possible and fallback to string comparison if unable to convert
|
||||
// In URL representation checks hostname in a case insensitive way and the path and
|
||||
func (i IRI) Equals(with IRI, checkScheme bool) bool {
|
||||
u, e := i.URL()
|
||||
uw, ew := with.URL()
|
||||
if e != nil || ew != nil || !validURL(u) || !validURL(uw) {
|
||||
return strings.ToLower(i.String()) == strings.ToLower(with.String())
|
||||
}
|
||||
if checkScheme {
|
||||
if strings.ToLower(u.Scheme) != strings.ToLower(uw.Scheme) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if strings.ToLower(u.Host) != strings.ToLower(uw.Host) {
|
||||
return false
|
||||
}
|
||||
if path.Clean(u.Path) != path.Clean(uw.Path) {
|
||||
return false
|
||||
}
|
||||
uq := u.Query()
|
||||
uwq := uw.Query()
|
||||
if len(uq) != len(uwq) {
|
||||
return false
|
||||
}
|
||||
for k, uqv := range uq {
|
||||
uwqv, ok := uwq[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(uqv) != len(uwqv) {
|
||||
return false
|
||||
}
|
||||
for _, uqvv := range uqv {
|
||||
eq := false
|
||||
for _, uwqvv := range uwqv {
|
||||
if uwqvv == uqvv {
|
||||
eq = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !eq {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (i IRI) Contains(what IRI, checkScheme bool) bool {
|
||||
u, e := i.URL()
|
||||
uw, ew := what.URL()
|
||||
if e != nil || ew != nil {
|
||||
return strings.Contains(i.String(), what.String())
|
||||
}
|
||||
if checkScheme {
|
||||
if u.Scheme != uw.Scheme {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if u.Host != uw.Host {
|
||||
return false
|
||||
}
|
||||
p := u.Path
|
||||
if p != "" {
|
||||
p = path.Clean(p)
|
||||
}
|
||||
pw := uw.Path
|
||||
if pw != "" {
|
||||
pw = path.Clean(pw)
|
||||
}
|
||||
return strings.Contains(p, pw)
|
||||
}
|
212
iri_test.go
Normal file
212
iri_test.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIRI_GetLink(t *testing.T) {
|
||||
val := "http://example.com"
|
||||
u := IRI(val)
|
||||
if u.GetLink() != IRI(val) {
|
||||
t.Errorf("IRI %q should equal %q", u, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRI_String(t *testing.T) {
|
||||
val := "http://example.com"
|
||||
u := IRI(val)
|
||||
if u.String() != val {
|
||||
t.Errorf("IRI %q should equal %q", u, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRI_GetID(t *testing.T) {
|
||||
i := IRI("http://example.com")
|
||||
if id := i.GetID(); !id.IsValid() || id != ObjectID(i) {
|
||||
t.Errorf("ObjectID %q (%T) should equal %q (%T)", id, id, i, ObjectID(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRI_GetType(t *testing.T) {
|
||||
i := IRI("http://example.com")
|
||||
if i.GetType() != LinkType {
|
||||
t.Errorf("Invalid type for %T object %s, expected %s", i, i.GetType(), LinkType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRI_IsLink(t *testing.T) {
|
||||
i := IRI("http://example.com")
|
||||
if i.IsLink() != true {
|
||||
t.Errorf("%T.IsLink() returned %t, expected %t", i, i.IsLink(), true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRI_IsObject(t *testing.T) {
|
||||
i := IRI("http://example.com")
|
||||
if i.IsObject() != false {
|
||||
t.Errorf("%T.IsObject() returned %t, expected %t", i, i.IsObject(), false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIRI_UnmarshalJSON(t *testing.T) {
|
||||
val := "http://example.com"
|
||||
i := IRI("")
|
||||
|
||||
err := i.UnmarshalJSON([]byte(val))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if val != i.String() {
|
||||
t.Errorf("%T invalid value after Unmarshal %q, expected %q", i, i, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenToIRI(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestIRI_URL(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestIRIs_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestIRI_Equals(t *testing.T) {
|
||||
{
|
||||
i1 := IRI("http://example.com")
|
||||
i2 := IRI("http://example.com")
|
||||
// same host same scheme
|
||||
if !i1.Equals(i2, true) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com/ana/are/mere")
|
||||
i2 := IRI("http://example.com/ana/are/mere")
|
||||
// same host, same scheme and same path
|
||||
if !i1.Equals(i2, true) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("https://example.com")
|
||||
i2 := IRI("http://example.com")
|
||||
// same host different scheme
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com/ana/are/mere")
|
||||
i2 := IRI("https://example.com/ana/are/mere")
|
||||
// same host, different scheme and same path
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("https://example.com?ana=mere")
|
||||
i2 := IRI("http://example.com?ana=mere")
|
||||
// same host different scheme, same query
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("https://example.com?ana=mere&foo=bar")
|
||||
i2 := IRI("http://example.com?foo=bar&ana=mere")
|
||||
// same host different scheme, same query - different order
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com/ana/are/mere?foo=bar&ana=mere")
|
||||
i2 := IRI("https://example.com/ana/are/mere?ana=mere&foo=bar")
|
||||
// same host, different scheme and same path, same query different order
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("https://example.com?ana=mere")
|
||||
i2 := IRI("http://example.com?ana=mere")
|
||||
// same host different scheme, same query
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("https://example.com?ana=mere&foo=bar")
|
||||
i2 := IRI("http://example.com?foo=bar&ana=mere")
|
||||
// same host different scheme, same query - different order
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com/ana/are/mere?foo=bar&ana=mere")
|
||||
i2 := IRI("https://example.com/ana/are/mere?ana=mere&foo=bar")
|
||||
// same host, different scheme and same path, same query different order
|
||||
if !i1.Equals(i2, false) {
|
||||
t.Errorf("%s should equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
///
|
||||
{
|
||||
i1 := IRI("http://example.com")
|
||||
i2 := IRI("https://example.com")
|
||||
// same host different scheme
|
||||
if i1.Equals(i2, true) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example1.com")
|
||||
i2 := IRI("http://example.com")
|
||||
// different host same scheme
|
||||
if i1.Equals(i2, true) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com/ana/1are/mere")
|
||||
i2 := IRI("http://example.com/ana/are/mere")
|
||||
// same host, same scheme and different path
|
||||
if i1.Equals(i2, true) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com?ana1=mere")
|
||||
i2 := IRI("http://example.com?ana=mere")
|
||||
// same host same scheme, different query key
|
||||
if i1.Equals(i2, false) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com?ana=mere")
|
||||
i2 := IRI("http://example.com?ana=mere1")
|
||||
// same host same scheme, different query value
|
||||
if i1.Equals(i2, false) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("https://example.com?ana=mere&foo=bar")
|
||||
i2 := IRI("http://example.com?foo=bar1&ana=mere")
|
||||
// same host different scheme, different query value - different order
|
||||
if i1.Equals(i2, false) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
{
|
||||
i1 := IRI("http://example.com/ana/are/mere?foo=bar&ana=mere")
|
||||
i2 := IRI("https://example.com/ana/are/mere?ana=mere&foo1=bar")
|
||||
// same host, different scheme and same path, differnt query key different order
|
||||
if i1.Equals(i2, false) {
|
||||
t.Errorf("%s should not equal %s", i1, i2)
|
||||
}
|
||||
}
|
||||
}
|
84
item.go
Normal file
84
item.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package activitypub
|
||||
|
||||
// ItemCollection represents an array of items
|
||||
type ItemCollection []Item
|
||||
|
||||
// Item struct
|
||||
type Item ObjectOrLink
|
||||
|
||||
const EmptyObjectID = ObjectID("")
|
||||
const EmptyIRI = IRI("")
|
||||
|
||||
// GetID returns the ObjectID corresponding to ItemCollection
|
||||
func (i ItemCollection) GetID() ObjectID {
|
||||
return EmptyObjectID
|
||||
}
|
||||
|
||||
// GetLink returns the empty IRI
|
||||
func (i ItemCollection) GetLink() IRI {
|
||||
return EmptyIRI
|
||||
}
|
||||
|
||||
// GetType returns the ItemCollection's type
|
||||
func (i ItemCollection) GetType() ActivityVocabularyType {
|
||||
return CollectionOfItems
|
||||
}
|
||||
|
||||
// IsLink returns false for an ItemCollection object
|
||||
func (i ItemCollection) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a ItemCollection object
|
||||
func (i ItemCollection) IsObject() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Append facilitates adding elements to Item arrays
|
||||
// and ensures ItemCollection implements the Collection interface
|
||||
func (i *ItemCollection) Append(o Item) error {
|
||||
oldLen := len(*i)
|
||||
d := make(ItemCollection, oldLen+1)
|
||||
for k, it := range *i {
|
||||
d[k] = it
|
||||
}
|
||||
d[oldLen] = o
|
||||
*i = d
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count returns the length of Items in the item collection
|
||||
func (i *ItemCollection) Count() uint {
|
||||
return uint(len(*i))
|
||||
}
|
||||
|
||||
// First returns the ObjectID corresponding to ItemCollection
|
||||
func (i ItemCollection) First() Item {
|
||||
if len(i) == 0 {
|
||||
return nil
|
||||
}
|
||||
return i[0]
|
||||
}
|
||||
|
||||
// Collection returns the current object as collection interface
|
||||
func (i *ItemCollection) Collection() ItemCollection {
|
||||
return *i
|
||||
}
|
||||
|
||||
// IsCollection returns true for ItemCollection arrays
|
||||
func (i ItemCollection) IsCollection() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Contains verifies if IRIs array contains the received one
|
||||
func (i ItemCollection) Contains(r IRI) bool {
|
||||
if len(i) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, iri := range i {
|
||||
if r.Equals(iri.GetLink(), false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
47
item_test.go
Normal file
47
item_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestItemCollection_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_First(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFlattenItemCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_Count(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollection_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
92
liked.go
92
liked.go
|
@ -1,103 +1,25 @@
|
|||
package activitypub
|
||||
|
||||
import as "github.com/go-ap/activitystreams"
|
||||
|
||||
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
|
||||
LikedCollection = Liked
|
||||
|
||||
// Liked is a type alias for an Ordered Collection
|
||||
Liked as.OrderedCollection
|
||||
Liked = OrderedCollection
|
||||
)
|
||||
|
||||
// LikedCollection initializes a new Outbox
|
||||
func LikedNew() *as.OrderedCollection {
|
||||
id := as.ObjectID("liked")
|
||||
func LikedNew() *OrderedCollection {
|
||||
id := ObjectID("liked")
|
||||
|
||||
l := as.OrderedCollection{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
l.Name = as.NaturalLanguageValuesNew()
|
||||
l.Content = as.NaturalLanguageValuesNew()
|
||||
l := OrderedCollection{ID: id, Type: CollectionType}
|
||||
l.Name = NaturalLanguageValuesNew()
|
||||
l.Content = NaturalLanguageValuesNew()
|
||||
|
||||
l.TotalItems = 0
|
||||
|
||||
return &l
|
||||
}
|
||||
|
||||
// Append adds an element to an LikedCollection
|
||||
func (l *LikedCollection) Append(o as.Item) error {
|
||||
l.OrderedItems = append(l.OrderedItems, o)
|
||||
l.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append adds an element to an Outbox
|
||||
func (l *Liked) Append(ob as.Item) error {
|
||||
l.OrderedItems = append(l.OrderedItems, ob)
|
||||
l.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the LikedCollection
|
||||
func (l LikedCollection) GetID() *as.ObjectID {
|
||||
return l.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current LikedCollection object
|
||||
func (l LikedCollection) GetLink() as.IRI {
|
||||
return as.IRI(l.ID)
|
||||
}
|
||||
|
||||
// GetType returns the LikedCollection's type
|
||||
func (l LikedCollection) GetType() as.ActivityVocabularyType {
|
||||
return l.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an LikedCollection object
|
||||
func (l LikedCollection) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a LikedCollection object
|
||||
func (l LikedCollection) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the Liked
|
||||
func (l Liked) GetID() *as.ObjectID {
|
||||
return l.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Liked object
|
||||
func (l Liked) GetLink() as.IRI {
|
||||
return as.IRI(l.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Liked's type
|
||||
func (l Liked) GetType() as.ActivityVocabularyType {
|
||||
return l.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Liked object
|
||||
func (l Liked) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Liked object
|
||||
func (l Liked) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (l Liked) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(l)
|
||||
return &c
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (l LikedCollection) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(l)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLikedNew(t *testing.T) {
|
||||
l := LikedNew()
|
||||
|
||||
id := as.ObjectID("liked")
|
||||
id := ObjectID("liked")
|
||||
if l.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", l, id, id)
|
||||
}
|
||||
|
@ -26,96 +24,3 @@ func TestLikedNew(t *testing.T) {
|
|||
t.Errorf("%T should be initialized with 0 TotalItems", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikedCollection_GetID(t *testing.T) {
|
||||
l := LikedCollection{}
|
||||
if *l.GetID() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", l, l.GetID())
|
||||
}
|
||||
id := as.ObjectID("test_out_stream")
|
||||
l.ID = id
|
||||
if *l.GetID() != id {
|
||||
t.Errorf("%T should have %T as %q", l, id, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikedCollection_GetType(t *testing.T) {
|
||||
l := LikedCollection{}
|
||||
|
||||
if l.GetType() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", l, l.GetType())
|
||||
}
|
||||
|
||||
l.Type = as.OrderedCollectionType
|
||||
if l.GetType() != as.OrderedCollectionType {
|
||||
t.Errorf("%T should have %T as %q", l, l.GetType(), as.OrderedCollectionType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLikedCollection_Append(t *testing.T) {
|
||||
l := LikedCollection{}
|
||||
|
||||
val := as.Object{ID: as.ObjectID("grrr")}
|
||||
|
||||
l.Append(val)
|
||||
if l.TotalItems != 1 {
|
||||
t.Errorf("%T should have exactly an element, found %d", l, l.TotalItems)
|
||||
}
|
||||
if !reflect.DeepEqual(l.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", l, l.OrderedItems, val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiked_Append(t *testing.T) {
|
||||
l := LikedNew()
|
||||
|
||||
val := as.Object{ID: as.ObjectID("grrr")}
|
||||
|
||||
l.Append(val)
|
||||
if l.TotalItems != 0 {
|
||||
t.Errorf("%T should have exactly an element, found %d", l, l.TotalItems)
|
||||
}
|
||||
if !reflect.DeepEqual(l.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", l, l.OrderedItems, val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiked_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLiked_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLiked_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLiked_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLiked_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLiked_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikedCollection_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikedCollection_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikedCollection_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikedCollection_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
90
likes.go
90
likes.go
|
@ -1,103 +1,25 @@
|
|||
package activitypub
|
||||
|
||||
import as "github.com/go-ap/activitystreams"
|
||||
|
||||
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
|
||||
LikesCollection = Likes
|
||||
|
||||
// Likes is a type alias for an Ordered Collection
|
||||
Likes as.OrderedCollection
|
||||
Likes = OrderedCollection
|
||||
)
|
||||
|
||||
// LikesCollection initializes a new Outbox
|
||||
func LikesNew() *Likes {
|
||||
id := as.ObjectID("likes")
|
||||
id := ObjectID("likes")
|
||||
|
||||
l := Likes{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
l.Name = as.NaturalLanguageValuesNew()
|
||||
l.Content = as.NaturalLanguageValuesNew()
|
||||
l := Likes{ID: id, Type: CollectionType}
|
||||
l.Name = NaturalLanguageValuesNew()
|
||||
l.Content = NaturalLanguageValuesNew()
|
||||
|
||||
l.TotalItems = 0
|
||||
|
||||
return &l
|
||||
}
|
||||
|
||||
// Append adds an element to an LikesCollection
|
||||
func (l *LikesCollection) Append(o as.Item) error {
|
||||
l.OrderedItems = append(l.OrderedItems, o)
|
||||
l.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append adds an element to an Outbox
|
||||
func (l *Likes) Append(ob as.Item) error {
|
||||
l.OrderedItems = append(l.OrderedItems, ob)
|
||||
l.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the LikesCollection
|
||||
func (l LikesCollection) GetID() *as.ObjectID {
|
||||
return l.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current LikesCollection object
|
||||
func (l LikesCollection) GetLink() as.IRI {
|
||||
return as.IRI(l.ID)
|
||||
}
|
||||
|
||||
// GetType returns the LikesCollection's type
|
||||
func (l LikesCollection) GetType() as.ActivityVocabularyType {
|
||||
return l.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an LikesCollection object
|
||||
func (l LikesCollection) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a LikesCollection object
|
||||
func (l LikesCollection) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the Likes
|
||||
func (l Likes) GetID() *as.ObjectID {
|
||||
return l.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Likes object
|
||||
func (l Likes) GetLink() as.IRI {
|
||||
return as.IRI(l.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Likes's type
|
||||
func (l Likes) GetType() as.ActivityVocabularyType {
|
||||
return l.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Likes object
|
||||
func (l Likes) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Likes object
|
||||
func (l Likes) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (l Likes) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(l)
|
||||
return &c
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (l LikesCollection) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(l)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -2,62 +2,6 @@ package activitypub
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestLikes_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikes_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikes_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikes_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikes_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikes_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikes_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesCollection_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLikesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
122
link.go
Normal file
122
link.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package activitypub
|
||||
|
||||
var validLinkTypes = [...]ActivityVocabularyType{
|
||||
MentionType,
|
||||
}
|
||||
|
||||
// A Link is an indirect, qualified reference to a resource identified by a URL.
|
||||
// The fundamental model for links is established by [ RFC5988].
|
||||
// Many of the properties defined by the Activity Vocabulary allow values that are either instances of APObject or Link.
|
||||
// When a Link is used, it establishes a qualified relation connecting the subject
|
||||
// (the containing object) to the resource identified by the href.
|
||||
// Properties of the Link are properties of the reference as opposed to properties of the resource.
|
||||
type Link struct {
|
||||
// Provides the globally unique identifier for an APObject or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Identifies the APObject or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// A simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// A link relation associated with a Link. The value must conform to both the [HTML5] and
|
||||
// [RFC5988](https://tools.ietf.org/html/rfc5988) "link relation" definitions.
|
||||
// In the [HTML5], any string not containing the "space" U+0020, "tab" (U+0009), "LF" (U+000A),
|
||||
// "FF" (U+000C), "CR" (U+000D) or "," (U+002C) characters can be used as a valid link relation.
|
||||
Rel IRI `jsonld:"rel,omitempty"`
|
||||
// When used on a Link, identifies the MIME media type of the referenced resource.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// On a Link, specifies a hint as to the rendering height in device-independent pixels of the linked resource.
|
||||
Height uint `jsonld:"height,omitempty"`
|
||||
// On a Link, specifies a hint as to the rendering width in device-independent pixels of the linked resource.
|
||||
Width uint `jsonld:"width,omitempty"`
|
||||
// Identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// The target resource pointed to by a Link.
|
||||
Href IRI `jsonld:"href,omitempty"`
|
||||
// Hints as to the language used by the target resource.
|
||||
// Value must be a [BCP47](https://tools.ietf.org/html/bcp47) Language-Tag.
|
||||
HrefLang LangRef `jsonld:"hrefLang,omitempty"`
|
||||
}
|
||||
|
||||
// Mention is a specialized Link that represents an @mention.
|
||||
type Mention = Link
|
||||
|
||||
// ValidLinkType validates a type against the valid link types
|
||||
func ValidLinkType(typ ActivityVocabularyType) bool {
|
||||
for _, v := range validLinkTypes {
|
||||
if v == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LinkNew initializes a new Link
|
||||
func LinkNew(id ObjectID, typ ActivityVocabularyType) *Link {
|
||||
if !ValidLinkType(typ) {
|
||||
typ = LinkType
|
||||
}
|
||||
return &Link{ID: id, Type: typ}
|
||||
}
|
||||
|
||||
// MentionNew initializes a new Mention
|
||||
func MentionNew(id ObjectID) *Mention {
|
||||
return &Mention{ID: id, Type: MentionType}
|
||||
}
|
||||
|
||||
// IsLink validates if current Link is a Link
|
||||
func (l Link) IsLink() bool {
|
||||
return l.Type == LinkType || ValidLinkType(l.Type)
|
||||
}
|
||||
|
||||
// IsObject validates if current Link is an GetID
|
||||
func (l Link) IsObject() bool {
|
||||
return l.Type == ObjectType || ObjectTypes.Contains(l.Type)
|
||||
}
|
||||
|
||||
// IsCollection returns false for Link objects
|
||||
func (l Link) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the Link object
|
||||
func (l Link) GetID() ObjectID {
|
||||
return l.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Link
|
||||
func (l Link) GetLink() IRI {
|
||||
return IRI(l.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Type corresponding to the Mention object
|
||||
func (l Link) GetType() ActivityVocabularyType {
|
||||
return l.Type
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (l *Link) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
l.ID = JSONGetObjectID(data)
|
||||
l.Type = JSONGetType(data)
|
||||
l.MediaType = JSONGetMimeType(data)
|
||||
l.Preview = JSONGetItem(data, "preview")
|
||||
l.Height = uint(JSONGetInt(data, "height"))
|
||||
l.Width = uint(JSONGetInt(data, "width"))
|
||||
l.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
l.HrefLang = JSONGetLangRefField(data, "hrefLang")
|
||||
href := JSONGetURIItem(data, "href")
|
||||
if href != nil && !href.IsObject() {
|
||||
l.Href = href.GetLink()
|
||||
}
|
||||
rel := JSONGetURIItem(data, "rel")
|
||||
if rel != nil && !rel.IsObject() {
|
||||
l.Rel = rel.GetLink()
|
||||
}
|
||||
|
||||
//fmt.Printf("%s\n %#v", data, l)
|
||||
|
||||
return nil
|
||||
}
|
81
link_test.go
Normal file
81
link_test.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLinkNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
var testType ActivityVocabularyType
|
||||
|
||||
l := LinkNew(testValue, testType)
|
||||
|
||||
if l.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", l.ID, testValue)
|
||||
}
|
||||
if l.Type != LinkType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", l.Type, LinkType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidLinkType(t *testing.T) {
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ValidLinkType(LinkType) {
|
||||
t.Errorf("Generic Link Type '%v' should not be valid", LinkType)
|
||||
}
|
||||
if ValidLinkType(invalidType) {
|
||||
t.Errorf("Link Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range validLinkTypes {
|
||||
if !ValidLinkType(validType) {
|
||||
t.Errorf("Link Type '%v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLink_IsLink(t *testing.T) {
|
||||
l := LinkNew("test", LinkType)
|
||||
if !l.IsLink() {
|
||||
t.Errorf("%#v should be a valid link", l.Type)
|
||||
}
|
||||
m := LinkNew("test", MentionType)
|
||||
if !m.IsLink() {
|
||||
t.Errorf("%#v should be a valid link", m.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLink_IsObject(t *testing.T) {
|
||||
l := LinkNew("test", LinkType)
|
||||
if l.IsObject() {
|
||||
t.Errorf("%#v should not be a valid object", l.Type)
|
||||
}
|
||||
m := LinkNew("test", MentionType)
|
||||
if m.IsObject() {
|
||||
t.Errorf("%#v should not be a valid object", m.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLink_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLink_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLink_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLink_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestMentionNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLink_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
808
object.go
808
object.go
|
@ -1,37 +1,772 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/buger/jsonparser"
|
||||
as "github.com/go-ap/activitystreams"
|
||||
json "github.com/go-ap/jsonld"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ObjectID designates an unique global identifier.
|
||||
// All Objects in [ActivityStreams] should have unique global identifiers.
|
||||
// ActivityPub extends this requirement; all objects distributed by the ActivityPub protocol MUST
|
||||
// have unique global identifiers, unless they are intentionally transient
|
||||
// (short lived activities that are not intended to be able to be looked up,
|
||||
// such as some kinds of chat messages or game notifications).
|
||||
// These identifiers must fall into one of the following groups:
|
||||
//
|
||||
// 1. Publicly dereferenceable URIs, such as HTTPS URIs, with their authority belonging
|
||||
// to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
|
||||
// 2. An ID explicitly specified as the JSON null object, which implies an anonymous object
|
||||
// (a part of its parent context)
|
||||
type ObjectID IRI
|
||||
|
||||
const (
|
||||
// ActivityBaseURI the basic URI for the activity streams namespaces
|
||||
ActivityBaseURI = IRI("https://www.w3.org/ns/activitystreams")
|
||||
ObjectType ActivityVocabularyType = "Object"
|
||||
LinkType ActivityVocabularyType = "Link"
|
||||
ActivityType ActivityVocabularyType = "Activity"
|
||||
IntransitiveActivityType ActivityVocabularyType = "IntransitiveActivity"
|
||||
ActorType ActivityVocabularyType = "Actor"
|
||||
CollectionType ActivityVocabularyType = "Collection"
|
||||
OrderedCollectionType ActivityVocabularyType = "OrderedCollection"
|
||||
CollectionPageType ActivityVocabularyType = "CollectionPage"
|
||||
OrderedCollectionPageType ActivityVocabularyType = "OrderedCollectionPage"
|
||||
|
||||
// Activity Pub Object Types
|
||||
ArticleType ActivityVocabularyType = "Article"
|
||||
AudioType ActivityVocabularyType = "Audio"
|
||||
DocumentType ActivityVocabularyType = "Document"
|
||||
EventType ActivityVocabularyType = "Event"
|
||||
ImageType ActivityVocabularyType = "Image"
|
||||
NoteType ActivityVocabularyType = "Note"
|
||||
PageType ActivityVocabularyType = "Page"
|
||||
PlaceType ActivityVocabularyType = "Place"
|
||||
ProfileType ActivityVocabularyType = "Profile"
|
||||
RelationshipType ActivityVocabularyType = "Relationship"
|
||||
TombstoneType ActivityVocabularyType = "Tombstone"
|
||||
VideoType ActivityVocabularyType = "Video"
|
||||
|
||||
// MentionType is a link type for @mentions
|
||||
MentionType ActivityVocabularyType = "Mention"
|
||||
)
|
||||
|
||||
const (
|
||||
NilLangRef LangRef = "-"
|
||||
)
|
||||
|
||||
var GenericObjectTypes = ActivityVocabularyTypes{
|
||||
ActivityType,
|
||||
IntransitiveActivityType,
|
||||
ObjectType,
|
||||
ActorType,
|
||||
CollectionType,
|
||||
OrderedCollectionType,
|
||||
}
|
||||
|
||||
var GenericLinkTypes = ActivityVocabularyTypes{
|
||||
LinkType,
|
||||
}
|
||||
|
||||
var GenericTypes = append(GenericObjectTypes[:], GenericLinkTypes[:]...)
|
||||
|
||||
var ObjectTypes = ActivityVocabularyTypes{
|
||||
ArticleType,
|
||||
AudioType,
|
||||
DocumentType,
|
||||
EventType,
|
||||
ImageType,
|
||||
NoteType,
|
||||
PageType,
|
||||
PlaceType,
|
||||
ProfileType,
|
||||
RelationshipType,
|
||||
TombstoneType,
|
||||
VideoType,
|
||||
}
|
||||
|
||||
type (
|
||||
// ActivityVocabularyType is the data type for an Activity type object
|
||||
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 {
|
||||
// GetID returns the dereferenceable ActivityStreams object id
|
||||
GetID() ObjectID
|
||||
// GetType returns the ActivityStreams type
|
||||
GetType() ActivityVocabularyType
|
||||
}
|
||||
// LinkOrIRI is an interface that Object and Link structs implement, and at the same time
|
||||
// they are kept disjointed
|
||||
LinkOrIRI interface {
|
||||
// GetLink returns the object id in IRI type
|
||||
GetLink() IRI
|
||||
}
|
||||
// Item describes the requirements of an ActivityStreams object
|
||||
ObjectOrLink interface {
|
||||
ActivityObject
|
||||
LinkOrIRI
|
||||
// IsLink shows if current item represents a Link object or an IRI
|
||||
IsLink() bool
|
||||
// IsObject shows if current item represents an ActivityStreams object
|
||||
IsObject() bool
|
||||
// IsCollection shows if the current item represents an ItemCollection
|
||||
IsCollection() bool
|
||||
}
|
||||
// Mapper interface allows external objects to implement their own mechanism for loading information
|
||||
// from an ActivityStreams vocabulary object
|
||||
Mapper interface {
|
||||
// FromActivityStreams maps an ActivityStreams object to another struct representation
|
||||
FromActivityStreams(Item) error
|
||||
}
|
||||
|
||||
// MimeType is the type for representing MIME types in certain ActivityStreams properties
|
||||
MimeType string
|
||||
// LangRef is the type for a language reference code, should be an ISO639-1 language specifier.
|
||||
LangRef string
|
||||
|
||||
// LangRefValue is a type for storing per language values
|
||||
LangRefValue struct {
|
||||
Ref LangRef
|
||||
Value string
|
||||
}
|
||||
// NaturalLanguageValues is a mapping for multiple language values
|
||||
NaturalLanguageValues []LangRefValue
|
||||
)
|
||||
|
||||
func NaturalLanguageValuesNew() NaturalLanguageValues {
|
||||
return make(NaturalLanguageValues, 0)
|
||||
}
|
||||
|
||||
func (n NaturalLanguageValues) String() string {
|
||||
cnt := len(n)
|
||||
if cnt == 1 {
|
||||
return n[0].String()
|
||||
}
|
||||
s := strings.Builder{}
|
||||
s.Write([]byte{'['})
|
||||
for k, v := range n {
|
||||
s.WriteString(v.String())
|
||||
if k != cnt-1 {
|
||||
s.Write([]byte{','})
|
||||
}
|
||||
}
|
||||
s.Write([]byte{']'})
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (n NaturalLanguageValues) Get(ref LangRef) string {
|
||||
for _, val := range n {
|
||||
if val.Ref == ref {
|
||||
return val.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Set sets a language, value pair in a NaturalLanguageValues array
|
||||
func (n *NaturalLanguageValues) Set(ref LangRef, v string) error {
|
||||
found := false
|
||||
for k, vv := range *n {
|
||||
if vv.Ref == ref {
|
||||
(*n)[k] = LangRefValue{ref, v}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
n.Append(ref, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the NaturalLanguageValues into JSON
|
||||
func (n NaturalLanguageValues) MarshalJSON() ([]byte, error) {
|
||||
if len(n) == 0 {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
if len(n) == 1 {
|
||||
v := n[0]
|
||||
if v.Ref == NilLangRef {
|
||||
return v.MarshalJSON()
|
||||
}
|
||||
}
|
||||
mm := make(map[LangRef]string)
|
||||
for _, val := range n {
|
||||
mm[val.Ref] = val.Value
|
||||
}
|
||||
|
||||
return json.Marshal(mm)
|
||||
}
|
||||
|
||||
// First returns the first element in the array
|
||||
func (n NaturalLanguageValues) First() LangRefValue {
|
||||
for _, v := range n {
|
||||
return v
|
||||
}
|
||||
return LangRefValue{}
|
||||
}
|
||||
|
||||
// MarshalText serializes the NaturalLanguageValues into Text
|
||||
func (n NaturalLanguageValues) MarshalText() ([]byte, error) {
|
||||
for _, v := range n {
|
||||
return []byte(fmt.Sprintf("%q", v)), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Append is syntactic sugar for resizing the NaturalLanguageValues map
|
||||
// and appending an element
|
||||
func (n *NaturalLanguageValues) Append(lang LangRef, value string) error {
|
||||
var t NaturalLanguageValues
|
||||
if len(*n) == 0 {
|
||||
t = make(NaturalLanguageValues, 0)
|
||||
} else {
|
||||
t = *n
|
||||
}
|
||||
t = append(*n, LangRefValue{lang, value})
|
||||
*n = t
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count returns the length of Items in the item collection
|
||||
func (n *NaturalLanguageValues) Count() uint {
|
||||
return uint(len(*n))
|
||||
}
|
||||
|
||||
// String adds support for Stringer interface. It returns the Value[LangRef] text or just Value if LangRef is NIL
|
||||
func (l LangRefValue) String() string {
|
||||
if l.Ref == NilLangRef {
|
||||
return l.Value
|
||||
}
|
||||
return fmt.Sprintf("%s[%s]", l.Value, l.Ref)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the JsonEncoder interface
|
||||
func (l *LangRefValue) UnmarshalJSON(data []byte) error {
|
||||
_, typ, _, err := jsonparser.Get(data)
|
||||
if err != nil {
|
||||
l.Ref = NilLangRef
|
||||
l.Value = string(unescape(data))
|
||||
return nil
|
||||
}
|
||||
switch typ {
|
||||
case jsonparser.Object:
|
||||
jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
|
||||
l.Ref = LangRef(key)
|
||||
l.Value = string(unescape(value))
|
||||
return err
|
||||
})
|
||||
case jsonparser.String:
|
||||
l.Ref = NilLangRef
|
||||
l.Value = string(unescape(data))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the TextEncoder interface
|
||||
func (l *LangRefValue) UnmarshalText(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the LangRefValue into JSON
|
||||
func (l LangRefValue) MarshalJSON() ([]byte, error) {
|
||||
buf := bytes.Buffer{}
|
||||
if l.Ref != NilLangRef {
|
||||
if l.Value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
buf.WriteString(string(l.Ref))
|
||||
buf.Write([]byte{'"', ':'})
|
||||
}
|
||||
l.Value = string(unescape([]byte(l.Value)))
|
||||
buf.WriteString(strconv.Quote(l.Value))
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// MarshalText serializes the LangRefValue into JSON
|
||||
func (l LangRefValue) MarshalText() ([]byte, error) {
|
||||
if l.Ref != NilLangRef && l.Value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(l.Value)
|
||||
if l.Ref != NilLangRef {
|
||||
buf.WriteByte('[')
|
||||
buf.WriteString(string(l.Ref))
|
||||
buf.WriteByte(']')
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the JsonEncoder interface
|
||||
func (l *LangRef) UnmarshalJSON(data []byte) error {
|
||||
return l.UnmarshalText(data)
|
||||
}
|
||||
|
||||
// UnmarshalText implements the TextEncoder interface
|
||||
func (l *LangRef) UnmarshalText(data []byte) error {
|
||||
*l = LangRef("")
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(data) > 2 {
|
||||
if data[0] == '"' && data[len(data)-1] == '"' {
|
||||
*l = LangRef(data[1 : len(data)-1])
|
||||
}
|
||||
} else {
|
||||
*l = LangRef(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unescape(b []byte) []byte {
|
||||
// FIMXE(marius): I feel like I'm missing something really obvious about encoding/decoding from Json regarding
|
||||
// escape characters, and that this function is just a hack. Be better future Marius, find the real problem!
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', 'a'}, []byte{'\a'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', 'f'}, []byte{'\f'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', 'n'}, []byte{'\n'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', 'r'}, []byte{'\r'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', 't'}, []byte{'\t'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', 'v'}, []byte{'\v'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', '"'}, []byte{'"'})
|
||||
b = bytes.ReplaceAll(b, []byte{'\\', '\\'}, []byte{'\\'}) // this should cover the case of \\u -> \u
|
||||
return b
|
||||
}
|
||||
|
||||
// UnmarshalJSON tries to load the NaturalLanguage array from the incoming json value
|
||||
func (n *NaturalLanguageValues) UnmarshalJSON(data []byte) error {
|
||||
val, typ, _, err := jsonparser.Get(data)
|
||||
if err != nil {
|
||||
// try our luck if data contains an unquoted string
|
||||
n.Append(NilLangRef, string(unescape(data)))
|
||||
return nil
|
||||
}
|
||||
switch typ {
|
||||
case jsonparser.Object:
|
||||
jsonparser.ObjectEach(data, func(key []byte, val []byte, dataType jsonparser.ValueType, offset int) error {
|
||||
n.Append(LangRef(key), string(unescape(val)))
|
||||
return err
|
||||
})
|
||||
case jsonparser.String:
|
||||
n.Append(NilLangRef, string(unescape(val)))
|
||||
case jsonparser.Array:
|
||||
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
l := LangRefValue{}
|
||||
l.UnmarshalJSON(value)
|
||||
n.Append(l.Ref, l.Value)
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalText tries to load the NaturalLanguage array from the incoming Text value
|
||||
func (n *NaturalLanguageValues) 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 unmarshaling %T value", n)
|
||||
}
|
||||
n.Append(LangRef(NilLangRef), 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 {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
}
|
||||
|
||||
// ObjectNew initializes a new Object
|
||||
func ObjectNew(typ ActivityVocabularyType) *Object {
|
||||
if !(ObjectTypes.Contains(typ)) {
|
||||
typ = ObjectType
|
||||
}
|
||||
o := Object{Type: typ}
|
||||
o.Name = NaturalLanguageValuesNew()
|
||||
o.Content = NaturalLanguageValuesNew()
|
||||
return &o
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the current Object
|
||||
func (o Object) GetID() ObjectID {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Object
|
||||
func (o Object) GetLink() IRI {
|
||||
return IRI(o.ID)
|
||||
}
|
||||
|
||||
// GetType returns the type of the current Object
|
||||
func (o Object) GetType() ActivityVocabularyType {
|
||||
return o.Type
|
||||
}
|
||||
|
||||
// IsLink validates if currentActivity Pub Object is a Link
|
||||
func (o Object) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject validates if currentActivity Pub Object is an Object
|
||||
func (o Object) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Object objects
|
||||
func (o Object) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (o *Object) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
o.ID = JSONGetObjectID(data)
|
||||
o.Type = JSONGetType(data)
|
||||
o.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
o.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
o.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
o.Context = JSONGetItem(data, "context")
|
||||
o.URL = JSONGetURIItem(data, "url")
|
||||
o.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
o.Generator = JSONGetItem(data, "generator")
|
||||
o.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
o.Attachment = JSONGetItem(data, "attachment")
|
||||
o.Location = JSONGetItem(data, "location")
|
||||
o.Published = JSONGetTime(data, "published")
|
||||
o.StartTime = JSONGetTime(data, "startTime")
|
||||
o.EndTime = JSONGetTime(data, "endTime")
|
||||
o.Duration = JSONGetDuration(data, "duration")
|
||||
o.Icon = JSONGetItem(data, "icon")
|
||||
o.Preview = JSONGetItem(data, "preview")
|
||||
o.Image = JSONGetItem(data, "image")
|
||||
o.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
o.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
o.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
o.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
o.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
o.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
o.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
o.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
o.Tag = tag
|
||||
}
|
||||
o.Likes = JSONGetItem(data, "likes")
|
||||
o.Shares = JSONGetItem(data, "shares")
|
||||
o.Source = GetAPSource(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recipients performs recipient de-duplication on the Object's To, Bto, CC and BCC properties
|
||||
func (o *Object) Recipients() ItemCollection {
|
||||
var aud ItemCollection
|
||||
rec, _ := ItemCollectionDeduplication(&aud, &o.To, &o.Bto, &o.CC, &o.BCC, &o.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (o *Object) Clean() {
|
||||
o.BCC = nil
|
||||
o.Bto = nil
|
||||
}
|
||||
|
||||
type (
|
||||
// Article represents any kind of multi-paragraph written work.
|
||||
Article = Object
|
||||
// Audio represents an audio document of any kind.
|
||||
Audio = Document
|
||||
// Document represents a document of any kind.
|
||||
Document = Object
|
||||
// Event represents any kind of event.
|
||||
Event = Object
|
||||
// Image An image document of any kind
|
||||
Image = Document
|
||||
// Note represents a short written work typically less than a single paragraph in length.
|
||||
Note = Object
|
||||
// Page represents a Web Page.
|
||||
Page = Document
|
||||
// Video represents a video document of any kind
|
||||
Video = Document
|
||||
)
|
||||
|
||||
// ItemCollectionDeduplication normalizes the received arguments lists into a single unified one
|
||||
func ItemCollectionDeduplication(recCols ...*ItemCollection) (ItemCollection, error) {
|
||||
rec := make(ItemCollection, 0)
|
||||
|
||||
for _, recCol := range recCols {
|
||||
if recCol == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
toRemove := make([]int, 0)
|
||||
for i, cur := range *recCol {
|
||||
save := true
|
||||
if cur == nil {
|
||||
continue
|
||||
}
|
||||
var testIt IRI
|
||||
if cur.IsObject() {
|
||||
testIt = IRI(cur.GetID())
|
||||
} else if cur.IsLink() {
|
||||
testIt = cur.GetLink()
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
for _, it := range rec {
|
||||
if testIt.Equals(IRI(it.GetID()), false) {
|
||||
// mark the element for removal
|
||||
toRemove = append(toRemove, i)
|
||||
save = false
|
||||
}
|
||||
}
|
||||
if save {
|
||||
rec = append(rec, testIt)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(toRemove)))
|
||||
for _, idx := range toRemove {
|
||||
*recCol = append((*recCol)[:idx], (*recCol)[idx+1:]...)
|
||||
}
|
||||
}
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (i *ObjectID) UnmarshalJSON(data []byte) error {
|
||||
*i = ObjectID(strings.Trim(string(data), "\""))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ObjectID) IsValid() bool {
|
||||
return i != nil && len(*i) > 0
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (c *MimeType) UnmarshalJSON(data []byte) error {
|
||||
*c = MimeType(strings.Trim(string(data), "\""))
|
||||
return 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) {
|
||||
switch i := it.(type) {
|
||||
case *Object:
|
||||
return i, nil
|
||||
case Object:
|
||||
return &i, nil
|
||||
case *Place:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Place:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *Profile:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Profile:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *Relationship:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Relationship:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *Tombstone:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Tombstone:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *Activity:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Activity:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *IntransitiveActivity:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case IntransitiveActivity:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *Question:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Question:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *Collection:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case Collection:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *CollectionPage:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case CollectionPage:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *OrderedCollection:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case OrderedCollection:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
case *OrderedCollectionPage:
|
||||
return (*Object)(unsafe.Pointer(i)), nil
|
||||
case OrderedCollectionPage:
|
||||
return (*Object)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert %q", it.GetType())
|
||||
}
|
||||
|
||||
// FlattenObjectProperties flattens the Object's properties from Object types to IRI
|
||||
func FlattenObjectProperties(o *Object) *Object {
|
||||
o.Replies = Flatten(o.Replies)
|
||||
o.AttributedTo = Flatten(o.AttributedTo)
|
||||
o.To = FlattenItemCollection(o.To)
|
||||
o.Bto = FlattenItemCollection(o.Bto)
|
||||
o.CC = FlattenItemCollection(o.CC)
|
||||
o.BCC = FlattenItemCollection(o.BCC)
|
||||
o.Audience = FlattenItemCollection(o.Audience)
|
||||
o.Tag = FlattenItemCollection(o.Tag)
|
||||
return o
|
||||
}
|
||||
|
||||
// FlattenProperties flattens the Item's properties from Object types to IRI
|
||||
func FlattenProperties(it Item) Item {
|
||||
if ActivityTypes.Contains(it.GetType()) {
|
||||
act, err := ToActivity(it)
|
||||
if err == nil {
|
||||
return FlattenActivityProperties(act)
|
||||
}
|
||||
}
|
||||
if ActorTypes.Contains(it.GetType()) || ObjectTypes.Contains(it.GetType()) {
|
||||
ob, err := ToObject(it)
|
||||
if err == nil {
|
||||
return FlattenObjectProperties(ob)
|
||||
}
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
// Source is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
type Source struct {
|
||||
// Content
|
||||
Content as.NaturalLanguageValues `jsonld:"content"`
|
||||
Content NaturalLanguageValues `jsonld:"content"`
|
||||
// MediaType
|
||||
MediaType as.MimeType `jsonld:"mediaType"`
|
||||
}
|
||||
|
||||
type Parent = Object
|
||||
|
||||
// Object
|
||||
type Object struct {
|
||||
as.Parent
|
||||
// This 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.
|
||||
Likes as.Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares as.Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
MediaType MimeType `jsonld:"mediaType"`
|
||||
}
|
||||
|
||||
// GetAPSource
|
||||
|
@ -53,30 +788,3 @@ func (s *Source) UnmarshalJSON(data []byte) error {
|
|||
*s = GetAPSource(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (o *Object) UnmarshalJSON(data []byte) error {
|
||||
if as.ItemTyperFunc == nil {
|
||||
as.ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
o.Parent.UnmarshalJSON(data)
|
||||
o.Likes = as.JSONGetItem(data, "likes")
|
||||
o.Shares = as.JSONGetItem(data, "shares")
|
||||
o.Source = GetAPSource(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToObject
|
||||
func ToObject(it as.Item) (*Object, error) {
|
||||
switch i := it.(type) {
|
||||
case *as.Object:
|
||||
return &Object{Parent: *i}, nil
|
||||
case as.Object:
|
||||
return &Object{Parent: i}, nil
|
||||
case *Object:
|
||||
return i, nil
|
||||
case Object:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert object")
|
||||
}
|
||||
|
|
906
object_test.go
906
object_test.go
|
@ -1,39 +1,547 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestObjectNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
var testType = ArticleType
|
||||
|
||||
o := ObjectNew(testType)
|
||||
o.ID = testValue
|
||||
|
||||
if o.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||
}
|
||||
if o.Type != testType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, testType)
|
||||
}
|
||||
|
||||
n := ObjectNew("")
|
||||
n.ID = testValue
|
||||
if n.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", n.ID, testValue)
|
||||
}
|
||||
if n.Type != ObjectType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", n.Type, ObjectType)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestActivityVocabularyTypes_Contains(t *testing.T) {
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ActivityTypes.Contains(ActivityType) {
|
||||
t.Errorf("Generic Activity Type '%v' should not be valid", ActivityType)
|
||||
}
|
||||
for _, inValidType := range ObjectTypes {
|
||||
if ActivityTypes.Contains(inValidType) {
|
||||
t.Errorf("APObject Type '%v' should be invalid", inValidType)
|
||||
}
|
||||
}
|
||||
if ActivityTypes.Contains(invalidType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range ActivityTypes {
|
||||
if !ActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if IntransitiveActivityTypes.Contains(ActivityType) {
|
||||
t.Errorf("Generic Activity Type '%v' should not be valid", ActivityType)
|
||||
}
|
||||
for _, inValidType := range ActivityTypes {
|
||||
if IntransitiveActivityTypes.Contains(inValidType) {
|
||||
t.Errorf("APObject Type '%v' should be invalid", inValidType)
|
||||
}
|
||||
}
|
||||
if IntransitiveActivityTypes.Contains(invalidType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range IntransitiveActivityTypes {
|
||||
if !IntransitiveActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ActivityTypes.Contains(ActivityType) {
|
||||
t.Errorf("Generic Activity Type '%v' should not be valid", ActivityType)
|
||||
}
|
||||
for _, inValidType := range CollectionManagementActivityTypes {
|
||||
if !CollectionManagementActivityTypes.Contains(inValidType) {
|
||||
t.Errorf("APObject Type '%v' should be valid", inValidType)
|
||||
}
|
||||
}
|
||||
if CollectionManagementActivityTypes.Contains(invalidType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range ContentManagementActivityTypes {
|
||||
if CollectionManagementActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", validType)
|
||||
}
|
||||
}
|
||||
for _, validType := range ReactionsActivityTypes {
|
||||
if CollectionManagementActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ActivityTypes.Contains(ActivityType) {
|
||||
t.Errorf("Generic Activity Type '%v' should not be valid", ActivityType)
|
||||
}
|
||||
for _, inValidType := range ContentManagementActivityTypes {
|
||||
if !ContentManagementActivityTypes.Contains(inValidType) {
|
||||
t.Errorf("APObject Type '%v' should be valid", inValidType)
|
||||
}
|
||||
}
|
||||
if ContentManagementActivityTypes.Contains(invalidType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range CollectionManagementActivityTypes {
|
||||
if ContentManagementActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", validType)
|
||||
}
|
||||
}
|
||||
for _, validType := range ReactionsActivityTypes {
|
||||
if ContentManagementActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ReactionsActivityTypes.Contains(ActivityType) {
|
||||
t.Errorf("Generic Activity Type '%v' should not be valid", ActivityType)
|
||||
}
|
||||
for _, inValidType := range ReactionsActivityTypes {
|
||||
if !ReactionsActivityTypes.Contains(inValidType) {
|
||||
t.Errorf("APObject Type '%v' should be valid", inValidType)
|
||||
}
|
||||
}
|
||||
if ReactionsActivityTypes.Contains(invalidType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range CollectionManagementActivityTypes {
|
||||
if ReactionsActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", validType)
|
||||
}
|
||||
}
|
||||
for _, validType := range ContentManagementActivityTypes {
|
||||
if ReactionsActivityTypes.Contains(validType) {
|
||||
t.Errorf("Activity Type '%v' should not be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
for _, validType := range CollectionTypes {
|
||||
if !CollectionTypes.Contains(validType) {
|
||||
t.Errorf("Generic Type '%#v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ActorTypes.Contains(invalidType) {
|
||||
t.Errorf("APObject Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range ActorTypes {
|
||||
if !ActorTypes.Contains(validType) {
|
||||
t.Errorf("APObject Type '%v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
for _, validType := range GenericObjectTypes {
|
||||
if !GenericObjectTypes.Contains(validType) {
|
||||
t.Errorf("Generic Type '%v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var invalidType ActivityVocabularyType = "RandomType"
|
||||
|
||||
if ObjectTypes.Contains(invalidType) {
|
||||
t.Errorf("APObject Type '%v' should not be valid", invalidType)
|
||||
}
|
||||
for _, validType := range ObjectTypes {
|
||||
if !ObjectTypes.Contains(validType) {
|
||||
t.Errorf("APObject Type '%v' should be valid", validType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_MarshalJSON(t *testing.T) {
|
||||
p := NaturalLanguageValues{
|
||||
{
|
||||
"en", "the test",
|
||||
},
|
||||
{
|
||||
"fr", "le test",
|
||||
},
|
||||
}
|
||||
js := "{\"en\":\"the test\",\"fr\":\"le test\"}"
|
||||
out, err := p.MarshalJSON()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error: '%s'", err)
|
||||
}
|
||||
if js != string(out) {
|
||||
t.Errorf("Different marshal result '%s', instead of '%s'", out, js)
|
||||
}
|
||||
p1 := NaturalLanguageValues{
|
||||
{
|
||||
"en", "the test",
|
||||
},
|
||||
}
|
||||
|
||||
out1, err1 := p1.MarshalJSON()
|
||||
|
||||
if err1 != nil {
|
||||
t.Errorf("Error: '%s'", err1)
|
||||
}
|
||||
txt := `{"en":"the test"}`
|
||||
if txt != string(out1) {
|
||||
t.Errorf("Different marshal result '%s', instead of '%s'", out1, txt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangRefValue_MarshalJSON(t *testing.T) {
|
||||
{
|
||||
tst := LangRefValue{
|
||||
Ref: NilLangRef,
|
||||
Value: "test",
|
||||
}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
expected := `"test"`
|
||||
if string(j) != expected {
|
||||
t.Errorf("Different marshal result '%s', expected '%s'", j, expected)
|
||||
}
|
||||
}
|
||||
{
|
||||
tst := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test",
|
||||
}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
expected := `"en":"test"`
|
||||
if string(j) != expected {
|
||||
t.Errorf("Different marshal result '%s', expected '%s'", j, expected)
|
||||
}
|
||||
}
|
||||
{
|
||||
tst := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test\nwith characters\tneeding escaping\r\n",
|
||||
}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
expected := `"en":"test\nwith characters\tneeding escaping\r\n"`
|
||||
if string(j) != expected {
|
||||
t.Errorf("Different marshal result '%s', expected '%s'", j, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangRefValue_MarshalText(t *testing.T) {
|
||||
{
|
||||
tst := LangRefValue{
|
||||
Ref: NilLangRef,
|
||||
Value: "test",
|
||||
}
|
||||
j, err := tst.MarshalText()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
expected := "test"
|
||||
if string(j) != expected {
|
||||
t.Errorf("Different marshal result '%s', expected '%s'", j, expected)
|
||||
}
|
||||
}
|
||||
{
|
||||
tst := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test",
|
||||
}
|
||||
j, err := tst.MarshalText()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
expected := "test[en]"
|
||||
if string(j) != expected {
|
||||
t.Errorf("Different marshal result '%s', expected '%s'", j, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestObject_IsLink(t *testing.T) {
|
||||
o := ObjectNew(ObjectType)
|
||||
o.ID = "test"
|
||||
if o.IsLink() {
|
||||
t.Errorf("%#v should not be a valid link", o.Type)
|
||||
}
|
||||
m := ObjectNew(AcceptType)
|
||||
m.ID = "test"
|
||||
if m.IsLink() {
|
||||
t.Errorf("%#v should not be a valid link", m.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObject_IsObject(t *testing.T) {
|
||||
o := ObjectNew(ObjectType)
|
||||
o.ID = "test"
|
||||
if !o.IsObject() {
|
||||
t.Errorf("%#v should be a valid object", o.Type)
|
||||
}
|
||||
m := ObjectNew(AcceptType)
|
||||
m.ID = "test"
|
||||
if !m.IsObject() {
|
||||
t.Errorf("%#v should be a valid object", m.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectsArr_Append(t *testing.T) {
|
||||
d := make(ItemCollection, 0)
|
||||
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
d.Append(val)
|
||||
|
||||
if len(d) != 1 {
|
||||
t.Errorf("Objects array should have exactly an element")
|
||||
}
|
||||
if !reflect.DeepEqual(d[0], val) {
|
||||
t.Errorf("First item in object array does not match %q", val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecipients(t *testing.T) {
|
||||
bob := PersonNew("bob")
|
||||
alice := PersonNew("alice")
|
||||
foo := OrganizationNew("foo")
|
||||
bar := GroupNew("bar")
|
||||
|
||||
first := make(ItemCollection, 0)
|
||||
if len(first) != 0 {
|
||||
t.Errorf("Objects array should have exactly an element")
|
||||
}
|
||||
|
||||
first.Append(bob)
|
||||
first.Append(alice)
|
||||
first.Append(foo)
|
||||
first.Append(bar)
|
||||
if len(first) != 4 {
|
||||
t.Errorf("Objects array should have exactly 4(four) elements, not %d", len(first))
|
||||
}
|
||||
|
||||
first.Append(bar)
|
||||
first.Append(alice)
|
||||
first.Append(foo)
|
||||
first.Append(bob)
|
||||
if len(first) != 8 {
|
||||
t.Errorf("Objects array should have exactly 8(eight) elements, not %d", len(first))
|
||||
}
|
||||
|
||||
ItemCollectionDeduplication(&first)
|
||||
if len(first) != 4 {
|
||||
t.Errorf("Objects array should have exactly 4(four) elements, not %d", len(first))
|
||||
}
|
||||
|
||||
second := make(ItemCollection, 0)
|
||||
second.Append(bar)
|
||||
second.Append(foo)
|
||||
|
||||
ItemCollectionDeduplication(&first, &second)
|
||||
if len(first) != 4 {
|
||||
t.Errorf("First Objects array should have exactly 8(eight) elements, not %d", len(first))
|
||||
}
|
||||
if len(second) != 0 {
|
||||
t.Errorf("Second Objects array should have exactly 0(zero) elements, not %d", len(second))
|
||||
}
|
||||
|
||||
_, err := ItemCollectionDeduplication(&first, &second, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Deduplication with empty array failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_Get(t *testing.T) {
|
||||
testVal := "test"
|
||||
a := NaturalLanguageValues{{NilLangRef, testVal}}
|
||||
if a.Get(NilLangRef) != testVal {
|
||||
t.Errorf("Invalid Get result. Expected %s received %s", testVal, a.Get(NilLangRef))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_Set(t *testing.T) {
|
||||
testVal := "test"
|
||||
a := NaturalLanguageValues{{NilLangRef, "ana are mere"}}
|
||||
err := a.Set(LangRef("en"), testVal)
|
||||
if err != nil {
|
||||
t.Errorf("Received error when doing Set %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_Append(t *testing.T) {
|
||||
var a NaturalLanguageValues
|
||||
|
||||
if len(a) != 0 {
|
||||
t.Errorf("Invalid initialization of %T. Size %d > 0 ", a, len(a))
|
||||
}
|
||||
langEn := LangRef("en")
|
||||
valEn := "random value"
|
||||
|
||||
a.Append(langEn, valEn)
|
||||
if len(a) != 1 {
|
||||
t.Errorf("Invalid append of one element to %T. Size %d != 1", a, len(a))
|
||||
}
|
||||
if a.Get(langEn) != valEn {
|
||||
t.Errorf("Invalid append of one element to %T. Value of %q not equal to %q, but %q", a, langEn, valEn, a.Get(langEn))
|
||||
}
|
||||
langDe := LangRef("de")
|
||||
valDe := "randomisch"
|
||||
a.Append(langDe, valDe)
|
||||
|
||||
if len(a) != 2 {
|
||||
t.Errorf("Invalid append of one element to %T. Size %d != 2", a, len(a))
|
||||
}
|
||||
if a.Get(langEn) != valEn {
|
||||
t.Errorf("Invalid append of one element to %T. Value of %q not equal to %q, but %q", a, langEn, valEn, a.Get(langEn))
|
||||
}
|
||||
if a.Get(langDe) != valDe {
|
||||
t.Errorf("Invalid append of one element to %T. Value of %q not equal to %q, but %q", a, langDe, valDe, a.Get(langDe))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangRef_UnmarshalJSON(t *testing.T) {
|
||||
lang := "en-US"
|
||||
json := `"` + lang + `"`
|
||||
|
||||
var a LangRef
|
||||
a.UnmarshalJSON([]byte(json))
|
||||
|
||||
if string(a) != lang {
|
||||
t.Errorf("Invalid json unmarshal for %T. Expected %q, found %q", lang, lang, string(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_UnmarshalFullObjectJSON(t *testing.T) {
|
||||
langEn := "en-US"
|
||||
valEn := "random"
|
||||
langDe := "de-DE"
|
||||
valDe := "zufällig\n"
|
||||
|
||||
//m := make(map[string]string)
|
||||
//m[langEn] = valEn
|
||||
//m[langDe] = valDe
|
||||
|
||||
json := `{
|
||||
"` + langEn + `": "` + valEn + `",
|
||||
"` + langDe + `": "` + valDe + `"
|
||||
}`
|
||||
|
||||
a := make(NaturalLanguageValues, 0)
|
||||
_ = a.Append(LangRef(langEn), valEn)
|
||||
_ = a.Append(LangRef(langDe), valDe)
|
||||
err := a.UnmarshalJSON([]byte(json))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
for lang, val := range a {
|
||||
if val.Ref != LangRef(langEn) && val.Ref != LangRef(langDe) {
|
||||
t.Errorf("Invalid json unmarshal for %T. Expected lang %q or %q, found %q", a, langEn, langDe, lang)
|
||||
}
|
||||
|
||||
if val.Ref == LangRef(langEn) && val.Value != valEn {
|
||||
t.Errorf("Invalid json unmarshal for %T. Expected value %q, found %q", a, valEn, val)
|
||||
}
|
||||
if val.Ref == LangRef(langDe) && val.Value != valDe {
|
||||
t.Errorf("Invalid json unmarshal for %T. Expected value %q, found %q", a, valDe, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateEmptyObject(o Object, t *testing.T) {
|
||||
if o.ID != "" {
|
||||
t.Errorf("Unmarshalled object %T should have empty ID, received %q", o, o.ID)
|
||||
t.Errorf("Unmarshaled object %T should have empty ID, received %q", o, o.ID)
|
||||
}
|
||||
if o.Type != "" {
|
||||
t.Errorf("Unmarshalled object %T should have empty Type, received %q", o, o.Type)
|
||||
t.Errorf("Unmarshaled object %T should have empty Type, received %q", o, o.Type)
|
||||
}
|
||||
if o.AttributedTo != nil {
|
||||
t.Errorf("Unmarshalled object %T should have empty AttributedTo, received %q", o, o.AttributedTo)
|
||||
t.Errorf("Unmarshaled object %T should have empty AttributedTo, received %q", o, o.AttributedTo)
|
||||
}
|
||||
if len(o.Name) != 0 {
|
||||
t.Errorf("Unmarshalled object %T should have empty Name, received %q", o, o.Name)
|
||||
t.Errorf("Unmarshaled object %T should have empty Name, received %q", o, o.Name)
|
||||
}
|
||||
if len(o.Summary) != 0 {
|
||||
t.Errorf("Unmarshalled object %T should have empty Summary, received %q", o, o.Summary)
|
||||
t.Errorf("Unmarshaled object %T should have empty Summary, received %q", o, o.Summary)
|
||||
}
|
||||
if len(o.Content) != 0 {
|
||||
t.Errorf("Unmarshalled object %T should have empty Content, received %q", o, o.Content)
|
||||
t.Errorf("Unmarshaled object %T should have empty Content, received %q", o, o.Content)
|
||||
}
|
||||
if o.URL != nil {
|
||||
t.Errorf("Unmarshalled object %T should have empty URL, received %v", o, o.URL)
|
||||
t.Errorf("Unmarshaled object %T should have empty URL, received %v", o, o.URL)
|
||||
}
|
||||
if o.Icon != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty Icon, received %v", o, o.Icon)
|
||||
}
|
||||
if o.Image != nil {
|
||||
t.Errorf("Unmarshaled object %T should have empty Image, received %v", o, o.Image)
|
||||
}
|
||||
if !o.Published.IsZero() {
|
||||
t.Errorf("Unmarshalled object %T should have empty Published, received %q", o, o.Published)
|
||||
t.Errorf("Unmarshaled object %T should have empty Published, received %q", o, o.Published)
|
||||
}
|
||||
if !o.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshalled object %T should have empty StartTime, received %q", o, o.StartTime)
|
||||
t.Errorf("Unmarshaled object %T should have empty StartTime, received %q", o, o.StartTime)
|
||||
}
|
||||
if !o.Updated.IsZero() {
|
||||
t.Errorf("Unmarshalled object %T should have empty Updated, received %q", o, o.Updated)
|
||||
t.Errorf("Unmarshaled object %T should have empty Updated, received %q", o, o.Updated)
|
||||
}
|
||||
if !o.EndTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object %T should have empty EndTime, received %q", o, o.EndTime)
|
||||
}
|
||||
if o.Duration != 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Duration, received %q", o, o.Duration)
|
||||
}
|
||||
if len(o.To) > 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty To, received %q", o, o.To)
|
||||
}
|
||||
if len(o.Bto) > 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty Bto, received %q", o, o.Bto)
|
||||
}
|
||||
if len(o.CC) > 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty CC, received %q", o, o.CC)
|
||||
}
|
||||
if len(o.BCC) > 0 {
|
||||
t.Errorf("Unmarshaled object %T should have empty BCC, received %q", o, o.BCC)
|
||||
}
|
||||
validateEmptySource(o.Source, t)
|
||||
}
|
||||
|
@ -55,6 +563,384 @@ func TestObject_UnmarshalJSON(t *testing.T) {
|
|||
validateEmptyObject(o, t)
|
||||
}
|
||||
|
||||
func TestMimeType_UnmarshalJSON(t *testing.T) {
|
||||
m := MimeType("")
|
||||
dataEmpty := []byte("")
|
||||
|
||||
m.UnmarshalJSON(dataEmpty)
|
||||
if m != "" {
|
||||
t.Errorf("Unmarshaled object %T should be an empty string, received %q", m, m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLangRefValue_String(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLangRefValue_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLangRefValue_UnmarshalText(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestLangRef_UnmarshalText(t *testing.T) {
|
||||
l := LangRef("")
|
||||
dataEmpty := []byte("")
|
||||
|
||||
l.UnmarshalText(dataEmpty)
|
||||
if l != "" {
|
||||
t.Errorf("Unmarshaled object %T should be an empty string, received %q", l, l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectID_UnmarshalJSON(t *testing.T) {
|
||||
o := ObjectID("")
|
||||
dataEmpty := []byte("")
|
||||
|
||||
o.UnmarshalJSON(dataEmpty)
|
||||
if o != "" {
|
||||
t.Errorf("Unmarshaled object %T should be an empty string, received %q", o, o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_UnmarshalJSON(t *testing.T) {
|
||||
l := LangRef("")
|
||||
dataEmpty := []byte("")
|
||||
|
||||
l.UnmarshalJSON(dataEmpty)
|
||||
if l != "" {
|
||||
t.Errorf("Unmarshaled object %T should be an empty string, received %q", l, l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_UnmarshalText(t *testing.T) {
|
||||
l := LangRef("")
|
||||
dataEmpty := []byte("")
|
||||
|
||||
l.UnmarshalText(dataEmpty)
|
||||
if l != "" {
|
||||
t.Errorf("Unmarshaled object %T should be an empty string, received %q", l, l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObject_GetID(t *testing.T) {
|
||||
a := Object{}
|
||||
testVal := "crash$"
|
||||
a.ID = ObjectID(testVal)
|
||||
if string(a.GetID()) != testVal {
|
||||
t.Errorf("%T should return %q, Received %q", a.GetID, testVal, a.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestObject_GetLink(t *testing.T) {
|
||||
a := Object{}
|
||||
testVal := "crash$"
|
||||
a.ID = ObjectID(testVal)
|
||||
if string(a.GetLink()) != testVal {
|
||||
t.Errorf("%T should return %q, Received %q", a.GetLink, testVal, a.GetLink())
|
||||
}
|
||||
}
|
||||
|
||||
func TestObject_GetType(t *testing.T) {
|
||||
a := Object{}
|
||||
a.Type = ActorType
|
||||
if a.GetType() != ActorType {
|
||||
t.Errorf("%T should return %q, Received %q", a.GetType, ActorType, a.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_First(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValueNew(t *testing.T) {
|
||||
n := NaturalLanguageValuesNew()
|
||||
|
||||
if len(n) != 0 {
|
||||
t.Errorf("Initial %T should have length 0, received %d", n, len(n))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValue_MarshalText(t *testing.T) {
|
||||
nlv := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test",
|
||||
}
|
||||
tst := NaturalLanguageValues{nlv}
|
||||
j, err := tst.MarshalText()
|
||||
if err != nil {
|
||||
t.Errorf("Error marshaling: %s", err)
|
||||
}
|
||||
if j == nil {
|
||||
t.Errorf("Error marshaling: nil value returned")
|
||||
}
|
||||
expected := fmt.Sprintf("\"%s[%s]\"", nlv.Value, nlv.Ref)
|
||||
if string(j) != expected {
|
||||
t.Errorf("Wrong value: %s, expected %s", j, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_First(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_Get(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_MarshalJSON(t *testing.T) {
|
||||
{
|
||||
m := NaturalLanguageValues{
|
||||
{
|
||||
"en", "test",
|
||||
},
|
||||
{
|
||||
"de", "test",
|
||||
},
|
||||
}
|
||||
result, err := m.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Failed marshaling '%v'", err)
|
||||
}
|
||||
mRes := "{\"de\":\"test\",\"en\":\"test\"}"
|
||||
if string(result) != mRes {
|
||||
t.Errorf("Different results '%v' vs. '%v'", string(result), mRes)
|
||||
}
|
||||
//n := NaturalLanguageValuesNew()
|
||||
//result, err := n.MarshalJSON()
|
||||
|
||||
s := make(map[LangRef]string)
|
||||
s["en"] = "test"
|
||||
n1 := NaturalLanguageValues{{
|
||||
"en", "test",
|
||||
}}
|
||||
result1, err1 := n1.MarshalJSON()
|
||||
if err1 != nil {
|
||||
t.Errorf("Failed marshaling '%v'", err1)
|
||||
}
|
||||
mRes1 := `{"en":"test"}`
|
||||
if string(result1) != mRes1 {
|
||||
t.Errorf("Different results '%v' vs. '%v'", string(result1), mRes1)
|
||||
}
|
||||
}
|
||||
{
|
||||
nlv := LangRefValue{
|
||||
Ref: NilLangRef,
|
||||
Value: "test",
|
||||
}
|
||||
tst := NaturalLanguageValues{nlv}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error marshaling: %s", err)
|
||||
}
|
||||
if j == nil {
|
||||
t.Errorf("Error marshaling: nil value returned")
|
||||
}
|
||||
expected := fmt.Sprintf("\"%s\"", nlv.Value)
|
||||
if string(j) != expected {
|
||||
t.Errorf("Wrong value: %s, expected %s", j, expected)
|
||||
}
|
||||
}
|
||||
{
|
||||
nlv := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test",
|
||||
}
|
||||
tst := NaturalLanguageValues{nlv}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error marshaling: %s", err)
|
||||
}
|
||||
if j == nil {
|
||||
t.Errorf("Error marshaling: nil value returned")
|
||||
}
|
||||
expected := fmt.Sprintf("{\"%s\":\"%s\"}", nlv.Ref, nlv.Value)
|
||||
if string(j) != expected {
|
||||
t.Errorf("Wrong value: %s, expected %s", j, expected)
|
||||
}
|
||||
}
|
||||
{
|
||||
nlvEn := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test",
|
||||
}
|
||||
nlvFr := LangRefValue{
|
||||
Ref: "fr",
|
||||
Value: "teste",
|
||||
}
|
||||
tst := NaturalLanguageValues{nlvEn, nlvFr}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error marshaling: %s", err)
|
||||
}
|
||||
if j == nil {
|
||||
t.Errorf("Error marshaling: nil value returned")
|
||||
}
|
||||
expected := fmt.Sprintf("{\"%s\":\"%s\",\"%s\":\"%s\"}", nlvEn.Ref, nlvEn.Value, nlvFr.Ref, nlvFr.Value)
|
||||
if string(j) != expected {
|
||||
t.Errorf("Wrong value: %s, expected %s", j, expected)
|
||||
}
|
||||
}
|
||||
{
|
||||
nlvEn := LangRefValue{
|
||||
Ref: "en",
|
||||
Value: "test\nwith new line",
|
||||
}
|
||||
nlvFr := LangRefValue{
|
||||
Ref: "fr",
|
||||
Value: "teste\navec une ligne nouvelle",
|
||||
}
|
||||
tst := NaturalLanguageValues{nlvEn, nlvFr}
|
||||
j, err := tst.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Error marshaling: %s", err)
|
||||
}
|
||||
if j == nil {
|
||||
t.Errorf("Error marshaling: nil value returned")
|
||||
}
|
||||
expected := fmt.Sprintf("{\"%s\":%s,\"%s\":%s}", nlvEn.Ref, strconv.Quote(nlvEn.Value), nlvFr.Ref, strconv.Quote(nlvFr.Value))
|
||||
if string(j) != expected {
|
||||
t.Errorf("Wrong value: %s, expected %s", j, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_MarshalText(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_Set(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_UnmarshalJSON(t *testing.T) {
|
||||
{
|
||||
lang := []byte{'e', 'n'}
|
||||
val := []byte{'a', 'n', 'a', ' ', 'a', 'r', 'e', ' ', 'm', 'e', 'r', 'e', '\n'}
|
||||
js := fmt.Sprintf(`[{"%s": "%s"}]`, lang, val)
|
||||
n := NaturalLanguageValues{}
|
||||
err := n.UnmarshalJSON([]byte(js))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when unmarshaling %T: %s", n, err)
|
||||
}
|
||||
|
||||
if n.Count() != 1 {
|
||||
t.Errorf("Invalid number of elements %d, expected %d", n.Count(), 1)
|
||||
}
|
||||
l := n.First()
|
||||
if l.Value != "ana are mere\n" {
|
||||
t.Errorf("Invalid %T value %q, expected %q", l, l.Value, "ana are mere\n")
|
||||
}
|
||||
if l.Ref != "en" {
|
||||
t.Errorf("Invalid %T ref %q, expected %q", l, l.Ref, "en")
|
||||
}
|
||||
}
|
||||
{
|
||||
ob := make(map[string]string)
|
||||
ob["en"] = "ana are mere\n"
|
||||
js, err := json.Marshal(ob)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when marshaling %T: %s", ob, err)
|
||||
}
|
||||
n := NaturalLanguageValues{}
|
||||
err = n.UnmarshalJSON(js)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when unmarshaling %T: %s", n, err)
|
||||
}
|
||||
|
||||
if n.Count() != 1 {
|
||||
t.Errorf("Invalid number of elements %d, expected %d", n.Count(), 1)
|
||||
}
|
||||
l := n.First()
|
||||
if l.Value != "ana are mere\n" {
|
||||
t.Errorf("Invalid %T value %q, expected %q", l, l.Value, "ana are mere\n")
|
||||
}
|
||||
if l.Ref != "en" {
|
||||
t.Errorf("Invalid %T ref %q, expected %q", l, l.Ref, "en")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_UnmarshalText(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValuesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestToObject(t *testing.T) {
|
||||
var it Item
|
||||
ob := ObjectNew(ArticleType)
|
||||
it = ob
|
||||
|
||||
o, err := ToObject(it)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if o != ob {
|
||||
t.Errorf("Invalid activity returned by ToObject #%v", ob)
|
||||
}
|
||||
|
||||
act := ActivityNew("test", CreateType, nil)
|
||||
it = act
|
||||
|
||||
a, err := ToObject(it)
|
||||
if err != nil {
|
||||
t.Errorf("Error returned when calling ToObject with activity should be nil, received %s", err)
|
||||
}
|
||||
if a == nil {
|
||||
t.Errorf("Invalid return by ToObject #%v, should have not been nil", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenObjectProperties(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestFlattenProperties(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestToTombstone(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestToRelationship(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestObject_Recipients(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_Recipients(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_Recipients(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_String(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestNaturalLanguageValues_Count(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestItemCollectionDeduplication(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
|
||||
func TestSource_UnmarshalJSON(t *testing.T) {
|
||||
s := Source{}
|
||||
|
||||
|
|
272
ordered_collection.go
Normal file
272
ordered_collection.go
Normal file
|
@ -0,0 +1,272 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OrderedCollection is a subtype of Collection in which members of the logical
|
||||
// collection are assumed to always be strictly ordered.
|
||||
type OrderedCollection struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// In a paged Collection, indicates the page that contains the most recently updated member items.
|
||||
Current ObjectOrLink `jsonld:"current,omitempty"`
|
||||
// In a paged Collection, indicates the furthest preceeding page of items in the collection.
|
||||
First ObjectOrLink `jsonld:"first,omitempty"`
|
||||
// In a paged Collection, indicates the furthest proceeding page of the collection.
|
||||
Last ObjectOrLink `jsonld:"last,omitempty"`
|
||||
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
|
||||
// This number might not reflect the actual number of items serialized within the Collection object instance.
|
||||
TotalItems uint `jsonld:"totalItems"`
|
||||
// Identifies the items contained in a collection. The items might be ordered or unordered.
|
||||
OrderedItems ItemCollection `jsonld:"orderedItems,omitempty"`
|
||||
}
|
||||
|
||||
// GetType returns the OrderedCollection's type
|
||||
func (o OrderedCollection) GetType() ActivityVocabularyType {
|
||||
return o.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an OrderedCollection object
|
||||
func (o OrderedCollection) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the OrderedCollection
|
||||
func (o OrderedCollection) GetID() ObjectID {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the OrderedCollection object
|
||||
func (o OrderedCollection) GetLink() IRI {
|
||||
return IRI(o.ID)
|
||||
}
|
||||
|
||||
// IsObject returns true for am OrderedCollection object
|
||||
func (o OrderedCollection) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (o *OrderedCollection) Collection() ItemCollection {
|
||||
return o.OrderedItems
|
||||
}
|
||||
|
||||
// IsCollection returns true for OrderedCollection objects
|
||||
func (o OrderedCollection) IsCollection() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Contains verifies if OrderedCollection array contains the received one
|
||||
func (o OrderedCollection) Contains(r IRI) bool {
|
||||
if len(o.OrderedItems) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, iri := range o.OrderedItems {
|
||||
if r.Equals(iri.GetLink(), false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Count returns the maximum between the length of Items in collection and its TotalItems property
|
||||
func (o *OrderedCollection) Count() uint {
|
||||
if o.TotalItems > 0 {
|
||||
return o.TotalItems
|
||||
}
|
||||
return uint(len(o.OrderedItems))
|
||||
}
|
||||
|
||||
// Append adds an element to an OrderedCollection
|
||||
func (o *OrderedCollection) Append(ob Item) error {
|
||||
o.OrderedItems = append(o.OrderedItems, ob)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (o *OrderedCollection) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
o.ID = JSONGetObjectID(data)
|
||||
o.Type = JSONGetType(data)
|
||||
o.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
o.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
o.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
o.Context = JSONGetItem(data, "context")
|
||||
o.URL = JSONGetURIItem(data, "url")
|
||||
o.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
o.Generator = JSONGetItem(data, "generator")
|
||||
o.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
o.Attachment = JSONGetItem(data, "attachment")
|
||||
o.Location = JSONGetItem(data, "location")
|
||||
o.Published = JSONGetTime(data, "published")
|
||||
o.StartTime = JSONGetTime(data, "startTime")
|
||||
o.EndTime = JSONGetTime(data, "endTime")
|
||||
o.Duration = JSONGetDuration(data, "duration")
|
||||
o.Icon = JSONGetItem(data, "icon")
|
||||
o.Preview = JSONGetItem(data, "preview")
|
||||
o.Image = JSONGetItem(data, "image")
|
||||
o.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
o.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
o.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
o.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
o.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
o.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
o.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
o.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
o.Tag = tag
|
||||
}
|
||||
|
||||
o.TotalItems = uint(JSONGetInt(data, "totalItems"))
|
||||
o.OrderedItems = JSONGetItems(data, "orderedItems")
|
||||
|
||||
o.Current = JSONGetItem(data, "current")
|
||||
o.First = JSONGetItem(data, "first")
|
||||
o.Last = JSONGetItem(data, "last")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderedCollectionPageNew initializes a new OrderedCollectionPage
|
||||
func OrderedCollectionPageNew(parent CollectionInterface) *OrderedCollectionPage {
|
||||
p := OrderedCollectionPage{
|
||||
PartOf: parent.GetLink(),
|
||||
}
|
||||
if pc, ok := parent.(*OrderedCollection); ok {
|
||||
copyOrderedCollectionToPage(pc, &p)
|
||||
}
|
||||
p.Type = OrderedCollectionPageType
|
||||
return &p
|
||||
}
|
||||
|
||||
// ToOrderedCollection
|
||||
func ToOrderedCollection(it Item) (*OrderedCollection, error) {
|
||||
switch i := it.(type) {
|
||||
case *OrderedCollection:
|
||||
return i, nil
|
||||
case OrderedCollection:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to ordered collection")
|
||||
}
|
||||
|
||||
func copyOrderedCollectionToPage(c *OrderedCollection, p *OrderedCollectionPage) error {
|
||||
p.ID = c.ID
|
||||
return nil
|
||||
}
|
273
ordered_collection_page.go
Normal file
273
ordered_collection_page.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/buger/jsonparser"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OrderedCollectionPage type extends from both CollectionPage and OrderedCollection.
|
||||
// In addition to the properties inherited from each of those, the OrderedCollectionPage
|
||||
// may contain an additional startIndex property whose value indicates the relative index position
|
||||
// of the first item contained by the page within the OrderedCollection to which the page belongs.
|
||||
type OrderedCollectionPage struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// In a paged Collection, indicates the page that contains the most recently updated member items.
|
||||
Current ObjectOrLink `jsonld:"current,omitempty"`
|
||||
// In a paged Collection, indicates the furthest preceeding page of items in the collection.
|
||||
First ObjectOrLink `jsonld:"first,omitempty"`
|
||||
// In a paged Collection, indicates the furthest proceeding page of the collection.
|
||||
Last ObjectOrLink `jsonld:"last,omitempty"`
|
||||
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
|
||||
// This number might not reflect the actual number of items serialized within the Collection object instance.
|
||||
TotalItems uint `jsonld:"totalItems"`
|
||||
// Identifies the items contained in a collection. The items might be ordered or unordered.
|
||||
OrderedItems ItemCollection `jsonld:"orderedItems,omitempty"`
|
||||
// Identifies the Collection to which a CollectionPage objects items belong.
|
||||
PartOf Item `jsonld:"partOf,omitempty"`
|
||||
// In a paged Collection, indicates the next page of items.
|
||||
Next Item `jsonld:"next,omitempty"`
|
||||
// In a paged Collection, identifies the previous page of items.
|
||||
Prev Item `jsonld:"prev,omitempty"`
|
||||
// A non-negative integer value identifying the relative position within the logical view of a strictly ordered collection.
|
||||
StartIndex uint `jsonld:"startIndex,omitempty"`
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the OrderedCollectionPage object
|
||||
func (o OrderedCollectionPage) GetID() ObjectID {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
// GetType returns the OrderedCollectionPage's type
|
||||
func (o OrderedCollectionPage) GetType() ActivityVocabularyType {
|
||||
return o.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for a OrderedCollectionPage object
|
||||
func (o OrderedCollectionPage) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a OrderedCollectionPage object
|
||||
func (o OrderedCollectionPage) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns true for OrderedCollectionPage objects
|
||||
func (o OrderedCollectionPage) IsCollection() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the OrderedCollectionPage object
|
||||
func (o OrderedCollectionPage) GetLink() IRI {
|
||||
return IRI(o.ID)
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (o *OrderedCollectionPage) Collection() ItemCollection {
|
||||
return o.OrderedItems
|
||||
}
|
||||
|
||||
// Count returns the maximum between the length of Items in the collection page and its TotalItems property
|
||||
func (o *OrderedCollectionPage) Count() uint {
|
||||
if o.TotalItems > 0 {
|
||||
return o.TotalItems
|
||||
}
|
||||
return uint(len(o.OrderedItems))
|
||||
}
|
||||
|
||||
// Append adds an element to an OrderedCollectionPage
|
||||
func (o *OrderedCollectionPage) Append(ob Item) error {
|
||||
o.OrderedItems = append(o.OrderedItems, ob)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contains verifies if OrderedCollectionPage array contains the received one
|
||||
func (o OrderedCollectionPage) Contains(r IRI) bool {
|
||||
if len(o.OrderedItems) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, iri := range o.OrderedItems {
|
||||
if r.Equals(iri.GetLink(), false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (o *OrderedCollectionPage) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
o.ID = JSONGetObjectID(data)
|
||||
o.Type = JSONGetType(data)
|
||||
o.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
o.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
o.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
o.Context = JSONGetItem(data, "context")
|
||||
o.URL = JSONGetURIItem(data, "url")
|
||||
o.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
o.Generator = JSONGetItem(data, "generator")
|
||||
o.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
o.Attachment = JSONGetItem(data, "attachment")
|
||||
o.Location = JSONGetItem(data, "location")
|
||||
o.Published = JSONGetTime(data, "published")
|
||||
o.StartTime = JSONGetTime(data, "startTime")
|
||||
o.EndTime = JSONGetTime(data, "endTime")
|
||||
o.Duration = JSONGetDuration(data, "duration")
|
||||
o.Icon = JSONGetItem(data, "icon")
|
||||
o.Preview = JSONGetItem(data, "preview")
|
||||
o.Image = JSONGetItem(data, "image")
|
||||
o.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
o.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
o.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
o.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
o.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
o.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
o.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
o.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
o.Tag = tag
|
||||
}
|
||||
|
||||
o.TotalItems = uint(JSONGetInt(data, "totalItems"))
|
||||
o.OrderedItems = JSONGetItems(data, "orderedItems")
|
||||
|
||||
o.Current = JSONGetItem(data, "current")
|
||||
o.First = JSONGetItem(data, "first")
|
||||
o.Last = JSONGetItem(data, "last")
|
||||
|
||||
o.Next = JSONGetItem(data, "next")
|
||||
o.Prev = JSONGetItem(data, "prev")
|
||||
o.PartOf = JSONGetItem(data, "partOf")
|
||||
|
||||
if si, err := jsonparser.GetInt(data, "startIndex"); err != nil {
|
||||
o.StartIndex = uint(si)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToOrderedCollectionPage
|
||||
func ToOrderedCollectionPage(it Item) (*OrderedCollectionPage, error) {
|
||||
switch i := it.(type) {
|
||||
case *OrderedCollectionPage:
|
||||
return i, nil
|
||||
case OrderedCollectionPage:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to ordered collection page")
|
||||
}
|
120
ordered_collection_page_test.go
Normal file
120
ordered_collection_page_test.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOrderedCollectionPageNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(testValue)
|
||||
p := OrderedCollectionPageNew(c)
|
||||
if reflect.DeepEqual(p, c) {
|
||||
t.Errorf("Invalid ordered collection parent '%v'", p.PartOf)
|
||||
}
|
||||
if p.PartOf != c.GetLink() {
|
||||
t.Errorf("Invalid collection '%v'", p.PartOf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollectionPage_UnmarshalJSON(t *testing.T) {
|
||||
p := OrderedCollectionPage{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
p.UnmarshalJSON(dataEmpty)
|
||||
if p.ID != "" {
|
||||
t.Errorf("Unmarshaled object should have empty ID, received %q", p.ID)
|
||||
}
|
||||
if p.Type != "" {
|
||||
t.Errorf("Unmarshaled object should have empty Type, received %q", p.Type)
|
||||
}
|
||||
if p.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object should have empty AttributedTo, received %q", p.AttributedTo)
|
||||
}
|
||||
if len(p.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Name, received %q", p.Name)
|
||||
}
|
||||
if len(p.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Summary, received %q", p.Summary)
|
||||
}
|
||||
if len(p.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Content, received %q", p.Content)
|
||||
}
|
||||
if p.TotalItems != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty TotalItems, received %d", p.TotalItems)
|
||||
}
|
||||
if len(p.OrderedItems) > 0 {
|
||||
t.Errorf("Unmarshaled object should have empty OrderedItems, received %v", p.OrderedItems)
|
||||
}
|
||||
if p.URL != nil {
|
||||
t.Errorf("Unmarshaled object should have empty URL, received %v", p.URL)
|
||||
}
|
||||
if !p.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Published, received %q", p.Published)
|
||||
}
|
||||
if !p.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty StartTime, received %q", p.StartTime)
|
||||
}
|
||||
if !p.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Updated, received %q", p.Updated)
|
||||
}
|
||||
if p.PartOf != nil {
|
||||
t.Errorf("Unmarshaled object should have empty PartOf, received %q", p.PartOf)
|
||||
}
|
||||
if p.Current != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Current, received %q", p.Current)
|
||||
}
|
||||
if p.First != nil {
|
||||
t.Errorf("Unmarshaled object should have empty First, received %q", p.First)
|
||||
}
|
||||
if p.Last != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Last, received %q", p.Last)
|
||||
}
|
||||
if p.Next != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Next, received %q", p.Next)
|
||||
}
|
||||
if p.Prev != nil {
|
||||
t.Errorf("Unmarshaled object should have empty Prev, received %q", p.Prev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollectionPage_Append(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
p := OrderedCollectionPageNew(c)
|
||||
p.Append(val)
|
||||
|
||||
if p.PartOf != c.GetLink() {
|
||||
t.Errorf("OrderedCollection page should point to OrderedCollection %q", c.GetLink())
|
||||
}
|
||||
if p.Count() != 1 {
|
||||
t.Errorf("OrderedCollection page of %q should have exactly one element", p.GetID())
|
||||
}
|
||||
if !reflect.DeepEqual(p.OrderedItems[0], val) {
|
||||
t.Errorf("First item in Inbox is does not match %q", val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollectionPage_Collection(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
p := OrderedCollectionPageNew(c)
|
||||
|
||||
if !reflect.DeepEqual(p.Collection(), p.OrderedItems) {
|
||||
t.Errorf("Collection items should be equal %v %v", p.Collection(), p.OrderedItems)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToOrderedCollectionPage(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrderedCollectionPage_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
218
ordered_collection_test.go
Normal file
218
ordered_collection_test.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOrderedCollectionNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(testValue)
|
||||
|
||||
if c.ID != testValue {
|
||||
t.Errorf("APObject Id '%v' different than expected '%v'", c.ID, testValue)
|
||||
}
|
||||
if c.Type != OrderedCollectionType {
|
||||
t.Errorf("APObject Type '%v' different than expected '%v'", c.Type, OrderedCollectionType)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_OrderedCollection_Append(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
c.Append(val)
|
||||
|
||||
if c.Count() != 1 {
|
||||
t.Errorf("Inbox collection of %q should have one element", c.GetID())
|
||||
}
|
||||
if !reflect.DeepEqual(c.OrderedItems[0], val) {
|
||||
t.Errorf("First item in Inbox is does not match %q", val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestOrderedCollection_Append(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
p := OrderedCollectionPageNew(c)
|
||||
p.Append(val)
|
||||
|
||||
if p.PartOf != c.GetLink() {
|
||||
t.Errorf("Ordereed collection page should point to ordered collection %q", c.GetLink())
|
||||
}
|
||||
if p.Count() != 1 {
|
||||
t.Errorf("Ordered collection page of %q should have exactly one element", p.GetID())
|
||||
}
|
||||
if !reflect.DeepEqual(p.OrderedItems[0], val) {
|
||||
t.Errorf("First item in Inbox is does not match %q", val.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_Collection(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
o := OrderedCollectionNew(id)
|
||||
|
||||
if !reflect.DeepEqual(o.Collection(), o.OrderedItems) {
|
||||
t.Errorf("Collection items should be equal %v %v", o.Collection(), o.OrderedItems)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_GetID(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
if c.GetID() != id {
|
||||
t.Errorf("GetID should return %q, received %q", id, c.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_GetLink(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
link := IRI(id)
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
if c.GetLink() != link {
|
||||
t.Errorf("GetLink should return %q, received %q", link, c.GetLink())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_GetType(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
if c.GetType() != OrderedCollectionType {
|
||||
t.Errorf("OrderedCollection Type should be %q, received %q", OrderedCollectionType, c.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_IsLink(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
if c.IsLink() != false {
|
||||
t.Errorf("OrderedCollection should not be a link, received %t", c.IsLink())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_IsObject(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
if c.IsObject() != true {
|
||||
t.Errorf("OrderedCollection should be an object, received %t", c.IsObject())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_UnmarshalJSON(t *testing.T) {
|
||||
c := OrderedCollection{}
|
||||
|
||||
dataEmpty := []byte("{}")
|
||||
c.UnmarshalJSON(dataEmpty)
|
||||
if c.ID != "" {
|
||||
t.Errorf("Unmarshaled object should have empty ID, received %q", c.ID)
|
||||
}
|
||||
if c.Type != "" {
|
||||
t.Errorf("Unmarshaled object should have empty Type, received %q", c.Type)
|
||||
}
|
||||
if c.AttributedTo != nil {
|
||||
t.Errorf("Unmarshaled object should have empty AttributedTo, received %q", c.AttributedTo)
|
||||
}
|
||||
if len(c.Name) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Name, received %q", c.Name)
|
||||
}
|
||||
if len(c.Summary) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Summary, received %q", c.Summary)
|
||||
}
|
||||
if len(c.Content) != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty Content, received %q", c.Content)
|
||||
}
|
||||
if c.TotalItems != 0 {
|
||||
t.Errorf("Unmarshaled object should have empty TotalItems, received %d", c.TotalItems)
|
||||
}
|
||||
if len(c.OrderedItems) > 0 {
|
||||
t.Errorf("Unmarshaled object should have empty OrderedItems, received %v", c.OrderedItems)
|
||||
}
|
||||
if c.URL != nil {
|
||||
t.Errorf("Unmarshaled object should have empty URL, received %v", c.URL)
|
||||
}
|
||||
if !c.Published.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Published, received %q", c.Published)
|
||||
}
|
||||
if !c.StartTime.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty StartTime, received %q", c.StartTime)
|
||||
}
|
||||
if !c.Updated.IsZero() {
|
||||
t.Errorf("Unmarshaled object should have empty Updated, received %q", c.Updated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollection_Count(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
|
||||
if c.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", c.TotalItems)
|
||||
}
|
||||
if len(c.OrderedItems) > 0 {
|
||||
t.Errorf("Empty object should have empty Items, received %v", c.OrderedItems)
|
||||
}
|
||||
if c.Count() != uint(len(c.OrderedItems)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, c.Count(), len(c.OrderedItems))
|
||||
}
|
||||
|
||||
c.Append(IRI("test"))
|
||||
if c.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", c.TotalItems)
|
||||
}
|
||||
if c.Count() != uint(len(c.OrderedItems)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, c.Count(), len(c.OrderedItems))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedCollectionPage_Count(t *testing.T) {
|
||||
id := ObjectID("test")
|
||||
|
||||
c := OrderedCollectionNew(id)
|
||||
p := OrderedCollectionPageNew(c)
|
||||
|
||||
if p.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", p.TotalItems)
|
||||
}
|
||||
if len(p.OrderedItems) > 0 {
|
||||
t.Errorf("Empty object should have empty Items, received %v", p.OrderedItems)
|
||||
}
|
||||
if p.Count() != uint(len(p.OrderedItems)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, p.Count(), len(p.OrderedItems))
|
||||
}
|
||||
|
||||
p.Append(IRI("test"))
|
||||
if p.TotalItems != 0 {
|
||||
t.Errorf("Empty object should have empty TotalItems, received %d", p.TotalItems)
|
||||
}
|
||||
if p.Count() != uint(len(p.OrderedItems)) {
|
||||
t.Errorf("%T.Count() returned %d, expected %d", c, p.Count(), len(p.OrderedItems))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToOrderedCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestOrderedCollection_Contains(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
62
outbox.go
62
outbox.go
|
@ -1,7 +1,5 @@
|
|||
package activitypub
|
||||
|
||||
import as "github.com/go-ap/activitystreams"
|
||||
|
||||
type (
|
||||
// OutboxStream contains activities the user has published,
|
||||
// subject to the ability of the requestor to retrieve the activity (that is,
|
||||
|
@ -9,66 +7,18 @@ type (
|
|||
OutboxStream = Outbox
|
||||
|
||||
// Outbox is a type alias for an Ordered Collection
|
||||
Outbox as.OrderedCollection
|
||||
Outbox = OrderedCollection
|
||||
)
|
||||
|
||||
// OutboxNew initializes a new Outbox
|
||||
func OutboxNew() *Outbox {
|
||||
id := as.ObjectID("outbox")
|
||||
|
||||
i := Outbox{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
i.Name = as.NaturalLanguageValuesNew()
|
||||
i.Content = as.NaturalLanguageValuesNew()
|
||||
id := ObjectID("outbox")
|
||||
|
||||
i := Outbox{ID: id, Type: OrderedCollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
i.TotalItems = 0
|
||||
i.OrderedItems = make(ItemCollection, 0)
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// Append adds an element to an Outbox
|
||||
func (o *Outbox) Append(ob as.Item) error {
|
||||
o.OrderedItems = append(o.OrderedItems, ob)
|
||||
o.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to Outbox
|
||||
func (o Outbox) GetID() *as.ObjectID {
|
||||
return o.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Outbox object
|
||||
func (o Outbox) GetLink() as.IRI {
|
||||
return as.IRI(o.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Outbox's type
|
||||
func (o Outbox) GetType() as.ActivityVocabularyType {
|
||||
return o.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Outbox object
|
||||
func (o Outbox) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Outbox object
|
||||
func (o Outbox) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (o *Outbox) UnmarshalJSON(data []byte) error {
|
||||
c := as.OrderedCollection(*o)
|
||||
err := c.UnmarshalJSON(data)
|
||||
|
||||
*o = Outbox(c)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (o Outbox) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(o)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -3,14 +3,12 @@ package activitypub
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
as "github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
func TestOutboxNew(t *testing.T) {
|
||||
o := OutboxNew()
|
||||
|
||||
id := as.ObjectID("outbox")
|
||||
id := ObjectID("outbox")
|
||||
if o.ID != id {
|
||||
t.Errorf("%T should be initialized with %q as %T", o, id, id)
|
||||
}
|
||||
|
@ -30,12 +28,12 @@ func TestOutboxNew(t *testing.T) {
|
|||
|
||||
func TestOutboxStream_GetID(t *testing.T) {
|
||||
o := OutboxStream{}
|
||||
if *o.GetID() != "" {
|
||||
if o.GetID() != "" {
|
||||
t.Errorf("%T should be initialized with empty %T", o, o.GetID())
|
||||
}
|
||||
id := as.ObjectID("test_out_stream")
|
||||
id := ObjectID("test_out_stream")
|
||||
o.ID = id
|
||||
if *o.GetID() != id {
|
||||
if o.GetID() != id {
|
||||
t.Errorf("%T should have %T as %q", o, id, id)
|
||||
}
|
||||
}
|
||||
|
@ -47,21 +45,18 @@ func TestOutboxStream_GetType(t *testing.T) {
|
|||
t.Errorf("%T should be initialized with empty %T", o, o.GetType())
|
||||
}
|
||||
|
||||
o.Type = as.OrderedCollectionType
|
||||
if o.GetType() != as.OrderedCollectionType {
|
||||
t.Errorf("%T should have %T as %q", o, o.GetType(), as.OrderedCollectionType)
|
||||
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 := as.Object{ID: as.ObjectID("grrr")}
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
o.Append(val)
|
||||
if o.TotalItems != 1 {
|
||||
t.Errorf("%T should have exactly an element, found %d", o, o.TotalItems)
|
||||
}
|
||||
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
||||
}
|
||||
|
@ -70,12 +65,9 @@ func TestOutboxStream_Append(t *testing.T) {
|
|||
func TestOutbox_Append(t *testing.T) {
|
||||
o := OutboxNew()
|
||||
|
||||
val := as.Object{ID: as.ObjectID("grrr")}
|
||||
val := Object{ID: ObjectID("grrr")}
|
||||
|
||||
o.Append(val)
|
||||
if o.TotalItems != 1 {
|
||||
t.Errorf("%T should have exactly an element, found %d", o, o.TotalItems)
|
||||
}
|
||||
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
||||
}
|
||||
|
|
251
place.go
Normal file
251
place.go
Normal file
|
@ -0,0 +1,251 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Place represents a logical or physical location. See 5.3 Representing Places for additional information.
|
||||
type Place struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
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
|
||||
// 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
|
||||
// Latitude the latitude of a place
|
||||
Latitude float64
|
||||
// Longitude the longitude of a place
|
||||
Longitude float64
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsLink returns false for Place objects
|
||||
func (p Place) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for Place objects
|
||||
func (p Place) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Place objects
|
||||
func (p Place) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Place object
|
||||
func (p Place) GetLink() IRI {
|
||||
return IRI(p.ID)
|
||||
}
|
||||
|
||||
// GetType returns the type of the current Place
|
||||
func (p Place) GetType() ActivityVocabularyType {
|
||||
return p.Type
|
||||
}
|
||||
|
||||
// GetID returns the ID corresponding to the current Place
|
||||
func (p Place) GetID() ObjectID {
|
||||
return p.ID
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (p *Place) UnmarshalJSON(data []byte) error {
|
||||
// TODO(marius): this is a candidate of using OnObject() for loading the common properties
|
||||
// and then loading the extra ones
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
p.ID = JSONGetObjectID(data)
|
||||
p.Type = JSONGetType(data)
|
||||
p.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
p.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
p.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
p.Context = JSONGetItem(data, "context")
|
||||
p.URL = JSONGetURIItem(data, "url")
|
||||
p.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
p.Generator = JSONGetItem(data, "generator")
|
||||
p.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
p.Attachment = JSONGetItem(data, "attachment")
|
||||
p.Location = JSONGetItem(data, "location")
|
||||
p.Published = JSONGetTime(data, "published")
|
||||
p.StartTime = JSONGetTime(data, "startTime")
|
||||
p.EndTime = JSONGetTime(data, "endTime")
|
||||
p.Duration = JSONGetDuration(data, "duration")
|
||||
p.Icon = JSONGetItem(data, "icon")
|
||||
p.Preview = JSONGetItem(data, "preview")
|
||||
p.Image = JSONGetItem(data, "image")
|
||||
p.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
p.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
p.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
p.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
p.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
p.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
p.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
p.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
p.Tag = tag
|
||||
}
|
||||
p.Accuracy = JSONGetFloat(data, "accuracy")
|
||||
p.Altitude = JSONGetFloat(data, "altitude")
|
||||
p.Latitude = JSONGetFloat(data, "latitude")
|
||||
p.Longitude = JSONGetFloat(data, "longitude")
|
||||
p.Radius = JSONGetInt(data, "radius")
|
||||
p.Units = JSONGetString(data, "units")
|
||||
return 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
|
||||
rec, _ := ItemCollectionDeduplication(&aud, &p.To, &p.Bto, &p.CC, &p.BCC, &p.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (p *Place) Clean(){
|
||||
p.BCC = nil
|
||||
p.Bto = nil
|
||||
}
|
||||
|
||||
// ToPlace
|
||||
func ToPlace(it Item) (*Place, error) {
|
||||
switch i := it.(type) {
|
||||
case *Place:
|
||||
return i, nil
|
||||
case Place:
|
||||
return &i, nil
|
||||
case *Object:
|
||||
// FIXME(marius): **memory_safety** Place has extra properties which will point to invalid memory
|
||||
// we need a safe version for converting from smaller objects to larger ones
|
||||
return (*Place)(unsafe.Pointer(i)), nil
|
||||
case Object:
|
||||
// FIXME(marius): **memory_safety** Place has extra properties which will point to invalid memory
|
||||
// we need a safe version for converting from smaller objects to larger ones
|
||||
return (*Place)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert %q", it.GetType())
|
||||
}
|
43
place_test.go
Normal file
43
place_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPlace_Recipients(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestToPlace(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestPlace_Clean(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
228
profile.go
Normal file
228
profile.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Profile a Profile is a content object that describes another Object,
|
||||
// typically used to describe CanReceiveActivities Type objects.
|
||||
// The describes property is used to reference the object being described by the profile.
|
||||
type Profile struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// Describes On a Profile object, the describes property identifies the object described by the Profile.
|
||||
Describes Item `jsonld:"describes,omitempty"`
|
||||
}
|
||||
|
||||
// IsLink returns false for Profile objects
|
||||
func (p Profile) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for Profile objects
|
||||
func (p Profile) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Profile objects
|
||||
func (p Profile) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Profile object
|
||||
func (p Profile) GetLink() IRI {
|
||||
return IRI(p.ID)
|
||||
}
|
||||
|
||||
// GetType returns the type of the current Profile
|
||||
func (p Profile) GetType() ActivityVocabularyType {
|
||||
return p.Type
|
||||
}
|
||||
|
||||
// GetID returns the ID corresponding to the current Profile
|
||||
func (p Profile) GetID() ObjectID {
|
||||
return p.ID
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (p *Profile) UnmarshalJSON(data []byte) error {
|
||||
// TODO(marius): this is a candidate of using OnObject() for loading the common properties
|
||||
// and then loading the extra ones
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
p.ID = JSONGetObjectID(data)
|
||||
p.Type = JSONGetType(data)
|
||||
p.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
p.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
p.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
p.Context = JSONGetItem(data, "context")
|
||||
p.URL = JSONGetURIItem(data, "url")
|
||||
p.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
p.Generator = JSONGetItem(data, "generator")
|
||||
p.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
p.Attachment = JSONGetItem(data, "attachment")
|
||||
p.Location = JSONGetItem(data, "location")
|
||||
p.Published = JSONGetTime(data, "published")
|
||||
p.StartTime = JSONGetTime(data, "startTime")
|
||||
p.EndTime = JSONGetTime(data, "endTime")
|
||||
p.Duration = JSONGetDuration(data, "duration")
|
||||
p.Icon = JSONGetItem(data, "icon")
|
||||
p.Preview = JSONGetItem(data, "preview")
|
||||
p.Image = JSONGetItem(data, "image")
|
||||
p.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
p.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
p.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
p.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
p.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
p.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
p.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
p.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
p.Tag = tag
|
||||
}
|
||||
p.Describes = JSONGetItem(data, "describes")
|
||||
return 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
|
||||
rec, _ := ItemCollectionDeduplication(&aud, &p.To, &p.Bto, &p.CC, &p.BCC, &p.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (p *Profile) Clean(){
|
||||
p.BCC = nil
|
||||
p.Bto = nil
|
||||
}
|
||||
|
||||
// ToProfile
|
||||
func ToProfile(it Item) (*Profile, error) {
|
||||
switch i := it.(type) {
|
||||
case *Profile:
|
||||
return i, nil
|
||||
case Profile:
|
||||
return &i, nil
|
||||
case *Object:
|
||||
return (*Profile)(unsafe.Pointer(i)), nil
|
||||
case Object:
|
||||
return (*Profile)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert %q", it.GetType())
|
||||
}
|
43
profile_test.go
Normal file
43
profile_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestProfile_Recipients(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestToProfile(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestProfile_Clean(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
249
question.go
Normal file
249
question.go
Normal file
|
@ -0,0 +1,249 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Question represents a question being asked. Question objects are an extension of IntransitiveActivity.
|
||||
// That is, the Question object is an Activity, but the direct object is the question
|
||||
// itself and therefore it would not contain an object property.
|
||||
// Either of the anyOf and oneOf properties may be used to express possible answers,
|
||||
// but a Question object must not have both properties.
|
||||
type Question struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// CanReceiveActivities describes one or more entities that either performed or are expected to perform the activity.
|
||||
// Any single activity can have multiple actors. The actor may be specified using an indirect Link.
|
||||
Actor CanReceiveActivities `jsonld:"actor,omitempty"`
|
||||
// Target describes the indirect object, or target, of the activity.
|
||||
// The precise meaning of the target is largely dependent on the type of action being described
|
||||
// but will often be the object of the English preposition "to".
|
||||
// For instance, in the activity "John added a movie to his wishlist",
|
||||
// the target of the activity is John's wishlist. An activity can have more than one target.
|
||||
Target Item `jsonld:"target,omitempty"`
|
||||
// Result describes the result of the activity. For instance, if a particular action results in the creation
|
||||
// of a new resource, the result property can be used to describe that new resource.
|
||||
Result Item `jsonld:"result,omitempty"`
|
||||
// Origin describes an indirect object of the activity from which the activity is directed.
|
||||
// The precise meaning of the origin is the object of the English preposition "from".
|
||||
// For instance, in the activity "John moved an item to List B from List A", the origin of the activity is "List A".
|
||||
Origin Item `jsonld:"origin,omitempty"`
|
||||
// Instrument identifies one or more objects used (or to be used) in the completion of an Activity.
|
||||
Instrument Item `jsonld:"instrument,omitempty"`
|
||||
// OneOf identifies an exclusive option for a Question. Use of oneOf implies that the Question
|
||||
// can have only a single answer. To indicate that a Question can have multiple answers, use anyOf.
|
||||
OneOf Item `jsonld:"oneOf,omitempty"`
|
||||
// AnyOf identifies an inclusive option for a Question. Use of anyOf implies that the Question can have multiple answers.
|
||||
// To indicate that a Question can have only one answer, use oneOf.
|
||||
AnyOf Item `jsonld:"anyOf,omitempty"`
|
||||
// Closed indicates that a question has been closed, and answers are no longer accepted.
|
||||
Closed bool `jsonld:"closed,omitempty"`
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to the Question object
|
||||
func (q Question) GetID() ObjectID {
|
||||
return q.ID
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the Question object
|
||||
func (q Question) GetLink() IRI {
|
||||
return IRI(q.ID)
|
||||
}
|
||||
|
||||
// GetType returns the ActivityVocabulary type of the current Activity
|
||||
func (q Question) GetType() ActivityVocabularyType {
|
||||
return q.Type
|
||||
}
|
||||
|
||||
// IsObject returns true for Question objects
|
||||
func (q Question) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsLink returns false for Question objects
|
||||
func (q Question) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCollection returns false for Question objects
|
||||
func (q Question) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (q *Question) UnmarshalJSON(data []byte) error {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
q.ID = JSONGetObjectID(data)
|
||||
q.Type = JSONGetType(data)
|
||||
q.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
q.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
q.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
q.Context = JSONGetItem(data, "context")
|
||||
q.URL = JSONGetURIItem(data, "url")
|
||||
q.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
q.Generator = JSONGetItem(data, "generator")
|
||||
q.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
q.Attachment = JSONGetItem(data, "attachment")
|
||||
q.Location = JSONGetItem(data, "location")
|
||||
q.Published = JSONGetTime(data, "published")
|
||||
q.StartTime = JSONGetTime(data, "startTime")
|
||||
q.EndTime = JSONGetTime(data, "endTime")
|
||||
q.Duration = JSONGetDuration(data, "duration")
|
||||
q.Icon = JSONGetItem(data, "icon")
|
||||
q.Preview = JSONGetItem(data, "preview")
|
||||
q.Image = JSONGetItem(data, "image")
|
||||
q.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
q.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
q.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
q.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
q.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
q.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
q.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
q.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
q.Tag = tag
|
||||
}
|
||||
q.Actor = JSONGetItem(data, "actor")
|
||||
q.Target = JSONGetItem(data, "target")
|
||||
q.Origin = JSONGetItem(data, "origin")
|
||||
q.Result = JSONGetItem(data, "result")
|
||||
q.Instrument = JSONGetItem(data, "instrument")
|
||||
q.OneOf = JSONGetItem(data, "oneOf")
|
||||
q.AnyOf = JSONGetItem(data, "anyOf")
|
||||
q.Closed = JSONGetBoolean(data, "closed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuestionNew initializes a Question activity
|
||||
func QuestionNew(id ObjectID) *Question {
|
||||
q := Question{ID: id, Type: QuestionType}
|
||||
q.Name = NaturalLanguageValuesNew()
|
||||
q.Content = NaturalLanguageValuesNew()
|
||||
return &q
|
||||
}
|
||||
|
||||
// ToQuestion
|
||||
func ToQuestion(it Item) (*Question, error) {
|
||||
switch i := it.(type) {
|
||||
case *Question:
|
||||
return i, nil
|
||||
case Question:
|
||||
return &i, nil
|
||||
}
|
||||
return nil, errors.New("unable to convert to question activity")
|
||||
}
|
90
question_test.go
Normal file
90
question_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQuestionNew(t *testing.T) {
|
||||
var testValue = ObjectID("test")
|
||||
|
||||
a := QuestionNew(testValue)
|
||||
|
||||
if a.ID != testValue {
|
||||
t.Errorf("Activity Id '%v' different than expected '%v'", a.ID, testValue)
|
||||
}
|
||||
if a.Type != QuestionType {
|
||||
t.Errorf("Activity Type '%v' different than expected '%v'", a.Type, QuestionType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestion_GetID(t *testing.T) {
|
||||
a := QuestionNew("test")
|
||||
|
||||
if a.GetID() != "test" {
|
||||
t.Errorf("%T should return an empty %T object. Received %#v", a, a.GetID(), a.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestion_IsObject(t *testing.T) {
|
||||
a := QuestionNew("test")
|
||||
|
||||
if !a.IsObject() {
|
||||
t.Errorf("%T should respond true to IsObject", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestion_IsLink(t *testing.T) {
|
||||
a := QuestionNew("test")
|
||||
|
||||
if a.IsLink() {
|
||||
t.Errorf("%T should respond false to IsLink", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestion_GetLink(t *testing.T) {
|
||||
a := QuestionNew("test")
|
||||
|
||||
if a.GetLink() != "test" {
|
||||
t.Errorf("GetLink should return \"test\" for %T, received %q", a, a.GetLink())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestion_GetType(t *testing.T) {
|
||||
a := QuestionNew("test")
|
||||
|
||||
if a.GetType() != QuestionType {
|
||||
t.Errorf("GetType should return %q for %T, received %q", QuestionType, a, a.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestToQuestion(t *testing.T) {
|
||||
var it Item
|
||||
act := QuestionNew("test")
|
||||
it = act
|
||||
|
||||
a, err := ToQuestion(it)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if a != act {
|
||||
t.Errorf("Invalid activity returned by ToActivity #%v", a)
|
||||
}
|
||||
|
||||
ob := ObjectNew(ArticleType)
|
||||
it = ob
|
||||
|
||||
o, err := ToQuestion(it)
|
||||
if err == nil {
|
||||
t.Errorf("Error returned when calling ToActivity with object should not be nil")
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("Invalid return by ToActivity #%v, should have been nil", o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuestion_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestQuestion_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
241
relationship.go
Normal file
241
relationship.go
Normal file
|
@ -0,0 +1,241 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Relationship describes a relationship between two individuals.
|
||||
// The subject and object properties are used to identify the connected individuals.
|
||||
//See 5.2 Representing Relationships Between Entities for additional information.
|
||||
// 5.2: The relationship property specifies the kind of relationship that exists between the two individuals identified
|
||||
// by the subject and object properties. Used together, these three properties form what is commonly known
|
||||
// as a "reified statement" where subject identifies the subject, relationship identifies the predicate,
|
||||
// and object identifies the object.
|
||||
type Relationship struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
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
|
||||
// Object
|
||||
Object Item
|
||||
// Relationship On a Relationship object, the relationship property identifies the kind
|
||||
// of relationship that exists between subject and object.
|
||||
Relationship Item
|
||||
}
|
||||
|
||||
// IsLink returns false for Relationship objects
|
||||
func (r Relationship) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for Relationship objects
|
||||
func (r Relationship) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Relationship objects
|
||||
func (r Relationship) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Relationship object
|
||||
func (r Relationship) GetLink() IRI {
|
||||
return IRI(r.ID)
|
||||
}
|
||||
|
||||
// GetType returns the type of the current Relationship
|
||||
func (r Relationship) GetType() ActivityVocabularyType {
|
||||
return r.Type
|
||||
}
|
||||
|
||||
// GetID returns the ID corresponding to the current Relationship
|
||||
func (r Relationship) GetID() ObjectID {
|
||||
return r.ID
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (r *Relationship) UnmarshalJSON(data []byte) error {
|
||||
// TODO(marius): this is a candidate of using OnObject() for loading the common properties
|
||||
// and then loading the extra ones
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
r.ID = JSONGetObjectID(data)
|
||||
r.Type = JSONGetType(data)
|
||||
r.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
r.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
r.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
r.Context = JSONGetItem(data, "context")
|
||||
r.URL = JSONGetURIItem(data, "url")
|
||||
r.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
r.Generator = JSONGetItem(data, "generator")
|
||||
r.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
r.Attachment = JSONGetItem(data, "attachment")
|
||||
r.Location = JSONGetItem(data, "location")
|
||||
r.Published = JSONGetTime(data, "published")
|
||||
r.StartTime = JSONGetTime(data, "startTime")
|
||||
r.EndTime = JSONGetTime(data, "endTime")
|
||||
r.Duration = JSONGetDuration(data, "duration")
|
||||
r.Icon = JSONGetItem(data, "icon")
|
||||
r.Preview = JSONGetItem(data, "preview")
|
||||
r.Image = JSONGetItem(data, "image")
|
||||
r.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
r.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
r.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
r.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
r.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
r.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
r.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
r.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
r.Tag = tag
|
||||
}
|
||||
r.Subject = JSONGetItem(data, "subject")
|
||||
r.Object = JSONGetItem(data, "object")
|
||||
r.Relationship = JSONGetItem(data, "relationship")
|
||||
return 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
|
||||
rec, _ := ItemCollectionDeduplication(&aud, &r.To, &r.Bto, &r.CC, &r.BCC, &r.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (r *Relationship) Clean(){
|
||||
r.BCC = nil
|
||||
r.Bto = nil
|
||||
}
|
||||
|
||||
|
||||
// ToRelationship
|
||||
func ToRelationship(it Item) (*Relationship, error) {
|
||||
switch i := it.(type) {
|
||||
case *Relationship:
|
||||
return i, nil
|
||||
case Relationship:
|
||||
return &i, nil
|
||||
case *Object:
|
||||
return (*Relationship)(unsafe.Pointer(i)), nil
|
||||
case Object:
|
||||
return (*Relationship)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert %q", it.GetType())
|
||||
}
|
35
relationship_test.go
Normal file
35
relationship_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRelationship_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestRelationship_Clean(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
63
shares.go
63
shares.go
|
@ -1,7 +1,5 @@
|
|||
package activitypub
|
||||
|
||||
import as "github.com/go-ap/activitystreams"
|
||||
|
||||
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
|
||||
|
@ -10,69 +8,18 @@ type (
|
|||
SharesCollection = Shares
|
||||
|
||||
// Shares is a type alias for an Ordered Collection
|
||||
Shares as.OrderedCollection
|
||||
Shares = OrderedCollection
|
||||
)
|
||||
|
||||
// SharesNew initializes a new Shares
|
||||
func SharesNew() *Shares {
|
||||
id := as.ObjectID("Shares")
|
||||
id := ObjectID("Shares")
|
||||
|
||||
i := Shares{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
||||
i.Name = as.NaturalLanguageValuesNew()
|
||||
i.Content = as.NaturalLanguageValuesNew()
|
||||
i := Shares{ID: id, Type: CollectionType}
|
||||
i.Name = NaturalLanguageValuesNew()
|
||||
i.Content = NaturalLanguageValuesNew()
|
||||
|
||||
i.TotalItems = 0
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// Append adds an element to an Shares
|
||||
func (o *Shares) Append(ob as.Item) error {
|
||||
o.OrderedItems = append(o.OrderedItems, ob)
|
||||
o.TotalItems++
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the ObjectID corresponding to Shares
|
||||
func (o Shares) GetID() *as.ObjectID {
|
||||
return o.Collection().GetID()
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Shares object
|
||||
func (o Shares) GetLink() as.IRI {
|
||||
return as.IRI(o.ID)
|
||||
}
|
||||
|
||||
// GetType returns the Shares's type
|
||||
func (o Shares) GetType() as.ActivityVocabularyType {
|
||||
return o.Type
|
||||
}
|
||||
|
||||
// IsLink returns false for an Shares object
|
||||
func (o Shares) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for a Shares object
|
||||
func (o Shares) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (o *Shares) UnmarshalJSON(data []byte) error {
|
||||
if as.ItemTyperFunc == nil {
|
||||
as.ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
c := as.OrderedCollection(*o)
|
||||
err := c.UnmarshalJSON(data)
|
||||
|
||||
*o = Shares(c)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Collection returns the underlying Collection type
|
||||
func (o Shares) Collection() as.CollectionInterface {
|
||||
c := as.OrderedCollection(o)
|
||||
return &c
|
||||
}
|
||||
|
|
|
@ -2,38 +2,6 @@ package activitypub
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestShares_Append(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_Collection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestShares_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestSharesNew(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
|
|
@ -3,15 +3,15 @@ package tests
|
|||
import (
|
||||
"testing"
|
||||
|
||||
a "github.com/go-ap/activitystreams"
|
||||
j "github.com/go-ap/jsonld"
|
||||
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TestAcceptSerialization(t *testing.T) {
|
||||
obj := a.AcceptNew("https://localhost/myactivity", nil)
|
||||
obj.Name = make(a.NaturalLanguageValues, 1)
|
||||
obj := pub.AcceptNew("https://localhost/myactivity", nil)
|
||||
obj.Name = make(pub.NaturalLanguageValues, 1)
|
||||
obj.Name.Set("en", "test")
|
||||
obj.Name.Set("fr", "teste")
|
||||
|
||||
|
@ -41,11 +41,11 @@ func TestAcceptSerialization(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateActivityHTTPSerialization(t *testing.T) {
|
||||
id := a.ObjectID("test_object")
|
||||
obj := a.AcceptNew(id, nil)
|
||||
id := pub.ObjectID("test_object")
|
||||
obj := pub.AcceptNew(id, nil)
|
||||
obj.Name.Set("en", "Accept New")
|
||||
|
||||
uri := string(a.ActivityBaseURI)
|
||||
uri := string(pub.ActivityBaseURI)
|
||||
|
||||
data, err := j.WithContext(j.IRI(uri)).Marshal(obj)
|
||||
if err != nil {
|
||||
|
|
22
tests/mocks/activity_create_multiple_objects.json
Normal file
22
tests/mocks/activity_create_multiple_objects.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"type": "Create",
|
||||
"actor": "https://littr.git/api/accounts/anonymous",
|
||||
"object": [
|
||||
{
|
||||
"type": "Note",
|
||||
"attributedTo": "https://littr.git/api/accounts/anonymous",
|
||||
"inReplyTo": "https://littr.git/api/accounts/system/outbox/7ca154ff",
|
||||
"content": "<p>Hello world</p>",
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public"
|
||||
},
|
||||
{
|
||||
"type": "Article",
|
||||
"id": "http://www.test.example/article/1",
|
||||
"name": "This someday will grow up to be an article",
|
||||
"inReplyTo": [
|
||||
"http://www.test.example/object/1",
|
||||
"http://www.test.example/object/778"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
10
tests/mocks/article_with_multiple_inreplyto.json
Normal file
10
tests/mocks/article_with_multiple_inreplyto.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"type": "Article",
|
||||
"id": "http://www.test.example/article/1",
|
||||
"name": "This someday will grow up to be an article",
|
||||
"inReplyTo": [
|
||||
"http://www.test.example/object/1",
|
||||
"http://www.test.example/object/778"
|
||||
]
|
||||
}
|
|
@ -4,7 +4,7 @@ package tests
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
a "github.com/go-ap/activitystreams"
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -22,9 +22,9 @@ S2S Server: Activities requiring the object property
|
|||
`
|
||||
t.Log(desc)
|
||||
|
||||
obj := a.MentionNew("gigel")
|
||||
obj := pub.MentionNew("gigel")
|
||||
|
||||
add := a.AddNew("https://localhost/myactivity", obj, nil)
|
||||
add := pub.AddNew("https://localhost/myactivity", obj, nil)
|
||||
if add.Object == nil {
|
||||
t.Errorf("Missing GetID in Add activity %#v", add.Object)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Add.GetID different than what we initialized %#v %#v", add.Object, obj)
|
||||
}
|
||||
|
||||
block := a.BlockNew("https://localhost/myactivity", obj)
|
||||
block := pub.BlockNew("https://localhost/myactivity", obj)
|
||||
if block.Object == nil {
|
||||
t.Errorf("Missing GetID in Add activity %#v", block.Object)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Block.GetID different than what we initialized %#v %#v", block.Object, obj)
|
||||
}
|
||||
|
||||
create := a.CreateNew("https://localhost/myactivity", obj)
|
||||
create := pub.CreateNew("https://localhost/myactivity", obj)
|
||||
if create.Object == nil {
|
||||
t.Errorf("Missing GetID in Add activity %#v", create.Object)
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Create.GetID different than what we initialized %#v %#v", create.Object, obj)
|
||||
}
|
||||
|
||||
delete := a.DeleteNew("https://localhost/myactivity", obj)
|
||||
delete := pub.DeleteNew("https://localhost/myactivity", obj)
|
||||
if delete.Object == nil {
|
||||
t.Errorf("Missing GetID in Delete activity %#v", delete.Object)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Delete.GetID different than what we initialized %#v %#v", delete.Object, obj)
|
||||
}
|
||||
|
||||
follow := a.FollowNew("https://localhost/myactivity", obj)
|
||||
follow := pub.FollowNew("https://localhost/myactivity", obj)
|
||||
if follow.Object == nil {
|
||||
t.Errorf("Missing GetID in Follow activity %#v", follow.Object)
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Follow.GetID different than what we initialized %#v %#v", follow.Object, obj)
|
||||
}
|
||||
|
||||
like := a.LikeNew("https://localhost/myactivity", obj)
|
||||
like := pub.LikeNew("https://localhost/myactivity", obj)
|
||||
if like.Object == nil {
|
||||
t.Errorf("Missing GetID in Like activity %#v", like.Object)
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Like.GetID different than what we initialized %#v %#v", add.Object, obj)
|
||||
}
|
||||
|
||||
update := a.UpdateNew("https://localhost/myactivity", obj)
|
||||
update := pub.UpdateNew("https://localhost/myactivity", obj)
|
||||
if update.Object == nil {
|
||||
t.Errorf("Missing GetID in Update activity %#v", update.Object)
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ S2S Server: Activities requiring the object property
|
|||
t.Errorf("Update.GetID different than what we initialized %#v %#v", update.Object, obj)
|
||||
}
|
||||
|
||||
undo := a.UndoNew("https://localhost/myactivity", obj)
|
||||
undo := pub.UndoNew("https://localhost/myactivity", obj)
|
||||
if undo.Object == nil {
|
||||
t.Errorf("Missing GetID in Undo activity %#v", undo.Object)
|
||||
}
|
||||
|
@ -103,10 +103,10 @@ property: Add, Remove.
|
|||
`
|
||||
t.Log(desc)
|
||||
|
||||
obj := a.MentionNew("foo")
|
||||
target := a.MentionNew("bar")
|
||||
obj := pub.MentionNew("foo")
|
||||
target := pub.MentionNew("bar")
|
||||
|
||||
add := a.AddNew("https://localhost/myactivity", obj, target)
|
||||
add := pub.AddNew("https://localhost/myactivity", obj, target)
|
||||
if add.Target == nil {
|
||||
t.Errorf("Missing Target in Add activity %#v", add.Target)
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ property: Add, Remove.
|
|||
t.Errorf("Add.Target different than what we initialized %#v %#v", add.Target, target)
|
||||
}
|
||||
|
||||
remove := a.RemoveNew("https://localhost/myactivity", obj, target)
|
||||
remove := pub.RemoveNew("https://localhost/myactivity", obj, target)
|
||||
if remove.Target == nil {
|
||||
t.Errorf("Missing Target in Remove activity %#v", remove.Target)
|
||||
}
|
||||
|
@ -141,32 +141,32 @@ S2S Server: Deduplication of recipient list
|
|||
`
|
||||
t.Log(desc)
|
||||
|
||||
to := a.PersonNew("bob")
|
||||
o := a.ObjectNew(a.ArticleType)
|
||||
cc := a.PersonNew("alice")
|
||||
to := pub.PersonNew("bob")
|
||||
o := pub.ObjectNew(pub.ArticleType)
|
||||
cc := pub.PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
c := a.CreateNew("create", o)
|
||||
c := pub.CreateNew("create", o)
|
||||
c.To.Append(to)
|
||||
c.CC.Append(cc)
|
||||
c.BCC.Append(cc)
|
||||
|
||||
c.Recipients()
|
||||
|
||||
checkDedup := func(list a.ItemCollection, recIds *[]a.ObjectID) error {
|
||||
checkDedup := func(list pub.ItemCollection, recIds *[]pub.ObjectID) error {
|
||||
for _, rec := range list {
|
||||
for _, id := range *recIds {
|
||||
if *rec.GetID() == id {
|
||||
if rec.GetID() == id {
|
||||
return fmt.Errorf("%T[%s] already stored in recipients list, Deduplication faild", rec, id)
|
||||
}
|
||||
}
|
||||
*recIds = append(*recIds, *rec.GetID())
|
||||
*recIds = append(*recIds, rec.GetID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
recIds := make([]a.ObjectID, 0)
|
||||
recIds := make([]pub.ObjectID, 0)
|
||||
err = checkDedup(c.To, &recIds)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
@ -197,15 +197,15 @@ Activity being notified about
|
|||
`
|
||||
t.Log(desc)
|
||||
|
||||
p := a.PersonNew("main actor")
|
||||
p := pub.PersonNew("main actor")
|
||||
|
||||
to := a.PersonNew("bob")
|
||||
o := a.ObjectNew(a.ArticleType)
|
||||
cc := a.PersonNew("alice")
|
||||
to := pub.PersonNew("bob")
|
||||
o := pub.ObjectNew(pub.ArticleType)
|
||||
cc := pub.PersonNew("alice")
|
||||
|
||||
o.ID = "something"
|
||||
c := a.CreateNew("create", o)
|
||||
c.Actor = a.Actor(*p)
|
||||
c := pub.CreateNew("create", o)
|
||||
c.Actor = *p
|
||||
|
||||
c.To.Append(p)
|
||||
c.To.Append(to)
|
||||
|
@ -216,10 +216,10 @@ Activity being notified about
|
|||
|
||||
c.Recipients()
|
||||
|
||||
checkActor := func(list a.ItemCollection, actor a.Item) error {
|
||||
checkActor := func(list pub.ItemCollection, actor pub.Item) error {
|
||||
for _, rec := range list {
|
||||
if rec.GetID() == actor.GetID() {
|
||||
return fmt.Errorf("%T[%s] Actor of activity should not be in the recipients list", rec, *actor.GetID())
|
||||
return fmt.Errorf("%T[%s] Actor of activity should not be in the recipients list", rec, actor.GetID())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -254,13 +254,13 @@ S2S Server: Do-not-deliver considerations
|
|||
`
|
||||
t.Log(desc)
|
||||
|
||||
p := a.PersonNew("blocked")
|
||||
p := pub.PersonNew("blocked")
|
||||
|
||||
bob := a.PersonNew("bob")
|
||||
jane := a.PersonNew("jane doe")
|
||||
bob := pub.PersonNew("bob")
|
||||
jane := pub.PersonNew("jane doe")
|
||||
|
||||
b := a.BlockNew("block actor", p)
|
||||
b.Actor = a.Actor(*bob)
|
||||
b := pub.BlockNew("block actor", p)
|
||||
b.Actor = *bob
|
||||
|
||||
b.To.Append(jane)
|
||||
b.To.Append(p)
|
||||
|
@ -268,10 +268,10 @@ S2S Server: Do-not-deliver considerations
|
|||
|
||||
b.Recipients()
|
||||
|
||||
checkActor := func(list a.ItemCollection, ob a.Item) error {
|
||||
checkActor := func(list pub.ItemCollection, ob pub.Item) error {
|
||||
for _, rec := range list {
|
||||
if rec.GetID() == ob.GetID() {
|
||||
return fmt.Errorf("%T[%s] of activity should not be in the recipients list", rec, *ob.GetID())
|
||||
return fmt.Errorf("%T[%s] of activity should not be in the recipients list", rec, ob.GetID())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
546
tests/unmarshal.go
Normal file
546
tests/unmarshal.go
Normal file
|
@ -0,0 +1,546 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
pub "github.com/go-ap/activitypub"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
j "github.com/go-ap/jsonld"
|
||||
)
|
||||
|
||||
const dir = "./mocks"
|
||||
|
||||
var stopOnFailure = false
|
||||
|
||||
type testPair struct {
|
||||
expected bool
|
||||
blank interface{}
|
||||
result interface{}
|
||||
}
|
||||
|
||||
type testMaps map[string]testPair
|
||||
|
||||
type visit struct {
|
||||
a1 unsafe.Pointer
|
||||
a2 unsafe.Pointer
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
type canErrorFunc func(format string, args ...interface{})
|
||||
|
||||
func assertDeepEquals(t canErrorFunc, x, y interface{}) bool {
|
||||
if x == nil || y == nil {
|
||||
return x == y
|
||||
}
|
||||
v1 := reflect.ValueOf(x)
|
||||
//if v1.CanAddr() {
|
||||
// v1 = v1.Addr()
|
||||
//}
|
||||
v2 := reflect.ValueOf(y)
|
||||
//if v2.CanAddr() {
|
||||
// v2 = v2.Addr()
|
||||
//}
|
||||
if v1.Type() != v2.Type() {
|
||||
t("%T != %T", x, y)
|
||||
return false
|
||||
}
|
||||
return deepValueEqual(t, v1, v2, make(map[visit]bool), 0)
|
||||
}
|
||||
|
||||
func deepValueEqual(t canErrorFunc, v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
||||
if !v1.IsValid() || !v2.IsValid() {
|
||||
return v1.IsValid() == v2.IsValid()
|
||||
}
|
||||
if v1.Type() != v2.Type() {
|
||||
t("types differ %s != %s", v1.Type().Name(), v2.Type().Name())
|
||||
return false
|
||||
}
|
||||
|
||||
// We want to avoid putting more in the visited map than we need to.
|
||||
// For any possible reference cycle that might be encountered,
|
||||
// hard(t) needs to return true for at least one of the types in the cycle.
|
||||
hard := func(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Interface:
|
||||
return true
|
||||
}
|
||||
//t("Invalid type for %s", k)
|
||||
return false
|
||||
}
|
||||
|
||||
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
|
||||
addr1 := unsafe.Pointer(v1.UnsafeAddr())
|
||||
addr2 := unsafe.Pointer(v2.UnsafeAddr())
|
||||
if uintptr(addr1) > uintptr(addr2) {
|
||||
// Canonicalize order to reduce number of entries in visited.
|
||||
// Assumes non-moving garbage collector.
|
||||
addr1, addr2 = addr2, addr1
|
||||
}
|
||||
|
||||
// Short circuit if references are already seen.
|
||||
typ := v1.Type()
|
||||
v := visit{addr1, addr2, typ}
|
||||
if visited[v] {
|
||||
return true
|
||||
}
|
||||
|
||||
// Remember for later.
|
||||
visited[v] = true
|
||||
}
|
||||
|
||||
switch v1.Kind() {
|
||||
case reflect.Array:
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !deepValueEqual(t, v1.Index(i), v2.Index(i), visited, depth+1) {
|
||||
t("Arrays not equal at index %d %s %s", i, v1.Index(i), v2.Index(i))
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Slice:
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
t("One of the slices is not nil %s[%d] vs %s[%d]", v1.Type().Name(), v1.Len(), v2.Type().Name(), v2.Len())
|
||||
return false
|
||||
}
|
||||
if v1.Len() != v2.Len() {
|
||||
t("Slices lengths are different %s[%d] vs %s[%d]", v1.Type().Name(), v1.Len(), v2.Type().Name(), v2.Len())
|
||||
return false
|
||||
}
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !deepValueEqual(t, v1.Index(i), v2.Index(i), visited, depth+1) {
|
||||
t("Slices elements at pos %d are not equal %#v vs %#v", i, v1.Index(i), v2.Index(i))
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Interface:
|
||||
if v1.IsNil() || v2.IsNil() {
|
||||
if v1.IsNil() == v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
var isNil1, isNil2 string
|
||||
if v1.IsNil() {
|
||||
isNil1 = "is"
|
||||
} else {
|
||||
isNil1 = "is not"
|
||||
}
|
||||
if v2.IsNil() {
|
||||
isNil2 = "is"
|
||||
} else {
|
||||
isNil2 = "is not"
|
||||
}
|
||||
t("Interface '%s' %s nil and '%s' %s nil", v1.Type().Name(), isNil1, v2.Type().Name(), isNil2)
|
||||
return false
|
||||
}
|
||||
return deepValueEqual(t, v1.Elem(), v2.Elem(), visited, depth+1)
|
||||
case reflect.Ptr:
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
return deepValueEqual(t, v1.Elem(), v2.Elem(), visited, depth+1)
|
||||
case reflect.Struct:
|
||||
for i, n := 0, v1.NumField(); i < n; i++ {
|
||||
if !deepValueEqual(t, v1.Field(i), v2.Field(i), visited, depth+1) {
|
||||
t("Struct fields at pos %d %s[%s] and %s[%s] are not deeply equal", i, v1.Type().Field(i).Name, v1.Field(i).Type().Name(), v2.Type().Field(i).Name, v2.Field(i).Type().Name())
|
||||
if v1.Field(i).CanAddr() && v2.Field(i).CanAddr() {
|
||||
t(" Values: %#v - %#v", v1.Field(i).Interface(), v2.Field(i).Interface())
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Map:
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
t("Maps are not nil", v1.Type().Name(), v2.Type().Name())
|
||||
return false
|
||||
}
|
||||
if v1.Len() != v2.Len() {
|
||||
t("Maps don't have the same length %d vs %d", v1.Len(), v2.Len())
|
||||
return false
|
||||
}
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
for _, k := range v1.MapKeys() {
|
||||
val1 := v1.MapIndex(k)
|
||||
val2 := v2.MapIndex(k)
|
||||
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(t, v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
|
||||
t("Maps values at index %s are not equal", k.String())
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Func:
|
||||
if v1.IsNil() && v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
// Can't do better than this:
|
||||
return false
|
||||
}
|
||||
return true // i guess?
|
||||
}
|
||||
|
||||
var zLoc, _ = time.LoadLocation("UTC")
|
||||
|
||||
var allTests = testMaps{
|
||||
"empty": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{},
|
||||
},
|
||||
"link_simple": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Link{},
|
||||
result: &pub.Link{
|
||||
Type: pub.LinkType,
|
||||
Href: pub.IRI("http://example.org/abc"),
|
||||
HrefLang: pub.LangRef("en"),
|
||||
MediaType: pub.MimeType("text/html"),
|
||||
Name: pub.NaturalLanguageValues{{
|
||||
pub.NilLangRef, "An example link",
|
||||
}},
|
||||
},
|
||||
},
|
||||
"object_with_url": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{
|
||||
URL: pub.IRI("http://littr.git/api/accounts/system"),
|
||||
},
|
||||
},
|
||||
"object_with_url_collection": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{
|
||||
URL: pub.ItemCollection{
|
||||
pub.IRI("http://littr.git/api/accounts/system"),
|
||||
pub.IRI("http://littr.git/~system"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_simple": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{
|
||||
Type: pub.ObjectType,
|
||||
ID: pub.ObjectID("http://www.test.example/object/1"),
|
||||
Name: pub.NaturalLanguageValues{{
|
||||
pub.NilLangRef, "A Simple, non-specific object",
|
||||
}},
|
||||
},
|
||||
},
|
||||
"object_with_tags": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{
|
||||
Type: pub.ObjectType,
|
||||
ID: pub.ObjectID("http://www.test.example/object/1"),
|
||||
Name: pub.NaturalLanguageValues{{
|
||||
pub.NilLangRef, "A Simple, non-specific object",
|
||||
}},
|
||||
Tag: pub.ItemCollection{
|
||||
&pub.Mention{
|
||||
Name: pub.NaturalLanguageValues{{
|
||||
pub.NilLangRef, "#my_tag",
|
||||
}},
|
||||
Type: pub.MentionType,
|
||||
ID: pub.ObjectID("http://example.com/tag/my_tag"),
|
||||
},
|
||||
&pub.Mention{
|
||||
Name: pub.NaturalLanguageValues{{
|
||||
pub.NilLangRef, "@ana",
|
||||
}},
|
||||
Type: pub.MentionType,
|
||||
ID: pub.ObjectID("http://example.com/users/ana"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_with_replies": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{
|
||||
Type: pub.ObjectType,
|
||||
ID: pub.ObjectID("http://www.test.example/object/1"),
|
||||
Replies: &pub.Collection{
|
||||
ID: pub.ObjectID("http://www.test.example/object/1/replies"),
|
||||
Type: pub.CollectionType,
|
||||
TotalItems: 1,
|
||||
Items: pub.ItemCollection{
|
||||
&pub.Object{
|
||||
ID: pub.ObjectID("http://www.test.example/object/1/replies/2"),
|
||||
Type: pub.ArticleType,
|
||||
Name: pub.NaturalLanguageValues{{
|
||||
pub.NilLangRef, "Example title",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"activity_simple": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Activity{
|
||||
Actor: &pub.Person{},
|
||||
},
|
||||
result: &pub.Activity{
|
||||
Type: pub.ActivityType,
|
||||
Summary: pub.NaturalLanguageValues{{pub.NilLangRef, "Sally did something to a note"}},
|
||||
Actor: &pub.Person{
|
||||
Type: pub.PersonType,
|
||||
Name: pub.NaturalLanguageValues{{pub.NilLangRef, "Sally"}},
|
||||
},
|
||||
Object: &pub.Object{
|
||||
Type: pub.NoteType,
|
||||
Name: pub.NaturalLanguageValues{{pub.NilLangRef, "A Note"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"person_with_outbox": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Person{},
|
||||
result: &pub.Person{
|
||||
ID: pub.ObjectID("http://example.com/accounts/ana"),
|
||||
Type: pub.PersonType,
|
||||
Name: pub.NaturalLanguageValues{{pub.NilLangRef, "ana"}},
|
||||
PreferredUsername: pub.NaturalLanguageValues{{pub.NilLangRef, "ana"}},
|
||||
URL: pub.IRI("http://example.com/accounts/ana"),
|
||||
Outbox: &pub.OrderedCollection{
|
||||
ID: "http://example.com/accounts/ana/outbox",
|
||||
Type: pub.OrderedCollectionType,
|
||||
URL: pub.IRI("http://example.com/outbox"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"ordered_collection": testPair{
|
||||
expected: true,
|
||||
blank: &pub.OrderedCollection{},
|
||||
result: &pub.OrderedCollection{
|
||||
ID: pub.ObjectID("http://example.com/outbox"),
|
||||
Type: pub.OrderedCollectionType,
|
||||
URL: pub.IRI("http://example.com/outbox"),
|
||||
TotalItems: 1,
|
||||
OrderedItems: pub.ItemCollection{
|
||||
&pub.Object{
|
||||
ID: pub.ObjectID("http://example.com/outbox/53c6fb47"),
|
||||
Type: pub.ArticleType,
|
||||
Name: pub.NaturalLanguageValues{{pub.NilLangRef, "Example title"}},
|
||||
Content: pub.NaturalLanguageValues{{pub.NilLangRef, "Example content!"}},
|
||||
URL: pub.IRI("http://example.com/53c6fb47"),
|
||||
MediaType: pub.MimeType("text/markdown"),
|
||||
Published: time.Date(2018, time.July, 5, 16, 46, 44, 0, zLoc),
|
||||
Generator: pub.IRI("http://example.com"),
|
||||
AttributedTo: pub.IRI("http://example.com/accounts/alice"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ordered_collection_page": testPair{
|
||||
expected: true,
|
||||
blank: &pub.OrderedCollectionPage{},
|
||||
result: &pub.OrderedCollectionPage{
|
||||
PartOf: pub.IRI("http://example.com/outbox"),
|
||||
Next: pub.IRI("http://example.com/outbox?page=3"),
|
||||
Prev: pub.IRI("http://example.com/outbox?page=1"),
|
||||
ID: pub.ObjectID("http://example.com/outbox?page=2"),
|
||||
Type: pub.OrderedCollectionPageType,
|
||||
URL: pub.IRI("http://example.com/outbox?page=2"),
|
||||
Current: pub.IRI("http://example.com/outbox?page=2"),
|
||||
TotalItems: 1,
|
||||
OrderedItems: pub.ItemCollection{
|
||||
&pub.Object{
|
||||
ID: pub.ObjectID("http://example.com/outbox/53c6fb47"),
|
||||
Type: pub.ArticleType,
|
||||
Name: pub.NaturalLanguageValues{{pub.NilLangRef, "Example title"}},
|
||||
Content: pub.NaturalLanguageValues{{pub.NilLangRef, "Example content!"}},
|
||||
URL: pub.IRI("http://example.com/53c6fb47"),
|
||||
MediaType: pub.MimeType("text/markdown"),
|
||||
Published: time.Date(2018, time.July, 5, 16, 46, 44, 0, zLoc),
|
||||
Generator: pub.IRI("http://example.com"),
|
||||
AttributedTo: pub.IRI("http://example.com/accounts/alice"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"natural_language_values": {
|
||||
expected: true,
|
||||
blank: &pub.NaturalLanguageValues{},
|
||||
result: &pub.NaturalLanguageValues{
|
||||
{
|
||||
pub.NilLangRef, `
|
||||
|
||||
`},
|
||||
{pub.LangRef("en"), "Ana got apples ⓐ"},
|
||||
{pub.LangRef("fr"), "Aná a des pommes ⒜"},
|
||||
{pub.LangRef("ro"), "Ana are mere"},
|
||||
},
|
||||
},
|
||||
"activity_create_simple": {
|
||||
expected: true,
|
||||
blank: &pub.Create{},
|
||||
result: &pub.Create{
|
||||
Type: pub.CreateType,
|
||||
Actor: pub.IRI("https://littr.git/api/accounts/anonymous"),
|
||||
Object: &pub.Object{
|
||||
Type: pub.NoteType,
|
||||
AttributedTo: pub.IRI("https://littr.git/api/accounts/anonymous"),
|
||||
InReplyTo: pub.ItemCollection{pub.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff")},
|
||||
Content: pub.NaturalLanguageValues{{pub.NilLangRef, "<p>Hello world</p>"}},
|
||||
To: pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")},
|
||||
},
|
||||
},
|
||||
},
|
||||
"activity_create_multiple_objects": {
|
||||
expected: true,
|
||||
blank: &pub.Create{},
|
||||
result: &pub.Create{
|
||||
Type: pub.CreateType,
|
||||
Actor: pub.IRI("https://littr.git/api/accounts/anonymous"),
|
||||
Object:pub.ItemCollection{
|
||||
&pub.Object{
|
||||
Type: pub.NoteType,
|
||||
AttributedTo: pub.IRI("https://littr.git/api/accounts/anonymous"),
|
||||
InReplyTo: pub.ItemCollection{pub.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff")},
|
||||
Content: pub.NaturalLanguageValues{{pub.NilLangRef, "<p>Hello world</p>"}},
|
||||
To: pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")},
|
||||
},
|
||||
&pub.Article{
|
||||
Type: pub.ArticleType,
|
||||
ID: pub.ObjectID("http://www.test.example/article/1"),
|
||||
Name: pub.NaturalLanguageValues{
|
||||
{
|
||||
pub.NilLangRef,
|
||||
"This someday will grow up to be an article",
|
||||
},
|
||||
},
|
||||
InReplyTo: pub.ItemCollection{
|
||||
pub.IRI("http://www.test.example/object/1"),
|
||||
pub.IRI("http://www.test.example/object/778"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_with_audience": testPair{
|
||||
expected: true,
|
||||
blank: &pub.Object{},
|
||||
result: &pub.Object{
|
||||
Type: pub.ObjectType,
|
||||
ID: pub.ObjectID("http://www.test.example/object/1"),
|
||||
To: pub.ItemCollection{
|
||||
pub.IRI("https://www.w3.org/ns/activitystreams#Public"),
|
||||
},
|
||||
Bto: pub.ItemCollection{
|
||||
pub.IRI("http://example.com/sharedInbox"),
|
||||
},
|
||||
CC: pub.ItemCollection{
|
||||
pub.IRI("https://example.com/actors/ana"),
|
||||
pub.IRI("https://example.com/actors/bob"),
|
||||
},
|
||||
BCC: pub.ItemCollection{
|
||||
pub.IRI("https://darkside.cookie/actors/darthvader"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"article_with_multiple_inreplyto": {
|
||||
expected: true,
|
||||
blank: &pub.Article{},
|
||||
result: &pub.Article{
|
||||
Type: pub.ArticleType,
|
||||
ID: pub.ObjectID("http://www.test.example/article/1"),
|
||||
Name: pub.NaturalLanguageValues{
|
||||
{
|
||||
pub.NilLangRef,
|
||||
"This someday will grow up to be an article",
|
||||
},
|
||||
},
|
||||
InReplyTo: pub.ItemCollection{
|
||||
pub.IRI("http://www.test.example/object/1"),
|
||||
pub.IRI("http://www.test.example/object/778"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getFileContents(path string) ([]byte, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]byte, st.Size())
|
||||
io.ReadFull(f, data)
|
||||
data = bytes.Trim(data, "\x00")
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
var err error
|
||||
|
||||
var f = t.Errorf
|
||||
if len(allTests) == 0 {
|
||||
t.Skip("No tests found")
|
||||
}
|
||||
|
||||
for k, pair := range allTests {
|
||||
path := filepath.Join(dir, fmt.Sprintf("%s.json", k))
|
||||
t.Run(path, func(t *testing.T) {
|
||||
var data []byte
|
||||
data, err = getFileContents(path)
|
||||
if err != nil {
|
||||
f("Error: %s for %s", err, path)
|
||||
return
|
||||
}
|
||||
object := pair.blank
|
||||
|
||||
err = j.Unmarshal(data, object)
|
||||
if err != nil {
|
||||
f("Error: %s for %s", err, data)
|
||||
return
|
||||
}
|
||||
expLbl := ""
|
||||
if !pair.expected {
|
||||
expLbl = "not be "
|
||||
}
|
||||
status := assertDeepEquals(f, object, pair.result)
|
||||
if pair.expected != status {
|
||||
if stopOnFailure {
|
||||
f = t.Fatalf
|
||||
}
|
||||
|
||||
f("Mock: %s: %s\n%#v\n should %sequal to expected\n%#v", k, path, object, expLbl, pair.result)
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
oj, err := j.Marshal(object)
|
||||
if err != nil {
|
||||
f(err.Error())
|
||||
}
|
||||
tj, err := j.Marshal(pair.result)
|
||||
if err != nil {
|
||||
f(err.Error())
|
||||
}
|
||||
f("Mock: %s: %s\n%s\n should %sequal to expected\n%s", k, path, oj, expLbl, tj)
|
||||
}
|
||||
//if err == nil {
|
||||
// fmt.Printf(" --- %s: %s\n %s\n", "PASS", k, path)
|
||||
//}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,520 +0,0 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
a "github.com/go-ap/activitystreams"
|
||||
j "github.com/go-ap/jsonld"
|
||||
)
|
||||
|
||||
const dir = "./mocks"
|
||||
|
||||
var stopOnFailure = false
|
||||
|
||||
type testPair struct {
|
||||
expected bool
|
||||
blank interface{}
|
||||
result interface{}
|
||||
}
|
||||
|
||||
type tests map[string]testPair
|
||||
|
||||
type visit struct {
|
||||
a1 unsafe.Pointer
|
||||
a2 unsafe.Pointer
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
type canErrorFunc func(format string, args ...interface{})
|
||||
|
||||
func assertDeepEquals(t canErrorFunc, x, y interface{}) bool {
|
||||
if x == nil || y == nil {
|
||||
return x == y
|
||||
}
|
||||
v1 := reflect.ValueOf(x)
|
||||
//if v1.CanAddr() {
|
||||
// v1 = v1.Addr()
|
||||
//}
|
||||
v2 := reflect.ValueOf(y)
|
||||
//if v2.CanAddr() {
|
||||
// v2 = v2.Addr()
|
||||
//}
|
||||
if v1.Type() != v2.Type() {
|
||||
t("%T != %T", x, y)
|
||||
return false
|
||||
}
|
||||
return deepValueEqual(t, v1, v2, make(map[visit]bool), 0)
|
||||
}
|
||||
|
||||
func deepValueEqual(t canErrorFunc, v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
|
||||
if !v1.IsValid() || !v2.IsValid() {
|
||||
return v1.IsValid() == v2.IsValid()
|
||||
}
|
||||
if v1.Type() != v2.Type() {
|
||||
t("types differ %s != %s", v1.Type().Name(), v2.Type().Name())
|
||||
return false
|
||||
}
|
||||
|
||||
// We want to avoid putting more in the visited map than we need to.
|
||||
// For any possible reference cycle that might be encountered,
|
||||
// hard(t) needs to return true for at least one of the types in the cycle.
|
||||
hard := func(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Interface:
|
||||
return true
|
||||
}
|
||||
//t("Invalid type for %s", k)
|
||||
return false
|
||||
}
|
||||
|
||||
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
|
||||
addr1 := unsafe.Pointer(v1.UnsafeAddr())
|
||||
addr2 := unsafe.Pointer(v2.UnsafeAddr())
|
||||
if uintptr(addr1) > uintptr(addr2) {
|
||||
// Canonicalize order to reduce number of entries in visited.
|
||||
// Assumes non-moving garbage collector.
|
||||
addr1, addr2 = addr2, addr1
|
||||
}
|
||||
|
||||
// Short circuit if references are already seen.
|
||||
typ := v1.Type()
|
||||
v := visit{addr1, addr2, typ}
|
||||
if visited[v] {
|
||||
return true
|
||||
}
|
||||
|
||||
// Remember for later.
|
||||
visited[v] = true
|
||||
}
|
||||
|
||||
switch v1.Kind() {
|
||||
case reflect.Array:
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !deepValueEqual(t, v1.Index(i), v2.Index(i), visited, depth+1) {
|
||||
t("Arrays not equal at index %d %s %s", i, v1.Index(i), v2.Index(i))
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Slice:
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
t("One of the slices is not nil %s[%d] vs %s[%d]", v1.Type().Name(), v1.Len(), v2.Type().Name(), v2.Len())
|
||||
return false
|
||||
}
|
||||
if v1.Len() != v2.Len() {
|
||||
t("Slices lengths are different %s[%d] vs %s[%d]", v1.Type().Name(), v1.Len(), v2.Type().Name(), v2.Len())
|
||||
return false
|
||||
}
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !deepValueEqual(t, v1.Index(i), v2.Index(i), visited, depth+1) {
|
||||
t("Slices elements at pos %d are not equal %#v vs %#v", i, v1.Index(i), v2.Index(i))
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Interface:
|
||||
if v1.IsNil() || v2.IsNil() {
|
||||
if v1.IsNil() == v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
var isNil1, isNil2 string
|
||||
if v1.IsNil() {
|
||||
isNil1 = "is"
|
||||
} else {
|
||||
isNil1 = "is not"
|
||||
}
|
||||
if v2.IsNil() {
|
||||
isNil2 = "is"
|
||||
} else {
|
||||
isNil2 = "is not"
|
||||
}
|
||||
t("Interface '%s' %s nil and '%s' %s nil", v1.Type().Name(), isNil1, v2.Type().Name(), isNil2)
|
||||
return false
|
||||
}
|
||||
return deepValueEqual(t, v1.Elem(), v2.Elem(), visited, depth+1)
|
||||
case reflect.Ptr:
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
return deepValueEqual(t, v1.Elem(), v2.Elem(), visited, depth+1)
|
||||
case reflect.Struct:
|
||||
for i, n := 0, v1.NumField(); i < n; i++ {
|
||||
if !deepValueEqual(t, v1.Field(i), v2.Field(i), visited, depth+1) {
|
||||
t("Struct fields at pos %d %s[%s] and %s[%s] are not deeply equal", i, v1.Type().Field(i).Name, v1.Field(i).Type().Name(), v2.Type().Field(i).Name, v2.Field(i).Type().Name())
|
||||
if v1.Field(i).CanAddr() && v2.Field(i).CanAddr() {
|
||||
t(" Values: %#v - %#v", v1.Field(i).Interface(), v2.Field(i).Interface())
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Map:
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
t("Maps are not nil", v1.Type().Name(), v2.Type().Name())
|
||||
return false
|
||||
}
|
||||
if v1.Len() != v2.Len() {
|
||||
t("Maps don't have the same length %d vs %d", v1.Len(), v2.Len())
|
||||
return false
|
||||
}
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
for _, k := range v1.MapKeys() {
|
||||
val1 := v1.MapIndex(k)
|
||||
val2 := v2.MapIndex(k)
|
||||
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(t, v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
|
||||
t("Maps values at index %s are not equal", k.String())
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Func:
|
||||
if v1.IsNil() && v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
// Can't do better than this:
|
||||
return false
|
||||
}
|
||||
return true // i guess?
|
||||
}
|
||||
|
||||
var zLoc, _ = time.LoadLocation("UTC")
|
||||
|
||||
var allTests = tests{
|
||||
"empty": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{},
|
||||
},
|
||||
"object_with_url": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{Parent: a.Parent{URL: a.IRI("http://littr.git/api/accounts/system")}},
|
||||
},
|
||||
"object_with_url_collection": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{
|
||||
Parent: a.Parent{
|
||||
URL: a.ItemCollection{
|
||||
a.IRI("http://littr.git/api/accounts/system"),
|
||||
a.IRI("http://littr.git/~system"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_simple": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{
|
||||
Parent: a.Parent{
|
||||
Type: a.ObjectType,
|
||||
ID: a.ObjectID("http://www.test.example/object/1"),
|
||||
Name: a.NaturalLanguageValues{{
|
||||
a.NilLangRef, "A Simple, non-specific object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_with_tags": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{
|
||||
Parent: a.Parent{
|
||||
Type: a.ObjectType,
|
||||
ID: a.ObjectID("http://www.test.example/object/1"),
|
||||
Name: a.NaturalLanguageValues{{
|
||||
a.NilLangRef, "A Simple, non-specific object",
|
||||
}},
|
||||
Tag: a.ItemCollection{
|
||||
&a.Mention{
|
||||
Name: a.NaturalLanguageValues{{
|
||||
a.NilLangRef, "#my_tag",
|
||||
}},
|
||||
ID: a.ObjectID("http://example.com/tag/my_tag"),
|
||||
Type: a.MentionType,
|
||||
},
|
||||
&a.Mention{
|
||||
Name: a.NaturalLanguageValues{{
|
||||
a.NilLangRef, "@ana",
|
||||
}},
|
||||
Type: a.MentionType,
|
||||
ID: a.ObjectID("http://example.com/users/ana"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_with_replies": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{
|
||||
Parent: a.Parent{
|
||||
Type: a.ObjectType,
|
||||
ID: a.ObjectID("http://www.test.example/object/1"),
|
||||
Replies: &a.Collection{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://www.test.example/object/1/replies"),
|
||||
Type: a.CollectionType,
|
||||
},
|
||||
TotalItems: 1,
|
||||
Items: a.ItemCollection{
|
||||
&ap.Object{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://www.test.example/object/1/replies/2"),
|
||||
Type: a.ArticleType,
|
||||
Name: a.NaturalLanguageValues{{a.NilLangRef, "Example title"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"person_with_outbox": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Person{},
|
||||
result: &ap.Person{
|
||||
Parent: ap.Parent{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://example.com/accounts/ana"),
|
||||
Type: a.PersonType,
|
||||
Name: a.NaturalLanguageValues{{a.NilLangRef, "ana"}},
|
||||
URL: a.IRI("http://example.com/accounts/ana"),
|
||||
},
|
||||
},
|
||||
PreferredUsername: a.NaturalLanguageValues{{a.NilLangRef, "Ana"}},
|
||||
Outbox: &a.OrderedCollection{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://example.com/accounts/ana/outbox"),
|
||||
Type: a.OrderedCollectionType,
|
||||
URL: a.IRI("http://example.com/outbox"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ordered_collection": testPair{
|
||||
expected: true,
|
||||
blank: &a.OrderedCollection{},
|
||||
result: &a.OrderedCollection{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://example.com/outbox"),
|
||||
Type: a.OrderedCollectionType,
|
||||
URL: a.IRI("http://example.com/outbox"),
|
||||
},
|
||||
TotalItems: 1,
|
||||
OrderedItems: a.ItemCollection{
|
||||
&ap.Object{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://example.com/outbox/53c6fb47"),
|
||||
Type: a.ArticleType,
|
||||
Name: a.NaturalLanguageValues{{a.NilLangRef, "Example title"}},
|
||||
Content: a.NaturalLanguageValues{{a.NilLangRef, "Example content!"}},
|
||||
URL: a.IRI("http://example.com/53c6fb47"),
|
||||
MediaType: a.MimeType("text/markdown"),
|
||||
Published: time.Date(2018, time.July, 5, 16, 46, 44, 0, zLoc),
|
||||
Generator: a.IRI("http://example.com"),
|
||||
AttributedTo: a.IRI("http://example.com/accounts/alice"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ordered_collection_page": testPair{
|
||||
expected: true,
|
||||
blank: &a.OrderedCollectionPage{},
|
||||
result: &a.OrderedCollectionPage{
|
||||
PartOf: a.IRI("http://example.com/outbox"),
|
||||
Next: a.IRI("http://example.com/outbox?page=3"),
|
||||
Prev: a.IRI("http://example.com/outbox?page=1"),
|
||||
OrderedCollection: a.OrderedCollection{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://example.com/outbox?page=2"),
|
||||
Type: a.OrderedCollectionPageType,
|
||||
URL: a.IRI("http://example.com/outbox?page=2"),
|
||||
},
|
||||
Current: a.IRI("http://example.com/outbox?page=2"),
|
||||
TotalItems: 1,
|
||||
OrderedItems: a.ItemCollection{
|
||||
&ap.Object{
|
||||
Parent: a.Parent{
|
||||
ID: a.ObjectID("http://example.com/outbox/53c6fb47"),
|
||||
Type: a.ArticleType,
|
||||
Name: a.NaturalLanguageValues{{a.NilLangRef, "Example title"}},
|
||||
Content: a.NaturalLanguageValues{{a.NilLangRef, "Example content!"}},
|
||||
URL: a.IRI("http://example.com/53c6fb47"),
|
||||
MediaType: a.MimeType("text/markdown"),
|
||||
Published: time.Date(2018, time.July, 5, 16, 46, 44, 0, zLoc),
|
||||
Generator: a.IRI("http://example.com"),
|
||||
AttributedTo: a.IRI("http://example.com/accounts/alice"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"natural_language_values": {
|
||||
expected: true,
|
||||
blank: &a.NaturalLanguageValues{},
|
||||
result: &a.NaturalLanguageValues{
|
||||
{
|
||||
a.NilLangRef, `
|
||||
|
||||
`},
|
||||
{a.LangRef("en"), "Ana got apples ⓐ"},
|
||||
{a.LangRef("fr"), "Aná a des pommes ⒜"},
|
||||
{a.LangRef("ro"), "Ana are mere"},
|
||||
},
|
||||
},
|
||||
"activity_create_simple": {
|
||||
expected: true,
|
||||
blank: &a.Create{},
|
||||
result: &a.Create{
|
||||
Parent: a.Parent{
|
||||
Type: a.CreateType,
|
||||
},
|
||||
Actor: a.IRI("https://littr.git/api/accounts/anonymous"),
|
||||
Object: &ap.Object{
|
||||
Parent: a.Parent{
|
||||
Type: a.NoteType,
|
||||
AttributedTo: a.IRI("https://littr.git/api/accounts/anonymous"),
|
||||
InReplyTo: a.ItemCollection{a.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff")},
|
||||
Content: a.NaturalLanguageValues{{a.NilLangRef, "<p>Hello world</p>"}},
|
||||
To: a.ItemCollection{a.IRI("https://www.w3.org/ns/activitystreams#Public")},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"like_activity_with_iri_actor": {
|
||||
expected: true,
|
||||
blank: &a.Like{},
|
||||
result: &a.Like{
|
||||
Parent: a.Parent{
|
||||
Type: a.LikeType,
|
||||
Published: time.Date(2018, time.September, 6, 15, 15, 9, 0, zLoc),
|
||||
},
|
||||
Actor: a.IRI("https://littr.git/api/accounts/24d4b96f"),
|
||||
Object: &ap.Object{
|
||||
Parent: a.Article{
|
||||
ID: a.ObjectID("https://littr.git/api/accounts/ana/liked/7ca154ff"),
|
||||
Type: a.ArticleType,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"object_with_audience": testPair{
|
||||
expected: true,
|
||||
blank: &ap.Object{},
|
||||
result: &ap.Object{
|
||||
Parent: a.Parent{
|
||||
Type: a.ObjectType,
|
||||
ID: a.ObjectID("http://www.test.example/object/1"),
|
||||
To: a.ItemCollection{
|
||||
a.IRI("https://www.w3.org/ns/activitystreams#Public"),
|
||||
},
|
||||
Bto: a.ItemCollection{
|
||||
a.IRI("http://example.com/sharedInbox"),
|
||||
},
|
||||
CC: a.ItemCollection{
|
||||
a.IRI("https://example.com/actors/ana"),
|
||||
a.IRI("https://example.com/actors/bob"),
|
||||
},
|
||||
BCC: a.ItemCollection{
|
||||
a.IRI("https://darkside.cookie/actors/darthvader"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getFileContents(path string) ([]byte, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]byte, st.Size())
|
||||
io.ReadFull(f, data)
|
||||
data = bytes.Trim(data, "\x00")
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func Test_ActivityPubUnmarshal(t *testing.T) {
|
||||
var err error
|
||||
|
||||
var f = t.Errorf
|
||||
if len(allTests) == 0 {
|
||||
t.Skip("No tests found")
|
||||
}
|
||||
|
||||
for k, pair := range allTests {
|
||||
path := filepath.Join(dir, fmt.Sprintf("%s.json", k))
|
||||
t.Run(path, func(t *testing.T) {
|
||||
var data []byte
|
||||
data, err = getFileContents(path)
|
||||
if err != nil {
|
||||
f("Error: %s for %s", err, path)
|
||||
return
|
||||
}
|
||||
object := pair.blank
|
||||
|
||||
err = j.Unmarshal(data, object)
|
||||
if err != nil {
|
||||
f("Error: %s for %s", err, data)
|
||||
return
|
||||
}
|
||||
expLbl := ""
|
||||
if !pair.expected {
|
||||
expLbl = "not be "
|
||||
}
|
||||
status := assertDeepEquals(f, object, pair.result)
|
||||
if pair.expected != status {
|
||||
if stopOnFailure {
|
||||
f = t.Fatalf
|
||||
}
|
||||
|
||||
f("Mock: %s: %s\n%#v\n should %sequal to expected\n%#v", k, path, object, expLbl, pair.result)
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
oj, err := j.Marshal(object)
|
||||
if err != nil {
|
||||
f(err.Error())
|
||||
}
|
||||
tj, err := j.Marshal(pair.result)
|
||||
if err != nil {
|
||||
f(err.Error())
|
||||
}
|
||||
f("Mock: %s: %s\n%s\n should %sequal to expected\n%s", k, path, oj, expLbl, tj)
|
||||
}
|
||||
//if err == nil {
|
||||
// fmt.Printf(" --- %s: %s\n %s\n", "PASS", k, path)
|
||||
//}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain (m *testing.M) {
|
||||
a.ItemTyperFunc = ap.JSONGetItemByType
|
||||
m.Run()
|
||||
}
|
232
tombstone.go
Normal file
232
tombstone.go
Normal file
|
@ -0,0 +1,232 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Tombstone a Tombstone represents a content object that has been deleted.
|
||||
// It can be used in Collections to signify that there used to be an object at this position,
|
||||
// but it has been deleted.
|
||||
type Tombstone struct {
|
||||
// ID provides the globally unique identifier for anActivity Pub Object or Link.
|
||||
ID ObjectID `jsonld:"id,omitempty"`
|
||||
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
|
||||
Type ActivityVocabularyType `jsonld:"type,omitempty"`
|
||||
// Name a simple, human-readable, plain-text name for the object.
|
||||
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
|
||||
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
|
||||
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
|
||||
// The intent is to provide a model that is at least semantically similar to attachments in email.
|
||||
Attachment Item `jsonld:"attachment,omitempty"`
|
||||
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
|
||||
// For instance, an object might be attributed to the completion of another activity.
|
||||
AttributedTo Item `jsonld:"attributedTo,omitempty"`
|
||||
// Audience identifies one or more entities that represent the total population of entities
|
||||
// for which the object can considered to be relevant.
|
||||
Audience ItemCollection `jsonld:"audience,omitempty"`
|
||||
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
|
||||
// By default, the value of content is HTML.
|
||||
// The mediaType property can be used in the object to indicate a different content type.
|
||||
// (The content MAY be expressed using multiple language-tagged values.)
|
||||
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
|
||||
// Context identifies the context within which the object exists or an activity was performed.
|
||||
// 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 Item `jsonld:"context,omitempty"`
|
||||
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
|
||||
// If not specified, the content property is assumed to contain text/html content.
|
||||
MediaType MimeType `jsonld:"mediaType,omitempty"`
|
||||
// EndTime 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.
|
||||
EndTime time.Time `jsonld:"endTime,omitempty"`
|
||||
// Generator identifies the entity (e.g. an application) that generated the object.
|
||||
Generator Item `jsonld:"generator,omitempty"`
|
||||
// Icon indicates an entity that describes an icon for this object.
|
||||
// The image should have an aspect ratio of one (horizontal) to one (vertical)
|
||||
// and should be suitable for presentation at a small size.
|
||||
Icon Item `jsonld:"icon,omitempty"`
|
||||
// Image indicates an entity that describes an image for this object.
|
||||
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
|
||||
Image Item `jsonld:"image,omitempty"`
|
||||
// InReplyTo indicates one or more entities for which this object is considered a response.
|
||||
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
|
||||
// Location indicates one or more physical or logical locations associated with the object.
|
||||
Location Item `jsonld:"location,omitempty"`
|
||||
// Preview identifies an entity that provides a preview of this object.
|
||||
Preview Item `jsonld:"preview,omitempty"`
|
||||
// Published the date and time at which the object was published
|
||||
Published time.Time `jsonld:"published,omitempty"`
|
||||
// Replies identifies a Collection containing objects considered to be responses to this object.
|
||||
Replies Item `jsonld:"replies,omitempty"`
|
||||
// StartTime the date and time describing the actual or expected starting time of the object.
|
||||
// When used with an Activity object, for instance, the startTime property specifies
|
||||
// the moment the activity began or is scheduled to begin.
|
||||
StartTime time.Time `jsonld:"startTime,omitempty"`
|
||||
// Summary a natural language summarization of the object encoded as HTML.
|
||||
// *Multiple language tagged summaries may be provided.)
|
||||
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
|
||||
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
|
||||
// The key difference between attachment and tag is that the former implies association by inclusion,
|
||||
// while the latter implies associated by reference.
|
||||
Tag ItemCollection `jsonld:"tag,omitempty"`
|
||||
// Updated the date and time at which the object was updated
|
||||
Updated time.Time `jsonld:"updated,omitempty"`
|
||||
// URL identifies one or more links to representations of the object
|
||||
URL LinkOrIRI `jsonld:"url,omitempty"`
|
||||
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
|
||||
To ItemCollection `jsonld:"to,omitempty"`
|
||||
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
|
||||
Bto ItemCollection `jsonld:"bto,omitempty"`
|
||||
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
|
||||
CC ItemCollection `jsonld:"cc,omitempty"`
|
||||
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
|
||||
BCC ItemCollection `jsonld:"bcc,omitempty"`
|
||||
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
|
||||
// the duration property indicates the object's approximate duration.
|
||||
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
|
||||
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
|
||||
Duration time.Duration `jsonld:"duration,omitempty"`
|
||||
// This 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.
|
||||
Likes Item `jsonld:"likes,omitempty"`
|
||||
// This 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.
|
||||
Shares Item `jsonld:"shares,omitempty"`
|
||||
// Source property is intended to convey some sort of source from which the content markup was derived,
|
||||
// as a form of provenance, or to support future editing by clients.
|
||||
// In general, clients do the conversion from source to content, not the other way around.
|
||||
Source Source `jsonld:"source,omitempty"`
|
||||
// FormerType On a Tombstone object, the formerType property identifies the type of the object that was deleted.
|
||||
FormerType ActivityVocabularyType `jsonld:"formerType,omitempty"`
|
||||
// Deleted On a Tombstone object, the deleted property is a timestamp for when the object was deleted.
|
||||
Deleted time.Time `jsonld:"deleted,omitempty"`
|
||||
}
|
||||
|
||||
// IsLink returns false for Tombstone objects
|
||||
func (t Tombstone) IsLink() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsObject returns true for Tombstone objects
|
||||
func (t Tombstone) IsObject() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCollection returns false for Tombstone objects
|
||||
func (t Tombstone) IsCollection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLink returns the IRI corresponding to the current Tombstone object
|
||||
func (t Tombstone) GetLink() IRI {
|
||||
return IRI(t.ID)
|
||||
}
|
||||
|
||||
// GetType returns the type of the current Tombstone
|
||||
func (t Tombstone) GetType() ActivityVocabularyType {
|
||||
return t.Type
|
||||
}
|
||||
|
||||
// GetID returns the ID corresponding to the current Tombstone
|
||||
func (t Tombstone) GetID() ObjectID {
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// UnmarshalJSON
|
||||
func (t *Tombstone) UnmarshalJSON(data []byte) error {
|
||||
// TODO(marius): this is a candidate of using OnObject() for loading the common properties
|
||||
// and then loading the extra ones
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
t.ID = JSONGetObjectID(data)
|
||||
t.Type = JSONGetType(data)
|
||||
t.Name = JSONGetNaturalLanguageField(data, "name")
|
||||
t.Content = JSONGetNaturalLanguageField(data, "content")
|
||||
t.Summary = JSONGetNaturalLanguageField(data, "summary")
|
||||
t.Context = JSONGetItem(data, "context")
|
||||
t.URL = JSONGetURIItem(data, "url")
|
||||
t.MediaType = MimeType(JSONGetString(data, "mediaType"))
|
||||
t.Generator = JSONGetItem(data, "generator")
|
||||
t.AttributedTo = JSONGetItem(data, "attributedTo")
|
||||
t.Attachment = JSONGetItem(data, "attachment")
|
||||
t.Location = JSONGetItem(data, "location")
|
||||
t.Published = JSONGetTime(data, "published")
|
||||
t.StartTime = JSONGetTime(data, "startTime")
|
||||
t.EndTime = JSONGetTime(data, "endTime")
|
||||
t.Duration = JSONGetDuration(data, "duration")
|
||||
t.Icon = JSONGetItem(data, "icon")
|
||||
t.Preview = JSONGetItem(data, "preview")
|
||||
t.Image = JSONGetItem(data, "image")
|
||||
t.Updated = JSONGetTime(data, "updated")
|
||||
inReplyTo := JSONGetItems(data, "inReplyTo")
|
||||
if len(inReplyTo) > 0 {
|
||||
t.InReplyTo = inReplyTo
|
||||
}
|
||||
to := JSONGetItems(data, "to")
|
||||
if len(to) > 0 {
|
||||
t.To = to
|
||||
}
|
||||
audience := JSONGetItems(data, "audience")
|
||||
if len(audience) > 0 {
|
||||
t.Audience = audience
|
||||
}
|
||||
bto := JSONGetItems(data, "bto")
|
||||
if len(bto) > 0 {
|
||||
t.Bto = bto
|
||||
}
|
||||
cc := JSONGetItems(data, "cc")
|
||||
if len(cc) > 0 {
|
||||
t.CC = cc
|
||||
}
|
||||
bcc := JSONGetItems(data, "bcc")
|
||||
if len(bcc) > 0 {
|
||||
t.BCC = bcc
|
||||
}
|
||||
replies := JSONGetItem(data, "replies")
|
||||
if replies != nil {
|
||||
t.Replies = replies
|
||||
}
|
||||
tag := JSONGetItems(data, "tag")
|
||||
if len(tag) > 0 {
|
||||
t.Tag = tag
|
||||
}
|
||||
t.FormerType = ActivityVocabularyType(JSONGetString(data, "formerType"))
|
||||
t.Deleted = JSONGetTime(data, "deleted")
|
||||
return 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
|
||||
rec, _ := ItemCollectionDeduplication(&aud, &t.To, &t.Bto, &t.CC, &t.BCC, &t.Audience)
|
||||
return rec
|
||||
}
|
||||
|
||||
// Clean removes Bto and BCC properties
|
||||
func (t *Tombstone) Clean(){
|
||||
t.BCC = nil
|
||||
t.Bto = nil
|
||||
}
|
||||
|
||||
|
||||
// ToTombstone
|
||||
func ToTombstone(it Item) (*Tombstone, error) {
|
||||
switch i := it.(type) {
|
||||
case *Tombstone:
|
||||
return i, nil
|
||||
case Tombstone:
|
||||
return &i, nil
|
||||
case *Object:
|
||||
return (*Tombstone)(unsafe.Pointer(i)), nil
|
||||
case Object:
|
||||
return (*Tombstone)(unsafe.Pointer(&i)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert %q", it.GetType())
|
||||
}
|
35
tombstone_test.go
Normal file
35
tombstone_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package activitypub
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTombstone_GetID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_GetLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_GetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_IsCollection(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_IsLink(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_IsObject(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_UnmarshalJSON(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestTombstone_Clean(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
461
unmarshal.go
Normal file
461
unmarshal.go
Normal file
|
@ -0,0 +1,461 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
)
|
||||
|
||||
var (
|
||||
apUnmarshalerType = reflect.TypeOf(new(Item)).Elem()
|
||||
unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
|
||||
)
|
||||
|
||||
// ItemTyperFunc will return an instance of a struct that implements activitystreams.Item
|
||||
// The default for this package is JSONGetItemByType but can be overwritten
|
||||
var ItemTyperFunc TyperFn
|
||||
|
||||
// TyperFn is the type of the function which returns an activitystreams.Item struct instance
|
||||
// for a specific ActivityVocabularyType
|
||||
type TyperFn func(ActivityVocabularyType) (Item, error)
|
||||
|
||||
func JSONGetObjectID(data []byte) ObjectID {
|
||||
i, err := jsonparser.GetString(data, "id")
|
||||
if err != nil {
|
||||
return ObjectID("")
|
||||
}
|
||||
return ObjectID(i)
|
||||
}
|
||||
|
||||
func JSONGetType(data []byte) ActivityVocabularyType {
|
||||
t, err := jsonparser.GetString(data, "type")
|
||||
typ := ActivityVocabularyType(t)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func JSONGetMimeType(data []byte) MimeType {
|
||||
t, err := jsonparser.GetString(data, "mediaType")
|
||||
if err != nil {
|
||||
return MimeType("")
|
||||
}
|
||||
return MimeType(t)
|
||||
}
|
||||
|
||||
func JSONGetInt(data []byte, prop string) int64 {
|
||||
val, err := jsonparser.GetInt(data, prop)
|
||||
if err != nil {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func JSONGetFloat(data []byte, prop string) float64 {
|
||||
val, err := jsonparser.GetFloat(data, prop)
|
||||
if err != nil {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func JSONGetString(data []byte, prop string) string {
|
||||
val, err := jsonparser.GetString(data, prop)
|
||||
if err != nil {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func JSONGetBytes(data []byte, prop string) []byte {
|
||||
val, _, _, err := jsonparser.Get(data, prop)
|
||||
if err != nil {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func JSONGetBoolean(data []byte, prop string) bool {
|
||||
val, err := jsonparser.GetBoolean(data, prop)
|
||||
if err != nil {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func JSONGetNaturalLanguageField(data []byte, prop string) NaturalLanguageValues {
|
||||
n := NaturalLanguageValues{}
|
||||
val, typ, _, err := jsonparser.Get(data, prop)
|
||||
if err != nil || val == nil {
|
||||
return nil
|
||||
}
|
||||
switch typ {
|
||||
case jsonparser.Object:
|
||||
jsonparser.ObjectEach(val, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
|
||||
if dataType == jsonparser.String {
|
||||
l := LangRefValue{}
|
||||
if err := l.UnmarshalJSON(value); err == nil {
|
||||
if l.Ref != NilLangRef || len(l.Value) > 0 {
|
||||
n = append(n, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
case jsonparser.String:
|
||||
l := LangRefValue{}
|
||||
if err := l.UnmarshalJSON(val); err == nil {
|
||||
n = append(n, l)
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func JSONGetTime(data []byte, prop string) time.Time {
|
||||
t := time.Time{}
|
||||
str, _ := jsonparser.GetUnsafeString(data, prop)
|
||||
t.UnmarshalText([]byte(str))
|
||||
return t
|
||||
}
|
||||
|
||||
func JSONGetDuration(data []byte, prop string) time.Duration {
|
||||
str, _ := jsonparser.GetUnsafeString(data, prop)
|
||||
d, _ := time.ParseDuration(str)
|
||||
return d
|
||||
}
|
||||
|
||||
func JSONGetPublicKey(data []byte, prop string) PublicKey {
|
||||
key := PublicKey{}
|
||||
key.UnmarshalJSON(JSONGetBytes(data, prop))
|
||||
return key
|
||||
}
|
||||
|
||||
func JSONGetStreams(data []byte, prop string) []ItemCollection {
|
||||
return nil
|
||||
}
|
||||
|
||||
func itemFn(data []byte) (Item, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
i, err := ItemTyperFunc(JSONGetType(data))
|
||||
if err != nil || i == nil {
|
||||
if i, ok := asIRI(data); ok {
|
||||
// try to see if it's an IRI
|
||||
return i, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
p := reflect.PtrTo(reflect.TypeOf(i))
|
||||
if reflect.TypeOf(i).Implements(unmarshalerType) || p.Implements(unmarshalerType) {
|
||||
err = i.(json.Unmarshaler).UnmarshalJSON(data)
|
||||
}
|
||||
if reflect.TypeOf(i).Implements(textUnmarshalerType) || p.Implements(textUnmarshalerType) {
|
||||
err = i.(encoding.TextUnmarshaler).UnmarshalText(data)
|
||||
}
|
||||
return i, err
|
||||
}
|
||||
|
||||
func JSONUnmarshalToItem(data []byte) Item {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if ItemTyperFunc == nil {
|
||||
return nil
|
||||
}
|
||||
val, typ, _, err := jsonparser.Get(data)
|
||||
if err != nil || len(val) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var i Item
|
||||
switch typ {
|
||||
case jsonparser.Array:
|
||||
items := make(ItemCollection, 0)
|
||||
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
var it Item
|
||||
it, err = itemFn(value)
|
||||
if it != nil && err == nil {
|
||||
items.Append(it)
|
||||
}
|
||||
})
|
||||
if len(items) == 1 {
|
||||
i = items.First()
|
||||
}
|
||||
if len(items) > 1 {
|
||||
i = items
|
||||
}
|
||||
case jsonparser.Object:
|
||||
i, err = itemFn(data)
|
||||
case jsonparser.String:
|
||||
if iri, ok := asIRI(data); ok {
|
||||
// try to see if it's an IRI
|
||||
i = iri
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func asIRI(val []byte) (IRI, bool) {
|
||||
u, err := url.ParseRequestURI(string(val))
|
||||
if err == nil && len(u.Scheme) > 0 && len(u.Host) > 0 {
|
||||
// try to see if it's an IRI
|
||||
return IRI(val), true
|
||||
}
|
||||
return IRI(""), false
|
||||
}
|
||||
|
||||
func JSONGetItem(data []byte, prop string) Item {
|
||||
val, typ, _, err := jsonparser.Get(data, prop)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
switch typ {
|
||||
case jsonparser.String:
|
||||
if i, ok := asIRI(val); ok {
|
||||
// try to see if it's an IRI
|
||||
return i
|
||||
}
|
||||
case jsonparser.Array:
|
||||
fallthrough
|
||||
case jsonparser.Object:
|
||||
return JSONUnmarshalToItem(val)
|
||||
case jsonparser.Number:
|
||||
fallthrough
|
||||
case jsonparser.Boolean:
|
||||
fallthrough
|
||||
case jsonparser.Null:
|
||||
fallthrough
|
||||
case jsonparser.Unknown:
|
||||
fallthrough
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func JSONGetURIItem(data []byte, prop string) Item {
|
||||
val, typ, _, err := jsonparser.Get(data, prop)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case jsonparser.Object:
|
||||
return JSONGetItem(data, prop)
|
||||
case jsonparser.Array:
|
||||
it := make(ItemCollection, 0)
|
||||
jsonparser.ArrayEach(val, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
if i, ok := asIRI(value); ok {
|
||||
it.Append(i)
|
||||
return
|
||||
}
|
||||
i, err := ItemTyperFunc(JSONGetType(value))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = i.(json.Unmarshaler).UnmarshalJSON(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
it.Append(i)
|
||||
})
|
||||
|
||||
return it
|
||||
case jsonparser.String:
|
||||
return IRI(val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func JSONGetItems(data []byte, prop string) ItemCollection {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
val, typ, _, err := jsonparser.Get(data, prop)
|
||||
if err != nil || len(val) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
it := make(ItemCollection, 0)
|
||||
switch typ {
|
||||
case jsonparser.Array:
|
||||
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
i, err := itemFn(value)
|
||||
if i != nil && err == nil {
|
||||
it.Append(i)
|
||||
}
|
||||
}, prop)
|
||||
case jsonparser.Object:
|
||||
// this should never happen :)
|
||||
case jsonparser.String:
|
||||
s, _ := jsonparser.GetString(val)
|
||||
it.Append(IRI(s))
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func JSONGetLangRefField(data []byte, prop string) LangRef {
|
||||
val, err := jsonparser.GetString(data, prop)
|
||||
if err != nil {
|
||||
return LangRef(err.Error())
|
||||
}
|
||||
return LangRef(val)
|
||||
}
|
||||
|
||||
func JSONGetIRI(data []byte, prop string) IRI {
|
||||
val, err := jsonparser.GetString(data, prop)
|
||||
if err != nil {
|
||||
return IRI("")
|
||||
}
|
||||
return IRI(val)
|
||||
}
|
||||
|
||||
// UnmarshalJSON tries to detect the type of the object in the json data and then outputs a matching
|
||||
// ActivityStreams object, if possible
|
||||
func UnmarshalJSON(data []byte) (Item, error) {
|
||||
if ItemTyperFunc == nil {
|
||||
ItemTyperFunc = JSONGetItemByType
|
||||
}
|
||||
return JSONUnmarshalToItem(data), nil
|
||||
}
|
||||
|
||||
func JSONGetItemByType(typ ActivityVocabularyType) (Item, error) {
|
||||
switch typ {
|
||||
case ObjectType:
|
||||
return ObjectNew(typ), nil
|
||||
case LinkType:
|
||||
return &Link{Type: typ}, nil
|
||||
case ActivityType:
|
||||
return &Activity{Type: typ}, nil
|
||||
case IntransitiveActivityType:
|
||||
return &IntransitiveActivity{Type: typ}, nil
|
||||
case ActorType:
|
||||
return &Actor{Type:typ}, nil
|
||||
case CollectionType:
|
||||
return &Collection{Type: typ}, nil
|
||||
case OrderedCollectionType:
|
||||
return &OrderedCollection{Type: typ}, nil
|
||||
case CollectionPageType:
|
||||
return &CollectionPage{Type: typ}, nil
|
||||
case OrderedCollectionPageType:
|
||||
return &OrderedCollectionPage{Type: typ}, nil
|
||||
case ArticleType:
|
||||
return ObjectNew(typ), nil
|
||||
case AudioType:
|
||||
return ObjectNew(typ), nil
|
||||
case DocumentType:
|
||||
return ObjectNew(typ), nil
|
||||
case EventType:
|
||||
return ObjectNew(typ), nil
|
||||
case ImageType:
|
||||
return ObjectNew(typ), nil
|
||||
case NoteType:
|
||||
return ObjectNew(typ), nil
|
||||
case PageType:
|
||||
return ObjectNew(typ), nil
|
||||
case PlaceType:
|
||||
return &Place{Type: typ}, nil
|
||||
case ProfileType:
|
||||
return &Profile{Type: typ}, nil
|
||||
case RelationshipType:
|
||||
return &Relationship{Type: typ}, nil
|
||||
case TombstoneType:
|
||||
return &Tombstone{Type: typ}, nil
|
||||
case VideoType:
|
||||
return ObjectNew(typ), nil
|
||||
case MentionType:
|
||||
return &Mention{Type: typ}, nil
|
||||
case ApplicationType:
|
||||
return &Application{Type:typ}, nil
|
||||
case GroupType:
|
||||
return &Group{Type:typ}, nil
|
||||
case OrganizationType:
|
||||
return &Organization{Type:typ}, nil
|
||||
case PersonType:
|
||||
return &Person{Type:typ}, nil
|
||||
case ServiceType:
|
||||
return &Service{Type:typ}, nil
|
||||
case AcceptType:
|
||||
return &Accept{Type: typ}, nil
|
||||
case AddType:
|
||||
return &Add{Type: typ}, nil
|
||||
case AnnounceType:
|
||||
return &Announce{Type: typ}, nil
|
||||
case ArriveType:
|
||||
return &Arrive{Type: typ}, nil
|
||||
case BlockType:
|
||||
return &Block{Type: typ}, nil
|
||||
case CreateType:
|
||||
return &Create{Type: typ}, nil
|
||||
case DeleteType:
|
||||
return &Delete{Type: typ}, nil
|
||||
case DislikeType:
|
||||
return &Dislike{Type: typ}, nil
|
||||
case FlagType:
|
||||
return &Flag{Type: typ}, nil
|
||||
case FollowType:
|
||||
return &Follow{Type: typ}, nil
|
||||
case IgnoreType:
|
||||
return &Ignore{Type: typ}, nil
|
||||
case InviteType:
|
||||
return &Invite{Type: typ}, nil
|
||||
case JoinType:
|
||||
return &Join{Type: typ}, nil
|
||||
case LeaveType:
|
||||
return &Leave{Type: typ}, nil
|
||||
case LikeType:
|
||||
return &Like{Type: typ}, nil
|
||||
case ListenType:
|
||||
return &Listen{Type: typ}, nil
|
||||
case MoveType:
|
||||
return &Move{Type: typ}, nil
|
||||
case OfferType:
|
||||
return &Offer{Type: typ}, nil
|
||||
case QuestionType:
|
||||
return &Question{Type: typ}, nil
|
||||
case RejectType:
|
||||
return &Reject{Type: typ}, nil
|
||||
case ReadType:
|
||||
return &Read{Type: typ}, nil
|
||||
case RemoveType:
|
||||
return &Remove{Type: typ}, nil
|
||||
case TentativeRejectType:
|
||||
return &TentativeReject{Type: typ}, nil
|
||||
case TentativeAcceptType:
|
||||
return &TentativeAccept{Type: typ}, nil
|
||||
case TravelType:
|
||||
return &Travel{Type: typ}, nil
|
||||
case UndoType:
|
||||
return &Undo{Type: typ}, nil
|
||||
case UpdateType:
|
||||
return &Update{Type: typ}, nil
|
||||
case ViewType:
|
||||
return &View{Type: typ}, nil
|
||||
case "":
|
||||
// when no type is available use a plain Object
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unrecognized ActivityStreams type %s", typ)
|
||||
}
|
||||
|
||||
func JSONGetActorEndpoints(data []byte, prop string) *Endpoints {
|
||||
str, _ := jsonparser.GetUnsafeString(data, prop)
|
||||
|
||||
var e *Endpoints
|
||||
if len(str) > 0 {
|
||||
e = &Endpoints{}
|
||||
e.UnmarshalJSON([]byte(str))
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
198
unmarshal_test.go
Normal file
198
unmarshal_test.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testPairs map[ActivityVocabularyType]reflect.Type
|
||||
|
||||
var objectPtrType = reflect.TypeOf(new(*Object)).Elem()
|
||||
var tombstoneType = reflect.TypeOf(new(*Tombstone)).Elem()
|
||||
var profileType = reflect.TypeOf(new(*Profile)).Elem()
|
||||
var placeType = reflect.TypeOf(new(*Place)).Elem()
|
||||
var relationshipType = reflect.TypeOf(new(*Relationship)).Elem()
|
||||
var linkPtrType = reflect.TypeOf(new(*Link)).Elem()
|
||||
var mentionPtrType = reflect.TypeOf(new(*Mention)).Elem()
|
||||
var activityPtrType = reflect.TypeOf(new(*Activity)).Elem()
|
||||
var intransitiveActivityPtrType = reflect.TypeOf(new(*IntransitiveActivity)).Elem()
|
||||
var collectionPtrType = reflect.TypeOf(new(*Collection)).Elem()
|
||||
var collectionPagePtrType = reflect.TypeOf(new(*CollectionPage)).Elem()
|
||||
var orderedCollectionPtrType = reflect.TypeOf(new(*OrderedCollection)).Elem()
|
||||
var orderedCollectionPagePtrType = reflect.TypeOf(new(*OrderedCollectionPage)).Elem()
|
||||
var actorPtrType = reflect.TypeOf(new(*Actor)).Elem()
|
||||
var applicationPtrType = reflect.TypeOf(new(*Application)).Elem()
|
||||
var servicePtrType = reflect.TypeOf(new(*Service)).Elem()
|
||||
var personPtrType = reflect.TypeOf(new(*Person)).Elem()
|
||||
var groupPtrType = reflect.TypeOf(new(*Group)).Elem()
|
||||
var organizationPtrType = reflect.TypeOf(new(*Organization)).Elem()
|
||||
var acceptPtrType = reflect.TypeOf(new(*Accept)).Elem()
|
||||
var addPtrType = reflect.TypeOf(new(*Add)).Elem()
|
||||
var announcePtrType = reflect.TypeOf(new(*Announce)).Elem()
|
||||
var arrivePtrType = reflect.TypeOf(new(*Arrive)).Elem()
|
||||
var blockPtrType = reflect.TypeOf(new(*Block)).Elem()
|
||||
var createPtrType = reflect.TypeOf(new(*Create)).Elem()
|
||||
var deletePtrType = reflect.TypeOf(new(*Delete)).Elem()
|
||||
var dislikePtrType = reflect.TypeOf(new(*Dislike)).Elem()
|
||||
var flagPtrType = reflect.TypeOf(new(*Flag)).Elem()
|
||||
var followPtrType = reflect.TypeOf(new(*Follow)).Elem()
|
||||
var ignorePtrType = reflect.TypeOf(new(*Ignore)).Elem()
|
||||
var invitePtrType = reflect.TypeOf(new(*Invite)).Elem()
|
||||
var joinPtrType = reflect.TypeOf(new(*Join)).Elem()
|
||||
var leavePtrType = reflect.TypeOf(new(*Leave)).Elem()
|
||||
var likePtrType = reflect.TypeOf(new(*Like)).Elem()
|
||||
var listenPtrType = reflect.TypeOf(new(*Listen)).Elem()
|
||||
var movePtrType = reflect.TypeOf(new(*Move)).Elem()
|
||||
var offerPtrType = reflect.TypeOf(new(*Offer)).Elem()
|
||||
var questionPtrType = reflect.TypeOf(new(*Question)).Elem()
|
||||
var rejectPtrType = reflect.TypeOf(new(*Reject)).Elem()
|
||||
var readPtrType = reflect.TypeOf(new(*Read)).Elem()
|
||||
var removePtrType = reflect.TypeOf(new(*Remove)).Elem()
|
||||
var tentativeRejectPtrType = reflect.TypeOf(new(*TentativeReject)).Elem()
|
||||
var tentativeAcceptPtrType = reflect.TypeOf(new(*TentativeAccept)).Elem()
|
||||
var travelPtrType = reflect.TypeOf(new(*Travel)).Elem()
|
||||
var undoPtrType = reflect.TypeOf(new(*Undo)).Elem()
|
||||
var updatePtrType = reflect.TypeOf(new(*Update)).Elem()
|
||||
var viewPtrType = reflect.TypeOf(new(*View)).Elem()
|
||||
|
||||
var tests = testPairs{
|
||||
ObjectType: objectPtrType,
|
||||
ArticleType: objectPtrType,
|
||||
AudioType: objectPtrType,
|
||||
DocumentType: objectPtrType,
|
||||
ImageType: objectPtrType,
|
||||
NoteType: objectPtrType,
|
||||
PageType: objectPtrType,
|
||||
PlaceType: placeType,
|
||||
ProfileType: profileType,
|
||||
RelationshipType: relationshipType,
|
||||
TombstoneType: tombstoneType,
|
||||
VideoType: objectPtrType,
|
||||
LinkType: linkPtrType,
|
||||
MentionType: mentionPtrType,
|
||||
CollectionType: collectionPtrType,
|
||||
CollectionPageType: collectionPagePtrType,
|
||||
OrderedCollectionType: orderedCollectionPtrType,
|
||||
OrderedCollectionPageType: orderedCollectionPagePtrType,
|
||||
ActorType: actorPtrType,
|
||||
ApplicationType: applicationPtrType,
|
||||
ServiceType: servicePtrType,
|
||||
PersonType: personPtrType,
|
||||
GroupType: groupPtrType,
|
||||
OrganizationType: organizationPtrType,
|
||||
ActivityType: activityPtrType,
|
||||
IntransitiveActivityType: intransitiveActivityPtrType,
|
||||
AcceptType: acceptPtrType,
|
||||
AddType: addPtrType,
|
||||
AnnounceType: announcePtrType,
|
||||
ArriveType: arrivePtrType,
|
||||
BlockType: blockPtrType,
|
||||
CreateType: createPtrType,
|
||||
DeleteType: deletePtrType,
|
||||
DislikeType: dislikePtrType,
|
||||
FlagType: flagPtrType,
|
||||
FollowType: followPtrType,
|
||||
IgnoreType: ignorePtrType,
|
||||
InviteType: invitePtrType,
|
||||
JoinType: joinPtrType,
|
||||
LeaveType: leavePtrType,
|
||||
LikeType: likePtrType,
|
||||
ListenType: listenPtrType,
|
||||
MoveType: movePtrType,
|
||||
OfferType: offerPtrType,
|
||||
QuestionType: questionPtrType,
|
||||
RejectType: rejectPtrType,
|
||||
ReadType: readPtrType,
|
||||
RemoveType: removePtrType,
|
||||
TentativeRejectType: tentativeRejectPtrType,
|
||||
TentativeAcceptType: tentativeAcceptPtrType,
|
||||
TravelType: travelPtrType,
|
||||
UndoType: undoPtrType,
|
||||
UpdateType: updatePtrType,
|
||||
ViewType: viewPtrType,
|
||||
}
|
||||
|
||||
func TestJSONGetItemByType(t *testing.T) {
|
||||
for typ, test := range tests {
|
||||
t.Run(string(typ), func(t *testing.T) {
|
||||
v, err := JSONGetItemByType(typ)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if reflect.TypeOf(v) != test {
|
||||
t.Errorf("Invalid type returned %T, expected %s", v, test.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
dataEmpty := []byte("{}")
|
||||
i, err := UnmarshalJSON(dataEmpty)
|
||||
if err != nil {
|
||||
t.Errorf("invalid unmarshaling %s", err)
|
||||
}
|
||||
if i != nil {
|
||||
t.Errorf("invalid unmarshaling, expected nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONGetDuration(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetInt(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestJSONGetIRI(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetItem(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetItems(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestJSONGetLangRefField(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetMimeType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetObjectID(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetNaturalLanguageField(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetString(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetTime(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetType(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetURIItem(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONUnmarshalToItem(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
||||
|
||||
func TestJSONGetActorEndpoints(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"github.com/buger/jsonparser"
|
||||
as "github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
func JSONGetItemByType(typ as.ActivityVocabularyType) (as.Item, error) {
|
||||
obTyp := as.ActivityVocabularyTypes{as.ObjectType}
|
||||
if as.ObjectTypes.Contains(typ) || obTyp.Contains(typ) {
|
||||
return &Object{Parent: as.Object{Type: typ}}, nil
|
||||
}
|
||||
actTyp := as.ActivityVocabularyTypes{as.ActorType}
|
||||
if as.ActorTypes.Contains(typ) || actTyp.Contains(typ) {
|
||||
return &actor{Parent: Parent{Parent: as.Object{Type: typ}}}, nil
|
||||
}
|
||||
return as.JSONGetItemByType(typ)
|
||||
}
|
||||
|
||||
func JSONGetActorEndpoints(data []byte, prop string) *Endpoints {
|
||||
str, _ := jsonparser.GetUnsafeString(data, prop)
|
||||
|
||||
var e *Endpoints
|
||||
if len(str) > 0 {
|
||||
e = &Endpoints{}
|
||||
e.UnmarshalJSON([]byte(str))
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testPairs map[as.ActivityVocabularyType]reflect.Type
|
||||
|
||||
var objectPtrType = reflect.TypeOf(new(*Object)).Elem()
|
||||
var actorPtrType = reflect.TypeOf(new(*actor)).Elem()
|
||||
var applicationPtrType = reflect.TypeOf(new(*Application)).Elem()
|
||||
var servicePtrType = reflect.TypeOf(new(*Service)).Elem()
|
||||
var personPtrType = reflect.TypeOf(new(*Person)).Elem()
|
||||
var groupPtrType = reflect.TypeOf(new(*Group)).Elem()
|
||||
var organizationPtrType = reflect.TypeOf(new(*Organization)).Elem()
|
||||
|
||||
var tests = testPairs{
|
||||
as.ObjectType: objectPtrType,
|
||||
as.ActorType: actorPtrType,
|
||||
as.ApplicationType: applicationPtrType,
|
||||
as.ServiceType: servicePtrType,
|
||||
as.PersonType: personPtrType,
|
||||
as.GroupType: groupPtrType,
|
||||
as.OrganizationType: organizationPtrType,
|
||||
}
|
||||
|
||||
func TestJSONGetItemByType(t *testing.T) {
|
||||
for typ, test := range tests {
|
||||
t.Run(string(typ), func(t *testing.T) {
|
||||
v, err := JSONGetItemByType(typ)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if reflect.TypeOf(v) != test {
|
||||
t.Errorf("Invalid type returned %T, expected %s", v, test.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONGetActorEndpoints(t *testing.T) {
|
||||
t.Skipf("TODO")
|
||||
}
|
|
@ -1,9 +1,5 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
as "github.com/go-ap/activitystreams"
|
||||
)
|
||||
|
||||
// ValidationErrors is an aggregated error interface that allows
|
||||
// a Validator implementation to return all possible errors.
|
||||
type ValidationErrors interface {
|
||||
|
@ -16,10 +12,10 @@ type ValidationErrors interface {
|
|||
// provide a validation mechanism for incoming ActivityPub Objects or IRIs
|
||||
// against an external set of rules.
|
||||
type Validator interface {
|
||||
Validate(receiver as.IRI, incoming as.Item) (bool, ValidationErrors)
|
||||
Validate(receiver IRI, incoming Item) (bool, ValidationErrors)
|
||||
}
|
||||
|
||||
func (v defaultValidator) Validate(receiver as.IRI, incoming as.Item) (bool, ValidationErrors) {
|
||||
func (v defaultValidator) Validate(receiver IRI, incoming Item) (bool, ValidationErrors) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue