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 (
|
import (
|
||||||
"errors"
|
"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 a json object which maps additional (typically server/domain-wide)
|
||||||
// endpoints which may be useful either for this actor or someone referencing this actor.
|
// 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
|
// 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.
|
// a JSON-LD document with these properties.
|
||||||
type Endpoints struct {
|
type Endpoints struct {
|
||||||
// UploadMedia Upload endpoint URI for this user for binary data.
|
// 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
|
// 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
|
// 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.
|
// 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,
|
// 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.
|
// 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,
|
// 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.
|
// 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,
|
// 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
|
// this endpoint specifies a URI at which browser-authenticated users may authorize a client's public
|
||||||
// key for client to server interactions.
|
// 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 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
|
// 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.
|
// 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"`
|
SharedInbox 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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON
|
// UnmarshalJSON
|
||||||
func (e *Endpoints) UnmarshalJSON(data []byte) error {
|
func (e *Endpoints) UnmarshalJSON(data []byte) error {
|
||||||
e.OauthAuthorizationEndpoint = as.JSONGetItem(data, "oauthAuthorizationEndpoint")
|
e.OauthAuthorizationEndpoint = JSONGetItem(data, "oauthAuthorizationEndpoint")
|
||||||
e.OauthTokenEndpoint = as.JSONGetItem(data, "oauthTokenEndpoint")
|
e.OauthTokenEndpoint = JSONGetItem(data, "oauthTokenEndpoint")
|
||||||
e.UploadMedia = as.JSONGetItem(data, "uploadMedia")
|
e.UploadMedia = JSONGetItem(data, "uploadMedia")
|
||||||
e.ProvideClientKey = as.JSONGetItem(data, "provideClientKey")
|
e.ProvideClientKey = JSONGetItem(data, "provideClientKey")
|
||||||
e.SignClientKey = as.JSONGetItem(data, "signClientKey")
|
e.SignClientKey = JSONGetItem(data, "signClientKey")
|
||||||
e.SharedInbox = as.JSONGetItem(data, "sharedInbox")
|
e.SharedInbox = JSONGetItem(data, "sharedInbox")
|
||||||
return nil
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
as "github.com/go-ap/activitystreams"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestActorNew(t *testing.T) {
|
func TestActorNew(t *testing.T) {
|
||||||
var testValue = as.ObjectID("test")
|
var testValue = ObjectID("test")
|
||||||
var testType = as.ApplicationType
|
var testType = ApplicationType
|
||||||
|
|
||||||
o := actorNew(testValue, testType)
|
o := ActorNew(testValue, testType)
|
||||||
|
|
||||||
if o.ID != testValue {
|
if o.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", 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)
|
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, testType)
|
||||||
}
|
}
|
||||||
|
|
||||||
n := actorNew(testValue, "")
|
n := ActorNew(testValue, "")
|
||||||
if n.ID != testValue {
|
if n.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", n.ID, testValue)
|
t.Errorf("APObject Id '%v' different than expected '%v'", n.ID, testValue)
|
||||||
}
|
}
|
||||||
if n.Type != as.ActorType {
|
if n.Type != ActorType {
|
||||||
t.Errorf("APObject Type '%v' different than expected '%v'", n.Type, as.ActorType)
|
t.Errorf("APObject Type '%v' different than expected '%v'", n.Type, ActorType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPersonNew(t *testing.T) {
|
func TestPersonNew(t *testing.T) {
|
||||||
var testValue = as.ObjectID("test")
|
var testValue = ObjectID("test")
|
||||||
|
|
||||||
o := PersonNew(testValue)
|
o := PersonNew(testValue)
|
||||||
if o.ID != testValue {
|
if o.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||||
}
|
}
|
||||||
if o.Type != as.PersonType {
|
if o.Type != PersonType {
|
||||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.PersonType)
|
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, PersonType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplicationNew(t *testing.T) {
|
func TestApplicationNew(t *testing.T) {
|
||||||
var testValue = as.ObjectID("test")
|
var testValue = ObjectID("test")
|
||||||
|
|
||||||
o := ApplicationNew(testValue)
|
o := ApplicationNew(testValue)
|
||||||
if o.ID != testValue {
|
if o.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||||
}
|
}
|
||||||
if o.Type != as.ApplicationType {
|
if o.Type != ApplicationType {
|
||||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.ApplicationType)
|
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, ApplicationType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupNew(t *testing.T) {
|
func TestGroupNew(t *testing.T) {
|
||||||
var testValue = as.ObjectID("test")
|
var testValue = ObjectID("test")
|
||||||
|
|
||||||
o := GroupNew(testValue)
|
o := GroupNew(testValue)
|
||||||
if o.ID != testValue {
|
if o.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||||
}
|
}
|
||||||
if o.Type != as.GroupType {
|
if o.Type != GroupType {
|
||||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.GroupType)
|
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, GroupType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrganizationNew(t *testing.T) {
|
func TestOrganizationNew(t *testing.T) {
|
||||||
var testValue = as.ObjectID("test")
|
var testValue = ObjectID("test")
|
||||||
|
|
||||||
o := OrganizationNew(testValue)
|
o := OrganizationNew(testValue)
|
||||||
if o.ID != testValue {
|
if o.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||||
}
|
}
|
||||||
if o.Type != as.OrganizationType {
|
if o.Type != OrganizationType {
|
||||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.OrganizationType)
|
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, OrganizationType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceNew(t *testing.T) {
|
func TestServiceNew(t *testing.T) {
|
||||||
var testValue = as.ObjectID("test")
|
var testValue = ObjectID("test")
|
||||||
|
|
||||||
o := ServiceNew(testValue)
|
o := ServiceNew(testValue)
|
||||||
if o.ID != testValue {
|
if o.ID != testValue {
|
||||||
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
t.Errorf("APObject Id '%v' different than expected '%v'", o.ID, testValue)
|
||||||
}
|
}
|
||||||
if o.Type != as.ServiceType {
|
if o.Type != ServiceType {
|
||||||
t.Errorf("APObject Type '%v' different than expected '%v'", o.Type, as.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")
|
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) {
|
func validateEmptyPerson(p Person, t *testing.T) {
|
||||||
if p.ID != "" {
|
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 != "" {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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() {
|
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() {
|
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() {
|
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/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
as "github.com/go-ap/activitystreams"
|
pub "github.com/go-ap/activitypub"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestSignFn func(*http.Request) error
|
type RequestSignFn func(*http.Request) error
|
||||||
|
@ -30,7 +30,7 @@ type CanSign interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client 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
|
// 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 {
|
type err struct {
|
||||||
msg string
|
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{
|
return &err{
|
||||||
msg: fmt.Sprintf(msg, p...),
|
msg: fmt.Sprintf(msg, p...),
|
||||||
iri: i,
|
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
|
// 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 {
|
if len(id) == 0 {
|
||||||
return nil, errorf(id, "Invalid IRI, nil value")
|
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)
|
return nil, errorf(id, "Invalid IRI: %s", err)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var obj as.Item
|
var obj pub.Item
|
||||||
|
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
if resp, err = c.Get(id.String()); err != nil {
|
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 obj, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return as.UnmarshalJSON(body)
|
return pub.UnmarshalJSON(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) req(method string, url string, body io.Reader) (*http.Request, error) {
|
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)
|
req.Header.Set("Content-Type", ContentTypeJsonLD)
|
||||||
}
|
}
|
||||||
if err = c.signFn(req); err != nil {
|
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, err
|
||||||
}
|
}
|
||||||
return req, nil
|
return req, nil
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
as "github.com/go-ap/activitystreams"
|
pub "github.com/go-ap/activitypub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
|
@ -18,7 +18,7 @@ func TestNewClient(t *testing.T) {
|
||||||
func TestErr_Error(t *testing.T) {
|
func TestErr_Error(t *testing.T) {
|
||||||
e := err{
|
e := err{
|
||||||
msg: "test",
|
msg: "test",
|
||||||
iri: as.IRI(""),
|
iri: pub.IRI(""),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(e.Error()) == 0 {
|
if len(e.Error()) == 0 {
|
||||||
|
@ -30,7 +30,7 @@ func TestErr_Error(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_LoadIRI(t *testing.T) {
|
func TestClient_LoadIRI(t *testing.T) {
|
||||||
empty := as.IRI("")
|
empty := pub.IRI("")
|
||||||
c := NewClient()
|
c := NewClient()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -41,7 +41,7 @@ func TestClient_LoadIRI(t *testing.T) {
|
||||||
t.Logf("Valid error received: %s", err)
|
t.Logf("Valid error received: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
inv := as.IRI("example.com")
|
inv := pub.IRI("example.com")
|
||||||
_, err = c.LoadIRI(inv)
|
_, err = c.LoadIRI(inv)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("LoadIRI should have failed when using invalid http url")
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// FollowersCollection is a collection of followers
|
// FollowersCollection is a collection of followers
|
||||||
FollowersCollection = Followers
|
FollowersCollection = Followers
|
||||||
|
|
||||||
// Followers is a Collection type
|
// 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"
|
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) {
|
func TestFollowersNew(t *testing.T) {
|
||||||
t.Skipf("TODO")
|
t.Skipf("TODO")
|
||||||
}
|
}
|
||||||
|
|
67
following.go
67
following.go
|
@ -1,9 +1,5 @@
|
||||||
package activitypub
|
package activitypub
|
||||||
|
|
||||||
import (
|
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// FollowingCollection is a list of everybody that the actor has followed, added as a side effect.
|
// 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
|
// The following collection MUST be either an OrderedCollection or a Collection and MAY
|
||||||
|
@ -11,70 +7,19 @@ type (
|
||||||
FollowingCollection = Following
|
FollowingCollection = Following
|
||||||
|
|
||||||
// Following is a type alias for a simple Collection
|
// Following is a type alias for a simple Collection
|
||||||
Following as.Collection
|
Following = Collection
|
||||||
)
|
)
|
||||||
|
|
||||||
// FollowingNew initializes a new Following
|
// FollowingNew initializes a new Following
|
||||||
func FollowingNew() *Following {
|
func FollowingNew() *Following {
|
||||||
id := as.ObjectID("following")
|
id := ObjectID("following")
|
||||||
|
|
||||||
i := Following{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
i := Following{ID: id, Type: CollectionType}
|
||||||
i.Name = as.NaturalLanguageValuesNew()
|
i.Name = NaturalLanguageValuesNew()
|
||||||
i.Content = as.NaturalLanguageValuesNew()
|
i.Content = NaturalLanguageValuesNew()
|
||||||
i.Summary = as.NaturalLanguageValuesNew()
|
i.Summary = NaturalLanguageValuesNew()
|
||||||
|
|
||||||
i.TotalItems = 0
|
i.TotalItems = 0
|
||||||
|
|
||||||
return &i
|
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"
|
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) {
|
func TestFollowingNew(t *testing.T) {
|
||||||
t.Skipf("TODO")
|
t.Skipf("TODO")
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,5 @@ go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/buger/jsonparser v0.0.0-20181023193515-52c6e1462ebd
|
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
|
github.com/go-ap/jsonld v0.0.0-20191123195936-1e43eac08b0c
|
||||||
)
|
)
|
||||||
|
|
107
helpers.go
107
helpers.go
|
@ -3,41 +3,40 @@ package activitypub
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-ap/activitystreams"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type withObjectFn func (*Object) error
|
type withObjectFn func (*Object) error
|
||||||
type withActivityFn func (*activitystreams.Activity) error
|
type withActivityFn func (*Activity) error
|
||||||
type withIntransitiveActivityFn func (*activitystreams.IntransitiveActivity) error
|
type withIntransitiveActivityFn func (*IntransitiveActivity) error
|
||||||
type withQuestionFn func (*activitystreams.Question) error
|
type withQuestionFn func (*Question) error
|
||||||
type withPersonFn func (*Person) error
|
type withPersonFn func (*Person) error
|
||||||
type withCollectionInterfaceFn func (collection activitystreams.CollectionInterface) error
|
type withCollectionInterfaceFn func (collection CollectionInterface) error
|
||||||
type withCollectionFn func (collection *activitystreams.Collection) error
|
type withCollectionFn func (collection *Collection) error
|
||||||
type withCollectionPageFn func (*activitystreams.CollectionPage) error
|
type withCollectionPageFn func (*CollectionPage) error
|
||||||
type withOrderedCollectionFn func (*activitystreams.OrderedCollection) error
|
type withOrderedCollectionFn func (*OrderedCollection) error
|
||||||
type withOrderedCollectionPageFn func (*activitystreams.OrderedCollectionPage) error
|
type withOrderedCollectionPageFn func (*OrderedCollectionPage) error
|
||||||
type withItemCollectionFn func (collection *activitystreams.ItemCollection) error
|
type withItemCollectionFn func (collection *ItemCollection) error
|
||||||
|
|
||||||
// OnObject
|
// OnObject
|
||||||
func OnObject(it activitystreams.Item, fn withObjectFn) error {
|
func OnObject(it Item, fn withObjectFn) error {
|
||||||
if activitystreams.ActivityTypes.Contains(it.GetType()) {
|
if ActivityTypes.Contains(it.GetType()) {
|
||||||
return OnActivity(it, func(a *activitystreams.Activity) error {
|
return OnActivity(it, func(a *Activity) error {
|
||||||
ob, err := ToObject(&a.Parent)
|
ob, err := ToObject(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fn(ob)
|
return fn(ob)
|
||||||
})
|
})
|
||||||
} else if activitystreams.ActorTypes.Contains(it.GetType()) {
|
} else if ActorTypes.Contains(it.GetType()) {
|
||||||
return OnPerson(it, func(p *Person) error {
|
return OnPerson(it, func(p *Person) error {
|
||||||
ob, err := ToObject(&p.Parent)
|
ob, err := ToObject(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fn(ob)
|
return fn(ob)
|
||||||
})
|
})
|
||||||
} else if it.IsCollection() {
|
} else if it.IsCollection() {
|
||||||
return OnCollection(it, func(col activitystreams.CollectionInterface) error {
|
return OnCollection(it, func(col CollectionInterface) error {
|
||||||
for _, it := range col.Collection() {
|
for _, it := range col.Collection() {
|
||||||
err := OnObject(it, fn)
|
err := OnObject(it, fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -56,11 +55,11 @@ func OnObject(it activitystreams.Item, fn withObjectFn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnActivity
|
// OnActivity
|
||||||
func OnActivity(it activitystreams.Item, fn withActivityFn) error {
|
func OnActivity(it Item, fn withActivityFn) error {
|
||||||
if !activitystreams.ActivityTypes.Contains(it.GetType()) {
|
if !ActivityTypes.Contains(it.GetType()) {
|
||||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Activity", it, 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -68,14 +67,14 @@ func OnActivity(it activitystreams.Item, fn withActivityFn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnIntransitiveActivity
|
// OnIntransitiveActivity
|
||||||
func OnIntransitiveActivity(it activitystreams.Item, fn withIntransitiveActivityFn) error {
|
func OnIntransitiveActivity(it Item, fn withIntransitiveActivityFn) error {
|
||||||
if !activitystreams.IntransitiveActivityTypes.Contains(it.GetType()) {
|
if !IntransitiveActivityTypes.Contains(it.GetType()) {
|
||||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Activity", it, 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()))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,11 +82,11 @@ func OnIntransitiveActivity(it activitystreams.Item, fn withIntransitiveActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnQuestion
|
// OnQuestion
|
||||||
func OnQuestion(it activitystreams.Item, fn withQuestionFn) error {
|
func OnQuestion(it Item, fn withQuestionFn) error {
|
||||||
if it.GetType() != activitystreams.QuestionType {
|
if it.GetType() != QuestionType {
|
||||||
errors.New(fmt.Sprintf("For %T[%s] can't be converted to Question", it, it.GetType()))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -95,11 +94,11 @@ func OnQuestion(it activitystreams.Item, fn withQuestionFn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnPerson
|
// OnPerson
|
||||||
func OnPerson(it activitystreams.Item, fn withPersonFn) error {
|
func OnPerson(it Item, fn withPersonFn) error {
|
||||||
if !activitystreams.ActorTypes.Contains(it.GetType()) {
|
if !ActorTypes.Contains(it.GetType()) {
|
||||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Person", it, 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -107,27 +106,27 @@ func OnPerson(it activitystreams.Item, fn withPersonFn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnCollection
|
// OnCollection
|
||||||
func OnCollection(it activitystreams.Item, fn withCollectionInterfaceFn) error {
|
func OnCollection(it Item, fn withCollectionInterfaceFn) error {
|
||||||
switch it.GetType() {
|
switch it.GetType() {
|
||||||
case activitystreams.CollectionOfItems:
|
case CollectionOfItems:
|
||||||
col, err := activitystreams.ToItemCollection(it)
|
col, err := ToItemCollection(it)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c := activitystreams.Collection{
|
c := Collection{
|
||||||
TotalItems: uint(len(*col)),
|
TotalItems: uint(len(*col)),
|
||||||
Items: *col,
|
Items: *col,
|
||||||
}
|
}
|
||||||
return fn(&c)
|
return fn(&c)
|
||||||
case activitystreams.CollectionType:
|
case CollectionType:
|
||||||
col, err := activitystreams.ToCollection(it)
|
col, err := ToCollection(it)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fn(col)
|
return fn(col)
|
||||||
case activitystreams.CollectionPageType:
|
case CollectionPageType:
|
||||||
return OnCollectionPage(it, func(p *activitystreams.CollectionPage) error {
|
return OnCollectionPage(it, func(p *CollectionPage) error {
|
||||||
col, err := activitystreams.ToCollection(&p.ParentCollection)
|
col, err := ToCollection(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -139,11 +138,11 @@ func OnCollection(it activitystreams.Item, fn withCollectionInterfaceFn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnCollectionPage
|
// OnCollectionPage
|
||||||
func OnCollectionPage(it activitystreams.Item, fn withCollectionPageFn) error {
|
func OnCollectionPage(it Item, fn withCollectionPageFn) error {
|
||||||
if it.GetType() != activitystreams.CollectionPageType {
|
if it.GetType() != CollectionPageType {
|
||||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to Collection Page", it, it.GetType()))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -151,17 +150,17 @@ func OnCollectionPage(it activitystreams.Item, fn withCollectionPageFn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnOrderedCollection
|
// OnOrderedCollection
|
||||||
func OnOrderedCollection(it activitystreams.Item, fn withOrderedCollectionFn) error {
|
func OnOrderedCollection(it Item, fn withOrderedCollectionFn) error {
|
||||||
switch it.GetType() {
|
switch it.GetType() {
|
||||||
case activitystreams.OrderedCollectionType:
|
case OrderedCollectionType:
|
||||||
col, err := activitystreams.ToOrderedCollection(it)
|
col, err := ToOrderedCollection(it)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fn(col)
|
return fn(col)
|
||||||
case activitystreams.OrderedCollectionPageType:
|
case OrderedCollectionPageType:
|
||||||
return OnOrderedCollectionPage(it, func(p *activitystreams.OrderedCollectionPage) error {
|
return OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error {
|
||||||
col, err := activitystreams.ToOrderedCollection(&p.OrderedCollection)
|
col, err := ToOrderedCollection(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -173,11 +172,11 @@ func OnOrderedCollection(it activitystreams.Item, fn withOrderedCollectionFn) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnOrderedCollectionPage
|
// OnOrderedCollectionPage
|
||||||
func OnOrderedCollectionPage(it activitystreams.Item, fn withOrderedCollectionPageFn) error {
|
func OnOrderedCollectionPage(it Item, fn withOrderedCollectionPageFn) error {
|
||||||
if it.GetType() != activitystreams.OrderedCollectionPageType {
|
if it.GetType() != OrderedCollectionPageType {
|
||||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to OrderedCollection Page", it, it.GetType()))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -185,11 +184,11 @@ func OnOrderedCollectionPage(it activitystreams.Item, fn withOrderedCollectionPa
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnOrderedCollectionPage
|
// OnOrderedCollectionPage
|
||||||
func OnItemCOllection(it activitystreams.Item, fn withOrderedCollectionPageFn) error {
|
func OnItemCOllection(it Item, fn withOrderedCollectionPageFn) error {
|
||||||
if it.GetType() != activitystreams.OrderedCollectionPageType {
|
if it.GetType() != OrderedCollectionPageType {
|
||||||
return errors.New(fmt.Sprintf("%T[%s] can't be converted to OrderedCollection Page", it, it.GetType()))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package activitypub
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-ap/activitystreams"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOnObject(t *testing.T) {
|
func TestOnObject(t *testing.T) {
|
||||||
ob := activitystreams.ObjectNew(activitystreams.ArticleType)
|
ob := ObjectNew(ArticleType)
|
||||||
|
|
||||||
err := OnObject(ob, func(o *Object) error {
|
err := OnObject(ob, func(o *Object) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -28,10 +27,10 @@ func TestOnObject(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOnActivity(t *testing.T) {
|
func TestOnActivity(t *testing.T) {
|
||||||
ob := activitystreams.ObjectNew(activitystreams.ArticleType)
|
ob := ObjectNew(ArticleType)
|
||||||
act := activitystreams.ActivityNew("test", activitystreams.CreateType, ob)
|
act := ActivityNew("test", CreateType, ob)
|
||||||
|
|
||||||
err := OnActivity(act, func(a *activitystreams.Activity) error {
|
err := OnActivity(act, func(a *Activity) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ func TestOnActivity(t *testing.T) {
|
||||||
t.Errorf("Unexpected error returned %s", err)
|
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 {
|
if a.Type != act.Type {
|
||||||
t.Errorf("In function type %s different than expected, %s", 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) {
|
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
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -67,7 +66,7 @@ func TestOnIntransitiveActivity(t *testing.T) {
|
||||||
t.Errorf("Unexpected error returned %s", err)
|
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 {
|
if a.Type != act.Type {
|
||||||
t.Errorf("In function type %s different than expected, %s", 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) {
|
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
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -92,7 +91,7 @@ func TestOnQuestion(t *testing.T) {
|
||||||
t.Errorf("Unexpected error returned %s", err)
|
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 {
|
if a.Type != act.Type {
|
||||||
t.Errorf("In function type %s different than expected, %s", 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) {
|
func TestOnPerson(t *testing.T) {
|
||||||
pers := activitystreams.PersonNew("testPerson")
|
pers := PersonNew("testPerson")
|
||||||
err := OnPerson(pers, func(a *Person) error {
|
err := OnPerson(pers, func(a *Person) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
67
inbox.go
67
inbox.go
|
@ -1,9 +1,5 @@
|
||||||
package activitypub
|
package activitypub
|
||||||
|
|
||||||
import (
|
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// InboxStream contains all activities received by the actor.
|
// InboxStream contains all activities received by the actor.
|
||||||
// The server SHOULD filter content according to the requester's permission.
|
// The server SHOULD filter content according to the requester's permission.
|
||||||
|
@ -13,69 +9,18 @@ type (
|
||||||
InboxStream = Inbox
|
InboxStream = Inbox
|
||||||
|
|
||||||
// Inbox is a type alias for an Ordered Collection
|
// Inbox is a type alias for an Ordered Collection
|
||||||
Inbox as.OrderedCollection
|
Inbox = OrderedCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// InboxNew initializes a new Inbox
|
// InboxNew initializes a new Inbox
|
||||||
func InboxNew() *as.OrderedCollection {
|
func InboxNew() *OrderedCollection {
|
||||||
id := as.ObjectID("inbox")
|
id := ObjectID("inbox")
|
||||||
|
|
||||||
i := as.OrderedCollection{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
i := OrderedCollection{ID: id, Type: CollectionType}
|
||||||
i.Name = as.NaturalLanguageValuesNew()
|
i.Name = NaturalLanguageValuesNew()
|
||||||
i.Content = as.NaturalLanguageValuesNew()
|
i.Content = NaturalLanguageValuesNew()
|
||||||
|
|
||||||
i.TotalItems = 0
|
i.TotalItems = 0
|
||||||
|
|
||||||
return &i
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInboxNew(t *testing.T) {
|
func TestInboxNew(t *testing.T) {
|
||||||
i := InboxNew()
|
i := InboxNew()
|
||||||
|
|
||||||
id := as.ObjectID("inbox")
|
id := ObjectID("inbox")
|
||||||
if i.ID != id {
|
if i.ID != id {
|
||||||
t.Errorf("%T should be initialized with %q as %T", 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)
|
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
|
package activitypub
|
||||||
|
|
||||||
import as "github.com/go-ap/activitystreams"
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// LikedCollection is a list of every object from all of the actor's Like activities,
|
// 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
|
// 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
|
// a Collection and MAY be filtered on privileges of an authenticated user or as
|
||||||
// appropriate when no authentication is given.
|
// appropriate when no authentication is given.
|
||||||
LikedCollection Liked
|
LikedCollection = Liked
|
||||||
|
|
||||||
// Liked is a type alias for an Ordered Collection
|
// Liked is a type alias for an Ordered Collection
|
||||||
Liked as.OrderedCollection
|
Liked = OrderedCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// LikedCollection initializes a new Outbox
|
// LikedCollection initializes a new Outbox
|
||||||
func LikedNew() *as.OrderedCollection {
|
func LikedNew() *OrderedCollection {
|
||||||
id := as.ObjectID("liked")
|
id := ObjectID("liked")
|
||||||
|
|
||||||
l := as.OrderedCollection{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
l := OrderedCollection{ID: id, Type: CollectionType}
|
||||||
l.Name = as.NaturalLanguageValuesNew()
|
l.Name = NaturalLanguageValuesNew()
|
||||||
l.Content = as.NaturalLanguageValuesNew()
|
l.Content = NaturalLanguageValuesNew()
|
||||||
|
|
||||||
l.TotalItems = 0
|
l.TotalItems = 0
|
||||||
|
|
||||||
return &l
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLikedNew(t *testing.T) {
|
func TestLikedNew(t *testing.T) {
|
||||||
l := LikedNew()
|
l := LikedNew()
|
||||||
|
|
||||||
id := as.ObjectID("liked")
|
id := ObjectID("liked")
|
||||||
if l.ID != id {
|
if l.ID != id {
|
||||||
t.Errorf("%T should be initialized with %q as %T", 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)
|
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
|
package activitypub
|
||||||
|
|
||||||
import as "github.com/go-ap/activitystreams"
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// LikesCollection is a list of all Like activities with this object as the object property,
|
// 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
|
// 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
|
// and MAY be filtered on privileges of an authenticated user or as appropriate when
|
||||||
// no authentication is given.
|
// no authentication is given.
|
||||||
LikesCollection Likes
|
LikesCollection = Likes
|
||||||
|
|
||||||
// Likes is a type alias for an Ordered Collection
|
// Likes is a type alias for an Ordered Collection
|
||||||
Likes as.OrderedCollection
|
Likes = OrderedCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// LikesCollection initializes a new Outbox
|
// LikesCollection initializes a new Outbox
|
||||||
func LikesNew() *Likes {
|
func LikesNew() *Likes {
|
||||||
id := as.ObjectID("likes")
|
id := ObjectID("likes")
|
||||||
|
|
||||||
l := Likes{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
l := Likes{ID: id, Type: CollectionType}
|
||||||
l.Name = as.NaturalLanguageValuesNew()
|
l.Name = NaturalLanguageValuesNew()
|
||||||
l.Content = as.NaturalLanguageValuesNew()
|
l.Content = NaturalLanguageValuesNew()
|
||||||
|
|
||||||
l.TotalItems = 0
|
l.TotalItems = 0
|
||||||
|
|
||||||
return &l
|
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"
|
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) {
|
func TestLikesNew(t *testing.T) {
|
||||||
t.Skipf("TODO")
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"github.com/buger/jsonparser"
|
"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,
|
// 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.
|
// as a form of provenance, or to support future editing by clients.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
// Content
|
// Content
|
||||||
Content as.NaturalLanguageValues `jsonld:"content"`
|
Content NaturalLanguageValues `jsonld:"content"`
|
||||||
// MediaType
|
// MediaType
|
||||||
MediaType as.MimeType `jsonld:"mediaType"`
|
MediaType 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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAPSource
|
// GetAPSource
|
||||||
|
@ -53,30 +788,3 @@ func (s *Source) UnmarshalJSON(data []byte) error {
|
||||||
*s = GetAPSource(data)
|
*s = GetAPSource(data)
|
||||||
return nil
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"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) {
|
func validateEmptyObject(o Object, t *testing.T) {
|
||||||
if o.ID != "" {
|
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 != "" {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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() {
|
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() {
|
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() {
|
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)
|
validateEmptySource(o.Source, t)
|
||||||
}
|
}
|
||||||
|
@ -55,6 +563,384 @@ func TestObject_UnmarshalJSON(t *testing.T) {
|
||||||
validateEmptyObject(o, 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) {
|
func TestSource_UnmarshalJSON(t *testing.T) {
|
||||||
s := Source{}
|
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
|
package activitypub
|
||||||
|
|
||||||
import as "github.com/go-ap/activitystreams"
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// OutboxStream contains activities the user has published,
|
// OutboxStream contains activities the user has published,
|
||||||
// subject to the ability of the requestor to retrieve the activity (that is,
|
// subject to the ability of the requestor to retrieve the activity (that is,
|
||||||
|
@ -9,66 +7,18 @@ type (
|
||||||
OutboxStream = Outbox
|
OutboxStream = Outbox
|
||||||
|
|
||||||
// Outbox is a type alias for an Ordered Collection
|
// Outbox is a type alias for an Ordered Collection
|
||||||
Outbox as.OrderedCollection
|
Outbox = OrderedCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutboxNew initializes a new Outbox
|
// OutboxNew initializes a new Outbox
|
||||||
func OutboxNew() *Outbox {
|
func OutboxNew() *Outbox {
|
||||||
id := as.ObjectID("outbox")
|
id := ObjectID("outbox")
|
||||||
|
|
||||||
i := Outbox{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
|
||||||
i.Name = as.NaturalLanguageValuesNew()
|
|
||||||
i.Content = as.NaturalLanguageValuesNew()
|
|
||||||
|
|
||||||
|
i := Outbox{ID: id, Type: OrderedCollectionType}
|
||||||
|
i.Name = NaturalLanguageValuesNew()
|
||||||
|
i.Content = NaturalLanguageValuesNew()
|
||||||
i.TotalItems = 0
|
i.TotalItems = 0
|
||||||
|
i.OrderedItems = make(ItemCollection, 0)
|
||||||
|
|
||||||
return &i
|
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 (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOutboxNew(t *testing.T) {
|
func TestOutboxNew(t *testing.T) {
|
||||||
o := OutboxNew()
|
o := OutboxNew()
|
||||||
|
|
||||||
id := as.ObjectID("outbox")
|
id := ObjectID("outbox")
|
||||||
if o.ID != id {
|
if o.ID != id {
|
||||||
t.Errorf("%T should be initialized with %q as %T", 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) {
|
func TestOutboxStream_GetID(t *testing.T) {
|
||||||
o := OutboxStream{}
|
o := OutboxStream{}
|
||||||
if *o.GetID() != "" {
|
if o.GetID() != "" {
|
||||||
t.Errorf("%T should be initialized with empty %T", o, 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
|
o.ID = id
|
||||||
if *o.GetID() != id {
|
if o.GetID() != id {
|
||||||
t.Errorf("%T should have %T as %q", o, id, 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())
|
t.Errorf("%T should be initialized with empty %T", o, o.GetType())
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Type = as.OrderedCollectionType
|
o.Type = OrderedCollectionType
|
||||||
if o.GetType() != as.OrderedCollectionType {
|
if o.GetType() != OrderedCollectionType {
|
||||||
t.Errorf("%T should have %T as %q", o, o.GetType(), as.OrderedCollectionType)
|
t.Errorf("%T should have %T as %q", o, o.GetType(), OrderedCollectionType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutboxStream_Append(t *testing.T) {
|
func TestOutboxStream_Append(t *testing.T) {
|
||||||
o := OutboxStream{}
|
o := OutboxStream{}
|
||||||
|
|
||||||
val := as.Object{ID: as.ObjectID("grrr")}
|
val := Object{ID: ObjectID("grrr")}
|
||||||
|
|
||||||
o.Append(val)
|
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) {
|
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
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) {
|
func TestOutbox_Append(t *testing.T) {
|
||||||
o := OutboxNew()
|
o := OutboxNew()
|
||||||
|
|
||||||
val := as.Object{ID: as.ObjectID("grrr")}
|
val := Object{ID: ObjectID("grrr")}
|
||||||
|
|
||||||
o.Append(val)
|
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) {
|
if !reflect.DeepEqual(o.OrderedItems[0], val) {
|
||||||
t.Errorf("First item in %T.%T does not match %q", o, o.OrderedItems, val.ID)
|
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
|
package activitypub
|
||||||
|
|
||||||
import as "github.com/go-ap/activitystreams"
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// SharesCollection is a list of all Announce activities with this object as the object property,
|
// 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
|
// added as a side effect. The shares collection MUST be either an OrderedCollection or a Collection
|
||||||
|
@ -10,69 +8,18 @@ type (
|
||||||
SharesCollection = Shares
|
SharesCollection = Shares
|
||||||
|
|
||||||
// Shares is a type alias for an Ordered Collection
|
// Shares is a type alias for an Ordered Collection
|
||||||
Shares as.OrderedCollection
|
Shares = OrderedCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// SharesNew initializes a new Shares
|
// SharesNew initializes a new Shares
|
||||||
func SharesNew() *Shares {
|
func SharesNew() *Shares {
|
||||||
id := as.ObjectID("Shares")
|
id := ObjectID("Shares")
|
||||||
|
|
||||||
i := Shares{Parent: as.Parent{ID: id, Type: as.CollectionType}}
|
i := Shares{ID: id, Type: CollectionType}
|
||||||
i.Name = as.NaturalLanguageValuesNew()
|
i.Name = NaturalLanguageValuesNew()
|
||||||
i.Content = as.NaturalLanguageValuesNew()
|
i.Content = NaturalLanguageValuesNew()
|
||||||
|
|
||||||
i.TotalItems = 0
|
i.TotalItems = 0
|
||||||
|
|
||||||
return &i
|
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"
|
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) {
|
func TestSharesNew(t *testing.T) {
|
||||||
t.Skipf("TODO")
|
t.Skipf("TODO")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,15 @@ package tests
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
a "github.com/go-ap/activitystreams"
|
|
||||||
j "github.com/go-ap/jsonld"
|
j "github.com/go-ap/jsonld"
|
||||||
|
|
||||||
|
pub "github.com/go-ap/activitypub"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAcceptSerialization(t *testing.T) {
|
func TestAcceptSerialization(t *testing.T) {
|
||||||
obj := a.AcceptNew("https://localhost/myactivity", nil)
|
obj := pub.AcceptNew("https://localhost/myactivity", nil)
|
||||||
obj.Name = make(a.NaturalLanguageValues, 1)
|
obj.Name = make(pub.NaturalLanguageValues, 1)
|
||||||
obj.Name.Set("en", "test")
|
obj.Name.Set("en", "test")
|
||||||
obj.Name.Set("fr", "teste")
|
obj.Name.Set("fr", "teste")
|
||||||
|
|
||||||
|
@ -41,11 +41,11 @@ func TestAcceptSerialization(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateActivityHTTPSerialization(t *testing.T) {
|
func TestCreateActivityHTTPSerialization(t *testing.T) {
|
||||||
id := a.ObjectID("test_object")
|
id := pub.ObjectID("test_object")
|
||||||
obj := a.AcceptNew(id, nil)
|
obj := pub.AcceptNew(id, nil)
|
||||||
obj.Name.Set("en", "Accept New")
|
obj.Name.Set("en", "Accept New")
|
||||||
|
|
||||||
uri := string(a.ActivityBaseURI)
|
uri := string(pub.ActivityBaseURI)
|
||||||
|
|
||||||
data, err := j.WithContext(j.IRI(uri)).Marshal(obj)
|
data, err := j.WithContext(j.IRI(uri)).Marshal(obj)
|
||||||
if err != nil {
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
a "github.com/go-ap/activitystreams"
|
pub "github.com/go-ap/activitypub"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ S2S Server: Activities requiring the object property
|
||||||
`
|
`
|
||||||
t.Log(desc)
|
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 {
|
if add.Object == nil {
|
||||||
t.Errorf("Missing GetID in Add activity %#v", add.Object)
|
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)
|
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 {
|
if block.Object == nil {
|
||||||
t.Errorf("Missing GetID in Add activity %#v", block.Object)
|
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)
|
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 {
|
if create.Object == nil {
|
||||||
t.Errorf("Missing GetID in Add activity %#v", create.Object)
|
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)
|
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 {
|
if delete.Object == nil {
|
||||||
t.Errorf("Missing GetID in Delete activity %#v", delete.Object)
|
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)
|
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 {
|
if follow.Object == nil {
|
||||||
t.Errorf("Missing GetID in Follow activity %#v", follow.Object)
|
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)
|
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 {
|
if like.Object == nil {
|
||||||
t.Errorf("Missing GetID in Like activity %#v", like.Object)
|
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)
|
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 {
|
if update.Object == nil {
|
||||||
t.Errorf("Missing GetID in Update activity %#v", update.Object)
|
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)
|
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 {
|
if undo.Object == nil {
|
||||||
t.Errorf("Missing GetID in Undo activity %#v", undo.Object)
|
t.Errorf("Missing GetID in Undo activity %#v", undo.Object)
|
||||||
}
|
}
|
||||||
|
@ -103,10 +103,10 @@ property: Add, Remove.
|
||||||
`
|
`
|
||||||
t.Log(desc)
|
t.Log(desc)
|
||||||
|
|
||||||
obj := a.MentionNew("foo")
|
obj := pub.MentionNew("foo")
|
||||||
target := a.MentionNew("bar")
|
target := pub.MentionNew("bar")
|
||||||
|
|
||||||
add := a.AddNew("https://localhost/myactivity", obj, target)
|
add := pub.AddNew("https://localhost/myactivity", obj, target)
|
||||||
if add.Target == nil {
|
if add.Target == nil {
|
||||||
t.Errorf("Missing Target in Add activity %#v", add.Target)
|
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)
|
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 {
|
if remove.Target == nil {
|
||||||
t.Errorf("Missing Target in Remove activity %#v", remove.Target)
|
t.Errorf("Missing Target in Remove activity %#v", remove.Target)
|
||||||
}
|
}
|
||||||
|
@ -141,32 +141,32 @@ S2S Server: Deduplication of recipient list
|
||||||
`
|
`
|
||||||
t.Log(desc)
|
t.Log(desc)
|
||||||
|
|
||||||
to := a.PersonNew("bob")
|
to := pub.PersonNew("bob")
|
||||||
o := a.ObjectNew(a.ArticleType)
|
o := pub.ObjectNew(pub.ArticleType)
|
||||||
cc := a.PersonNew("alice")
|
cc := pub.PersonNew("alice")
|
||||||
|
|
||||||
o.ID = "something"
|
o.ID = "something"
|
||||||
c := a.CreateNew("create", o)
|
c := pub.CreateNew("create", o)
|
||||||
c.To.Append(to)
|
c.To.Append(to)
|
||||||
c.CC.Append(cc)
|
c.CC.Append(cc)
|
||||||
c.BCC.Append(cc)
|
c.BCC.Append(cc)
|
||||||
|
|
||||||
c.Recipients()
|
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 _, rec := range list {
|
||||||
for _, id := range *recIds {
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
recIds := make([]a.ObjectID, 0)
|
recIds := make([]pub.ObjectID, 0)
|
||||||
err = checkDedup(c.To, &recIds)
|
err = checkDedup(c.To, &recIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -197,15 +197,15 @@ Activity being notified about
|
||||||
`
|
`
|
||||||
t.Log(desc)
|
t.Log(desc)
|
||||||
|
|
||||||
p := a.PersonNew("main actor")
|
p := pub.PersonNew("main actor")
|
||||||
|
|
||||||
to := a.PersonNew("bob")
|
to := pub.PersonNew("bob")
|
||||||
o := a.ObjectNew(a.ArticleType)
|
o := pub.ObjectNew(pub.ArticleType)
|
||||||
cc := a.PersonNew("alice")
|
cc := pub.PersonNew("alice")
|
||||||
|
|
||||||
o.ID = "something"
|
o.ID = "something"
|
||||||
c := a.CreateNew("create", o)
|
c := pub.CreateNew("create", o)
|
||||||
c.Actor = a.Actor(*p)
|
c.Actor = *p
|
||||||
|
|
||||||
c.To.Append(p)
|
c.To.Append(p)
|
||||||
c.To.Append(to)
|
c.To.Append(to)
|
||||||
|
@ -216,10 +216,10 @@ Activity being notified about
|
||||||
|
|
||||||
c.Recipients()
|
c.Recipients()
|
||||||
|
|
||||||
checkActor := func(list a.ItemCollection, actor a.Item) error {
|
checkActor := func(list pub.ItemCollection, actor pub.Item) error {
|
||||||
for _, rec := range list {
|
for _, rec := range list {
|
||||||
if rec.GetID() == actor.GetID() {
|
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
|
return nil
|
||||||
|
@ -254,13 +254,13 @@ S2S Server: Do-not-deliver considerations
|
||||||
`
|
`
|
||||||
t.Log(desc)
|
t.Log(desc)
|
||||||
|
|
||||||
p := a.PersonNew("blocked")
|
p := pub.PersonNew("blocked")
|
||||||
|
|
||||||
bob := a.PersonNew("bob")
|
bob := pub.PersonNew("bob")
|
||||||
jane := a.PersonNew("jane doe")
|
jane := pub.PersonNew("jane doe")
|
||||||
|
|
||||||
b := a.BlockNew("block actor", p)
|
b := pub.BlockNew("block actor", p)
|
||||||
b.Actor = a.Actor(*bob)
|
b.Actor = *bob
|
||||||
|
|
||||||
b.To.Append(jane)
|
b.To.Append(jane)
|
||||||
b.To.Append(p)
|
b.To.Append(p)
|
||||||
|
@ -268,10 +268,10 @@ S2S Server: Do-not-deliver considerations
|
||||||
|
|
||||||
b.Recipients()
|
b.Recipients()
|
||||||
|
|
||||||
checkActor := func(list a.ItemCollection, ob a.Item) error {
|
checkActor := func(list pub.ItemCollection, ob pub.Item) error {
|
||||||
for _, rec := range list {
|
for _, rec := range list {
|
||||||
if rec.GetID() == ob.GetID() {
|
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
|
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
|
package activitypub
|
||||||
|
|
||||||
import (
|
|
||||||
as "github.com/go-ap/activitystreams"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidationErrors is an aggregated error interface that allows
|
// ValidationErrors is an aggregated error interface that allows
|
||||||
// a Validator implementation to return all possible errors.
|
// a Validator implementation to return all possible errors.
|
||||||
type ValidationErrors interface {
|
type ValidationErrors interface {
|
||||||
|
@ -16,10 +12,10 @@ type ValidationErrors interface {
|
||||||
// provide a validation mechanism for incoming ActivityPub Objects or IRIs
|
// provide a validation mechanism for incoming ActivityPub Objects or IRIs
|
||||||
// against an external set of rules.
|
// against an external set of rules.
|
||||||
type Validator interface {
|
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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue