package activitypub import ( "fmt" "reflect" "strings" ) // Item struct type Item = ObjectOrLink const ( // EmptyIRI represents a zero length IRI EmptyIRI IRI = "" // NilIRI represents by convention an IRI which is nil // Its use is mostly to check if a property of an ActivityPub Item is nil NilIRI IRI = "-" // EmptyID represents a zero length ID EmptyID = EmptyIRI // NilID represents by convention an ID which is nil, see details of NilIRI NilID = NilIRI ) func itemsNeedSwapping(i1, i2 Item) bool { if IsIRI(i1) && !IsIRI(i2) { return true } t1 := i1.GetType() t2 := i2.GetType() if ObjectTypes.Contains(t2) { return !ObjectTypes.Contains(t1) } return false } // ItemsEqual checks if it and with Items are equal func ItemsEqual(it, with Item) bool { if IsNil(it) || IsNil(with) { return IsNil(with) && IsNil(it) } if itemsNeedSwapping(it, with) { return ItemsEqual(with, it) } result := false if IsIRI(with) || IsIRI(it) { // NOTE(marius): I'm not sure this logic is sound: // if only one item is an IRI it should not be equal to the other even if it has the same ID result = it.GetLink().Equals(with.GetLink(), false) } else if IsItemCollection(it) { if !IsItemCollection(with) { return false } _ = OnItemCollection(it, func(c *ItemCollection) error { result = c.Equals(with) return nil }) } else if IsObject(it) { _ = OnObject(it, func(i *Object) error { result = i.Equals(with) return nil }) if ActivityTypes.Contains(with.GetType()) { _ = OnActivity(it, func(i *Activity) error { result = i.Equals(with) return nil }) } else if ActorTypes.Contains(with.GetType()) { _ = OnActor(it, func(i *Actor) error { result = i.Equals(with) return nil }) } else if it.IsCollection() { if it.GetType() == CollectionType { _ = OnCollection(it, func(c *Collection) error { result = c.Equals(with) return nil }) } if it.GetType() == OrderedCollectionType { _ = OnOrderedCollection(it, func(c *OrderedCollection) error { result = c.Equals(with) return nil }) } if it.GetType() == CollectionPageType { _ = OnCollectionPage(it, func(c *CollectionPage) error { result = c.Equals(with) return nil }) } if it.GetType() == OrderedCollectionPageType { _ = OnOrderedCollectionPage(it, func(c *OrderedCollectionPage) error { result = c.Equals(with) return nil }) } } } return result } // IsItemCollection returns if the current Item interface holds a Collection func IsItemCollection(it LinkOrIRI) bool { _, ok := it.(ItemCollection) _, okP := it.(*ItemCollection) return ok || okP || IsIRIs(it) } // IsIRI returns if the current Item interface holds an IRI func IsIRI(it LinkOrIRI) bool { _, okV := it.(IRI) _, okP := it.(*IRI) return okV || okP } // IsIRIs returns if the current Item interface holds an IRI slice func IsIRIs(it LinkOrIRI) bool { _, okV := it.(IRIs) _, okP := it.(*IRIs) return okV || okP } // IsLink returns if the current Item interface holds a Link func IsLink(it LinkOrIRI) bool { _, okV := it.(Link) _, okP := it.(*Link) return okV || okP } // IsObject returns if the current Item interface holds an Object func IsObject(it LinkOrIRI) bool { switch ob := it.(type) { case Actor, *Actor, Object, *Object, Profile, *Profile, Place, *Place, Relationship, *Relationship, Tombstone, *Tombstone, Activity, *Activity, IntransitiveActivity, *IntransitiveActivity, Question, *Question, Collection, *Collection, CollectionPage, *CollectionPage, OrderedCollection, *OrderedCollection, OrderedCollectionPage, *OrderedCollectionPage: return ob != nil default: return false } } // IsNil checks if the object matching an ObjectOrLink interface is nil func IsNil(it LinkOrIRI) bool { if it == nil { return true } // This is the default if the argument can't be cast to Object, as is the case for an ItemCollection isNil := false if IsIRI(it) { isNil = len(it.GetLink()) == 0 || strings.EqualFold(it.GetLink().String(), NilIRI.String()) } else if IsItemCollection(it) { if v, ok := it.(ItemCollection); ok { return v == nil } if v, ok := it.(*ItemCollection); ok { return v == nil } if v, ok := it.(IRIs); ok { return v == nil } if v, ok := it.(*IRIs); ok { return v == nil } } else if IsObject(it) { if ob, ok := it.(Item); ok { _ = OnObject(ob, func(o *Object) error { isNil = o == nil return nil }) } } else if IsLink(it) { _ = OnLink(it, func(l *Link) error { isNil = l == nil return nil }) } else { // NOTE(marius): we're not dealing with a type that we know about, so we use slow reflection // as we still care about the result v := reflect.ValueOf(it) isNil = v.Kind() == reflect.Pointer && v.IsNil() } return isNil } func ErrorInvalidType[T Objects | Links | IRIs](received LinkOrIRI) error { return fmt.Errorf("unable to convert %T to %T", received, new(T)) } // OnItem runs function "fn" on the Item "it", with the benefit of destructuring "it" to individual // items if it's actually an ItemCollection or an object holding an ItemCollection // // It is expected that the caller handles the logic of dealing with different Item implementations // internally in "fn". func OnItem(it Item, fn func(Item) error) error { if it == nil { return nil } if !IsItemCollection(it) { return fn(it) } return OnItemCollection(it, func(col *ItemCollection) error { for _, it := range *col { if err := OnItem(it, fn); err != nil { return err } } return nil }) }