From 648b2cf0975ebd07254388d558557a413b61df64 Mon Sep 17 00:00:00 2001 From: Marius Orcsik Date: Sun, 7 Nov 2021 14:39:30 +0100 Subject: [PATCH 1/6] Added generics compatible interface type constraints for Objects, Collections, Activities, Actors using type unions --- Makefile | 2 +- activity.go | 4 ++++ actor.go | 4 ++++ collection.go | 4 ++++ intransitive_activity.go | 4 ++++ object.go | 9 +++++++++ 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e0162d0..322906d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TEST := go test -TEST_FLAGS ?= -v +TEST_FLAGS ?= -v -gcflags=-G=3 TEST_TARGET ?= . GO111MODULE = on PROJECT_NAME := $(shell basename $(PWD)) diff --git a/activity.go b/activity.go index 3c3e151..b916253 100644 --- a/activity.go +++ b/activity.go @@ -197,6 +197,10 @@ type HasRecipients interface { Clean() } +type Activities interface { + ~Activity +} + // 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. diff --git a/actor.go b/actor.go index 75219de..b230cd3 100644 --- a/actor.go +++ b/actor.go @@ -34,6 +34,10 @@ var ActorTypes = ActivityVocabularyTypes{ // Like other ActivityStreams objects, actors have an id, which is a URI. type CanReceiveActivities Item +type Actors interface { + ~Actor +} + // 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. diff --git a/collection.go b/collection.go index db4d595..cd90e07 100644 --- a/collection.go +++ b/collection.go @@ -19,6 +19,10 @@ var CollectionTypes = ActivityVocabularyTypes{ OrderedCollectionPageType, } +type Collections interface { + ~Collection | ~CollectionPage | ~OrderedCollection | ~OrderedCollectionPage | ~CollectionOfItems +} + type CollectionInterface interface { ObjectOrLink Collection() ItemCollection diff --git a/intransitive_activity.go b/intransitive_activity.go index d22e7ba..e6bc903 100644 --- a/intransitive_activity.go +++ b/intransitive_activity.go @@ -9,6 +9,10 @@ import ( "github.com/valyala/fastjson" ) +type IntransitiveActivities interface { + ~IntransitiveActivity | ~Question +} + // IntransitiveActivity Instances of IntransitiveActivity are a subtype of Activity representing intransitive actions. // The object property is therefore inappropriate for these activities. type IntransitiveActivity struct { diff --git a/object.go b/object.go index eb8c060..72d550f 100644 --- a/object.go +++ b/object.go @@ -139,6 +139,15 @@ func (a *ActivityVocabularyType) GobDecode([]byte) error { } */ +type Objects interface { + //go1.18: Object | Activity | IRI ... + ~Object | ~Tombstone | ~Place | ~Profile | ~Relationship | + Activities | + IntransitiveActivities | + Collections | + IRI +} + // Object describes an ActivityPub object of any kind. // It 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. From 56da692c28675183a3fcb3587251461c997958e6 Mon Sep 17 00:00:00 2001 From: mariusor Date: Tue, 9 Nov 2021 16:26:02 +0100 Subject: [PATCH 2/6] Install and use gotip to test generics --- .build.yml | 10 +++++++++- Makefile | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.build.yml b/.build.yml index 75a809b..505019c 100644 --- a/.build.yml +++ b/.build.yml @@ -8,12 +8,20 @@ sources: environment: GO111MODULE: 'on' tasks: + - install_go_tip: | + go install golang.org/dl/gotip@latest + export PATH=$PATH:$(go env GOPATH)/bin + gotip download - tests: | cd activitypub - go mod vendor + export PATH=$PATH:$(go env GOPATH)/bin + export GO=gotip + gotip mod tidy make test - coverage: | set -a +x + export PATH=$PATH:$(go env GOPATH)/bin + export GO=gotip cd activitypub && make coverage GIT_SHA=$(git rev-parse --verify HEAD) GIT_BRANCH=$(git name-rev --name-only HEAD) diff --git a/Makefile b/Makefile index 322906d..087e2c4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -TEST := go test +GO ?= go +TEST := $(GO) test TEST_FLAGS ?= -v -gcflags=-G=3 TEST_TARGET ?= . GO111MODULE = on From 3f601dee05db03df7b39fa323f952daa7ff84083 Mon Sep 17 00:00:00 2001 From: mariusor Date: Tue, 9 Nov 2021 17:15:45 +0100 Subject: [PATCH 3/6] Fix union types --- activity.go | 2 +- actor.go | 2 +- collection.go | 2 +- go.mod | 2 +- intransitive_activity.go | 2 +- object.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activity.go b/activity.go index b916253..9deb94b 100644 --- a/activity.go +++ b/activity.go @@ -198,7 +198,7 @@ type HasRecipients interface { } type Activities interface { - ~Activity + Activity } // Activity is a subtype of Object that describes some form of action that may happen, diff --git a/actor.go b/actor.go index b230cd3..ddaa8ad 100644 --- a/actor.go +++ b/actor.go @@ -35,7 +35,7 @@ var ActorTypes = ActivityVocabularyTypes{ type CanReceiveActivities Item type Actors interface { - ~Actor + Actor } // Actor is generally one of the ActivityStreams actor Types, but they don't have to be. diff --git a/collection.go b/collection.go index cd90e07..bfe68d9 100644 --- a/collection.go +++ b/collection.go @@ -20,7 +20,7 @@ var CollectionTypes = ActivityVocabularyTypes{ } type Collections interface { - ~Collection | ~CollectionPage | ~OrderedCollection | ~OrderedCollectionPage | ~CollectionOfItems + Collection | CollectionPage | OrderedCollection | OrderedCollectionPage | ItemCollection } type CollectionInterface interface { diff --git a/go.mod b/go.mod index 8c6979e..3d289cc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-ap/activitypub -go 1.13 +go 1.18 require ( git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20200411073322-f0bcc40f0bf2 diff --git a/intransitive_activity.go b/intransitive_activity.go index e6bc903..d7f427f 100644 --- a/intransitive_activity.go +++ b/intransitive_activity.go @@ -10,7 +10,7 @@ import ( ) type IntransitiveActivities interface { - ~IntransitiveActivity | ~Question + IntransitiveActivity | Question } // IntransitiveActivity Instances of IntransitiveActivity are a subtype of Activity representing intransitive actions. diff --git a/object.go b/object.go index 72d550f..f7945ed 100644 --- a/object.go +++ b/object.go @@ -141,7 +141,7 @@ func (a *ActivityVocabularyType) GobDecode([]byte) error { type Objects interface { //go1.18: Object | Activity | IRI ... - ~Object | ~Tombstone | ~Place | ~Profile | ~Relationship | + Object | Tombstone | Place | Profile | Relationship | Activities | IntransitiveActivities | Collections | From dc55be1bb11dfc76fd12a660b4ffcfbb89ab7403 Mon Sep 17 00:00:00 2001 From: mariusor Date: Tue, 9 Nov 2021 17:25:47 +0100 Subject: [PATCH 4/6] Adding Links interface --- link.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/link.go b/link.go index e85b08a..f0f3c59 100644 --- a/link.go +++ b/link.go @@ -12,6 +12,10 @@ var LinkTypes = ActivityVocabularyTypes{ MentionType, } +type Links interface { + Link | IRI +} + // 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. From e2db125414317cdcd99e30b28c487f09016788cd Mon Sep 17 00:00:00 2001 From: Marius Orcsik Date: Sun, 14 Nov 2021 21:18:05 +0100 Subject: [PATCH 5/6] Aligned types in the Objects interface --- object.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/object.go b/object.go index f7945ed..9b0c888 100644 --- a/object.go +++ b/object.go @@ -140,12 +140,11 @@ func (a *ActivityVocabularyType) GobDecode([]byte) error { */ type Objects interface { - //go1.18: Object | Activity | IRI ... Object | Tombstone | Place | Profile | Relationship | - Activities | - IntransitiveActivities | - Collections | - IRI + Activities | + IntransitiveActivities | + Collections | + IRI } // Object describes an ActivityPub object of any kind. From 95eb2994e59e989a0ea5e4990a3c7cc1df854900 Mon Sep 17 00:00:00 2001 From: Marius Orcsik Date: Sun, 14 Nov 2021 21:18:24 +0100 Subject: [PATCH 6/6] An eventual test for a generic On[Object] function --- helpers.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ helpers_test.go | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/helpers.go b/helpers.go index 1a9ba8a..a0a9e4a 100644 --- a/helpers.go +++ b/helpers.go @@ -52,6 +52,73 @@ func OnLink(it Item, fn WithLinkFn) error { return fn(ob) } +func On[T Objects](it Item, fn func(*T) error) error { +/* + switch i := it.(type) { + case *Object: + return fn(i) + case Object: + return fn(&i) + case *Place: + return fn(i) + case Place: + return fn(&i) + case *Profile: + return fn(i) + case Profile: + return fn(&i) + case *Relationship: + return fn(i) + case Relationship: + return fn(&i) + case *Tombstone: + return fn(i) + case Tombstone: + return fn(&i) + case *Actor: + return fn(i) + case Actor: + return fn(&i) + case *Activity: + return fn(i) + case Activity: + return fn(&i) + case *IntransitiveActivity: + return fn(i) + case IntransitiveActivity: + return fn(&i) + case *Question: + return fn(i) + case Question: + return fn(&i) + case *Collection: + return fn(i) + case Collection: + return fn(&i) + case *CollectionPage: + return fn(i) + case CollectionPage: + return fn(&i) + case *OrderedCollection: + return fn(i) + case OrderedCollection: + return fn(&i) + case *OrderedCollectionPage: + return fn(i) + case OrderedCollectionPage: + return fn(&i) + default: + typ := reflect.TypeOf(new(Object)) + if reflect.TypeOf(it).ConvertibleTo(typ) { + if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Object); ok { + return fn(i) + } + } + } +*/ + return fmt.Errorf("invalid type %T for generic function", it) +} + // OnObject calls function fn on it Item if it can be asserted to type Object func OnObject(it Item, fn WithObjectFn) error { if it == nil { diff --git a/helpers_test.go b/helpers_test.go index e2ee5b6..6a1448a 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -268,3 +268,49 @@ func TestOnCollectionPage(t *testing.T) { func TestOnOrderedCollectionPage(t *testing.T) { t.Skipf("TODO") } + +type args[T Objects] struct { + it T + fn func (fn canErrorFunc, expected T) func(*T) error +} + +type testPair[T Objects] struct { + name string + args args[T] + expected T + wantErr bool +} + +func assert[T Objects](fn canErrorFunc, expected T) func(*T) error { + return func(p *T) error { + if !assertDeepEquals(fn, *p, expected) { + return fmt.Errorf("not equal") + } + return nil + } +} + +func TestOn(t *testing.T) { + testQuestion := Object{ID: "https://example.com"} + var tests []testPair[Object] = []testPair[Object]{ + { + name: "single", + args: args[Object]{testQuestion, assert[Object]}, + expected: testQuestion, + wantErr: true, + }, + } + for _, tt := range tests { + var logFn canErrorFunc + if tt.wantErr { + logFn = t.Logf + } else { + logFn = t.Errorf + } + t.Run(tt.name, func(t *testing.T) { + if err := On(tt.args.it, tt.args.fn(logFn, tt.expected)); (err != nil) != tt.wantErr { + t.Errorf("On[%T]() error = %v, wantErr %v", tt.args.it, err, tt.wantErr) + } + }) + } +}